| 1 | import * as ScriptProgress from "@t3tools/provider-core/server/ScriptProgress"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { expect, it } from "@effect/vitest"; |
| 4 | import { ProviderInstanceId } from "@t3tools/contracts"; |
| 5 | import * as HostProcess from "@t3tools/shared/HostProcess"; |
| 6 | import * as Effect from "effect/Effect"; |
| 7 | import * as FileSystem from "effect/FileSystem"; |
| 8 | import * as Layer from "effect/Layer"; |
| 9 | import * as Path from "effect/Path"; |
| 10 | import * as HttpClient from "effect/http/HttpClient"; |
| 11 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 12 | |
| 13 | import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; |
| 14 | import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; |
| 15 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; |
| 16 | import { GrokDriver } from "./driver.ts"; |
| 17 | |
| 18 | import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; |
| 19 | import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; |
| 20 | |
| 21 | const layerTest = TestProviderHost.layer({ runBackgroundWork: false }).pipe( |
| 22 | Layer.provideMerge(NodeServices.layer), |
| 23 | Layer.provideMerge(IdAllocator.layer), |
| 24 | Layer.provideMerge(Layer.merge(McpProviderSessions.layer, ScriptProgress.layerUnavailable)), |
| 25 | Layer.provideMerge(ProviderLatestVersions.layer), |
| 26 | Layer.provideMerge( |
| 27 | Layer.succeed( |
| 28 | ProviderEventLoggers.ProviderEventLoggers, |
| 29 | ProviderEventLoggers.NoOpProviderEventLoggers, |
| 30 | ), |
| 31 | ), |
| 32 | Layer.provideMerge( |
| 33 | Layer.succeed( |
| 34 | HttpClient.HttpClient, |
| 35 | HttpClient.make(() => Effect.die("Disabled Grok must not make an HTTP request")), |
| 36 | ), |
| 37 | ), |
| 38 | ); |
| 39 | |
| 40 | const noSpawner = ChildProcessSpawner.make(() => |
| 41 | Effect.die("Disabled Grok must not spawn a process"), |
| 42 | ); |
| 43 | |
| 44 | // The `#!/bin/sh` stub below cannot be resolved as an executable on Windows. |
| 45 | const windowsHost = HostProcess.Platform.defaultValue() === "win32"; |
| 46 | |
| 47 | it.layer(layerTest)("GrokDriver", (it) => { |
| 48 | it.effect.skipIf(windowsHost)("updates through the configured executable's own updater", () => |
| 49 | Effect.gen(function* () { |
| 50 | const fs = yield* FileSystem.FileSystem; |
| 51 | const path = yield* Path.Path; |
| 52 | const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-grok-driver-" }); |
| 53 | const grokHome = path.join(tempDir, "Grok Home"); |
| 54 | const binaryPath = path.join(grokHome, "bin", "grok"); |
| 55 | yield* fs.makeDirectory(path.dirname(binaryPath), { recursive: true }); |
| 56 | yield* fs.writeFileString(binaryPath, "#!/bin/sh\n"); |
| 57 | yield* fs.chmod(binaryPath, 0o755); |
| 58 | |
| 59 | const instance = yield* GrokDriver.create({ |
| 60 | instanceId: ProviderInstanceId.make("grok-update"), |
| 61 | displayName: "Grok test", |
| 62 | enabled: false, |
| 63 | environment: [{ name: "GROK_HOME", value: grokHome, sensitive: false }], |
| 64 | config: { ...GrokDriver.defaultConfig(), binaryPath }, |
| 65 | }); |
| 66 | |
| 67 | const capabilities = yield* instance.snapshot.resolveMaintenance(); |
| 68 | expect(capabilities.packageName).toBe("@xai-official/grok"); |
| 69 | expect(capabilities.update).toMatchObject({ |
| 70 | command: `'${binaryPath}' update`, |
| 71 | executable: binaryPath, |
| 72 | args: ["update"], |
| 73 | }); |
| 74 | // `grok update` installs under GROK_HOME, so it must target this instance's home. |
| 75 | expect(capabilities.update?.env?.GROK_HOME).toBe(grokHome); |
| 76 | }).pipe( |
| 77 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawner), |
| 78 | Effect.scoped, |
| 79 | ), |
| 80 | ); |
| 81 | |
| 82 | it.effect("stays manual-only when the configured executable does not exist", () => |
| 83 | Effect.gen(function* () { |
| 84 | const fs = yield* FileSystem.FileSystem; |
| 85 | const path = yield* Path.Path; |
| 86 | const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-grok-missing-" }); |
| 87 | const instance = yield* GrokDriver.create({ |
| 88 | instanceId: ProviderInstanceId.make("grok-missing"), |
| 89 | displayName: "Grok test", |
| 90 | enabled: false, |
| 91 | environment: [], |
| 92 | config: { ...GrokDriver.defaultConfig(), binaryPath: path.join(tempDir, "grok") }, |
| 93 | }); |
| 94 | expect((yield* instance.snapshot.resolveMaintenance()).update).toBeNull(); |
| 95 | }).pipe( |
| 96 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawner), |
| 97 | Effect.scoped, |
| 98 | ), |
| 99 | ); |
| 100 | }); |