Build workspace agent, capability graph, and versioned cutovers
This commit is contained in:
+137
-1
@@ -25,7 +25,8 @@ import {
|
||||
InjectedDependencySchema,
|
||||
PackageExportRefSchema,
|
||||
} from "../dist/quixos/refs_pb.js";
|
||||
import { PackageRuntime } from "../dist/quixos/runtime_pb.js";
|
||||
import { InvokeCapabilityResponseSchema, OrchestratorRuntime } from "../dist/quixos/orch_pb.js";
|
||||
import { DerivedDependencySchema, PackageRuntime } from "../dist/quixos/runtime_pb.js";
|
||||
|
||||
const listen = async (routes) => {
|
||||
const server = http.createServer((request, response) => void connectNodeAdapter({ routes })(request, response));
|
||||
@@ -164,6 +165,141 @@ test("injected ports preserve an explicitly traversed object through PackageRunt
|
||||
}
|
||||
});
|
||||
|
||||
test("interface views invoke a related object and propagate transitive live dependencies", async () => {
|
||||
const invocations = [];
|
||||
const sourceValue = create(ValueSchema, {
|
||||
kind: { case: "stringValue", value: "Project name" },
|
||||
source: create(ValueSourceSchema, {
|
||||
state: create(StateValueSourceSchema, {
|
||||
objectId: "obj:project",
|
||||
slotId: "slot:project:name",
|
||||
revision: 4n,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
const orchServer = await listen((router) => router.service(OrchestratorRuntime, {
|
||||
invokeCapability: (request) => {
|
||||
invocations.push(request);
|
||||
return create(InvokeCapabilityResponseSchema, {
|
||||
ok: true,
|
||||
result: sourceValue,
|
||||
dependencies: [create(DerivedDependencySchema, {
|
||||
kind: "state",
|
||||
objectId: "obj:project",
|
||||
attachmentId: "slot:project:name",
|
||||
})],
|
||||
});
|
||||
},
|
||||
}));
|
||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
orchUrl: orchServer.url,
|
||||
exports: {
|
||||
"export:test:value": derived((context) =>
|
||||
context.interface("port:named").live("operation:named:name:get")),
|
||||
},
|
||||
}));
|
||||
const client = createClient(PackageRuntime, createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}));
|
||||
try {
|
||||
const response = await client.invoke({
|
||||
export: create(PackageExportRefSchema, {
|
||||
packageRevisionId: "package:test@1",
|
||||
exportId: "export:test:value",
|
||||
}),
|
||||
objectId: "obj:component",
|
||||
dependencies: [create(InjectedDependencySchema, {
|
||||
portId: "port:named",
|
||||
objectId: "obj:project",
|
||||
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
||||
})],
|
||||
});
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(invocations[0]?.objectId, "obj:project");
|
||||
assert.equal(invocations[0]?.capability?.interfaceRevisionId, "interface:named@1");
|
||||
assert.equal(response.dependencies[0]?.objectId, "obj:project");
|
||||
assert.equal(response.dependencies[0]?.attachmentId, "slot:project:name");
|
||||
assert.equal(response.result?.source?.state?.revision, 4n);
|
||||
} finally {
|
||||
await runtimeServer.close();
|
||||
await orchServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("derived interface views reread after their transitive subscriptions become live", async () => {
|
||||
let current = "before subscription";
|
||||
let invocationCount = 0;
|
||||
const caminoServer = await listen((router) => router.service(CaminoService, {
|
||||
watchObject: async function* (request, context) {
|
||||
// Model a write racing the first nested capability read. Camino makes
|
||||
// the subscription live before yielding this snapshot.
|
||||
current = "after subscription";
|
||||
yield {
|
||||
objectId: request.objectId,
|
||||
snapshot: create(CaminoObjectSchema, {
|
||||
id: request.objectId,
|
||||
atomId: "atom:project",
|
||||
workspaceRevisionId: "workspace:test@1",
|
||||
}),
|
||||
};
|
||||
await new Promise((resolve) =>
|
||||
context.signal.addEventListener("abort", resolve, { once: true }));
|
||||
},
|
||||
}));
|
||||
const orchServer = await listen((router) => router.service(OrchestratorRuntime, {
|
||||
invokeCapability: () => {
|
||||
invocationCount += 1;
|
||||
return create(InvokeCapabilityResponseSchema, {
|
||||
ok: true,
|
||||
result: jsToProtoValue(current),
|
||||
dependencies: [create(DerivedDependencySchema, {
|
||||
kind: "state",
|
||||
objectId: "obj:project",
|
||||
attachmentId: "slot:project:name",
|
||||
})],
|
||||
});
|
||||
},
|
||||
}));
|
||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
caminoUrl: caminoServer.url,
|
||||
orchUrl: orchServer.url,
|
||||
exports: {
|
||||
"export:test:value": derived((context) =>
|
||||
context.interface("port:named").invoke("operation:named:name:get")),
|
||||
},
|
||||
}));
|
||||
const client = createClient(PackageRuntime, createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}));
|
||||
const controller = new AbortController();
|
||||
try {
|
||||
const stream = client.watch({
|
||||
export: create(PackageExportRefSchema, {
|
||||
packageRevisionId: "package:test@1",
|
||||
exportId: "export:test:value",
|
||||
}),
|
||||
objectId: "obj:component",
|
||||
dependencies: [create(InjectedDependencySchema, {
|
||||
portId: "port:named",
|
||||
objectId: "obj:project",
|
||||
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
||||
})],
|
||||
}, { signal: controller.signal })[Symbol.asyncIterator]();
|
||||
const initial = await stream.next();
|
||||
assert.equal(protoValueToJs(initial.value?.value), "after subscription");
|
||||
assert.equal(invocationCount, 2);
|
||||
} finally {
|
||||
controller.abort();
|
||||
await runtimeServer.close();
|
||||
await orchServer.close();
|
||||
await caminoServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("derived watches subscribe before reading and emit only real changes", async () => {
|
||||
let current = "before";
|
||||
let subscribed = false;
|
||||
|
||||
Reference in New Issue
Block a user