Skip to content
Source
Sign inSign up

serverRuntimeStartup.autoPull.test.ts

202 lines9.0 KB

1import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
2import { expect, it } from "@effect/vitest";
3import { DEFAULT_SERVER_SETTINGS, EnvironmentId, ProjectId } from "@t3tools/contracts";
4import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability";
5import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
6import * as Deferred from "effect/Deferred";
7import * as Effect from "effect/Effect";
8import * as FileSystem from "effect/FileSystem";
9import * as Layer from "effect/Layer";
10import * as Path from "effect/Path";
11import { HttpServer } from "effect/http";
12import * as NetAddress from "effect/net/NetAddress";
13​
14import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
15import * as ServiceLauncherClient from "./cloud/serviceLauncherClient.ts";
16import { SERVICE_LAUNCHER_PROTOCOL } from "./cloud/serviceProtocol.ts";
17import * as ServerConfig from "./config.ts";
18import * as ServerEnvironment from "./environment/ServerEnvironment.ts";
19import * as Keybindings from "./keybindings.ts";
20import * as EffectWorker from "./orchestration-v2/EffectWorker.ts";
21import * as LegacyV1ThreadImporter from "./orchestration-v2/legacy/LegacyV1ThreadImporter.ts";
22import * as Orchestrator from "./orchestration-v2/Orchestrator.ts";
23import * as ProjectStore from "./orchestration-v2/ProjectStore.ts";
24import * as ProviderRuntimeRecovery from "./orchestration-v2/ProviderRuntimeRecoveryService.ts";
25import * as ProviderSessionManager from "./orchestration-v2/ProviderSessionManager.ts";
26import * as ThreadLaunch from "./orchestration-v2/ThreadLaunchService.ts";
27import * as ThreadManagement from "./orchestration-v2/ThreadManagementService.ts";
28import * as ExternalLauncher from "./process/externalLauncher.ts";
29import * as ProjectService from "./project/ProjectService.ts";
30import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts";
31import { ServerActivation } from "./serverActivation.ts";
32import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
33import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
34import * as ServerSettings from "./serverSettings.ts";
35import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
36import * as AnalyticsService from "./telemetry/AnalyticsService.ts";
37import * as GitVcsDriver from "./vcs/GitVcsDriver.ts";
38​
39it.effect("parks automatic pull until activation without delaying command readiness", () =>
40 Effect.scoped(
41 Effect.gen(function* () {
42 const activation = yield* Deferred.make<void>();
43 const prepared = yield* Deferred.make<void>();
44 const commitTrial = yield* Deferred.make<void>();
45 const statusCalled = yield* Deferred.make<string>();
46 const statusInterrupted = yield* Deferred.make<void>();
47 const cwd = "/auto-pull-project";
48 const updatedAt = "2026-01-01T00:00:00.000Z";
49 const snapshot = {
50 projects: [
51 {
52 id: ProjectId.make("auto-pull-project"),
53 title: "Auto pull project",
54 workspaceRoot: cwd,
55 repositoryIdentity: null,
56 defaultModelSelection: null,
57 scripts: [],
58 createdAt: updatedAt,
59 updatedAt,
60 deletedAt: null,
61 },
62 ],
63 updatedAt,
64 };
65 const recovery = {
66 terminalizedRuns: 0,
67 stoppedSessions: 0,
68 closedRequests: 0,
69 retiredEffects: 0,
70 requeuedEffects: 0,
71 };
72 const importSummary = { importedThreadCount: 0, importedMessageCount: 0 };
73 const dependencies: Layer.Layer<
74 Layer.Services<ReturnType<typeof ServerRuntimeStartup.layerWithOptions>>
75 > = Layer.mergeAll(
76 Layer.mock(ServerConfig.ServerConfig)({
77 ...(yield* ServerConfig.deriveServerPaths(cwd, undefined).pipe(
78 Effect.provide(Path.layer),
79 )),
80 baseDir: cwd,
81 logLevel: "Error",
82 traceMinLevel: "Info",
83 traceTimingEnabled: false,
84 traceBatchWindowMs: 200,
85 traceMaxBytes: 1024,
86 traceMaxFiles: 1,
87 otlpTracesUrl: undefined,
88 otlpMetricsUrl: undefined,
89 otlpLogsUrl: undefined,
90 otlpTracesExport: DEFAULT_SIGNAL_EXPORT,
91 otlpMetricsExport: DEFAULT_SIGNAL_EXPORT,
92 otlpLogsExport: DEFAULT_SIGNAL_EXPORT,
93 otelEnvironment: OtelEnvironment.none,
94 staticDir: undefined,
95 devAllowedOrigins: [],
96 desktopBootstrapToken: undefined,
97 logWebSocketEvents: false,
98 tailscaleServeEnabled: false,
99 tailscaleServePort: 443,
100 mode: "desktop",
101 cwd,
102 host: "localhost",
103 port: 3773,
104 devUrl: undefined,
105 noBrowser: true,
106 startupPresentation: "browser",
107 autoBootstrapProjectFromCwd: false,
108 }),
109 Layer.mock(Keybindings.Keybindings)({ start: Effect.void }),
110 Layer.mock(LegacyV1ThreadImporter.LegacyV1ThreadImporter)({
111 pendingThreadCount: Effect.succeed(0),
112 reconcileShells: Effect.succeed(importSummary),
113 importPendingTranscripts: Effect.succeed(importSummary),
114 }),
115 Layer.mock(ProviderRuntimeRecovery.ProviderRuntimeRecoveryService)({
116 recover: Effect.succeed(recovery),
117 prepareForShutdown: Effect.void,
118 reconcile: () => Effect.succeed(recovery),
119 }),
120 Layer.mock(Orchestrator.OrchestratorV2)({ recoverDelegatedTasks: Effect.void }),
121 Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({ shutdown: Effect.void }),
122 Layer.mock(AgentAwarenessRelay.AgentAwarenessRelay)({ start: () => Effect.void }),
123 Layer.mock(EffectWorker.OrchestrationEffectWorkerV2)({ runOnce: () => Effect.never }),
124 Layer.mock(ServerLifecycleEvents.ServerLifecycleEvents)({
125 publish: (event) => Effect.succeed({ ...event, sequence: 1 }),
126 }),
127 Layer.mock(ServerSettings.ServerSettingsService)({
128 start: Effect.void,
129 getSettings: Effect.succeed({ ...DEFAULT_SERVER_SETTINGS, defaultAutoPull: true }),
130 }),
131 Layer.mock(ServerEnvironment.ServerEnvironment)({
132 getDescriptor: Effect.succeed({
133 environmentId: EnvironmentId.make("auto-pull-environment"),
134 label: "Test environment",
135 platform: { os: "darwin", arch: "arm64" },
136 serverVersion: "0.0.0-test",
137 capabilities: { repositoryIdentity: true },
138 }),
139 }),
140 Layer.mock(ProjectStore.ProjectStoreV2)({
141 listShells: () => Effect.succeed(snapshot.projects),
142 }),
143 Layer.mock(ProjectService.ProjectService)({ snapshot: Effect.succeed(snapshot) }),
144 Layer.mock(ThreadManagement.ThreadManagementService)({
145 countThreads: () => Effect.succeed(0),
146 }),
147 Layer.mock(ThreadLaunch.ThreadLaunchService)({}),
148 Layer.mock(ServiceLauncherClient.ServiceLauncherClient)({
149 managed: true,
150 protocol: SERVICE_LAUNCHER_PROTOCOL,
151 prepareTrial: () =>
152 Deferred.succeed(prepared, undefined).pipe(
153 Effect.andThen(Deferred.await(commitTrial)),
154 Effect.as(undefined),
155 ),
156 }),
157 Layer.mock(GitVcsDriver.GitVcsDriver)({
158 statusDetails: (root) =>
159 Deferred.succeed(statusCalled, root).pipe(
160 Effect.andThen(Effect.never),
161 Effect.onInterrupt(() => Deferred.succeed(statusInterrupted, undefined)),
162 ),
163 }),
164 Layer.mock(AnalyticsService.AnalyticsService)({ record: () => Effect.void }),
165 // No provider logins or Connect link, so startup records no owners.
166 FileSystem.layerNoop({}),
167 Layer.mock(ServerSecretStore.ServerSecretStore)({ get: () => Effect.succeedNone }),
168 NodeCrypto.layer,
169 Layer.mock(EnvironmentAuth.EnvironmentAuth)({}),
170 Layer.mock(ExternalLauncher.ExternalLauncher)({}),
171 Layer.mock(HttpServer.HttpServer)({
172 address: NetAddress.inetAddressFromIpStringUnsafe("127.0.0.1", 3773),
173 }),
174 Path.layer,
175 );
176​
177 yield* Effect.gen(function* () {
178 const startup = yield* ServerRuntimeStartup.ServerRuntimeStartup;
179 yield* startup.markHttpListening;
180​
181 yield* Effect.raceFirst(Deferred.await(prepared), Deferred.await(statusCalled));
182 expect(yield* Deferred.isDone(activation)).toBe(false);
183 expect(yield* Deferred.isDone(statusCalled)).toBe(false);
184 expect(yield* Deferred.isDone(prepared)).toBe(true);
185​
186 yield* Deferred.succeed(commitTrial, undefined);
187 yield* Deferred.await(activation);
188 expect(yield* Deferred.await(statusCalled)).toBe(cwd);
189 yield* startup.awaitCommandReady;
190 }).pipe(
191 Effect.provide(
192 ServerRuntimeStartup.layerWithOptions({
193 activate: Deferred.succeed(activation, undefined).pipe(Effect.asVoid),
194 awaitAuxiliaryParked: Effect.void,
195 }).pipe(Layer.provide(dependencies)),
196 ),
197 Effect.provideService(ServerActivation, Deferred.await(activation)),
198 );
199 expect(yield* Deferred.isDone(statusInterrupted)).toBe(true);
200 }),
201 ),
202);