Checkpoint peering identity and opt-in discovery

This commit is contained in:
Codex committed 2026-10-13 22:00:54 +02:00
1 parent eba9b50e30
commit 47de081721
44 files changed
+1557 -55

No files matched your search

+31 -2
View File
@@ -8,7 +8,16 @@ const root = path.resolve(__dirname, '..'), dir = fs.mkdtempSync(path.join(os.tm
const project = 'mc-qa-' + crypto.randomBytes(3).toString('hex'), port = process.env.QA_PORT || '8180';
const library = path.join(dir, 'library'), data = path.join(dir, 'data'), tokenFile = path.join(dir, 'token');
const directionQa = process.argv.includes('--direction-ui');
const peerGatewayQa = process.argv.includes('--peer-gateway');
fs.mkdirSync(path.join(data, 'scraper'), { recursive: true }); fs.mkdirSync(path.join(data, 'sentinel'));
if (peerGatewayQa) {
const peerData = path.join(data, 'peering');
const peerUsage = path.join(data, 'peering-usage');
fs.mkdirSync(peerUsage, { recursive: true });
const { PeeringState } = require('../peering/state');
if (process.platform === 'win32' && !process.env.OPENSSL_BIN) process.env.OPENSSL_BIN = 'C:\\Program Files\\Git\\usr\\bin\\openssl.exe';
new PeeringState(peerData).generateIdentity('ComposeQA');
}
fs.writeFileSync(path.join(data, 'scraper', 'queue.json'), JSON.stringify({ paused: true, queue: [], settings: { autoUpdateEnabled: false } }));
fs.writeFileSync(tokenFile, crypto.randomBytes(32).toString('hex'), { mode: 0o600 });
const png = new Canvas(180, 270, [60, 90, 140]).png();
@@ -28,8 +37,9 @@ for (const [index, name] of ['Moon Garden', 'Before the Rain', 'Iron Harbor'].en
status: index ? 'completed' : 'ongoing', tags: ['Fantasy', 'Demo'] }));
}
const env = { ...process.env, LIBRARY_PATH: library, DATA_PATH: data, SCRAPER_DATA_PATH: path.join(data, 'scraper'), SENTINEL_DATA_PATH: path.join(data, 'sentinel'),
SCRAPER_TOKEN_PATH: tokenFile, READER_PORT: port, READER_BIND: '127.0.0.1', COOKIE_SECURE: '0', APP_IMAGE: process.env.APP_IMAGE || 'mangacircus:local' };
const args = ['compose', '-p', project, '-f', path.join(root, 'docker-compose.yml')];
SCRAPER_TOKEN_PATH: tokenFile, READER_PORT: port, READER_BIND: '127.0.0.1', COOKIE_SECURE: '0', APP_IMAGE: process.env.APP_IMAGE || 'mangacircus:local',
...(peerGatewayQa ? { PEER_DATA_PATH: path.join(data, 'peering'), PEER_USAGE_PATH: path.join(data, 'peering-usage'), PEER_PORT: process.env.QA_PEER_PORT || '8193' } : {}) };
const args = ['compose', '-p', project, '-f', path.join(root, 'docker-compose.yml'), ...(peerGatewayQa ? ['--profile', 'peering'] : [])];
const docker = (...command) => new Promise((resolve, reject) => {
const child = spawn('docker', [...args, ...command], { cwd: root, env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
let result = ''; child.stdout.on('data', chunk => result += chunk); child.stderr.on('data', chunk => process.stderr.write(chunk));
@@ -46,6 +56,15 @@ let keep = false;
await docker('config', '-q'); await docker('up', '-d', '--no-build');
const url = `http://127.0.0.1:${port}`;
await until(() => fetch(url + '/health').then(r => r.ok), 'Reader did not start');
if (peerGatewayQa) {
const https = require('node:https');
try {
await until(() => new Promise(resolve => https.get({ hostname: '127.0.0.1', port: Number(env.PEER_PORT), path: '/health', rejectUnauthorized: false },
response => { response.resume(); resolve(response.statusCode === 200); }).on('error', () => resolve(false))), 'Peer gateway did not start');
} catch (error) { console.error(await docker('logs', '--tail', '30', 'peer-gateway').catch(() => 'Gateway logs unavailable')); throw error; }
const gateway = await docker('ps', '--format', 'json');
assert.ok(gateway.includes('peer-gateway'), 'Peer gateway missing from isolated Compose');
}
const signup = await fetch(url + '/api/signup', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'qa', password: 'disposable-local-test' }) });
assert.equal(signup.status, 200); const cookie = signup.headers.get('set-cookie').split(';')[0];
const api = (endpoint, body) => fetch(url + endpoint, { headers: { cookie, 'content-type': 'application/json' }, ...(body ? { method: 'POST', body: JSON.stringify(body) } : {}) });
@@ -79,6 +98,16 @@ let keep = false;
const image = await api('/lib/' + encodeURIComponent('Moon Garden') + '/cover.png'); assert.equal(image.status, 200);
state = await (await api('/api/admin/scraper')).json(); assert.equal(state.paused, true);
if (process.argv.includes('--settings-ui')) await require('./test-settings-ui')(url, cookie);
if (process.argv.includes('--discovery-ui')) {
const login = await api('/api/login', { name: 'qa', password: 'disposable-local-test' });
assert.equal(login.status, 200);
await require('./test-discovery-ui')(url, login.headers.get('set-cookie').split(';')[0]);
}
if (process.argv.includes('--peer-ui')) {
const login = await api('/api/login', { name: 'qa', password: 'disposable-local-test' });
assert.equal(login.status, 200);
await require('./test-peer-ui')(url, login.headers.get('set-cookie').split(';')[0]);
}
if (process.argv.includes('--collections-ui')) {
// Each UI suite tests logout/ordinary-reader boundaries. Give the next suite
// a fresh disposable admin session rather than reusing a logged-out cookie.