PronounsPage/server/cards.ts
Adaline Simonian 23a3862ca0
test: introduce snapshot-based smoke tests
- Adds a new test suite with Docker-based smoke tests for all locales.
  Can be run using the ./smoketest.sh script.
- Replaces all calls to Math.random() with a new helper that returns 0.5
  in snapshot testing mode, ensuring deterministic snapshots.
- Similarly replaces all calls to new Date() and Date.now() with new
  helpers that return a fixed date in snapshot testing mode.
- Replaces checks against NODE_ENV with APP_ENV, to ensure that the
  bundles can be built with Nuxt for testing without losing code that
  would otherwise be stripped out by production optimizations.
- Adds a database init script that can be used to initialize the
  database with a single admin user and a long-lived JWT token for use
  in automation tests.
- Adds a JWT decoding/encoding CLI tool for debugging JWTs.

Note: Snapshots are not checked in, and must be generated manually. See
test/__snapshots__/.gitignore for more information.
2025-02-02 23:11:19 -08:00

138 lines
4.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import './setup.ts';
import { S3 } from '@aws-sdk/client-s3';
import * as Sentry from '@sentry/node';
import Pageres from 'pageres';
import type { Screenshot } from 'pageres';
import { ulid } from 'ulid';
import allLocales from '../locale/locales.ts';
import { awsConfig, awsParams } from './aws.ts';
import dbConnection from './db.ts';
import type { Database } from './db.ts';
import { env } from './env.ts';
import isHighLoadTime from './overload.js';
import jwt from '~/server/jwt.ts';
const s3 = new S3(awsConfig);
const urlBases: Record<string, string> = {};
if ((env === 'development' || process.env.ENV === 'test') && process.env.BASE_URL) {
for (const { code } of allLocales) {
urlBases[code] = `${process.env.BASE_URL}/card/@`;
}
} else {
for (const { code, url } of allLocales) {
urlBases[code] = `${url}/card/@`;
}
}
const sleep = (ms: number): Promise<void> => new Promise((res) => setTimeout(res, ms));
const modes = ['light', 'dark'] as const;
const shoot = async (db: Database, mode: 'light' | 'dark'): Promise<void> => {
const profiles = (await db.all<{ id: string; locale: string; username: string }>(`
SELECT profiles.id, profiles.locale, users.username
FROM profiles
LEFT JOIN users on profiles.userId = users.id
WHERE profiles.${mode === 'dark' ? 'cardDark' : 'card'} = ''
ORDER BY RANDOM()
LIMIT 6
`)).filter(({ locale }) => !isHighLoadTime(locale));
if (profiles.length === 0) {
console.log('No profiles in the queue');
return;
}
const results: Record<string, Screenshot> = {};
try {
const pr = new Pageres({
darkMode: mode === 'dark',
delay: 3,
scale: 1.5,
launchOptions: {
headless: 'new',
},
});
for (const { locale, username } of profiles) {
const token = await jwt.sign(
{
username: 'example',
email: 'example@pronouns.page',
roles: '',
avatarSource: '',
bannedReason: null,
mfa: false,
authenticated: true,
},
'15m',
);
pr.source(`${urlBases[locale] + username}?token=${encodeURIComponent(token)}`, ['1024x300'], {
filename: `${username}-${locale}`,
});
}
for (const buffer of await pr.run()) {
const match = buffer.filename.match(/(.+)-(\w+)\.png/);
if (!match) {
console.error('invalid filename', buffer.filename);
continue;
}
const [, username, locale] = match;
results[`${locale}/${username.replace(/[^A-Za-z0-9.-]/g, '_')}`] = buffer;
}
} catch (error) {
Sentry.captureException(error);
return;
}
for (const { id, locale, username } of profiles) {
const cardId = ulid();
let key = `card/${locale}/${encodeURIComponent(username).replace(/'/g, '_')}-${cardId}.png`;
if (mode === 'dark') {
key = mode === 'dark' ? key.replace('.png', '-dark.png') : key;
}
console.log(`Uploading @${username} (${locale}, ${mode}) ${cardId}`);
const buffer = results[`${locale}/${username.replace(/[^A-Za-z0-9.-]/g, '_')}`];
if (buffer === undefined) {
console.error('Cannot find the proper buffer!');
continue;
}
await s3.putObject({
Key: key,
Body: buffer,
ContentType: 'image/png',
ACL: 'public-read',
...awsParams,
});
await db.get(`
UPDATE profiles
SET ${mode === 'dark' ? 'cardDark' : 'card'}='https://${awsParams.Bucket}.s3.${awsConfig.region}.amazonaws.com/${key}'
WHERE id='${id}'`);
}
};
(async (): Promise<void> => {
const db = await dbConnection();
while (true) {
for (const mode of modes) {
await sleep(3000);
console.log(`Starting mode: ${mode}`);
await shoot(db, mode);
}
}
})();