Files
HamHam/scripts/verify-docker.ts
T

108 lines
4.1 KiB
TypeScript

import { execFileSync } from 'node:child_process';
import { randomBytes } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import assert from 'node:assert/strict';
// Isolated disposable Compose project. Never targets the user's deployment or volumes.
const project = `hamham-check-${randomBytes(4).toString('hex')}`;
const port = process.env.VERIFY_PORT ?? '3200';
const origin = `http://localhost:${port}`;
const password = randomBytes(30).toString('base64url');
const env = {
...process.env,
POSTGRES_PASSWORD: randomBytes(32).toString('hex'),
HOST_PORT: port,
APP_ORIGIN: origin,
COOKIE_SECURE: 'false',
};
const compose = (args: string[], input?: string | Buffer) =>
execFileSync('docker', ['compose', '-p', project, ...args], {
env,
input,
maxBuffer: 32 * 1024 * 1024,
stdio: input === undefined ? ['ignore', 'pipe', 'inherit'] : ['pipe', 'pipe', 'inherit'],
});
let cookie = '';
async function request(path: string, method = 'GET', body?: unknown) {
const response = await fetch(`${origin}/api/${path}`, {
method,
headers: {
Origin: origin,
Cookie: cookie,
Connection: 'close',
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
},
body: body === undefined ? undefined : JSON.stringify(body),
});
assert.ok(
response.ok,
`${method} ${path}: ${response.status} ${response.ok ? '' : await response.text()}`,
);
if (response.headers.get('set-cookie'))
cookie = response.headers.get('set-cookie')!.split(';')[0];
return response;
}
try {
console.log(`Compose-Integrationstest: ${project} (${origin})`);
console.log(compose(['up', '-d', '--build', '--wait', '--wait-timeout', '240']).toString());
assert.equal(compose(['exec', '-T', 'app', 'id', '-u']).toString().trim(), '1000');
console.log(
compose(
['exec', '-T', 'app', 'npm', 'run', 'admin:create', '--', 'dockercheck'],
`${password}\n`,
).toString(),
);
await request('auth/login', 'POST', { username: 'dockercheck', password });
const raw = await readFile('public/examples/kochbuch.json', 'utf8');
const recipes = await (
await request('import', 'POST', { raw, format: 'json', preview: false })
).json();
assert.equal(recipes.length, 3);
const imageResponse = await fetch(`${origin}/api/media`, {
method: 'POST',
headers: { Origin: origin, Cookie: cookie, Connection: 'close', 'Content-Type': 'image/jpeg' },
body: await readFile('public/images/garden.jpg'),
});
assert.equal(imageResponse.status, 201);
const { url: image } = await imageResponse.json();
const before = await fetch(`${origin}${image}`, { headers: { Connection: 'close' } }).then((r) =>
r.arrayBuffer(),
);
await request(`recipes/${recipes[0].id}`, 'PUT', { ...recipes[0], image });
assert.equal((await (await request('export')).json()).recipes.length, 3);
console.log('Admin-CLI, Login, Import, Bild-Upload, Rezeptänderung und Export: OK');
console.log(compose(['restart']).toString());
console.log(compose(['up', '-d', '--wait', '--wait-timeout', '120']).toString());
const after = await fetch(`${origin}${image}`, { headers: { Connection: 'close' } }).then((r) =>
r.arrayBuffer(),
);
assert.deepEqual(Buffer.from(after), Buffer.from(before));
assert.equal((await (await request('recipes')).json()).length, 3);
console.log('Persistenz nach Neustart: Datenbank, Sitzung und Bildbytes OK');
const dump = compose(['exec', '-T', 'db', 'pg_dump', '-U', 'hamham', '-d', 'hamham', '-Fc']);
compose(['exec', '-T', 'db', 'createdb', '-U', 'hamham', 'hamham_restore']);
compose(['exec', '-T', 'db', 'pg_restore', '-U', 'hamham', '-d', 'hamham_restore'], dump);
assert.equal(
compose([
'exec',
'-T',
'db',
'psql',
'-U',
'hamham',
'-d',
'hamham_restore',
'-Atc',
'SELECT count(*) FROM recipes',
])
.toString()
.trim(),
'3',
);
console.log('PostgreSQL-Backup/Restore und wiederholte Migrationen: OK');
console.log(compose(['ps']).toString());
} finally {
// Only the unique verification project created above is removed, including its test volumes.
compose(['down', '-v', '--remove-orphans']);
}