Apply the supplied inquiry layout to the list and native write/detail dialogs while preserving existing API requests, author controls, attachments, comments and search. Keep legacy entry links and confirm before discarding drafts. Validation: 98 tests and syntax checks pass; mocked Chromium desktop/mobile and six author/session visibility scenarios pass.
156 lines
9.2 KiB
JavaScript
156 lines
9.2 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { productionSettings } from '../scripts/production-settings.mjs';
|
|
import { verifyProduction } from '../scripts/verify-production.mjs';
|
|
import worker from '../src/index.js';
|
|
|
|
const SHA = 'a'.repeat(40);
|
|
const fixture = () => ({
|
|
CLOUDFLARE_API_TOKEN: 'fixture-cloudflare-token', SESSION_SECRET: 'fixture-session-secret-'.repeat(3),
|
|
ABC_API_KEY: 'fixture-abc-key', AUTH_CLIENT_ID: 'production-client', ABC_API_BASE_URL: 'https://feedback.example.test/',
|
|
ABC_PROJECT_ID: '01900000-0000-7000-8000-000000000001', ABC_CHANNEL_ID: '01900000-0000-7000-8000-000000000002',
|
|
ORG_CONTEXT_ENABLED: 'true', ORG_CONTEXT_TENANT_IDS: '["038326b6-954a-48a7-a85f-efd83f62b82a"]',
|
|
ORG_CONTEXT_KEY_ID: 'fixture-org-key', ORG_CONTEXT_KEY_SECRET: 'fixture-org-secret', DEPLOY_COMMIT_SHA: SHA
|
|
});
|
|
|
|
test('production preserves dashboard-managed domains without requesting zone routing changes', () => {
|
|
const config = readFileSync(new URL('../wrangler.production.toml', import.meta.url), 'utf8');
|
|
assert.doesNotMatch(config, /^\s*routes?\s*=/m);
|
|
assert.match(config, /^workers_dev\s*=\s*false\s*$/m);
|
|
});
|
|
|
|
test('production requires its own credentials, target IDs, organization lookup and immutable release', () => {
|
|
for (const name of Object.keys(fixture())) {
|
|
const env = fixture();
|
|
delete env[name];
|
|
assert.throws(() => productionSettings(env), new RegExp(name === 'ORG_CONTEXT_TENANT_IDS' ? 'org_context_invalid_tenants' : name));
|
|
}
|
|
assert.throws(() => productionSettings({ ...fixture(), ORG_CONTEXT_ENABLED: 'false' }), /must be true/);
|
|
assert.throws(() => productionSettings({ ...fixture(), SESSION_SECRET: 'short' }), /at least 32/);
|
|
assert.throws(() => productionSettings({ ...fixture(), ABC_CHANNEL_ID: '123' }), /UUID/);
|
|
assert.throws(() => productionSettings({ ...fixture(), DEPLOY_COMMIT_SHA: '../staging' }), /full lowercase/);
|
|
assert.throws(() => productionSettings({ ...fixture(), ABC_API_BASE_URL: 'https://secret:password@example.test' }), /HTTPS origin/);
|
|
const settings = productionSettings(fixture());
|
|
assert.equal(settings.worker, 'baron-qa-gateway-prod');
|
|
assert.equal(settings.bucket, 'baron-qa-write');
|
|
assert.equal(settings.vars.ABC_API_BASE_URL, 'https://feedback.example.test');
|
|
assert.equal(settings.vars.QA_ASSET_PREFIX, 'releases/' + SHA);
|
|
for (const name of Object.keys(settings.secrets)) assert.equal(settings.vars[name], undefined);
|
|
assert.equal(settings.secrets.CLOUDFLARE_API_TOKEN, undefined);
|
|
});
|
|
|
|
function withFakeWrangler(body) {
|
|
const dir = mkdtempSync(join(tmpdir(), 'qa-deploy-test-'));
|
|
const capture = join(dir, 'commands.jsonl');
|
|
writeFileSync(join(dir, 'npx'), `#!/usr/bin/env node
|
|
const fs = require('node:fs');
|
|
const args = process.argv.slice(2);
|
|
const index = args.indexOf('--secrets-file');
|
|
const path = index === -1 ? null : args[index + 1];
|
|
fs.appendFileSync(process.env.QA_TEST_CAPTURE, JSON.stringify({ args, path,
|
|
secrets: path ? JSON.parse(fs.readFileSync(path)) : null,
|
|
mode: path ? fs.statSync(path).mode & 0o777 : null }) + '\\n');
|
|
process.exit(Number(process.env.QA_TEST_EXIT || 0));
|
|
`, { mode: 0o700 });
|
|
const env = { ...fixture(), PATH: dir + ':' + process.env.PATH, QA_TEST_CAPTURE: capture };
|
|
try { body({ dir, env, capture }); } finally { rmSync(dir, { recursive: true, force: true }); }
|
|
}
|
|
|
|
test('production validation and dry run never register secrets or deploy remotely', () => withFakeWrangler(({ env, capture }) => {
|
|
const script = fileURLToPath(new URL('../scripts/deploy-production.mjs', import.meta.url));
|
|
const validation = spawnSync(process.execPath, [script, '--validate-only'], { env, encoding: 'utf8' });
|
|
assert.equal(validation.status, 0, validation.stderr);
|
|
assert.equal(existsSync(capture), false);
|
|
const dry = spawnSync(process.execPath, [script, '--dry-run'], { env, encoding: 'utf8' });
|
|
assert.equal(dry.status, 0, dry.stderr);
|
|
const command = JSON.parse(readFileSync(capture, 'utf8'));
|
|
assert.ok(command.args.includes('--dry-run'));
|
|
assert.ok(command.args.includes('wrangler.production.toml'));
|
|
assert.ok(!command.args.some((arg) => ['--route', '--routes', '--domain', '--domains'].includes(arg)));
|
|
assert.equal(command.path, null);
|
|
for (const name of ['SESSION_SECRET', 'ABC_API_KEY', 'ORG_CONTEXT_KEY_SECRET']) {
|
|
assert.ok(!JSON.stringify(command.args).includes(env[name]));
|
|
assert.ok(!validation.stdout.includes(env[name]));
|
|
}
|
|
}));
|
|
|
|
for (const exitCode of [0, 1]) {
|
|
test(`production uploads secrets with code and removes the private temporary file after exit ${exitCode}`, () => withFakeWrangler(({ env, capture }) => {
|
|
const result = spawnSync(process.execPath, [fileURLToPath(new URL('../scripts/deploy-production.mjs', import.meta.url))], {
|
|
env: { ...env, QA_TEST_EXIT: String(exitCode) }, encoding: 'utf8'
|
|
});
|
|
assert.equal(result.status, exitCode);
|
|
const command = JSON.parse(readFileSync(capture, 'utf8'));
|
|
assert.equal(command.secrets.ABC_API_KEY, env.ABC_API_KEY);
|
|
assert.equal(command.mode, 0o600);
|
|
assert.equal(command.secrets.CLOUDFLARE_API_TOKEN, undefined);
|
|
assert.ok(!JSON.stringify(command.args).includes(env.ORG_CONTEXT_KEY_SECRET));
|
|
assert.equal(existsSync(command.path), false);
|
|
}));
|
|
}
|
|
|
|
test('production uploader rejects root uploads and sends every file to the selected commit path', () => withFakeWrangler(({ dir, env, capture }) => {
|
|
const script = fileURLToPath(new URL('../scripts/upload-r2.sh', import.meta.url));
|
|
const refused = spawnSync('bash', [script, 'baron-qa-write'], { env, cwd: dir });
|
|
assert.equal(refused.status, 1);
|
|
assert.equal(existsSync(capture), false);
|
|
mkdirSync(join(dir, 'egbim'));
|
|
mkdirSync(join(dir, 'assets'));
|
|
for (const file of ['index.html', 'write.html', 'detail.html', 'app.js', 'config.js']) writeFileSync(join(dir, 'egbim', file), 'fixture');
|
|
writeFileSync(join(dir, 'assets/styles.css'), 'body {}');
|
|
mkdirSync(join(dir, 'assets/inquiry'));
|
|
writeFileSync(join(dir, 'assets/inquiry/inquiry-hero.webp'), 'fixture-image');
|
|
writeFileSync(join(dir, 'assets/inquiry/entry.js'), 'fixture-entry');
|
|
const uploaded = spawnSync('bash', [script, 'baron-qa-write', 'wrangler.production.toml', 'releases/' + SHA], { env, cwd: dir, encoding: 'utf8' });
|
|
assert.equal(uploaded.status, 0, uploaded.stderr);
|
|
const commands = readFileSync(capture, 'utf8').trim().split('\n').map(JSON.parse);
|
|
assert.equal(commands.length, 8);
|
|
const heroUpload = commands.find(command => command.args[5].endsWith('/assets/inquiry/inquiry-hero.webp'));
|
|
assert.equal(heroUpload.args[heroUpload.args.indexOf('--content-type') + 1], 'image/webp');
|
|
const entryUpload = commands.find(command => command.args[5].endsWith('/assets/inquiry/entry.js'));
|
|
assert.equal(entryUpload.args[entryUpload.args.indexOf('--content-type') + 1], 'application/javascript');
|
|
for (const command of commands) {
|
|
assert.ok(command.args[5].startsWith('baron-qa-write/releases/' + SHA + '/'));
|
|
assert.ok(command.args.includes('wrangler.production.toml'));
|
|
assert.ok(!command.args.includes('baron-qa-pages'));
|
|
}
|
|
writeFileSync(capture, '');
|
|
const staging = spawnSync('bash', [script], { env, cwd: dir, encoding: 'utf8' });
|
|
assert.equal(staging.status, 0, staging.stderr);
|
|
for (const command of readFileSync(capture, 'utf8').trim().split('\n').map(JSON.parse)) {
|
|
assert.ok(command.args[5].startsWith('baron-qa-pages/'));
|
|
assert.ok(!command.args[5].includes('releases/'));
|
|
assert.ok(command.args.includes('wrangler.toml'));
|
|
}
|
|
}));
|
|
|
|
for (const prefix of ['', 'releases/' + SHA]) {
|
|
test(`Worker uses ${prefix ? 'release-specific' : 'legacy staging'} asset keys`, async () => {
|
|
const response = await worker.fetch(new Request('https://qa.example.test/assets/styles.css'), {
|
|
QA_ASSET_PREFIX: prefix, QA_BUCKET: { async get(key) {
|
|
assert.equal(key, (prefix ? prefix + '/' : '') + 'assets/styles.css');
|
|
return { body: 'body {}', httpEtag: 'fixture', writeHttpMetadata(headers) { headers.set('content-type', 'text/css'); } };
|
|
} }
|
|
});
|
|
assert.equal(response.status, 200);
|
|
assert.equal(response.headers.get('x-qa-release'), prefix || null);
|
|
});
|
|
}
|
|
|
|
test('post-deploy verification rejects Hello World, old releases and unprotected write pages', async () => {
|
|
await assert.rejects(verifyProduction(async () => new Response('Hello World!'), SHA));
|
|
const http = (oldRelease = false, unprotected = false) => async (url) => {
|
|
if (url.endsWith('/auth/session')) return Response.json({ authenticated: false, user: null });
|
|
if (url.endsWith('/assets/styles.css')) return new Response('body {}', { headers: { 'content-type': 'text/css', 'x-qa-release': oldRelease ? 'releases/old' : 'releases/' + SHA } });
|
|
return new Response('', { status: unprotected ? 200 : 302, headers: { location: '/auth/login?return_url=write' } });
|
|
};
|
|
await assert.rejects(verifyProduction(http(true), SHA), /deployed commit/);
|
|
await assert.rejects(verifyProduction(http(false, true), SHA), /redirect/);
|
|
await verifyProduction(http(), SHA);
|
|
});
|