Files
ipx/tests/ui/fixtures/serve.js
rays 9a41641eaa Signals: an admin chooses whether the server's TypeSafe key pays for everyone (#271)
With signals_on_server set and TYPESAFE_KEY present, the judge asks Jev
about everyone's Signals on the server's key; otherwise each person turns
Signals on in Settings with a key of their own, which is now checked with
TypeSafe before it is saved. The Signals folder shows only when they are on.

The browser suite's daemon inherited the shell's TYPESAFE_KEY, so fixture
feeds were sent to the real TypeSafe to be categorised; it is blanked now,
and an empty key counts as none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:18:03 +00:00

58 lines
2.8 KiB
JavaScript

// Serves the fixture feeds so the daemon under test has something real to scan.
const http = require('http');
const fs = require('fs');
const path = require('path');
const dir = __dirname;
const port = Number(process.env.FIXTURE_PORT || 8792);
http.createServer((req, res) => {
// Jev, for Signals (#254): sure of an item whose text has a question's keyword in it. Refuses
// a request in the shape TypeSafe refuses, as it does (#257).
if (req.method === 'POST' && req.url === '/jev') {
let body = '';
req.on('data', c => { body += c; });
req.on('end', () => {
// As TypeSafe answers a key it does not know (#271).
if (req.headers.authorization === 'Bearer bad-key') {
res.writeHead(401, { 'content-type': 'application/json' }).end('{"detail":"invalid api key"}');
return;
}
const { state, questions } = JSON.parse(body);
if (Object.values(questions).some(q => typeof q.criteria !== 'object')) {
res.writeHead(422, { 'content-type': 'application/json' }).end('{"detail":"criteria should be an object"}');
return;
}
const title = String(state).toLowerCase();
const answers = {};
for (const [id, q] of Object.entries(questions)) {
// A key's check asks about no Signal's words.
const words = /described by these words: ([^.]*)\./.exec(q.instructions)?.[1].split(', ') ?? [];
answers[id] = { noul: words.some(w => title.includes(w.toLowerCase())) ? 0.9 : 0.1 };
}
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ answers }));
});
return;
}
// Dated today, since a Signal looks at the last 30 days only.
if (req.url === '/signal.xml') {
const now = new Date().toUTCString();
const item = (guid, title) => `<item><guid>${guid}</guid><title>${title}</title><pubDate>${now}</pubDate></item>`;
res.writeHead(200, { 'content-type': 'application/xml' }).end(`<?xml version="1.0"?><rss version="2.0"><channel>
<title>Signal Show</title>${item('sig-1', 'Monaco qualifying')}${item('sig-2', 'Baking bread')}</channel></rss>`);
return;
}
const name = decodeURIComponent(req.url.split('?')[0].replace(/^\//, '')) || 'index';
const file = path.join(dir, path.basename(name));
fs.readFile(file, (err, body) => {
if (err) { res.writeHead(404).end('no'); return; }
const type = file.endsWith('.mp3') ? 'audio/mpeg'
: file.endsWith('.opml') ? 'text/x-opml'
// Artwork as an image, or /api/art refuses it as not one.
: file.endsWith('.jpg') ? 'image/jpeg'
: file.endsWith('.html') ? 'text/html' : 'application/xml';
res.writeHead(200, { 'content-type': type, 'content-length': body.length });
res.end(body);
});
}).listen(port, '127.0.0.1', () => console.log(`fixtures on ${port}`));