import assert from "node:assert/strict"; import { test } from "node:test"; import { compileAggregation as compile, compileAggregationDocument } from "./fixtures/query-aggregation.js"; import type { QuerySelection } from "../src/query/types.js"; import type { QueryRelationalPlan } from "../src/query/relational.js"; function findPlan(selections: QuerySelection[]): QueryRelationalPlan | undefined { for (const selection of selections) { const plan = selection.relational ?? findPlan(selection.selection); if (plan) return plan; } } test("aggregate output uses exact count and sum types and nullable reductions", async () => { const checked = await compile( `aggregate { count countPresent { estimatedHours } sum { estimatedHours cost } avg { cost } }`, ); const json = JSON.stringify(checked.output); assert.match(json, /"count":\{"kind":"scalar","name":"uint64"\}/); assert.match(json, /"cost":\{"kind":"optional","value":\{"kind":"scalar","name":"int64"\}\}/); assert.ok(checked.effects.some((e) => e.memberId === "hours" && e.uses.includes("aggregate"))); const plan = findPlan(checked.selection); assert.ok(plan); assert.equal(plan.terminals[0]!.reductions.length, 5); }); test("explicit expansion and identity distinct retain checked grouped operands", async () => { const checked = await compile(`expand { tags { distinct(by: {source: {_qx: {ref: true}}, target: {color: true}}) { groups(by: {target: {color: true}}, first: 50, having: {sum: {source: {estimatedHours: {gt: 0}}}}, orderBy: [{sum: {source: {estimatedHours: DESC}}}]) { entries { group {target {color}} aggregate {sum {source {estimatedHours}}} } pageInfo {hasNextPage endCursor} } } } }`); const plan = findPlan(checked.selection)!; assert.deepEqual( plan.stages.map((s) => s.operation.kind), ["source", "expand", "distinct"], ); assert.equal(plan.terminals[0]!.kind, "groups"); assert.ok( checked.effects.some( (e) => e.memberId === "hours" && ["aggregate", "predicate", "order"].every((u) => e.uses.includes(u as never)), ), ); }); test("distinct and grouping reject fields not determined by their selected keys", async () => { await assert.rejects(compile(`distinct(by: {cost: true}) {aggregate {sum {estimatedHours}}}`), /dropped by distinct/); await assert.rejects(compile(`groups(by: {cost: true}, first: 10) {entries {group {estimatedHours}}}`), /group key/); }); test("relationship existence predicates compile to typed plans", async () => { const checked = await compile( `filter(where: {_qx: {relations: {tags: {some: {color: {in: ["red","blue"]}}}}}}) {aggregate {count}}`, ); const plan = findPlan(checked.selection)!; assert.equal(plan.stages[1]!.operation.kind, "filter"); assert.ok(checked.effects.some((e) => e.memberId === "color" && e.uses.includes("predicate"))); }); test("group structure is literal, bounded and cannot project a representative field", async () => { await assert.rejects(compile(`groups(by: {}, first: 2) {entries {aggregate {count}}}`), /key|empty/i); await assert.rejects(compile(`groups(by: {cost: false}, first: 2) {entries {aggregate {count}}}`), /true/); await assert.rejects(compile(`groups(by: {cost: true}) {entries {aggregate {count}}}`), /first or all/); await assert.rejects( compile( `groups(by: {cost: true}, first: 2, orderBy: [{sum: {cost: ASC, estimatedHours: DESC}}]) {entries {aggregate {count}}}`, ), /exactly one field/, ); await assert.rejects(compile(`expand {tags {aggregate {sum {target {color}}}}}`), /QUERY_VALIDATION/); }); test("RPC continuation rejection is local to the affected membership boundary", async () => { await assert.rejects( compile(`groups(by: {cost: true}, first: 2, after: null) {entries {aggregate {sum {score}}}}`), /without continuation/, ); await assert.rejects( compileAggregationDocument( `query Report {root {tasks(first: 2, after: null, where: {score: {gt: 0}}) {entries {node {cost}}}}}`, ), /without continuation/, ); const checked = await compileAggregationDocument(`query Report($after: Cursor) {root { tasks(first: 2, after: $after) {entries {node {cost}}} _qx {relations {tasks {aggregate {sum {score}}}}} }}`); assert.equal(findPlan(checked.selection)!.terminals[0]!.residual, true); }); test("typed reference operands and scalar membership tests reject unbounded literal lists", async () => { const checked = await compile( `filter(where: {_qx: {ref: {in: $tasks}}}) {aggregate {count}}`, "($tasks: [QxRef_Task!]!)", ); assert.match(JSON.stringify(checked.variables), /object-ref/); await assert.rejects( compile(`filter(where: {_qx: {ref: {eq: "obj:made-up"}}}) {aggregate {count}}`), /typed variables/, ); await assert.rejects( compile(`filter(where: {cost: {in: [${Array(1001).fill("1").join(",")}]}}) {aggregate {count}}`), /1000 operands/, ); });