Changeset 6c152a5 in Klonkt for test/guardianship.test.js
- Timestamp:
- 07/28/2026 08:15:31 PM (6 weeks ago)
- Branches:
- main
- Children:
- 6eab7e9
- Parents:
- 742ba7e
- File:
-
- 1 edited
-
test/guardianship.test.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
test/guardianship.test.js
r742ba7e r6c152a5 133 133 assert.equal(G.isHelpRequest({}), false); 134 134 }); 135 136 // ── §3.2/§3.3: ending a guardianship ───────────────────────────────────── 137 // This used to be a local delete that never left the building: the guardian's 138 // dashboard forgot the ward, while the ward's server kept listing them in 139 // shaer:guardians. Robin calls that a bug, and it is: the Undo has to travel. 140 // At this point in the file the kid has two guardians, parent and gran. 141 142 test('a guardian leaving sends an Undo that both sides act on (§3.2)', async () => { 143 assert.deepEqual(G.listGuardians('kid').map((g) => g.other_uri).sort(), [ME, GRAN].sort(), 'two guardians to start'); 144 145 const r = await G.endGuardianship(gran, KID); 146 assert.equal(r.status, 202); 147 assert.equal(r.delivered, true, 'the Undo went out, it is not a local delete'); 148 149 // The ward's own actor document is the thing that had to change. 150 assert.deepEqual(G.listGuardians('kid').map((g) => g.other_uri), [ME]); 151 assert.deepEqual(AP.buildActor('https://test.example', kid)['shaer:guardians'], [ME]); 152 assert.deepEqual(G.listWards('gran'), [], 'and the leaving guardian lost the ward'); 153 assert.deepEqual(G.listWards('parent').map((w) => w.other_uri), [KID], 'the other guardian stays'); 154 }); 155 156 test('the last guardian cannot walk out alone: that is emancipation (§3.4)', async () => { 157 const r = await G.endGuardianship(parent, KID); 158 assert.equal(r.status, 409); 159 assert.equal(r.error, 'would_emancipate'); 160 // Nothing moved on either side. Emptying shaer:guardians takes the flow of 161 // §3.4 (three consenting adults, or a majority plus two witnesses), never one 162 // party's click. 163 assert.deepEqual(G.listGuardians('kid').map((g) => g.other_uri), [ME]); 164 assert.deepEqual(G.listWards('parent').map((w) => w.other_uri), [KID]); 165 }); 166 167 test('an Undo for a ward that is not yours is refused', async () => { 168 const r = await G.endGuardianship(gran, KID); // gran already left 169 assert.equal(r.status, 404); 170 assert.equal(r.error, 'not_my_ward'); 171 }); 172 173 test('the same Undo over C2S takes the same path', async () => { 174 // A Guardian app POSTs this to its own outbox; the dashboard button calls 175 // endGuardianship directly. One path, so the two cannot drift apart. 176 const undo = { type: 'Undo', object: { type: 'Relationship', subject: KID, relationship: 'shaer:Guardian', object: GRAN } }; 177 const mine = await G.handleGuardianshipOutbox(gran, undo); 178 assert.equal(mine.status, 404, 'gran no longer guards the kid'); 179 180 // And you cannot end someone else's relation by describing it. 181 const notMine = await G.handleGuardianshipOutbox(parent, undo); 182 assert.equal(notMine.status, 403); 183 assert.equal(notMine.error, 'not_your_relation'); 184 }); 185 186 test('an inbound Undo from someone who is not the guardian changes nothing', async () => { 187 const before = G.listGuardians('kid').map((g) => g.other_uri); 188 await G.handleGuardianshipInbox(kid, { 189 actor: GRAN, // gran claims to end PARENT's relation 190 type: 'Undo', object: { type: 'Relationship', subject: KID, relationship: 'shaer:Guardian', object: ME }, 191 }); 192 assert.deepEqual(G.listGuardians('kid').map((g) => g.other_uri), before); 193 }); 194 195 test('a ward on this same instance is updated even though nothing is delivered', async () => { 196 // The browser found this: an inbox on this machine is not reachable over HTTP 197 // from this machine (nor should it be), so a co-located ward never receives 198 // the Undo. The guardian's side had dropped the ward while the ward's side 199 // still listed the guardian. Each instance must write what it hosts. 200 const kid2 = site('s4', 'kid2'); 201 const g1 = site('s5', 'g1'); 202 const g2 = site('s6', 'g2'); 203 const [KID2, G1, G2] = [A('kid2'), A('g1'), A('g2')]; 204 205 const o1 = await G.handleGuardianshipOutbox(g1, { 206 type: 'Offer', object: { type: 'Relationship', subject: KID2, relationship: 'shaer:Guardian', object: G1 } }); 207 await G.handleGuardianshipOutbox(kid2, { type: 'Accept', object: o1.id }); 208 const o2 = await G.handleGuardianshipOutbox(g2, { 209 type: 'Offer', object: { type: 'Relationship', subject: KID2, relationship: 'shaer:Guardian', object: G2 } }); 210 await G.handleGuardianshipOutbox(kid2, { type: 'Accept', object: o2.id }); 211 await G.handleGuardianshipOutbox(g1, { type: 'Accept', object: o2.id }); 212 assert.deepEqual(G.listGuardians('kid2').map((g) => g.other_uri).sort(), [G1, G2].sort()); 213 214 // Now deliver nothing at all, the way a loopback inbox behaves in practice. 215 const wired = { 216 selfId: A, 217 localSlug: (uri) => (uri.startsWith('https://test.example/ap/users/') ? uri.split('/').pop() : null), 218 deriveHandle: (uri) => '@' + uri.split('/').pop() + '@test.example', 219 fetchActor: async () => null, 220 deliverTo: async () => ({ delivered: false }), 221 onEvent: null, 222 }; 223 G.wireHandshake(wired); 224 const r = await G.endGuardianship(g2, KID2); 225 assert.equal(r.status, 202); 226 assert.equal(r.delivered, false, 'nothing went over the wire'); 227 assert.deepEqual(G.listWards('g2'), [], "the guardian's side is clear"); 228 assert.deepEqual(G.listGuardians('kid2').map((g) => g.other_uri), [G1], "and so is the ward's"); 229 assert.deepEqual(AP.buildActor('https://test.example', kid2)['shaer:guardians'], [G1]); 230 231 // Even undelivered, it must not empty the set: that is still emancipation. 232 const last = await G.endGuardianship(g1, KID2); 233 assert.equal(last.status, 409); 234 assert.deepEqual(G.listGuardians('kid2').map((g) => g.other_uri), [G1]); 235 });
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)