test: cover publishing, authentication and storage persistence

This commit is contained in:
2026-09-15 21:48:15 +03:30
parent ee9f49e4b7
commit c17b4b57b2
11 changed files with 294 additions and 23 deletions

16
scripts/e2e-server.mjs Normal file
View File

@@ -0,0 +1,16 @@
import { mkdtemp, rm } from 'node:fs/promises';
import { spawn, spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import path from 'node:path';
const cwd = path.resolve('.next/standalone');
const directory = await mkdtemp(path.join(tmpdir(), 'portfolio-e2e-'));
const port = process.env.PLAYWRIGHT_PORT || '43119';
const env = { ...process.env, DATA_DIR: directory, SITE_URL: `http://127.0.0.1:${port}`, HOSTNAME: '127.0.0.1', PORT: port, OWNER_EMAIL: 'e2e@example.com', OWNER_PASSWORD: 'Only-for-isolated-e2e-tests-2026' };
for (const command of ['import-legacy', 'create-owner']) {
const result = spawnSync(process.execPath, ['maintenance.mjs', command], { cwd, env, stdio: 'inherit' });
if (result.status !== 0) { await rm(directory, { recursive: true, force: true }); process.exit(result.status || 1); }
}
const server = spawn(process.execPath, ['server.js'], { cwd, env, stdio: 'inherit' });
process.on('SIGTERM', () => server.kill()); process.on('SIGINT', () => server.kill());
server.on('exit', async code => { await rm(directory, { recursive: true, force: true }); process.exit(code || 0); });

View File

@@ -3,14 +3,20 @@ set -Eeuo pipefail
image="${1:?runtime image is required}"
container_name="portfolio-smoke-${GITEA_RUN_ID:-$$}"
volume_name="${container_name}-data"
cleanup() {
docker rm --force "$container_name" >/dev/null 2>&1 || true
docker volume rm "$volume_name" >/dev/null 2>&1 || true
}
trap cleanup EXIT
cleanup
docker run --detach --name "$container_name" "$image" >/dev/null
docker volume create "$volume_name" >/dev/null
start_container() {
docker run --detach --name "$container_name" --mount "type=volume,source=$volume_name,target=/data" --env SITE_URL=http://127.0.0.1:3000 "$image" >/dev/null
}
start_container
for _ in {1..30}; do
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_name")"
@@ -32,6 +38,11 @@ if [[ "$(docker inspect --format '{{.State.Health.Status}}' "$container_name")"
exit 1
fi
docker exec "$container_name" node -e "fetch('http://127.0.0.1:3000/').then(async r=>{if(!r.ok||!(await r.text()).includes('Your Name'))process.exit(1)})"
# Exercise the packaged maintenance command and native SQLite driver in the image.
docker exec "$container_name" node maintenance.mjs import-legacy
docker exec --env OWNER_EMAIL=smoke@example.com --env OWNER_PASSWORD=Isolated-container-smoke-password-2026 "$container_name" node maintenance.mjs create-owner
docker exec -i "$container_name" node <<'NODE'
const origin = 'http://127.0.0.1:3000';
@@ -63,3 +74,39 @@ if ((await optimized.arrayBuffer()).byteLength === 0) {
throw new Error('Optimized image response was empty.');
}
NODE
# Upload through the real authenticated endpoint, then replace the container.
docker exec -i "$container_name" node --input-type=module <<'NODE'
import { writeFileSync } from 'node:fs';
const origin = 'http://127.0.0.1:3000';
const login = await fetch(`${origin}/api/auth/sign-in/email`, {
method: 'POST', headers: { Origin: origin, 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'smoke@example.com', password: 'Isolated-container-smoke-password-2026' }),
});
if (!login.ok) throw new Error('Container owner login failed');
const cookie = login.headers.getSetCookie().map(value => value.split(';')[0]).join('; ');
const form = new FormData();
form.set('file', new Blob(['%PDF-1.4\nContainer persistence check\n%%EOF'], { type: 'application/pdf' }), 'check.pdf');
const response = await fetch(`${origin}/api/admin/media`, { method: 'POST', headers: { Origin: origin, Cookie: cookie }, body: form });
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
const uploaded = await response.json();
writeFileSync('/data/smoke-media-key', uploaded.key);
NODE
docker rm --force "$container_name" >/dev/null
start_container
docker exec -i "$container_name" node --input-type=module <<'NODE'
import { readFileSync } from 'node:fs';
const origin = 'http://127.0.0.1:3000';
let ready = false;
for (let attempt = 0; attempt < 30; attempt++) {
try { if ((await fetch(`${origin}/api/health`)).ok) { ready = true; break; } } catch {}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if (!ready) throw new Error('Replacement container did not start');
const page = await fetch(`${origin}/projects/interanet-solutions`);
if (!page.ok) throw new Error('Database did not survive container replacement');
const key = readFileSync('/data/smoke-media-key', 'utf8');
const media = await fetch(`${origin}/media/${key}`);
if (!media.ok || !(await media.text()).includes('Container persistence check')) throw new Error('Upload did not survive container replacement');
console.log('Container startup, native SQLite, owner login, uploads, and replacement persistence passed.');
NODE