test(rete): port pararules golden tests; tag Phase 1 parity (P1.14)
10 integration tests in packages/rete/tests/golden/ manually wire AlphaNode → BetaMemory → JoinNode → ProductionNode chains and drive them via Session.insert/retract. Each test maps to a pararules Nim reference test (documented in GOLDEN-MAP.md). Coverage: packages/rete/src at 96.8% statements / 95.4% branch / 97.8% functions — all well above the 90% Phase 1 gate. Tests: 227 total (166 pre-existing + 61 new golden), all green.
This commit is contained in:
parent
0259268e34
commit
0401295bbc
12 changed files with 1510 additions and 2 deletions
108
packages/rete/tests/golden/g4-multiple-entities.test.ts
Normal file
108
packages/rete/tests/golden/g4-multiple-entities.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* G4 — Multiple entities matching a wildcard condition
|
||||
*
|
||||
* Pararules origin: tests/test1.nim "number of conditions != number of facts":
|
||||
* rule numCondsAndFacts(Fact):
|
||||
* what:
|
||||
* (b, Color, "blue")
|
||||
* (y, LeftOf, z)
|
||||
* (a, Color, "maize")
|
||||
* (y, RightOf, b)
|
||||
* (x, Height, h) ← wildcard on x; George/Thomas/Xavier all match
|
||||
* check session.queryAll(rule1).len == 3
|
||||
*
|
||||
* Our equivalent: a single wildcard condition `(?id, Health, ?hp)` matches
|
||||
* all entities that have a Health fact. Inserting N entities → N matches.
|
||||
*
|
||||
* Network:
|
||||
* root → join(alphaHealth, no tests) → prod
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { Session } from "../../src/session.js";
|
||||
import { BetaMemory } from "../../src/beta.js";
|
||||
import { Token } from "../../src/beta.js";
|
||||
import { JoinNode } from "../../src/join.js";
|
||||
import { ProductionNode, queryAll } from "../../src/query.js";
|
||||
import type { EntityId } from "../../src/schema.js";
|
||||
import type { TokenFact } from "../../src/beta.js";
|
||||
|
||||
const ROOT_FACT: TokenFact = { id: 0 as EntityId, attr: "__root", value: null };
|
||||
|
||||
function buildHealthNetwork() {
|
||||
const session = new Session({ autoFire: false });
|
||||
const alpha = session._getAlpha();
|
||||
|
||||
const rootMem = new BetaMemory();
|
||||
rootMem.leftActivate(new Token(null, ROOT_FACT, {}));
|
||||
|
||||
const alphaHealth = alpha.buildNode({ id: null, attr: "Health" });
|
||||
const join = new JoinNode(rootMem, alphaHealth.memory, [], "hp", "eid");
|
||||
alphaHealth.onActivate((id, attr, value) => join.rightActivate(id, attr, value));
|
||||
alphaHealth.onDeactivate((id, attr, value) => join.rightDeactivate(id, attr, value));
|
||||
|
||||
const prod = new ProductionNode("healthRule");
|
||||
join.addDownstreamActivate(prod.leftActivate.bind(prod));
|
||||
join.addDownstreamDeactivate(prod.leftDeactivate.bind(prod));
|
||||
|
||||
return { session, prod };
|
||||
}
|
||||
|
||||
describe("G4 — multiple entities matching wildcard condition", () => {
|
||||
it("N entities each with Health fact → N matches", () => {
|
||||
const { session, prod } = buildHealthNetwork();
|
||||
|
||||
const count = 3;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const e = session.nextId();
|
||||
session.insert(e, "Health", (i + 1) * 10);
|
||||
}
|
||||
|
||||
expect(prod.matches).toHaveLength(count);
|
||||
});
|
||||
|
||||
it("5 entities → 5 matches (pararules test1.nim '3 height entities' scaled)", () => {
|
||||
const { session, prod } = buildHealthNetwork();
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const e = session.nextId();
|
||||
session.insert(e, "Health", 72);
|
||||
}
|
||||
|
||||
expect(prod.matches).toHaveLength(5);
|
||||
});
|
||||
|
||||
it("queryAll returns one bindings object per entity", () => {
|
||||
const { session, prod } = buildHealthNetwork();
|
||||
|
||||
const ids: EntityId[] = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const e = session.nextId();
|
||||
ids.push(e);
|
||||
session.insert(e, "Health", i * 100);
|
||||
}
|
||||
|
||||
const all = queryAll(prod);
|
||||
expect(all).toHaveLength(4);
|
||||
// Every matched entity id appears in the results
|
||||
const matchedIds = all.map((b) => b["eid"]);
|
||||
for (const id of ids) {
|
||||
expect(matchedIds).toContain(id);
|
||||
}
|
||||
});
|
||||
|
||||
it("entity without Health fact is not counted", () => {
|
||||
const { session, prod } = buildHealthNetwork();
|
||||
|
||||
const e1 = session.nextId();
|
||||
const e2 = session.nextId();
|
||||
session.insert(e1, "Health", 100);
|
||||
session.insert(e2, "Position", "a1"); // no Health
|
||||
|
||||
expect(prod.matches).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("0 entities → 0 matches", () => {
|
||||
const { prod } = buildHealthNetwork();
|
||||
expect(prod.matches).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue