diff --git a/src/lib/onboard/machine/flow-phases/agent-policy-finalization.test.ts b/src/lib/onboard/machine/flow-phases/agent-policy-finalization.test.ts index 6a858175469..5f848695f23 100644 --- a/src/lib/onboard/machine/flow-phases/agent-policy-finalization.test.ts +++ b/src/lib/onboard/machine/flow-phases/agent-policy-finalization.test.ts @@ -3,14 +3,6 @@ import { describe, expect, it } from "vitest"; -import { - createSession, - filterSafeUpdates, - MACHINE_SNAPSHOT_VERSION, - normalizeSession, - type Session, - type SessionUpdates, -} from "../../../state/onboard-session"; import type { OnboardFlowContext } from "../flow-context"; import { advanceTo, completeOnboardMachine } from "../result"; import { OnboardRuntime, type OnboardRuntimeDeps } from "../runtime"; @@ -22,6 +14,18 @@ import { createPoliciesPhase, createPostVerifyPhase, } from "./agent-policy-finalization"; +import { + MACHINE_SNAPSHOT_VERSION, + type Session, + type SessionUpdates, + cloneSession, + createSession, + createTestRuntime, +} from "../../../../../test/helpers/onboard-machine-runtime-fixture"; + +function createRuntime(initialSession: Session = createSession()) { + return createTestRuntime(initialSession, { now: () => "2026-05-29T00:00:00.000Z" }); +} function context(): OnboardFlowContext { return { @@ -52,51 +56,6 @@ function context(): OnboardFlowContext { }; } -function cloneSession(session: Session): Session { - return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; -} - -function createRuntime(initialSession: Session = createSession()) { - let session = cloneSession(initialSession); - const updateSession = (mutator: (value: Session) => Session | void): Session => { - session = cloneSession(mutator(cloneSession(session)) ?? session); - return cloneSession(session); - }; - const deps: OnboardRuntimeDeps = { - loadSession: () => cloneSession(session), - createSession, - saveSession: (next) => { - session = cloneSession(next); - return cloneSession(session); - }, - updateSession, - markStepStarted: () => cloneSession(session), - markStepComplete: (_stepName, updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - return current; - }), - markStepSkipped: () => cloneSession(session), - markStepFailed: (stepName, message) => - updateSession((current) => { - current.steps[stepName].status = "failed"; - current.steps[stepName].error = message ?? null; - return current; - }), - completeSession: (updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - current.status = "complete"; - current.resumable = false; - return current; - }), - filterSafeUpdates, - emitEvent: () => undefined, - now: () => "2026-05-29T00:00:00.000Z", - }; - return new OnboardRuntime(deps); -} - describe("agent/policy/finalization phases", () => { it("creates branch-specific setup phases", async () => { const agentPhase = createAgentSetupPhase(async () => ({ result: advanceTo("policies") })); diff --git a/src/lib/onboard/machine/flow-sequence.test.ts b/src/lib/onboard/machine/flow-sequence.test.ts index a0e8d6c763e..1fe51188963 100644 --- a/src/lib/onboard/machine/flow-sequence.test.ts +++ b/src/lib/onboard/machine/flow-sequence.test.ts @@ -3,20 +3,24 @@ import { describe, expect, it } from "vitest"; -import { - createSession, - filterSafeUpdates, - MACHINE_SNAPSHOT_VERSION, - normalizeSession, - type Session, - type SessionUpdates, -} from "../../state/onboard-session"; import type { OnboardFlowContext, OnboardFlowPhaseResult } from "./flow-context"; import { onboardFlowPhaseResult } from "./flow-context"; import { buildOnboardFlowPhaseSequence } from "./flow-sequence"; import { advanceTo, branchTo, completeOnboardMachine } from "./result"; import { OnboardRuntime, type OnboardRuntimeDeps } from "./runtime"; import { runOnboardSequenceWithRunner } from "./sequence-runner"; +import { + MACHINE_SNAPSHOT_VERSION, + type Session, + type SessionUpdates, + cloneSession, + createSession, + createTestRuntime, +} from "../../../../test/helpers/onboard-machine-runtime-fixture"; + +function createRuntime(initialSession: Session = createSession()) { + return createTestRuntime(initialSession, { now: () => "2026-05-29T00:00:00.000Z" }); +} type Context = OnboardFlowContext; @@ -57,51 +61,6 @@ function result( return onboardFlowPhaseResult(ctx, advanceTo(next)); } -function cloneSession(session: Session): Session { - return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; -} - -function createRuntime(initialSession: Session = createSession()) { - let session = cloneSession(initialSession); - const updateSession = (mutator: (value: Session) => Session | void): Session => { - session = cloneSession(mutator(cloneSession(session)) ?? session); - return cloneSession(session); - }; - const deps: OnboardRuntimeDeps = { - loadSession: () => cloneSession(session), - createSession, - saveSession: (next) => { - session = cloneSession(next); - return cloneSession(session); - }, - updateSession, - markStepStarted: () => cloneSession(session), - markStepComplete: (_stepName, updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - return current; - }), - markStepSkipped: () => cloneSession(session), - markStepFailed: (stepName, message) => - updateSession((current) => { - current.steps[stepName].status = "failed"; - current.steps[stepName].error = message ?? null; - return current; - }), - completeSession: (updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - current.status = "complete"; - current.resumable = false; - return current; - }), - filterSafeUpdates, - emitEvent: () => undefined, - now: () => "2026-05-29T00:00:00.000Z", - }; - return new OnboardRuntime(deps); -} - describe("onboard flow phase sequence", () => { it("assembles phases in machine order", () => { const phases = buildOnboardFlowPhaseSequence({ diff --git a/src/lib/onboard/machine/flow-slices.test.ts b/src/lib/onboard/machine/flow-slices.test.ts index b4b4cafb55a..f76d6f41d9f 100644 --- a/src/lib/onboard/machine/flow-slices.test.ts +++ b/src/lib/onboard/machine/flow-slices.test.ts @@ -3,14 +3,6 @@ import { describe, expect, it } from "vitest"; -import { - createSession, - filterSafeUpdates, - MACHINE_SNAPSHOT_VERSION, - normalizeSession, - type Session, - type SessionUpdates, -} from "../../state/onboard-session"; import type { OnboardFlowContext } from "./flow-context"; import { coreOnboardFlowPhases, @@ -23,10 +15,14 @@ import { import { advanceTo, branchTo, completeOnboardMachine } from "./result"; import { OnboardRuntime, type OnboardRuntimeDeps } from "./runtime"; import type { OnboardSequencePhase } from "./sequence-runner"; - -function cloneSession(session: Session): Session { - return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; -} +import { + MACHINE_SNAPSHOT_VERSION, + type Session, + type SessionUpdates, + cloneSession, + createSession, + filterSafeUpdates, +} from "../../../../test/helpers/onboard-machine-runtime-fixture"; function runtime(initialSession: Session = createSession()) { let session = cloneSession(initialSession); diff --git a/src/lib/onboard/machine/runner-sequence.test.ts b/src/lib/onboard/machine/runner-sequence.test.ts index 97837bbed3b..8a13f838e36 100644 --- a/src/lib/onboard/machine/runner-sequence.test.ts +++ b/src/lib/onboard/machine/runner-sequence.test.ts @@ -3,14 +3,6 @@ import { describe, expect, it, vi } from "vitest"; -import { - createSession, - filterSafeUpdates, - MACHINE_SNAPSHOT_VERSION, - normalizeSession, - type Session, - type SessionUpdates, -} from "../../state/onboard-session"; import { advanceTo, branchTo, completeOnboardMachine, failOnboardMachine, retryTo } from "./result"; import { EmptyOnboardStateHandlerResultError, @@ -21,58 +13,20 @@ import { runOnboardMachine, } from "./runner"; import { OnboardRuntime, type OnboardRuntimeDeps } from "./runtime"; +import { + MACHINE_SNAPSHOT_VERSION, + type Session, + type SessionUpdates, + cloneSession, + createSession, + createTestRuntime as createRuntime, +} from "../../../../test/helpers/onboard-machine-runtime-fixture"; interface RunnerContext { attempts: number; visited: string[]; } -function cloneSession(session: Session): Session { - return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; -} - -function createRuntime(initialSession: Session = createSession()) { - let session = cloneSession(initialSession); - const updateSession = (mutator: (value: Session) => Session | void): Session => { - const next = mutator(cloneSession(session)) ?? session; - session = cloneSession(next); - return cloneSession(session); - }; - const deps: OnboardRuntimeDeps = { - loadSession: () => cloneSession(session), - createSession, - saveSession: (next) => { - session = cloneSession(next); - return cloneSession(session); - }, - updateSession, - markStepStarted: () => cloneSession(session), - markStepComplete: (_stepName, updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - return current; - }), - markStepSkipped: () => cloneSession(session), - markStepFailed: (stepName, message) => - updateSession((current) => { - current.steps[stepName].status = "failed"; - current.steps[stepName].error = message ?? null; - return current; - }), - completeSession: (updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - current.status = "complete"; - current.resumable = false; - return current; - }), - filterSafeUpdates, - emitEvent: () => undefined, - now: () => "2026-05-28T00:00:00.000Z", - }; - return new OnboardRuntime(deps); -} - describe("runOnboardMachine result sequences", () => { it("runs handlers until completion while applying multiple results in order", async () => { const runtime = createRuntime(); diff --git a/src/lib/onboard/machine/runner.test.ts b/src/lib/onboard/machine/runner.test.ts index f520f480b59..5d5bca43c8a 100644 --- a/src/lib/onboard/machine/runner.test.ts +++ b/src/lib/onboard/machine/runner.test.ts @@ -3,14 +3,6 @@ import { describe, expect, it, vi } from "vitest"; -import { - createSession, - filterSafeUpdates, - MACHINE_SNAPSHOT_VERSION, - normalizeSession, - type Session, - type SessionUpdates, -} from "../../state/onboard-session"; import { advanceTo, branchTo, @@ -26,58 +18,20 @@ import { runOnboardMachine, } from "./runner"; import { OnboardRuntime, type OnboardRuntimeDeps } from "./runtime"; +import { + MACHINE_SNAPSHOT_VERSION, + type Session, + type SessionUpdates, + cloneSession, + createSession, + createTestRuntime as createRuntime, +} from "../../../../test/helpers/onboard-machine-runtime-fixture"; interface RunnerContext { attempts: number; visited: string[]; } -function cloneSession(session: Session): Session { - return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; -} - -function createRuntime(initialSession: Session = createSession()) { - let session = cloneSession(initialSession); - const updateSession = (mutator: (value: Session) => Session | void): Session => { - const next = mutator(cloneSession(session)) ?? session; - session = cloneSession(next); - return cloneSession(session); - }; - const deps: OnboardRuntimeDeps = { - loadSession: () => cloneSession(session), - createSession, - saveSession: (next) => { - session = cloneSession(next); - return cloneSession(session); - }, - updateSession, - markStepStarted: () => cloneSession(session), - markStepComplete: (_stepName, updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - return current; - }), - markStepSkipped: () => cloneSession(session), - markStepFailed: (stepName, message) => - updateSession((current) => { - current.steps[stepName].status = "failed"; - current.steps[stepName].error = message ?? null; - return current; - }), - completeSession: (updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - current.status = "complete"; - current.resumable = false; - return current; - }), - filterSafeUpdates, - emitEvent: () => undefined, - now: () => "2026-05-28T00:00:00.000Z", - }; - return new OnboardRuntime(deps); -} - describe("runOnboardMachine", () => { it("runs handlers until completion while applying retry and branch transitions", async () => { const runtime = createRuntime(); diff --git a/src/lib/onboard/machine/runtime.test.ts b/src/lib/onboard/machine/runtime.test.ts index 84d3add6852..d76820af90a 100644 --- a/src/lib/onboard/machine/runtime.test.ts +++ b/src/lib/onboard/machine/runtime.test.ts @@ -3,13 +3,6 @@ import { describe, expect, it } from "vitest"; -import { - createSession, - filterSafeUpdates, - normalizeSession, - type Session, - type SessionUpdates, -} from "../../state/onboard-session"; import type { OnboardMachineEvent } from "./events"; import { advanceTo, @@ -21,10 +14,13 @@ import { } from "./result"; import { OnboardRuntime, type OnboardRuntimeDeps } from "./runtime"; import { InvalidOnboardMachineTransitionError } from "./transitions"; - -function cloneSession(session: Session): Session { - return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; -} +import { + type Session, + type SessionUpdates, + cloneSession, + createSession, + filterSafeUpdates, +} from "../../../../test/helpers/onboard-machine-runtime-fixture"; function createHarness(initialSession: Session | null = createSession()) { let session = initialSession ? cloneSession(initialSession) : null; diff --git a/src/lib/onboard/machine/sequence-runner.test.ts b/src/lib/onboard/machine/sequence-runner.test.ts index 579b2158943..ed8c3026baf 100644 --- a/src/lib/onboard/machine/sequence-runner.test.ts +++ b/src/lib/onboard/machine/sequence-runner.test.ts @@ -3,13 +3,6 @@ import { describe, expect, it } from "vitest"; -import { - createSession, - filterSafeUpdates, - normalizeSession, - type Session, - type SessionUpdates, -} from "../../state/onboard-session"; import { advanceTo, branchTo, completeOnboardMachine, retryTo } from "./result"; import { OnboardRuntime, type OnboardRuntimeDeps } from "./runtime"; import { @@ -18,57 +11,23 @@ import { type OnboardSequencePhase, runOnboardSequenceWithRunner, } from "./sequence-runner"; +import { + type Session, + type SessionUpdates, + cloneSession, + createSession, + createTestRuntime, +} from "../../../../test/helpers/onboard-machine-runtime-fixture"; + +function createRuntime(initialSession: Session = createSession()) { + return createTestRuntime(initialSession, { now: () => "2026-05-29T00:00:00.000Z" }); +} interface SequenceContext { attempt: number; log: string[]; } -function cloneSession(session: Session): Session { - return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; -} - -function createRuntime(initialSession: Session = createSession()) { - let session = cloneSession(initialSession); - const updateSession = (mutator: (value: Session) => Session | void): Session => { - session = cloneSession(mutator(cloneSession(session)) ?? session); - return cloneSession(session); - }; - const deps: OnboardRuntimeDeps = { - loadSession: () => cloneSession(session), - createSession, - saveSession: (next) => { - session = cloneSession(next); - return cloneSession(session); - }, - updateSession, - markStepStarted: () => cloneSession(session), - markStepComplete: (_stepName, updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - return current; - }), - markStepSkipped: () => cloneSession(session), - markStepFailed: (stepName, message) => - updateSession((current) => { - current.steps[stepName].status = "failed"; - current.steps[stepName].error = message ?? null; - return current; - }), - completeSession: (updates: SessionUpdates = {}) => - updateSession((current) => { - Object.assign(current, filterSafeUpdates(updates)); - current.status = "complete"; - current.resumable = false; - return current; - }), - filterSafeUpdates, - emitEvent: () => undefined, - now: () => "2026-05-29T00:00:00.000Z", - }; - return new OnboardRuntime(deps); -} - function phase( state: OnboardSequencePhase["state"], run: OnboardSequencePhase["run"], diff --git a/src/lib/onboard/machine/transition-traces.test.ts b/src/lib/onboard/machine/transition-traces.test.ts index a21951cc017..9e28ad929fe 100644 --- a/src/lib/onboard/machine/transition-traces.test.ts +++ b/src/lib/onboard/machine/transition-traces.test.ts @@ -14,16 +14,6 @@ import { describe, expect, it, vi } from "vitest"; -import { - createSession, - filterSafeUpdates, - MACHINE_SNAPSHOT_VERSION, - normalizeSession, - type OnboardMachineSnapshot, - type Session, - type SessionUpdates, - type StepState, -} from "../../state/onboard-session"; import type { OnboardMachineEvent } from "./events"; import { handleSandboxState } from "./handlers/sandbox"; import { baseOptions, bindJournaledRecreate, createDeps } from "./handlers/sandbox-test-fixtures"; @@ -31,13 +21,18 @@ import { advanceTo, branchTo, completeOnboardMachine, failOnboardMachine } from import { type OnboardStateHandlers, runOnboardMachine } from "./runner"; import { OnboardRuntime, type OnboardRuntimeDeps } from "./runtime"; import type { OnboardMachineState } from "./types"; +import type { OnboardMachineSnapshot, StepState } from "../../state/onboard-session"; +import { + MACHINE_SNAPSHOT_VERSION, + type Session, + type SessionUpdates, + cloneSession, + createSession, + filterSafeUpdates, +} from "../../../../test/helpers/onboard-machine-runtime-fixture"; const NOW = "2026-07-04T00:00:00.000Z"; -function cloneSession(session: Session): Session { - return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; -} - function machineAt(state: OnboardMachineState, revision = 0): OnboardMachineSnapshot { return { version: MACHINE_SNAPSHOT_VERSION, state, stateEnteredAt: NOW, revision }; } diff --git a/test/helpers/onboard-machine-runtime-fixture.ts b/test/helpers/onboard-machine-runtime-fixture.ts new file mode 100644 index 00000000000..58b88d01a95 --- /dev/null +++ b/test/helpers/onboard-machine-runtime-fixture.ts @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + createSession, + filterSafeUpdates, + normalizeSession, + type Session, + type SessionUpdates, +} from "../../src/lib/state/onboard-session"; +import { OnboardRuntime, type OnboardRuntimeDeps } from "../../src/lib/onboard/machine/runtime"; + +export { + createSession, + filterSafeUpdates, + MACHINE_SNAPSHOT_VERSION, +} from "../../src/lib/state/onboard-session"; +export type { Session, SessionUpdates } from "../../src/lib/state/onboard-session"; + +/** + * In-memory onboarding-machine runtime for the machine test suites. The + * fixture owns the session store mechanics the suites previously each + * declared inline; scenario sessions, timestamps that assertions depend on, + * and behavior expectations stay in each test. Every call returns fresh + * state, and nothing outside the returned runtime is touched. + */ + +/** A deep session copy that survives independent mutation. */ +export function cloneSession(session: Session): Session { + return normalizeSession(JSON.parse(JSON.stringify(session))) ?? session; +} + +/** Behavior options for createTestRuntime. */ +export interface TestRuntimeOptions { + /** The deterministic clock the runtime stamps sessions with. */ + now?: () => string; +} + +/** + * An OnboardRuntime over an in-memory session store. Step transitions + * mutate the stored session the way the suites' inline copies did; the + * clock defaults to a fixed timestamp and is overridable where assertions + * depend on a specific value. + */ +export function createTestRuntime( + initialSession: Session = createSession(), + options?: TestRuntimeOptions, +): OnboardRuntime { + let session = cloneSession(initialSession); + const updateSession = (mutator: (value: Session) => Session | void): Session => { + session = cloneSession(mutator(cloneSession(session)) ?? session); + return cloneSession(session); + }; + const deps: OnboardRuntimeDeps = { + loadSession: () => cloneSession(session), + createSession, + saveSession: (next) => { + session = cloneSession(next); + return cloneSession(session); + }, + updateSession, + markStepStarted: () => cloneSession(session), + markStepComplete: (_stepName, updates: SessionUpdates = {}) => + updateSession((current) => { + Object.assign(current, filterSafeUpdates(updates)); + return current; + }), + markStepSkipped: () => cloneSession(session), + markStepFailed: (stepName, message) => + updateSession((current) => { + current.steps[stepName].status = "failed"; + current.steps[stepName].error = message ?? null; + return current; + }), + completeSession: (updates: SessionUpdates = {}) => + updateSession((current) => { + Object.assign(current, filterSafeUpdates(updates)); + current.status = "complete"; + current.resumable = false; + return current; + }), + filterSafeUpdates, + emitEvent: () => undefined, + now: options?.now ?? (() => "2026-05-28T00:00:00.000Z"), + }; + return new OnboardRuntime(deps); +}