Any member can destroy a room; observer sees highlighted whose turn it is
deploy / deploy (push) Canceled after 0s

- DELETE /api/rooms/:id + destroy button in the header of human pages
  (participant and observer views), with confirmation
- turn indicator now names and highlights the expected participant(s)
This commit is contained in:
2026-09-06 20:44:37 +03:00
parent 91fc74efc1
commit 1fa7e733ba
9 changed files with 164 additions and 18 deletions
+33
View File
@@ -116,6 +116,17 @@ test('HTTP: cannot read room by id without token; unknown routes 404; human page
// wrong observer token -> forbidden
assert.equal((await fetch(`${baseUrl}/o/${created.room_id}/nope`)).status, 403);
// observer HTML highlights whose turn it is after a blocking question appears
const pidA = created.participants[0].id;
const tokB = created.invite_urls[1].split('/').pop();
await fetch(`${baseUrl}/api/rooms/${created.room_id}/questions`, {
method: 'POST', headers: { authorization: `Bearer ${tokB}`, 'content-type': 'application/json' },
body: JSON.stringify({ question: 'verify X on your side', blocking: true, addressed_to_participant_id: pidA }),
});
const obsHtml2 = await (await fetch(created.observer_url)).text();
assert.match(obsHtml2, /turn-role/);
assert.match(obsHtml2, /windows-1c/);
const r = await fetch(`${baseUrl}/api/rooms/${created.room_id}`);
assert.equal(r.status, 403);
const r2 = await fetch(`${baseUrl}/api/rooms/${created.room_id}?token=${created.participants[0].token}`);
@@ -136,6 +147,28 @@ test('HTTP: cannot read room by id without token; unknown routes 404; human page
assert.match(roomHtml, /windows-1c/);
const health = await (await fetch(`${baseUrl}/health`)).json();
assert.ok(health.ok);
// any member can destroy (checked last — it deletes the room)
const del = await fetch(`${baseUrl}/api/rooms/${created.room_id}`, {
method: 'DELETE', headers: { authorization: `Bearer ${obsToken}` },
});
assert.equal(del.status, 200);
assert.equal(
(await fetch(`${baseUrl}/api/rooms/${created.room_id}`, { headers: { authorization: `Bearer ${created.participants[0].token}` } })).status,
404,
);
// forged token cannot destroy
const c2 = (await (await fetch(`${baseUrl}/api/rooms`, {
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(ROOM_PAYLOAD),
})).json()) as any;
assert.equal(
(await fetch(`${baseUrl}/api/rooms/${c2.room_id}`, { method: 'DELETE', headers: { authorization: 'Bearer nope' } })).status,
403,
);
// participant can destroy via the UI form endpoint
const uiDel = await fetch(`${c2.invite_urls[0]}/destroy`, { method: 'POST' });
assert.equal(uiDel.status, 200);
assert.match(await uiDel.text(), /destroyed/i);
} finally {
app.close();
}