3547967d37
- Migration des comptes legacy supprimée (migrateLegacyRecords + garde migratedInto + call-sites). Un wallet pré-fix (records store-root, pas de pointeur) provisionne simplement un doc-shim frais; contenu legacy ignoré (voulu, données = dev). La résolution barrière-autoritative + anti-fork (resolvePointer/ensureRepoOpen/ canonicalDoc/ensureInFlight/pointerGuard) est inchangée. - Logs d'accès SDK préfixés [polyfill] + NURI tronqué via shortNuri() (retire did:ng:o: et :v:…, garde 8 chars) → moins verbeux. Ex: [polyfill] [user1] READ vDlwbZio… (resolvePointer) → 1 triple-rows Tests: bun test unit 126/0. Docs (nextgraph-current-state/simulation/migration-guide) mis à jour (migration legacy retirée du modèle décrit). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
286 lines
9.8 KiB
TypeScript
286 lines
9.8 KiB
TypeScript
/**
|
|
* access-log.test.ts — behavioral tests for logAccess / setAccessLog / enabled
|
|
* (src/access-log.ts), as wired through the docs primitives (src/docs.ts).
|
|
*
|
|
* Tests:
|
|
* (a) OFF by default: reads + writes via sparqlQuery / sparqlUpdate / docCreate
|
|
* emit nothing to console.log.
|
|
* (b) ON via configure({ debugAccessLog: true }): each read/write emits a line
|
|
* matching `[polyfill] [<identity>] READ/WRITE <shortNuri> (<label>)` plus
|
|
* row-count suffix on READs. The NURI is shortened by shortNuri (did:ng:o:
|
|
* prefix + :v: suffix stripped, RepoID truncated to 8 chars + ellipsis).
|
|
* (c) ON via env var NG_EVENTUALLY_ACCESS_LOG=1: same behavior without changing
|
|
* calling code.
|
|
* (d) Identity follows setCurrentUser: after setCurrentUser the prefix changes.
|
|
*
|
|
* Spy approach: replace console.log with a mock, restore it after each test.
|
|
* Env var tests set/delete process.env.NG_EVENTUALLY_ACCESS_LOG and restore it.
|
|
*/
|
|
|
|
import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test";
|
|
import { setAccessLog, enabled, shortNuri } from "../src/access-log";
|
|
import { docCreate, sparqlUpdate, sparqlQuery } from "../src/docs";
|
|
import {
|
|
configure,
|
|
configureStoreRegistry,
|
|
resetStoreRegistry,
|
|
resetConfig,
|
|
setCurrentUser,
|
|
getCurrentUser,
|
|
} from "../src/polyfill";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function fakeNg() {
|
|
return {
|
|
doc_create: mock(async (..._a: unknown[]) => "did:ng:o:log-doc"),
|
|
sparql_update: mock(async (..._a: unknown[]) => undefined),
|
|
sparql_query: mock(async (..._a: unknown[]) => ({
|
|
results: { bindings: [{ x: { value: "v" } }] }, // 1 row so row-count is visible
|
|
})),
|
|
};
|
|
}
|
|
|
|
function injectFake(debugAccessLog = false) {
|
|
const ng = fakeNg();
|
|
configure({ ng: ng as any, useShape: (() => {}) as any, debugAccessLog });
|
|
configureStoreRegistry({
|
|
getSession: async () => ({ sessionId: "sid-log", privateStoreId: "P" }),
|
|
});
|
|
return ng;
|
|
}
|
|
|
|
// Capture console.log lines for the duration of a test.
|
|
// Returns the captured lines array and a restore function.
|
|
function spyConsoleLog(): { lines: string[]; restore: () => void } {
|
|
const lines: string[] = [];
|
|
const orig = console.log;
|
|
console.log = (...args: unknown[]) => {
|
|
lines.push(args.map(String).join(" "));
|
|
};
|
|
return { lines, restore: () => { console.log = orig; } };
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Lifecycle: restore config state after each test to avoid cross-test bleed.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Save the env var value that was present BEFORE any test ran, so tests
|
|
// that run in an environment where NG_EVENTUALLY_ACCESS_LOG is already set
|
|
// don't permanently destroy that value.
|
|
const _originalEnvVar = process.env?.NG_EVENTUALLY_ACCESS_LOG;
|
|
|
|
afterEach(() => {
|
|
setAccessLog(false); // always reset the config toggle
|
|
setCurrentUser(null); // clear active identity
|
|
// Restore the original env var value (don't just delete — it may have existed before)
|
|
if ((globalThis as any)?.process?.env) {
|
|
if (_originalEnvVar === undefined) {
|
|
delete process.env.NG_EVENTUALLY_ACCESS_LOG;
|
|
} else {
|
|
process.env.NG_EVENTUALLY_ACCESS_LOG = _originalEnvVar;
|
|
}
|
|
}
|
|
});
|
|
|
|
afterAll(() => {
|
|
resetConfig();
|
|
resetStoreRegistry();
|
|
setAccessLog(false);
|
|
setCurrentUser(null);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("access-log: OFF by default", () => {
|
|
beforeEach(() => {
|
|
// Force the env var OFF for these tests, regardless of the shell environment.
|
|
if ((globalThis as any)?.process?.env) {
|
|
delete process.env.NG_EVENTUALLY_ACCESS_LOG;
|
|
}
|
|
setAccessLog(false);
|
|
});
|
|
|
|
it("no console.log output for sparqlQuery when disabled", async () => {
|
|
injectFake(false);
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await sparqlQuery("sid-log", "SELECT * {}");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(0);
|
|
});
|
|
|
|
it("no console.log output for sparqlUpdate when disabled", async () => {
|
|
injectFake(false);
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await sparqlUpdate("sid-log", "INSERT DATA {}", "did:ng:o:x");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(0);
|
|
});
|
|
|
|
it("no console.log output for docCreate when disabled", async () => {
|
|
injectFake(false);
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await docCreate("sid-log", "Graph", "data:graph", "store");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(0);
|
|
});
|
|
|
|
it("enabled() returns false when disabled", () => {
|
|
setAccessLog(false);
|
|
if (process.env) delete process.env.NG_EVENTUALLY_ACCESS_LOG;
|
|
expect(enabled()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("access-log: ON via configure({ debugAccessLog: true })", () => {
|
|
beforeEach(() => {
|
|
setCurrentUser("alice");
|
|
});
|
|
|
|
it("sparqlQuery emits a READ line with identity, nuri, label, and row-count", async () => {
|
|
injectFake(true);
|
|
setCurrentUser("alice");
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await sparqlQuery("sid-log", "SELECT * {}", undefined, "did:ng:o:q", "myLabel");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(1);
|
|
expect(lines[0]).toMatch(/^\[polyfill\] /); // SDK-layer access-log prefix
|
|
expect(lines[0]).toMatch(/\[alice\]/);
|
|
expect(lines[0]).toMatch(/READ/);
|
|
expect(lines[0]).toContain(shortNuri("did:ng:o:q")); // NURI shortened
|
|
expect(lines[0]).not.toContain("did:ng:o:"); // full prefix stripped
|
|
expect(lines[0]).toMatch(/myLabel/);
|
|
expect(lines[0]).toMatch(/→ 1 triple-rows/); // triple-count from the 1-row fake result
|
|
});
|
|
|
|
it("sparqlUpdate emits a WRITE line with identity, anchor nuri, and label", async () => {
|
|
injectFake(true);
|
|
setCurrentUser("alice");
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await sparqlUpdate("sid-log", "INSERT DATA {}", "did:ng:o:w", "writeLabel");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(1);
|
|
expect(lines[0]).toMatch(/^\[polyfill\] /);
|
|
expect(lines[0]).toMatch(/\[alice\]/);
|
|
expect(lines[0]).toMatch(/WRITE/);
|
|
expect(lines[0]).toContain(shortNuri("did:ng:o:w"));
|
|
expect(lines[0]).toMatch(/writeLabel/);
|
|
});
|
|
|
|
it("docCreate emits a WRITE line with identity and the returned nuri", async () => {
|
|
injectFake(true);
|
|
setCurrentUser("alice");
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await docCreate("sid-log", "Graph", "data:graph", "store");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(1);
|
|
expect(lines[0]).toMatch(/^\[polyfill\] /);
|
|
expect(lines[0]).toMatch(/\[alice\]/);
|
|
expect(lines[0]).toMatch(/WRITE/);
|
|
// The nuri is the value returned by ng.doc_create, shortened by shortNuri.
|
|
expect(lines[0]).toContain(shortNuri("did:ng:o:log-doc"));
|
|
});
|
|
|
|
it("enabled() returns true when set via setAccessLog", () => {
|
|
setAccessLog(true);
|
|
expect(enabled()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("access-log: ON via env var NG_EVENTUALLY_ACCESS_LOG=1", () => {
|
|
it("emits READ line when env var is set, even without configure() setting", async () => {
|
|
// Set env var but do NOT pass debugAccessLog=true to configure
|
|
if (!(globalThis as any)?.process?.env) return; // skip in env-less runtimes
|
|
process.env.NG_EVENTUALLY_ACCESS_LOG = "1";
|
|
injectFake(false); // debugAccessLog = false explicitly
|
|
setCurrentUser("bob");
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await sparqlQuery("sid-log", "SELECT * {}", undefined, "did:ng:o:env-q");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(1);
|
|
expect(lines[0]).toMatch(/^\[polyfill\] /);
|
|
expect(lines[0]).toMatch(/\[bob\]/);
|
|
expect(lines[0]).toMatch(/READ/);
|
|
expect(lines[0]).toContain(shortNuri("did:ng:o:env-q"));
|
|
});
|
|
|
|
it("env var NG_EVENTUALLY_ACCESS_LOG=true also enables the log", async () => {
|
|
if (!(globalThis as any)?.process?.env) return;
|
|
process.env.NG_EVENTUALLY_ACCESS_LOG = "true";
|
|
injectFake(false);
|
|
setCurrentUser("charlie");
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await sparqlUpdate("sid-log", "INSERT DATA {}", "did:ng:o:env-w");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(1);
|
|
expect(lines[0]).toMatch(/\[charlie\]/);
|
|
expect(lines[0]).toMatch(/WRITE/);
|
|
});
|
|
|
|
it("enabled() returns true when env var is set", () => {
|
|
if (!(globalThis as any)?.process?.env) return;
|
|
process.env.NG_EVENTUALLY_ACCESS_LOG = "1";
|
|
setAccessLog(false); // config toggle is off
|
|
expect(enabled()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("access-log: identity follows setCurrentUser", () => {
|
|
it("prefix changes after setCurrentUser", async () => {
|
|
injectFake(true);
|
|
setCurrentUser("first-user");
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await sparqlUpdate("sid-log", "INSERT DATA {}", "did:ng:o:id1", "step1");
|
|
setCurrentUser("second-user");
|
|
await sparqlUpdate("sid-log", "INSERT DATA {}", "did:ng:o:id2", "step2");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(2);
|
|
expect(lines[0]).toMatch(/\[first-user\]/);
|
|
expect(lines[1]).toMatch(/\[second-user\]/);
|
|
});
|
|
|
|
it("prefix is (none) when no identity is set", async () => {
|
|
injectFake(true);
|
|
setCurrentUser(null); // no active identity
|
|
const { lines, restore } = spyConsoleLog();
|
|
try {
|
|
await sparqlUpdate("sid-log", "INSERT DATA {}", "did:ng:o:anon");
|
|
} finally {
|
|
restore();
|
|
}
|
|
expect(lines.length).toBe(1);
|
|
expect(lines[0]).toMatch(/\[\(none\)\]/);
|
|
});
|
|
});
|