Files
ng-eventually/packages/polyfill/test/docs.test.ts
T
Sylvain Duchesne 737729c9ce refactor: le paquet s'appelle polyfill, « SDK » désigne celui de NextGraph
Le nom @ng-eventually/sdk entrait en collision avec le SDK de NextGraph, dont
ce paquet est justement un polyfill. Impossible d'écrire « le SDK » sans lever
l'ambiguïté à chaque phrase — et le contrat publié, lu par une application,
était le pire endroit pour laisser traîner ça.

packages/sdk → packages/polyfill, @ng-eventually/sdk → @ng-eventually/polyfill,
contract_sdk-surface → contract_polyfill-surface, e2e/sdk-entry.ts →
e2e/polyfill-entry.ts, docs/sdk-reference.md → docs/polyfill-reference.md.

Les occurrences de « SDK » qui désignent celui de NextGraph restent intactes,
y compris les chemins dans nextgraph-rs (sdk/js/orm, sdk/js/web). Le tri s'est
fait occurrence par occurrence, pas par substitution.

Le contrat énonce désormais son identité en une phrase : « This package is a
polyfill of NextGraph's SDK. »
2026-08-10 17:14:25 +02:00

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);
});