Tests for Signals' judging, pacing, icons and API (#398)

signals.rs's pass, judge, pace, back_off, check_key and pick_icon ran
only against a live Jev. In cargo tests ask now goes to a stand-in for
it, served from a thread of its own so every test shares it, answering
by key as TypeSafe would: a pass judges each new item once, keeps its
verdicts and bills its owner's tokens on their own key; a refused key
stops its owner's judging, not anyone else's, and is not sent again; a
429 stops the pass and the next call to Jev waits out Retry-After; a
422 stops the pass rather than repeating on every item; a key check
costs tokens; an icon is one Jev was offered, on the server's key.

The API: a Signal made, its words trimmed and deduplicated, bad names,
words, icons and descriptions refused, renamed and re-worded, its icon
cleared, its picked items listed, deleted with its verdicts; someone
else's is 404 every way. In the browser, a Signal renamed and deleted
from its own page. The coverage run runs the browser Signals tests too
(IPX_SIGNALS_TESTS=1): Jev there is the fixture server's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 04:15:49 +00:00
parent 3bb59c215a
commit 980fbc3f66
5 changed files with 292 additions and 2 deletions

View File

@@ -2383,6 +2383,32 @@ signalsTest('a Signal with no key to judge it links to where one is got and wher
await page.request.delete(`/api/signals/${id}`);
});
signalsTest('a Signal is renamed from its own page and deleted from there', async ({ page }) => {
// #398: signalHead's edit and delete, which nothing clicked.
expect((await page.request.patch('/api/me', { data: { typesafe_key: 'test-key' } })).status()).toBe(204);
const { id } = await (await page.request.post('/api/signals', { data: { name: 'Baking', keywords: ['bread'] } })).json();
try {
await page.reload();
await page.evaluate((id: number) => selectFeed(':signal:' + id), id);
await page.locator('#content [data-a="settings"]').click();
await expect(page.locator('#sgname')).toHaveValue('Baking');
await page.locator('#sgname').fill('Bread and cakes');
await page.locator('#sgsave').click();
await expect(page.locator('#feedlist .feed.child', { hasText: 'Bread and cakes' })).toBeVisible();
const one = async () => (await (await page.request.get('/api/signals')).json() as Signal[]).find(s => s.id === id);
expect((await one())?.name).toBe('Bread and cakes');
page.once('dialog', d => d.accept());
await page.locator('#content [data-a="rm"]').click();
await expect(page.locator('.toast', { hasText: 'Deleted' })).toBeVisible();
await expect(page.locator('#feedlist .feed.child', { hasText: 'Bread and cakes' })).toHaveCount(0);
expect(await one()).toBeUndefined();
} finally {
await page.request.delete(`/api/signals/${id}`);
await page.request.patch('/api/me', { data: { typesafe_key: null } });
}
});
signalsTest('Signals are turned on in Settings with a key TypeSafe takes, and off again by forgetting it', async ({ page }) => {
// #271. The test daemon has no key of its own, so each person brings theirs.
await page.request.patch('/api/me', { data: { typesafe_key: null } });