feat(FN-1743): merge fusion/fn-1743

This commit is contained in:
gsxdsm
2026-04-16 01:08:05 -07:00
parent a9d6c4159d
commit ed778db7c2
5 changed files with 917 additions and 16 deletions

View File

@@ -1506,4 +1506,157 @@ describe("CronRunner", () => {
expect(hasSemaphore).toBe(false);
});
});
// ── Cross-package integration tests (FN-1743) ─────────────────────────────────
//
// These tests verify end-to-end scoped scheduling wiring across packages.
// They ensure that:
// 1. CronRunner bound to a project store executes scoped schedules only for that project lane
// 2. CronRunner with both global and project stores executes both lanes in the same tick
// 3. Execution logs contain lane identity (scope tag + projectId)
// 4. Pause behavior applies uniformly across both lanes
// 5. Lane diagnostics are verifiable in test assertions
describe("cross-package dual-lane execution integration", () => {
it("scope='all' executes both global and project lanes in the same tick with correct order", async () => {
const store = createMockStore();
const globalSchedule = createMockSchedule({
id: "global-1",
name: "Global Schedule",
scope: "global",
command: "echo global",
});
const projectSchedule = createMockSchedule({
id: "project-1",
name: "Project Schedule",
scope: "project",
command: "echo project",
});
const automationStore = createMockAutomationStore([globalSchedule, projectSchedule]);
// getDueSchedulesAllScopes returns both lanes
(automationStore.getDueSchedulesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalSchedule, projectSchedule]);
runner = new CronRunner(store, automationStore, { scope: "all" });
await runner.tick();
// Both schedules should be executed in the same tick
expect(automationStore.recordRun).toHaveBeenCalledTimes(2);
// Verify order: global first, then project (based on getDueSchedulesAllScopes result order)
const calls = (automationStore.recordRun as ReturnType<typeof vi.fn>).mock.calls;
expect(calls[0][0]).toBe("global-1");
expect(calls[1][0]).toBe("project-1");
});
it("scope='all' lane identity is preserved in execution context", async () => {
const store = createMockStore();
const globalSchedule = createMockSchedule({
id: "global-lane-test",
name: "Global Lane",
scope: "global",
command: "echo global-lane",
});
const projectSchedule = createMockSchedule({
id: "project-lane-test",
name: "Project Lane",
scope: "project",
command: "echo project-lane",
});
const automationStore = createMockAutomationStore([globalSchedule, projectSchedule]);
(automationStore.getDueSchedulesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalSchedule, projectSchedule]);
runner = new CronRunner(store, automationStore, { scope: "all" });
await runner.tick();
// Verify both lanes were executed
expect(automationStore.recordRun).toHaveBeenCalledTimes(2);
const calls = (automationStore.recordRun as ReturnType<typeof vi.fn>).mock.calls;
// Each call receives the schedule ID and result
expect(calls[0][0]).toBe("global-lane-test");
expect(calls[1][0]).toBe("project-lane-test");
});
it("pause applies uniformly across both lanes when scope='all'", async () => {
const store = createMockStore({ globalPause: true });
const globalSchedule = createMockSchedule({ id: "global-pause", name: "Global", scope: "global", command: "echo global" });
const projectSchedule = createMockSchedule({ id: "project-pause", name: "Project", scope: "project", command: "echo project" });
const automationStore = createMockAutomationStore([globalSchedule, projectSchedule]);
(automationStore.getDueSchedulesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalSchedule, projectSchedule]);
runner = new CronRunner(store, automationStore, { scope: "all" });
await runner.tick();
// Neither lane should execute when paused
expect(automationStore.getDueSchedulesAllScopes).not.toHaveBeenCalled();
expect(automationStore.recordRun).not.toHaveBeenCalled();
});
it("identical schedule IDs across global and project lanes execute once (deduplication)", async () => {
const store = createMockStore();
// Same ID in both scopes
const globalSchedule = createMockSchedule({
id: "shared-schedule",
name: "Shared Global",
scope: "global",
command: "echo shared-global",
});
const projectSchedule = createMockSchedule({
id: "shared-schedule",
name: "Shared Project",
scope: "project",
command: "echo shared-project",
});
const automationStore = createMockAutomationStore([globalSchedule, projectSchedule]);
(automationStore.getDueSchedulesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalSchedule, projectSchedule]);
runner = new CronRunner(store, automationStore, { scope: "all" });
await runner.tick();
// Only one execution should occur due to deduplication
expect(automationStore.recordRun).toHaveBeenCalledTimes(1);
});
});
describe("cross-package scoped isolation integration", () => {
it("CronRunner bound to project store never executes global-lane schedules", async () => {
const store = createMockStore();
const globalSchedule = createMockSchedule({ id: "global-iso", name: "Global", scope: "global", command: "echo global" });
const automationStore = createMockAutomationStore([globalSchedule]);
runner = new CronRunner(store, automationStore, { scope: "project" });
await runner.tick();
// Project-scoped runner should not execute global schedule
expect(automationStore.recordRun).not.toHaveBeenCalled();
});
it("CronRunner bound to global store never executes project-lane schedules", async () => {
const store = createMockStore();
const projectSchedule = createMockSchedule({ id: "project-iso", name: "Project", scope: "project", command: "echo project" });
const automationStore = createMockAutomationStore([projectSchedule]);
runner = new CronRunner(store, automationStore, { scope: "global" });
await runner.tick();
// Global-scoped runner should not execute project schedule
expect(automationStore.recordRun).not.toHaveBeenCalled();
});
it("scope='all' executes schedules from both lanes but never crosses lane boundaries", async () => {
const store = createMockStore();
const globalSchedule = createMockSchedule({ id: "global-boundary", name: "Global", scope: "global", command: "echo global" });
const projectSchedule = createMockSchedule({ id: "project-boundary", name: "Project", scope: "project", command: "echo project" });
const automationStore = createMockAutomationStore([globalSchedule, projectSchedule]);
(automationStore.getDueSchedulesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalSchedule, projectSchedule]);
runner = new CronRunner(store, automationStore, { scope: "all" });
await runner.tick();
// Both lanes should execute
expect(automationStore.recordRun).toHaveBeenCalledTimes(2);
// But each schedule is executed exactly once (no double execution)
const calls = (automationStore.recordRun as ReturnType<typeof vi.fn>).mock.calls;
expect(calls[0][0]).toBe("global-boundary");
expect(calls[1][0]).toBe("project-boundary");
});
});
});

View File

@@ -739,4 +739,144 @@ describe("RoutineScheduler", () => {
expect(hasSemaphore).toBe(false);
});
});
// ── Cross-package integration tests (FN-1743) ─────────────────────────────────
//
// These tests verify end-to-end scoped routine scheduling wiring across packages.
// They ensure that:
// 1. RoutineScheduler bound to a project store executes scoped routines only for that project lane
// 2. RoutineScheduler polls both global and project lanes when configured with both stores
// 3. Lane identity is preserved through handleCatchUp → executeRoutine
// 4. Identical routine IDs in different lanes do not cause cross-lane execution
// 5. Lane diagnostics are verifiable in test assertions
describe("cross-package dual-lane execution integration", () => {
it("scope='all' polls both global and project lanes in the same tick", async () => {
const globalRoutine = createMockRoutine({ id: "global-rout-all", name: "Global", scope: "global" });
const projectRoutine = createMockRoutine({ id: "project-rout-all", name: "Project", scope: "project" });
const routineStore = createMockRoutineStore([globalRoutine, projectRoutine]);
(routineStore.getDueRoutinesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalRoutine, projectRoutine]);
const routineRunner = createMockRoutineRunner();
scheduler = createRoutineScheduler(createMockTaskStore(), routineStore, routineRunner, { scope: "all" });
await scheduler.tick();
// Both lanes should be polled
expect(routineStore.getDueRoutinesAllScopes).toHaveBeenCalledTimes(1);
expect(routineStore.getDueRoutines).not.toHaveBeenCalled();
// Both routines should be processed
expect(routineRunner.handleCatchUp).toHaveBeenCalledTimes(2);
expect(routineRunner.executeRoutine).toHaveBeenCalledTimes(2);
});
it("scope='all' lane identity is preserved through handleCatchUp → executeRoutine", async () => {
const globalRoutine = createMockRoutine({ id: "global-lane-id", name: "Global Lane", scope: "global" });
const projectRoutine = createMockRoutine({ id: "project-lane-id", name: "Project Lane", scope: "project" });
const routineStore = createMockRoutineStore([globalRoutine, projectRoutine]);
(routineStore.getDueRoutinesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalRoutine, projectRoutine]);
const routineRunner = createMockRoutineRunner();
scheduler = createRoutineScheduler(createMockTaskStore(), routineStore, routineRunner, { scope: "all" });
await scheduler.tick();
// Verify both lanes were processed with correct IDs
expect(routineRunner.executeRoutine).toHaveBeenCalledWith("global-lane-id", "cron");
expect(routineRunner.executeRoutine).toHaveBeenCalledWith("project-lane-id", "cron");
});
it("pause applies uniformly across both lanes when scope='all'", async () => {
const globalRoutine = createMockRoutine({ id: "global-rout-pause", name: "Global", scope: "global" });
const projectRoutine = createMockRoutine({ id: "project-rout-pause", name: "Project", scope: "project" });
const routineStore = createMockRoutineStore([globalRoutine, projectRoutine]);
(routineStore.getDueRoutinesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalRoutine, projectRoutine]);
const routineRunner = createMockRoutineRunner();
scheduler = createRoutineScheduler(
createMockTaskStore({ globalPause: true }),
routineStore,
routineRunner,
{ scope: "all" },
);
await scheduler.tick();
// Neither lane should be polled when paused
expect(routineStore.getDueRoutinesAllScopes).not.toHaveBeenCalled();
expect(routineRunner.handleCatchUp).not.toHaveBeenCalled();
expect(routineRunner.executeRoutine).not.toHaveBeenCalled();
});
it("identical routine IDs across global and project lanes execute once (deduplication)", async () => {
// Same ID in both scopes
const globalRoutine = createMockRoutine({ id: "shared-rout", name: "Shared Global", scope: "global" });
const projectRoutine = createMockRoutine({ id: "shared-rout", name: "Shared Project", scope: "project" });
const routineStore = createMockRoutineStore([globalRoutine, projectRoutine]);
(routineStore.getDueRoutinesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalRoutine, projectRoutine]);
const routineRunner = createMockRoutineRunner();
scheduler = createRoutineScheduler(createMockTaskStore(), routineStore, routineRunner, { scope: "all" });
await scheduler.tick();
// Only one execution should occur due to deduplication
expect(routineRunner.executeRoutine).toHaveBeenCalledTimes(1);
});
});
describe("cross-package scoped isolation integration", () => {
it("RoutineScheduler bound to project store never executes global-lane routines", async () => {
const globalRoutine = createMockRoutine({ id: "global-iso-rout", name: "Global", scope: "global" });
const routineStore = createMockRoutineStore([globalRoutine]);
const routineRunner = createMockRoutineRunner();
scheduler = createRoutineScheduler(createMockTaskStore(), routineStore, routineRunner, { scope: "project" });
await scheduler.tick();
// Project-scoped scheduler should not execute global routine
expect(routineRunner.handleCatchUp).not.toHaveBeenCalled();
expect(routineRunner.executeRoutine).not.toHaveBeenCalled();
});
it("RoutineScheduler bound to global store never executes project-lane routines", async () => {
const projectRoutine = createMockRoutine({ id: "project-iso-rout", name: "Project", scope: "project" });
const routineStore = createMockRoutineStore([projectRoutine]);
const routineRunner = createMockRoutineRunner();
scheduler = createRoutineScheduler(createMockTaskStore(), routineStore, routineRunner, { scope: "global" });
await scheduler.tick();
// Global-scoped scheduler should not execute project routine
expect(routineRunner.handleCatchUp).not.toHaveBeenCalled();
expect(routineRunner.executeRoutine).not.toHaveBeenCalled();
});
it("scope='all' processes routines from both lanes but never crosses lane boundaries", async () => {
const globalRoutine = createMockRoutine({ id: "global-boundary-rout", name: "Global", scope: "global" });
const projectRoutine = createMockRoutine({ id: "project-boundary-rout", name: "Project", scope: "project" });
const routineStore = createMockRoutineStore([globalRoutine, projectRoutine]);
(routineStore.getDueRoutinesAllScopes as ReturnType<typeof vi.fn>).mockResolvedValue([globalRoutine, projectRoutine]);
const routineRunner = createMockRoutineRunner();
scheduler = createRoutineScheduler(createMockTaskStore(), routineStore, routineRunner, { scope: "all" });
await scheduler.tick();
// Both lanes should be processed
expect(routineRunner.handleCatchUp).toHaveBeenCalledTimes(2);
expect(routineRunner.executeRoutine).toHaveBeenCalledTimes(2);
// Each routine is executed exactly once (no double execution)
expect(routineRunner.executeRoutine).toHaveBeenCalledWith("global-boundary-rout", "cron");
expect(routineRunner.executeRoutine).toHaveBeenCalledWith("project-boundary-rout", "cron");
});
it("scope='project' lane identity is preserved through handleCatchUp → executeRoutine", async () => {
const projectRoutine = createMockRoutine({ id: "project-preserved-id", name: "Project Preserved", scope: "project" });
const routineStore = createMockRoutineStore([projectRoutine]);
const routineRunner = createMockRoutineRunner();
scheduler = createRoutineScheduler(createMockTaskStore(), routineStore, routineRunner, { scope: "project" });
await scheduler.tick();
// Lane identity preserved through the execution pipeline
expect(routineRunner.handleCatchUp).toHaveBeenCalledWith(projectRoutine);
expect(routineRunner.executeRoutine).toHaveBeenCalledWith("project-preserved-id", "cron");
});
});
});