Files

160 lines
5.7 KiB
TypeScript
Raw Permalink Normal View History

import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import {
compileCapabilityResourceRepository,
type PackageQueryGenerator,
} from "../src/capability-language/assembly.js";
import { defaultQueryBudgets } from "../src/query/types.js";
test("an explicit non-React frontend emits checked queries for its own selection model", async (context) => {
const directory = await mkdtemp(path.join(os.tmpdir(), "qx-query-generator-"));
context.after(() => rm(directory, { recursive: true, force: true }));
const commit = "a".repeat(40);
const source = (name: string) => ({
resolver: "git" as const,
repository: `https://example.test/${name}.git`,
commit,
});
const lock = (imports: string) => `quixos-lock version 1 {
quixos source { repository "https://example.test/quixos.git"; commit "${commit}"; }
${imports}
}`;
const dependency = (name: string) =>
`interface ${name} source { repository "${source(name).repository}"; commit "${commit}"; }`;
for (const name of ["Sensor", "DeviceSelection", "Device"]) await mkdir(path.join(directory, name));
await writeFile(path.join(directory, "Sensor", "quixos.lock"), lock(""));
await writeFile(
path.join(directory, "Sensor", "interface.qx"),
`
interface Sensor id "sensor" revision "sensor@1" {
queryable value reading id "reading" : int64 { get id "reading:get"; }
}
`,
);
await writeFile(path.join(directory, "DeviceSelection", "quixos.lock"), lock(dependency("Sensor")));
await writeFile(
path.join(directory, "DeviceSelection", "interface.qx"),
`
import interface Sensor;
interface DeviceSelection id "device-selection" revision "device-selection@1" {
queryable relation selectedSensor id "selected-sensor" : optional-one interface Sensor {
resolve id "selected-sensor:resolve";
}
}
`,
);
await writeFile(path.join(directory, "Device", "quixos.lock"), lock(dependency("DeviceSelection")));
await writeFile(
path.join(directory, "Device", "package.qx"),
`
import interface DeviceSelection;
package Device id "device" revision "device@1" {}
`,
);
await writeFile(
path.join(directory, "Device", "inputs.graphql"),
`query Selected { root { selectedSensor { reading } } }`,
);
const options = {
rootDirectory: path.join(directory, "Device"),
kind: "package" as const,
source: source("Device"),
resolveResource: async (ref: { repository: string }) => ({
directory: path.join(directory, new URL(ref.repository).pathname.slice(1, -4)),
}),
};
const plain = await compileCapabilityResourceRepository(options);
assert.equal(plain.resource.kind, "package");
if (plain.resource.kind !== "package") throw Error("Expected package");
assert.equal(plain.resource.revision.queries?.length ?? 0, 0);
const generateQueries: PackageQueryGenerator = async ({ interfaces, readSource }) => ({
queries: [
{
id: "selected-inputs",
displayName: "SelectedInputs",
root: {
kind: "object",
interface: interfaces.find((iface) => iface.displayName === "DeviceSelection")!.revisionId,
},
document: "generated/selected.graphql",
operation: "Selected",
fragments: [],
views: [],
allowances: [],
budgets: { ...defaultQueryBudgets },
watch: false,
placement: { kind: "replica" },
},
],
documents: new Map([["generated/selected.graphql", await readSource("inputs.graphql")]]),
compositions: [
{
id: "display",
maxQueries: 1,
input: {
kind: "record",
fields: {
device: {
kind: "object-ref",
expectation: {
kind: "interface",
interfaceRevisionId: interfaces.find((iface) => iface.displayName === "DeviceSelection")!.revisionId,
},
},
},
},
steps: [
{
name: "selection",
query: "selected-inputs",
root: { from: "input", path: ["device"] },
arguments: { record: {} },
},
],
},
],
});
const built = await compileCapabilityResourceRepository({ ...options, generateQueries });
assert.equal(built.resource.kind, "package");
if (built.resource.kind !== "package") throw Error("Expected package");
assert.equal(built.resource.revision.checkedQueries?.length, 1);
const checked = built.resource.revision.checkedQueries![0]!;
assert.equal(checked.declaration.root.interface, "device-selection@1");
assert.ok(checked.effects.some((effect) => effect.memberId === "reading"));
assert.ok(checked.effects.some((effect) => effect.memberId === "selected-sensor"));
assert.ok(checked.definitionDigest);
assert.equal(built.resource.revision.queryCompositions?.[0]?.dependencies[0]?.digest, checked.definitionDigest);
assert.equal(
built.resources.some((resource) => /React|PresentationMount/.test(resource.key)),
false,
);
await assert.rejects(
compileCapabilityResourceRepository({
...options,
generateQueries: async (input) => {
const result = await generateQueries(input);
return {
...result,
documents: new Map([["generated/selected.graphql", "query Selected { root { invented } }"]]),
};
},
}),
/invented/,
);
await assert.rejects(
compileCapabilityResourceRepository({
...options,
generateQueries: async (input) => {
const result = await generateQueries(input);
return { ...result, queries: [...result.queries, ...result.queries] };
},
}),
/Generated query conflicts/,
);
});