feat!: curer n'est plus un appel, c'est ce que fait le traitement de l'inbox
This commit is contained in:
+35
-30
@@ -1,5 +1,6 @@
|
||||
import { expect, mock, test } from "bun:test";
|
||||
import { indexing } from "../src/indexing";
|
||||
import { curate } from "../src/curator";
|
||||
import { entriesOf, entryValue } from "../src/index-document";
|
||||
import { resolutionFromFailure, resolutionFromRead } from "../src/resolution";
|
||||
import type { Nuri, UnionSubject } from "../src/port";
|
||||
@@ -64,11 +65,11 @@ test("an entry whose value is the empty string is still an entry", () => {
|
||||
test("adding a second value to an entry cannot make it disappear", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const ownerPort = network.portFor("alice");
|
||||
const owner = indexing(ownerPort);
|
||||
const owner = await indexing(ownerPort);
|
||||
const index = await owner.createIndex(FIELD);
|
||||
const article = await publishObject(network.portFor("bob"), FIELD, "2026-01-01");
|
||||
await indexing(network.portFor("bob")).refer(index, article);
|
||||
await owner.curate(index);
|
||||
await (await indexing(network.portFor("bob"))).refer(index, article);
|
||||
await curate(ownerPort, index);
|
||||
|
||||
// A pure ADD — the only write this package has. Before the fix this emptied
|
||||
// `read()` while both triples sat in the document.
|
||||
@@ -80,12 +81,12 @@ test("adding a second value to an entry cannot make it disappear", async () => {
|
||||
test("a raced double-add settles, and does not make every later run re-add", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const ownerPort = network.portFor("alice");
|
||||
const owner = indexing(ownerPort);
|
||||
const owner = await indexing(ownerPort);
|
||||
const index = await owner.createIndex(FIELD);
|
||||
const bobPort = network.portFor("bob");
|
||||
const article = await publishObject(bobPort, FIELD, "2026-01-01");
|
||||
await indexing(bobPort).refer(index, article);
|
||||
await owner.curate(index);
|
||||
await (await indexing(bobPort)).refer(index, article);
|
||||
await curate(ownerPort, index);
|
||||
|
||||
// What two curation runs racing each other leave behind: the object's owner
|
||||
// edited it between their reads, so each added its own value.
|
||||
@@ -94,7 +95,7 @@ test("a raced double-add settles, and does not make every later run re-add", asy
|
||||
|
||||
// The entry is still there, and the curator recognises it as already indexed —
|
||||
// before the fix it was invisible, so every run added yet another value.
|
||||
const report = await owner.curate(index);
|
||||
const report = await curate(ownerPort, index);
|
||||
expect(report.outcomes).toEqual([{ result: "unchanged", object: article }]);
|
||||
expect(await owner.read(index)).toEqual([{ object: article, value: "2026-01-01" }]);
|
||||
});
|
||||
@@ -104,13 +105,13 @@ test("a raced double-add settles, and does not make every later run re-add", asy
|
||||
test("a second declared field stops curation LOUDLY and costs no entry", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const ownerPort = network.portFor("alice");
|
||||
const owner = indexing(ownerPort);
|
||||
const owner = await indexing(ownerPort);
|
||||
const bobPort = network.portFor("bob");
|
||||
const index = await owner.createIndex(FIELD);
|
||||
for (const date of ["2026-01-01", "2026-02-02", "2026-03-03"]) {
|
||||
await indexing(bobPort).refer(index, await publishObject(bobPort, FIELD, date));
|
||||
await (await indexing(bobPort)).refer(index, await publishObject(bobPort, FIELD, date));
|
||||
}
|
||||
await owner.curate(index);
|
||||
await curate(ownerPort, index);
|
||||
expect(await owner.read(index)).toHaveLength(3);
|
||||
|
||||
// One add-only write through the published surface — and the SMALLER string, the
|
||||
@@ -121,28 +122,28 @@ test("a second declared field stops curation LOUDLY and costs no entry", async (
|
||||
// unreadable because the declaration above it turned ambiguous.
|
||||
expect(await owner.read(index)).toHaveLength(3);
|
||||
// Curating refuses, and says why instead of quietly picking one.
|
||||
await expect(owner.curate(index)).rejects.toThrow(/declares 2 index fields/);
|
||||
await expect(curate(ownerPort, index)).rejects.toThrow(/declares 2 index fields/);
|
||||
});
|
||||
|
||||
test("a mixed-field index is never produced: curation refuses before adding anything", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const ownerPort = network.portFor("alice");
|
||||
const owner = indexing(ownerPort);
|
||||
const owner = await indexing(ownerPort);
|
||||
const bobPort = network.portFor("bob");
|
||||
const NAME = "http://schema.org/name";
|
||||
|
||||
const index = await owner.createIndex(NAME);
|
||||
const first = await publishObject(bobPort, NAME, "Anemone");
|
||||
await indexing(bobPort).refer(index, first);
|
||||
await owner.curate(index);
|
||||
await (await indexing(bobPort)).refer(index, first);
|
||||
await curate(ownerPort, index);
|
||||
|
||||
// "…/datePublished" < "…/name", so under "smallest wins" the new field took over
|
||||
// while `first` kept its old value forever — one list ordered by two properties.
|
||||
await ownerPort.addLiteralProperty(index, index, INDEX_FIELD, FIELD);
|
||||
const second = await publishObject(bobPort, FIELD, "2026-02-02");
|
||||
await indexing(bobPort).refer(index, second);
|
||||
await (await indexing(bobPort)).refer(index, second);
|
||||
|
||||
await expect(owner.curate(index)).rejects.toThrow(/refusing to curate rather than pick one/);
|
||||
await expect(curate(ownerPort, index)).rejects.toThrow(/refusing to curate rather than pick one/);
|
||||
expect(await owner.read(index)).toEqual([{ object: first, value: "Anemone" }]);
|
||||
});
|
||||
|
||||
@@ -150,11 +151,11 @@ test("an index declaring no field at all is still refused", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const ownerPort = network.portFor("alice");
|
||||
const ordinary = await ownerPort.createPublicDocument();
|
||||
await expect(indexing(ownerPort).read(ordinary)).rejects.toThrow(/declares no index field/);
|
||||
await expect((await indexing(ownerPort)).read(ordinary)).rejects.toThrow(/declares no index field/);
|
||||
});
|
||||
|
||||
test("a field that could never match an object is refused at creation", async () => {
|
||||
const owner = indexing(new FakeNextGraph().portFor("alice"));
|
||||
const owner = await indexing(new FakeNextGraph().portFor("alice"));
|
||||
// It cannot be corrected later — nothing here deletes — so it is refused now.
|
||||
await expect(owner.createIndex("")).rejects.toThrow(/cannot be changed later/);
|
||||
await expect(owner.createIndex(" ")).rejects.toThrow(/cannot be changed later/);
|
||||
@@ -164,7 +165,8 @@ test("a field that could never match an object is refused at creation", async ()
|
||||
|
||||
test("a field colliding with Object.prototype neither crashes nor is silently mis-read", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const owner = indexing(network.portFor("alice"));
|
||||
const ownerPort = network.portFor("alice");
|
||||
const owner = await indexing(ownerPort);
|
||||
const bobPort = network.portFor("bob");
|
||||
|
||||
for (const field of ["constructor", "toString", "valueOf", "hasOwnProperty"]) {
|
||||
@@ -178,10 +180,10 @@ test("a field colliding with Object.prototype neither crashes nor is silently mi
|
||||
// An object that merely LACKS it must still resolve cleanly: reading the field
|
||||
// off a plain object literal would otherwise hand back an inherited function.
|
||||
const lacks = await publishObject(bobPort, "http://schema.org/name", "unrelated");
|
||||
await indexing(bobPort).refer(index, lacks);
|
||||
await indexing(bobPort).refer(index, carries);
|
||||
await (await indexing(bobPort)).refer(index, lacks);
|
||||
await (await indexing(bobPort)).refer(index, carries);
|
||||
|
||||
const report = await owner.curate(index);
|
||||
const report = await curate(ownerPort, index);
|
||||
expect(report.outcomes[0]).toEqual({ result: "skipped", object: lacks, reason: "no-field" });
|
||||
expect(report.outcomes[1]?.result).toBe("unresolved");
|
||||
expect(await owner.read(index)).toEqual([]);
|
||||
@@ -212,17 +214,18 @@ test("a read that threw resolves as unresolved, naming the error", () => {
|
||||
|
||||
test("an unresolved reference is warned about, not only reported", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const owner = indexing(network.portFor("alice"));
|
||||
const ownerPort = network.portFor("alice");
|
||||
const owner = await indexing(ownerPort);
|
||||
const index = await owner.createIndex(FIELD);
|
||||
const article = await publishObject(network.portFor("bob"), FIELD, "2026-01-01");
|
||||
await indexing(network.portFor("bob")).refer(index, article);
|
||||
await (await indexing(network.portFor("bob"))).refer(index, article);
|
||||
network.breakReadsOf(article, "broker unreachable");
|
||||
|
||||
const warn = mock((..._args: unknown[]) => {});
|
||||
const original = console.warn;
|
||||
console.warn = warn;
|
||||
try {
|
||||
await owner.curate(index);
|
||||
await curate(ownerPort, index);
|
||||
} finally {
|
||||
console.warn = original;
|
||||
}
|
||||
@@ -233,17 +236,18 @@ test("an unresolved reference is warned about, not only reported", async () => {
|
||||
|
||||
test("a normal run warns about nothing", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const owner = indexing(network.portFor("alice"));
|
||||
const ownerPort = network.portFor("alice");
|
||||
const owner = await indexing(ownerPort);
|
||||
const index = await owner.createIndex(FIELD);
|
||||
const article = await publishObject(network.portFor("bob"), FIELD, "2026-01-01");
|
||||
await indexing(network.portFor("bob")).refer(index, article);
|
||||
await (await indexing(network.portFor("bob"))).refer(index, article);
|
||||
|
||||
const warn = mock((..._args: unknown[]) => {});
|
||||
const original = console.warn;
|
||||
console.warn = warn;
|
||||
let report;
|
||||
try {
|
||||
report = await owner.curate(index);
|
||||
report = await curate(ownerPort, index);
|
||||
} finally {
|
||||
console.warn = original;
|
||||
}
|
||||
@@ -257,13 +261,14 @@ test("a normal run warns about nothing", async () => {
|
||||
|
||||
test("an index that could not be read is refused, and says so without blaming the document", async () => {
|
||||
const network = new FakeNextGraph();
|
||||
const owner = indexing(network.portFor("alice"));
|
||||
const ownerPort = network.portFor("alice");
|
||||
const owner = await indexing(ownerPort);
|
||||
const index = await owner.createIndex(FIELD);
|
||||
network.breakReadsOf(index, "broker unreachable");
|
||||
|
||||
// The real `readUnion` turns a failed read into `[]`, so the failure arrives
|
||||
// looking like a blank document. Whatever the shape, nothing may be written.
|
||||
await expect(owner.curate(index)).rejects.toThrow();
|
||||
await expect(curate(ownerPort, index)).rejects.toThrow();
|
||||
await expect(owner.read(index)).rejects.toThrow();
|
||||
|
||||
network.healReadsOf(index);
|
||||
|
||||
Reference in New Issue
Block a user