mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-31 06:12:26 +00:00
152 lines
5.6 KiB
TypeScript
152 lines
5.6 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import { Effect, Layer, Stream } from "effect"
|
|
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
|
import { Installation } from "../../src/installation"
|
|
|
|
const encoder = new TextEncoder()
|
|
|
|
function mockHttpClient(handler: (request: HttpClientRequest.HttpClientRequest) => Response) {
|
|
const client = HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, handler(request))))
|
|
return Layer.succeed(HttpClient.HttpClient, client)
|
|
}
|
|
|
|
function mockSpawner(handler: (cmd: string, args: readonly string[]) => string = () => "") {
|
|
const spawner = ChildProcessSpawner.make((command) => {
|
|
const std = ChildProcess.isStandardCommand(command) ? command : undefined
|
|
const output = handler(std?.command ?? "", std?.args ?? [])
|
|
return Effect.succeed(
|
|
ChildProcessSpawner.makeHandle({
|
|
pid: ChildProcessSpawner.ProcessId(0),
|
|
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
|
|
isRunning: Effect.succeed(false),
|
|
kill: () => Effect.void,
|
|
stdin: { [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") } as any,
|
|
stdout: output ? Stream.make(encoder.encode(output)) : Stream.empty,
|
|
stderr: Stream.empty,
|
|
all: Stream.empty,
|
|
getInputFd: () => ({ [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") }) as any,
|
|
getOutputFd: () => Stream.empty,
|
|
}),
|
|
)
|
|
})
|
|
return Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner)
|
|
}
|
|
|
|
function jsonResponse(body: unknown) {
|
|
return new Response(JSON.stringify(body), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
})
|
|
}
|
|
|
|
function testLayer(
|
|
httpHandler: (request: HttpClientRequest.HttpClientRequest) => Response,
|
|
spawnHandler?: (cmd: string, args: readonly string[]) => string,
|
|
) {
|
|
return Installation.layer.pipe(Layer.provide(mockHttpClient(httpHandler)), Layer.provide(mockSpawner(spawnHandler)))
|
|
}
|
|
|
|
describe("installation", () => {
|
|
describe("latest", () => {
|
|
test("reads release version from GitHub releases", async () => {
|
|
const layer = testLayer(() => jsonResponse({ tag_name: "v1.2.3" }))
|
|
|
|
const result = await Effect.runPromise(
|
|
Installation.Service.use((svc) => svc.latest("unknown")).pipe(Effect.provide(layer)),
|
|
)
|
|
expect(result).toBe("1.2.3")
|
|
})
|
|
|
|
test("strips v prefix from GitHub release tag", async () => {
|
|
const layer = testLayer(() => jsonResponse({ tag_name: "v4.0.0-beta.1" }))
|
|
|
|
const result = await Effect.runPromise(
|
|
Installation.Service.use((svc) => svc.latest("curl")).pipe(Effect.provide(layer)),
|
|
)
|
|
expect(result).toBe("4.0.0-beta.1")
|
|
})
|
|
|
|
test("reads npm registry versions", async () => {
|
|
const layer = testLayer(
|
|
() => jsonResponse({ version: "1.5.0" }),
|
|
(cmd, args) => {
|
|
if (cmd === "npm" && args.includes("registry")) return "https://registry.npmjs.org\n"
|
|
return ""
|
|
},
|
|
)
|
|
|
|
const result = await Effect.runPromise(
|
|
Installation.Service.use((svc) => svc.latest("npm")).pipe(Effect.provide(layer)),
|
|
)
|
|
expect(result).toBe("1.5.0")
|
|
})
|
|
|
|
test("reads npm registry versions for bun method", async () => {
|
|
const layer = testLayer(
|
|
() => jsonResponse({ version: "1.6.0" }),
|
|
() => "",
|
|
)
|
|
|
|
const result = await Effect.runPromise(
|
|
Installation.Service.use((svc) => svc.latest("bun")).pipe(Effect.provide(layer)),
|
|
)
|
|
expect(result).toBe("1.6.0")
|
|
})
|
|
|
|
test("reads scoop manifest versions", async () => {
|
|
const layer = testLayer(() => jsonResponse({ version: "2.3.4" }))
|
|
|
|
const result = await Effect.runPromise(
|
|
Installation.Service.use((svc) => svc.latest("scoop")).pipe(Effect.provide(layer)),
|
|
)
|
|
expect(result).toBe("2.3.4")
|
|
})
|
|
|
|
test("reads chocolatey feed versions", async () => {
|
|
const layer = testLayer(() => jsonResponse({ d: { results: [{ Version: "3.4.5" }] } }))
|
|
|
|
const result = await Effect.runPromise(
|
|
Installation.Service.use((svc) => svc.latest("choco")).pipe(Effect.provide(layer)),
|
|
)
|
|
expect(result).toBe("3.4.5")
|
|
})
|
|
|
|
test("reads brew formulae API versions", async () => {
|
|
const layer = testLayer(
|
|
() => jsonResponse({ versions: { stable: "2.0.0" } }),
|
|
(cmd, args) => {
|
|
// getBrewFormula: return core formula (no tap)
|
|
if (cmd === "brew" && args.includes("--formula") && args.includes("anomalyco/tap/opencode")) return ""
|
|
if (cmd === "brew" && args.includes("--formula") && args.includes("opencode")) return "opencode"
|
|
return ""
|
|
},
|
|
)
|
|
|
|
const result = await Effect.runPromise(
|
|
Installation.Service.use((svc) => svc.latest("brew")).pipe(Effect.provide(layer)),
|
|
)
|
|
expect(result).toBe("2.0.0")
|
|
})
|
|
|
|
test("reads brew tap info JSON via CLI", async () => {
|
|
const brewInfoJson = JSON.stringify({
|
|
formulae: [{ versions: { stable: "2.1.0" } }],
|
|
})
|
|
const layer = testLayer(
|
|
() => jsonResponse({}), // HTTP not used for tap formula
|
|
(cmd, args) => {
|
|
if (cmd === "brew" && args.includes("anomalyco/tap/opencode") && args.includes("--formula")) return "opencode"
|
|
if (cmd === "brew" && args.includes("--json=v2")) return brewInfoJson
|
|
return ""
|
|
},
|
|
)
|
|
|
|
const result = await Effect.runPromise(
|
|
Installation.Service.use((svc) => svc.latest("brew")).pipe(Effect.provide(layer)),
|
|
)
|
|
expect(result).toBe("2.1.0")
|
|
})
|
|
})
|
|
})
|