feat: complete TASK-WP0-02 contract baseline
This commit is contained in:
@@ -20,7 +20,7 @@ describe("TASK-WP0-01 minimum toolchain", () => {
|
||||
});
|
||||
|
||||
it("loads and closes Fastify with the frozen Swagger plugin", async () => {
|
||||
const app = createApp();
|
||||
const app = await createApp();
|
||||
await app.ready();
|
||||
expect(app.hasPlugin("@fastify/swagger")).toBe(true);
|
||||
await app.close();
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
createEventSyncController,
|
||||
type EventSyncDependencies,
|
||||
} from "../../apps/web/src/event-sync.js";
|
||||
import { isSseEvent } from "../../packages/shared-contracts/src/index.js";
|
||||
|
||||
function dependencies() {
|
||||
return {
|
||||
bootstrap: vi.fn(async () => undefined),
|
||||
refetchEntity: vi.fn(async () => undefined),
|
||||
refetchModels: vi.fn(async () => undefined),
|
||||
} satisfies EventSyncDependencies;
|
||||
}
|
||||
|
||||
describe("TDD-WP0-EVT-001 REST refetch", () => {
|
||||
it("accepts only fixed non-sensitive event fields and separated versions", () => {
|
||||
expect(
|
||||
isSseEvent({
|
||||
config_set_version: 7,
|
||||
entity_ref: "models:current",
|
||||
event_id: 1,
|
||||
event_type: "model_config_changed",
|
||||
occurred_at: "2026-07-27T09:00:00.000Z",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isSseEvent({
|
||||
entity_ref: "models:runtime",
|
||||
event_id: 2,
|
||||
event_type: "model_runtime_changed",
|
||||
occurred_at: "2026-07-27T09:00:01.000Z",
|
||||
runtime_availability_version: 12,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isSseEvent({
|
||||
config_set_version: 7,
|
||||
entity_ref: "models:current",
|
||||
event_id: 3,
|
||||
event_type: "model_changed",
|
||||
occurred_at: "2026-07-27T09:00:02.000Z",
|
||||
prompt: "private body",
|
||||
runtime_availability_version: 12,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("uses REST for every event and bootstraps once after a sequence gap", async () => {
|
||||
const calls = dependencies();
|
||||
const controller = createEventSyncController(calls);
|
||||
|
||||
await controller.handleEvent({
|
||||
entity_ref: "projects:project-1",
|
||||
event_id: 10,
|
||||
event_type: "project_state_changed",
|
||||
occurred_at: "2026-07-27T09:00:00.000Z",
|
||||
state_version: 4,
|
||||
});
|
||||
await controller.handleEvent({
|
||||
config_set_version: 8,
|
||||
entity_ref: "models:current",
|
||||
event_id: 12,
|
||||
event_type: "model_config_changed",
|
||||
occurred_at: "2026-07-27T09:00:01.000Z",
|
||||
});
|
||||
|
||||
expect(calls.refetchEntity).toHaveBeenCalledWith("projects:project-1");
|
||||
expect(calls.bootstrap).toHaveBeenCalledTimes(1);
|
||||
expect(calls.refetchModels).toHaveBeenCalledWith({
|
||||
configSetVersion: 8,
|
||||
reason: "config",
|
||||
});
|
||||
});
|
||||
|
||||
it("bootstraps once per disconnect incident and never applies event payload as truth", async () => {
|
||||
const calls = dependencies();
|
||||
const controller = createEventSyncController(calls);
|
||||
|
||||
await controller.handleDisconnect();
|
||||
await controller.handleDisconnect();
|
||||
expect(calls.bootstrap).toHaveBeenCalledTimes(1);
|
||||
|
||||
await controller.handleEvent({
|
||||
entity_ref: "storage:local",
|
||||
event_id: 21,
|
||||
event_type: "storage_state_changed",
|
||||
occurred_at: "2026-07-27T09:00:02.000Z",
|
||||
state_version: 9,
|
||||
});
|
||||
await controller.handleDisconnect();
|
||||
|
||||
expect(calls.bootstrap).toHaveBeenCalledTimes(2);
|
||||
expect(calls.refetchEntity).toHaveBeenCalledWith("storage:local");
|
||||
expect(calls.refetchEntity).not.toHaveBeenCalledWith(expect.objectContaining({ state_version: 9 }));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user