Write the failing tests for direct sign-up to an event

This commit is contained in:
Sylvain Duchesne
2026-11-06 22:57:46 +01:00
parent f45fc564ec
commit 4abab5a55a
11 changed files with 909 additions and 11 deletions
+46
View File
@@ -118,12 +118,14 @@ import { tmpdir } from 'node:os';
import { join as joinPath } from 'node:path';
import { chromium, type Browser, type Page } from 'playwright';
import {
appActorUri,
attendeesCollectionUri,
linkId,
MEMBER_URN_PREFIX,
memberUrnOf,
participationSecretOf,
participationsCollectionOf,
postOutboxActivity,
recordUri,
asRecord,
type ApodsSession,
@@ -1291,6 +1293,49 @@ async function leaveAfterContact(): Promise<void> {
});
}
/**
* A direct sign-up to the event itself (FP-001): Alice posts a Join naming E, the way
* the app does; her participations collection then lists E, under the same audience as
* a meeting point membership — her /contacts group only.
*/
async function directSignup(): Promise<void> {
const G = 'direct sign-up to E (Alice; Bob her contact, Carol not)';
const E = (): string => need(S.E, 'E');
const listsE = async (): Promise<{ ok: boolean; items: string[] }> => {
const alice = need(S.alice, 'Alice');
const uri = need(await ownParticipations(alice), 'her participations collection');
const r = await refIn(uri, E(), true, alice.token);
return { ok: r.ok, items: r.items };
};
await m.hard('direct.join', G, 'Alice posts a Join naming E, to the app actor alone; her own participations collection lists E', 'items ∋ E within 30 s (her read)', async () => {
await postOutboxActivity(need(S.alice, 'Alice'), 'Join', E(), appActorUri(config));
const r = await listsE();
return { ok: r.ok, actual: `items=${JSON.stringify(r.items)}` };
});
await m.hard('direct.non-contact-refused', G, "a direct sign-up is not listable by a non-contact: Carol is refused Alice's participations collection although it now lists E (URI handed out of band)", 'refused (403, or the pod\'s 404 while Alice reads it 200)', async () => {
const alice = need(S.alice, 'Alice');
const listed = await listsE();
need(listed.ok, 'the direct sign-up landed (direct.join)');
const refusal = await collectionRefusal(need(await ownParticipations(alice), 'her participations collection'), need(S.carol, 'Carol').token, alice, AUTHN);
return { ok: refusal.ok, actual: refusal.actual };
});
await m.hard('direct.contact-lists', G, "a contact lists the direct sign-ups: Bob, her contact, reads Alice's participations collection (linked from her profile) and finds E", '200, items ∋ E', async () => {
need(S.contacts, 'the contact pair (contact.accepted)');
const profile = need(await profileUriOf(need(S.alice, 'Alice').webId), "the profile Alice's public actor document links");
const link = need(participationsCollectionOf(parseJson((await http('GET', profile, { token: need(S.bob, 'Bob').token })).text)), "the participations link on Alice's profile, read by Bob");
const r = await poll(() => http('GET', link, { token: need(S.bob, 'Bob').token }), v => v.status === 200 && itemUris(parseJson(v.text)).includes(E()), 30000);
const items = itemUris(parseJson(r.text));
return { ok: r.status === 200 && items.includes(E()), actual: `HTTP ${r.status} items=${JSON.stringify(items)}` };
});
await m.hard('direct.leave', G, 'Alice posts a Leave naming E: her participations collection no longer lists it (the group leaves the event as it found it)', 'items ∌ E within 30 s (her read)', async () => {
const alice = need(S.alice, 'Alice');
await postOutboxActivity(alice, 'Leave', E(), appActorUri(config));
const uri = need(await ownParticipations(alice), 'her participations collection');
const r = await refIn(uri, E(), false, alice.token);
return { ok: r.ok, actual: `HTTP ${r.status} items=${JSON.stringify(r.items)}` };
});
}
/**
* Who reads a person's participations is her /contacts WAC group, through ONE
@@ -1845,6 +1890,7 @@ async function cleanupAccounts(): Promise<void> {
await contactChannel();
await afterContact();
await leaveAfterContact();
await directSignup();
await visibility();
await forgedContact();
await contactChanges();