fix!: une lecture qui n'atteint pas son document echoue, elle ne repond plus vide
`fetchReadCap` rendait `false` pour trois situations sans rapport : l'emulation inerte, le document qui n'expose aucune capacite, et le reseau qu'on n'a pas pu interroger. Les trois appelants vivants lisaient ce `false` comme « pas de capacite » et transformaient une panne en affirmation sur le document : `ensureRepoOpen` renoncait en silence et laissait la lecture suivante accuser le lecteur de ne rien detenir, et `documentInboxAddress` repondait « ce document n'a pas d'inbox », ce qu'une application lit comme zero depot. Le telechargement rejette desormais jusqu'a l'appelant, avec le document nomme et la cause d'origine conservee. `false` ne veut plus dire qu'une chose : le document n'expose rien, et c'est une reponse. Le memo garde la meme asymetrie qu'avant. `documentInboxAddress` propage aussi l'echec de sa propre lecture d'adresse : le `catch` qui rendait `undefined` se justifiait par un refus que `mustNotAttempt` a deja ecarte une ligne plus haut, donc il n'avalait que des pannes. `useShapeQuery` franchit la barriere sur chaque document NOMME par `scope.graphs` avant d'ouvrir la requete : capacite demandee au store public, refus a voix haute si elle n'est pas detenue, ouverture et synchronisation. Un document nomme qu'on ne peut pas lire donnait `isSuccess` avec `data: []` apres que le filtre de capacites l'eut vide - la seule reponse que cette surface existe pour reserver a « ce scope ne contient rien ». Restent intacts et succes : `did:ng:i`, `graphs: []`, un document joignable et vide, et tout le cas inerte. `fetchReadCaps` supprime : aucun appelant, et une politique de tolerance par lot sans proprietaire au-dessus d'un appel qui rejette desormais.
This commit is contained in:
@@ -498,6 +498,117 @@ describe("useShapeQuery", () => {
|
||||
unsub();
|
||||
});
|
||||
|
||||
// -- a NAMED document is reached before the query opens, since `1.0.0-dev.9` ---
|
||||
//
|
||||
// `graphs` does not only narrow a search: each entry NAMES a document, and this surface
|
||||
// used to name one, hand it to the engine, and empty the result with the capability
|
||||
// filter - publishing `isSuccess` with `data: []`, which is the one snapshot it exists
|
||||
// to reserve for "this scope holds nothing". The four cases below are the distinct ones:
|
||||
// the document that cannot be reached, the one that can and is empty, the list where
|
||||
// only one member fails, and the emulation not yet in force.
|
||||
|
||||
it("(l) a named document the holder holds no capability for is isError, not an empty success", async () => {
|
||||
const ng = makeFake();
|
||||
const orm = makeOrm();
|
||||
inject(ng, orm);
|
||||
setCurrentUser("kate");
|
||||
// Creating one document is what puts the emulation in force - the cap registry is
|
||||
// inert until a first cap exists, and (o) below is the case where it still is.
|
||||
await createEntityDoc("kate", "protected");
|
||||
const theirs = "did:ng:o:someone-elses";
|
||||
seedEvent(orm, theirs, `${theirs}:q:one`, "Theirs");
|
||||
expect(getCaps().isEnforcing()).toBe(true);
|
||||
|
||||
const obs = useShapeQuery(EventShape, { graphs: [theirs] });
|
||||
const unsub = obs.subscribe(() => {});
|
||||
await tick();
|
||||
await tick();
|
||||
|
||||
const snap = obs.getSnapshot();
|
||||
expect(snap.isError).toBe(true);
|
||||
expect(snap.isSuccess).toBe(false);
|
||||
expect(snap.isPending).toBe(false);
|
||||
expect(String((snap.error as Error).message)).toContain("does not hold this document");
|
||||
// RULE 2, and it is the half a filter could never give: the question was not put at
|
||||
// all. Kate's session never asked the engine about a document she cannot open.
|
||||
expect(orm.openCount()).toBe(0);
|
||||
unsub();
|
||||
});
|
||||
|
||||
it("(m) a named document that IS reachable and holds nothing of the shape is isSuccess with data []", async () => {
|
||||
const ng = makeFake();
|
||||
const orm = makeOrm();
|
||||
inject(ng, orm);
|
||||
setCurrentUser("liam");
|
||||
// His own, so the barrier passes; nothing of the shape is ever seeded into it.
|
||||
const mine = await createEntityDoc("liam", "protected");
|
||||
expect(getCaps().isEnforcing()).toBe(true);
|
||||
|
||||
const obs = useShapeQuery(EventShape, { graphs: [mine] });
|
||||
const unsub = obs.subscribe(() => {});
|
||||
await tick();
|
||||
await tick();
|
||||
|
||||
// THE line that must not move. An empty document is a document that answered, and
|
||||
// the repair above would only have relocated the lie if this had become an error.
|
||||
const snap = obs.getSnapshot();
|
||||
expect(snap.isSuccess).toBe(true);
|
||||
expect(snap.isError).toBe(false);
|
||||
expect(snap.isPending).toBe(false);
|
||||
expect(snap.data).toEqual([]);
|
||||
unsub();
|
||||
});
|
||||
|
||||
it("(n) one unreachable document in a named list refuses the whole scope", async () => {
|
||||
const ng = makeFake();
|
||||
const orm = makeOrm();
|
||||
inject(ng, orm);
|
||||
setCurrentUser("maya");
|
||||
const mine = await createEntityDoc("maya", "protected");
|
||||
seedEvent(orm, mine, `${mine}:q:one`, "Mine");
|
||||
const theirs = "did:ng:o:not-mayas";
|
||||
seedEvent(orm, theirs, `${theirs}:q:one`, "Theirs");
|
||||
|
||||
const obs = useShapeQuery(EventShape, { graphs: [mine, theirs] });
|
||||
const unsub = obs.subscribe(() => {});
|
||||
await tick();
|
||||
await tick();
|
||||
|
||||
// Not a short list. Answering "Mine" alone would say the scope holds one object when
|
||||
// the truth is that half of it was never asked - the tolerance `readUnion` was
|
||||
// removed for in `1.0.0-dev.8`, arriving by another door.
|
||||
const snap = obs.getSnapshot();
|
||||
expect(snap.isError).toBe(true);
|
||||
expect(snap.isSuccess).toBe(false);
|
||||
expect(snap.data).toEqual([]);
|
||||
expect(orm.openCount()).toBe(0);
|
||||
unsub();
|
||||
});
|
||||
|
||||
it("(o) INERT: before the first cap exists a named document reads, and nothing refuses", async () => {
|
||||
const ng = makeFake();
|
||||
const orm = makeOrm();
|
||||
inject(ng, orm);
|
||||
setCurrentUser("nora");
|
||||
// No document created here, so no cap was ever minted: the emulation is not in force,
|
||||
// which is the state every early path runs in. A barrier that fired here would break
|
||||
// all of them.
|
||||
const early = "did:ng:o:early-doc";
|
||||
seedEvent(orm, early, `${early}:q:one`, "Early");
|
||||
expect(getCaps().isEnforcing()).toBe(false);
|
||||
|
||||
const obs = useShapeQuery(EventShape, { graphs: [early] });
|
||||
const unsub = obs.subscribe(() => {});
|
||||
await tick();
|
||||
await tick();
|
||||
|
||||
const snap = obs.getSnapshot();
|
||||
expect(snap.isError).toBe(false);
|
||||
expect(snap.isSuccess).toBe(true);
|
||||
expect(titles(snap.data)).toEqual(["Early"]);
|
||||
unsub();
|
||||
});
|
||||
|
||||
// -- teardown ------------------------------------------------------------
|
||||
|
||||
it("(k) the last unsubscribe closes the shape query", async () => {
|
||||
|
||||
Reference in New Issue
Block a user