Changeset d56d471 in Klonkt for test/gated-settings.test.js


Ignore:
Timestamp:
07/29/2026 12:36:50 PM (6 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
7a93fcf
Parents:
6100ce9
Message:

Een voorstel krijgt een antwoord, een status en een zichtbare kring

Robin meldde drie dingen na de voorstelronde: er lijkt niets te gebeuren, de
status van een voorstel is nergens te volgen, en in /guardian zie je niet wie
de mede-guardians van een kind zijn. Onderzoek op beta wees de kern aan: de
ronde is aangekomen en geteld, maar elk voorstel was shaer:externalEmbeds, en
die stond al aan. Afspelen is nooit voorgesteld, en dat KON ook niet: de knop
"Afspelen voorstellen" verscheen alleen bij embeds === true, en voor een ward
op een andere server is die waarde onbekend, dus null, dus geen knop. Onbekend
is niet uit.

Drie reparaties, een per klacht.

Een: de lus sluit. De server van het kind beantwoordt de Offer die de
beslissing opende, terug naar de voorsteller: Accept als hij settelde op het
voorgestelde, Reject bij het tegendeel. Alleen de stem van het kind-account
telt als uitkomst; een vreemde die onze boeken wil sluiten wordt genegeerd.
Zonder dit kon het scherm van de voorsteller alleen maar eeuwig "wacht"
zeggen, wat er ook gebeurd was: de telling is het prive-grootboek van de
server van het kind.

Twee: de voorsteller houdt een eigen boek bij (ap_gated_sent) en het paneel
toont per ward de stand: wacht, aangenomen, afgewezen, of eerlijk verlopen als
het venster leegliep. Stilte na het venster is geen "loopt nog".

Drie: voor een ward op een andere server toont het paneel nu wel wie de
guardians zijn. Het lidmaatschap is publiek op het actordocument
(shaer:guardians, 2.1); de beschikbaarheid is en blijft het prive-grootboek
van hun server (3.6.1) en wordt alleen benoemd, niet getoond.

Changed files:
src/config/database.js

  • tabel ap_gated_sent (het boek van de voorsteller)
  • kolom proposer op ap_gated_offers, voor het antwoord naar huis

src/services/guardianship/gated.js

  • recordSent/recallSent/settleSent/listSent en sentStatus (puur, getest)
  • rememberGatedOffer onthoudt de voorsteller

src/services/guardianship/handshake.js

  • answerGatedProposer: het settle-antwoord naar de voorsteller
  • de inbox herkent dat antwoord en settelt het eigen boek; alleen het kind-account mag dat

src/routes/guardian.js

  • proposeGated schrijft het boek; dashboardState serveert per ward de voorstellen met status
  • GET /wards/guardians: lokaal met beschikbaarheid, remote de publieke lijst van hun actordocument

src/assets/js/guardian.js

  • de afspeel-knop verschijnt ook bij onbekende embeds-stand (de bug)
  • statusregels onder de instellingen-knoppen
  • remote guardians opgehaald bij het openen van het paneel

src/assets/css/guardian.css

  • g-prop-kleuren: de staat spreekt voor de woorden uit

src/services/i18n.js

  • prop_*- en panel_guards_far-strings, NL/EN/DE

test/gated-settings.test.js

  • de lus sluit: de voorsteller krijgt het antwoord, van alleen het kind
  • het boek: open, aangenomen, afgewezen, verlopen

remarks: 334 tests groen, server start op een schone database. De guardian-kant
(sound-fabrics) en de ward-kant (beta) hebben allebei deze commit nodig: de
knop en de status leven bij de guardian, het antwoord bij het kind.

-robo
Co-Authored-By: Claude Opus 5 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • test/gated-settings.test.js

    r6100ce9 rd56d471  
    147147  assert.equal(database.prepare('SELECT external_embeds FROM sites WHERE slug = ?').get('kid9').external_embeds, 1,
    148148    'two of three agreed, so the ward may see link previews');
     149
     150  // And the loop CLOSES: the ward's server answers the Offer that opened the
     151  // decision, back to the proposer. Without this the proposer's screen can
     152  // only ever say "waiting", forever, whatever actually happened: the tally is
     153  // the ward server's private ledger and nobody else may read it.
     154  const answer = sent.find((x) => x.to === A && (x.act.type === 'Accept' || x.act.type === 'Reject'));
     155  assert.ok(answer, 'the proposer is told the outcome');
     156  assert.equal(answer.act.type, 'Accept', 'it settled on what A proposed');
     157  assert.equal(answer.act.actor, WARD, 'and only the ward may say so');
     158  assert.equal(answer.act.object, 'https://a.test/gated/1', 'referencing the offer it answers');
     159});
     160
     161test("the proposer's own book: sent, answered, or honestly expired", async () => {
     162  const dbMod3 = await import('../src/config/database.js');
     163  const database = dbMod3.default;
     164  const G = await import('../src/services/guardianship/index.js');
     165  const WARD = 'https://kids.example/ap/users/maan';
     166  const ME = 'https://test.example/ap/users/oma1';
     167  database.prepare('INSERT OR IGNORE INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u11', 'u11', 'u11@t', 'x', 'god');
     168  database.prepare('INSERT OR IGNORE INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,0)').run('s11', 'oma1', 'oma1', 'u11');
     169  database.prepare("INSERT OR IGNORE INTO ap_guardianships (slug, role, other_uri, status, offer_id) VALUES ('oma1','guardian',?, 'accepted','o')").run(WARD);
     170  const site = database.prepare('SELECT * FROM sites WHERE slug = ?').get('oma1');
     171
     172  // The route records what it sent; here we do what the route does.
     173  G.gated.recordSent(`${ME}/gated/p1`, 'oma1', WARD, 'shaer:externalPlayback', true);
     174  assert.equal(G.gated.sentStatus(G.gated.recallSent(`${ME}/gated/p1`), Date.now()), 'open');
     175
     176  // A stranger claiming an outcome is not one: only the ward's voice counts.
     177  assert.equal(await G.handleGuardianshipInbox(site, { type: 'Accept', actor: 'https://evil.test/u/x', object: `${ME}/gated/p1` }), false);
     178  assert.equal(G.gated.recallSent(`${ME}/gated/p1`).status, 'open', 'still open: a stranger cannot close our books');
     179
     180  // The ward's server answers: the row settles.
     181  assert.equal(await G.handleGuardianshipInbox(site, { type: 'Accept', actor: WARD, object: `${ME}/gated/p1` }), true);
     182  assert.equal(G.gated.recallSent(`${ME}/gated/p1`).status, 'accepted');
     183  assert.equal(G.gated.sentStatus(G.gated.recallSent(`${ME}/gated/p1`), Date.now()), 'accepted');
     184
     185  // A Reject is an answer too, and lands as one.
     186  G.gated.recordSent(`${ME}/gated/p2`, 'oma1', WARD, 'shaer:externalEmbeds', false);
     187  assert.equal(await G.handleGuardianshipInbox(site, { type: 'Reject', actor: WARD, object: `${ME}/gated/p2` }), true);
     188  assert.equal(G.gated.sentStatus(G.gated.recallSent(`${ME}/gated/p2`), Date.now()), 'rejected');
     189
     190  // Silence past the window is not "still running": it is over, and the
     191  // screen must say so instead of promising forever.
     192  G.gated.recordSent(`${ME}/gated/p3`, 'oma1', WARD, 'shaer:externalEmbeds', true);
     193  const row = G.gated.recallSent(`${ME}/gated/p3`);
     194  assert.equal(G.gated.sentStatus(row, Date.now()), 'open');
     195  assert.equal(G.gated.sentStatus(row, Date.now() + 25 * 60 * 60 * 1000), 'expired');
    149196});
    150197
Note: See TracChangeset for help on using the changeset viewer.