0455a408b6
L'objectif acté était deux appels spécifiques au polyfill, voire un. Il en publiait
quatre. Chacun des trois de trop était une raison que la BIBLIOTHÈQUE a, pas un besoin
qu'une application a :
- **`configureStoreRegistry`** existait parce qu'il y a deux internes à câbler — le SDK
injecté d'un côté, la session de l'autre. Vu de l'appelant, les deux disent « voici ce
qu'il te faut pour tourner ». Replié dans `configure`, qui prend désormais
`getSession` / `normalizeId` / `pointerGuard`.
- **`setCurrentUser`** n'a plus lieu d'être publié depuis que le portail d'accès est
passé dans le polyfill : c'est lui qui pose l'identité. Et une application qui nomme
sa propre identité est exactement le geste qui inverse le modèle — il ne doit pas
exister d'appel publié vers lequel se tourner. Le harnais e2e, lui, joue plusieurs
identités sur une même page ; il y accède par le chemin interne, ce qu'un harnais a
le droit de faire et une application non.
- **`connectedUser`** est maintenant attendu DANS `ensureIdentity`. Ce n'était pas une
commodité : la suite applicative avait montré qu'une app devait l'attendre elle-même,
sinon une note qu'on venait de lui partager se lisait comme illisible. J'avais traité
le symptôme dans l'app d'exemple ; le défaut était côté bibliothèque. En amont, ouvrir
la session EST la connexion — aucune application n'attend un second appel.
Reste donc `configure({ … })`, plus `await ensureIdentity()` dont le site d'appel
survit à la migration : une application attendra toujours une session avant de rendre.
Le test étendu hier a fait son travail : les deux contrôles de contrat sont passés au
rouge sur `configureStoreRegistry`, `connectedUser` et `StoreRegistryDeps` dès que la
surface a bougé.
180 tests unitaires, e2e 40/40 (3,4 min) et applicatif 10/10 (0,8 min).
101 lines
4.0 KiB
TypeScript
101 lines
4.0 KiB
TypeScript
import { test, expect, mock, beforeEach } from "bun:test";
|
|
import { docCreate, sparqlUpdate, sparqlQuery } from "../src/surface/docs";
|
|
|
|
// The reach guard is process-wide: once ANY cap exists it applies to every reader.
|
|
// This suite declares none, so it must not inherit another suite's enforcement.
|
|
beforeEach(() => {
|
|
resetCaps();
|
|
setCurrentUser(null);
|
|
});
|
|
import * as ngProxy from "../src/surface/ng-proxy";
|
|
|
|
// NOTE ORDER: the "not configured → throw" case MUST run before any configure()
|
|
// call, because configure() sets a module-level singleton with no public reset.
|
|
|
|
test("throws a clear error when configure() was not called", async () => {
|
|
await expect(docCreate("sid", "Graph", "data:graph", "store")).rejects.toThrow(
|
|
/configure\(\) must be called before use/,
|
|
);
|
|
await expect(sparqlUpdate("sid", "INSERT DATA {}")).rejects.toThrow(
|
|
/configure\(\) must be called before use/,
|
|
);
|
|
await expect(sparqlQuery("sid", "SELECT * {}")).rejects.toThrow(
|
|
/configure\(\) must be called before use/,
|
|
);
|
|
});
|
|
|
|
// From here on, a fake real `ng` is injected via configure().
|
|
import { configure } from "../src/index";
|
|
import { setCurrentUser } from "../src/shared-wallet/bootstrap";
|
|
import { resetCaps } from "../src/shared-wallet/bootstrap";
|
|
|
|
function fakeNg() {
|
|
return {
|
|
doc_create: mock(async (..._a: unknown[]) => "did:ng:o:new-doc"),
|
|
sparql_update: mock(async (..._a: unknown[]) => undefined),
|
|
sparql_query: mock(async (..._a: unknown[]) => ({ results: { bindings: [] } })),
|
|
// A sentinel: makeNg(), if ever used, would `.bind` and call THIS through
|
|
// the JS Proxy. We assert the primitives call the raw fns above directly.
|
|
};
|
|
}
|
|
|
|
function inject() {
|
|
const ng = fakeNg();
|
|
configure({ ng: ng as any, useShape: (() => {}) as any });
|
|
return ng;
|
|
}
|
|
|
|
test("docCreate calls the real injected ng.doc_create with the exact args", async () => {
|
|
const ng = inject();
|
|
const nuri = await docCreate("sid-1", "Graph", "data:graph", "store", undefined);
|
|
expect(nuri).toBe("did:ng:o:new-doc");
|
|
expect(ng.doc_create).toHaveBeenCalledTimes(1);
|
|
expect(ng.doc_create.mock.calls[0]).toEqual(["sid-1", "Graph", "data:graph", "store", undefined]);
|
|
});
|
|
|
|
test("sparqlUpdate forwards (sessionId, query, anchor) to the real ng.sparql_update", async () => {
|
|
const ng = inject();
|
|
await sparqlUpdate("sid-2", "INSERT DATA { GRAPH <did:ng:o:a> { <s> <p> <o> } }", "did:ng:o:a");
|
|
expect(ng.sparql_update).toHaveBeenCalledTimes(1);
|
|
expect(ng.sparql_update.mock.calls[0]).toEqual([
|
|
"sid-2",
|
|
"INSERT DATA { GRAPH <did:ng:o:a> { <s> <p> <o> } }",
|
|
"did:ng:o:a",
|
|
]);
|
|
});
|
|
|
|
test("sparqlUpdate passes anchor=undefined when omitted", async () => {
|
|
const ng = inject();
|
|
await sparqlUpdate("sid-3", "INSERT DATA {}");
|
|
expect(ng.sparql_update.mock.calls[0]).toEqual(["sid-3", "INSERT DATA {}", undefined]);
|
|
});
|
|
|
|
test("sparqlQuery forwards (sessionId, query, base, anchor) and returns the raw result", async () => {
|
|
const ng = inject();
|
|
const res = await sparqlQuery("sid-4", "SELECT ?e { GRAPH <g> { ?s ?p ?e } }", undefined, "did:ng:o:g");
|
|
expect(res).toEqual({ results: { bindings: [] } });
|
|
expect(ng.sparql_query).toHaveBeenCalledTimes(1);
|
|
expect(ng.sparql_query.mock.calls[0]).toEqual([
|
|
"sid-4",
|
|
"SELECT ?e { GRAPH <g> { ?s ?p ?e } }",
|
|
undefined,
|
|
"did:ng:o:g",
|
|
]);
|
|
});
|
|
|
|
test("the primitives do NOT route through the public ng proxy (makeNg)", async () => {
|
|
// makeNg builds a JS Proxy over the injected ng. If a primitive went through
|
|
// it, calls would land on the proxy's `get` trap, not on our raw mock fns.
|
|
// Spy on makeNg: it must never be invoked by the docs primitives.
|
|
const spy = mock(ngProxy.makeNg);
|
|
const ng = inject();
|
|
await docCreate("sid", "Graph", "data:graph", "store");
|
|
await sparqlUpdate("sid", "INSERT DATA {}");
|
|
await sparqlQuery("sid", "SELECT * {}");
|
|
expect(spy).toHaveBeenCalledTimes(0);
|
|
// And the raw injected fns were reached directly:
|
|
expect(ng.doc_create).toHaveBeenCalledTimes(1);
|
|
expect(ng.sparql_update).toHaveBeenCalledTimes(1);
|
|
expect(ng.sparql_query).toHaveBeenCalledTimes(1);
|
|
});
|