opencode/packages/httpapi-codegen/test/write.test.ts

160 lines
4.9 KiB
TypeScript

import { describe, expect } from "bun:test"
import { Effect, FileSystem, Option } from "effect"
import { write, type Output } from "../src"
import { it } from "./effect"
describe("HttpApiCodegen.write", () => {
it.effect("writes compiled files beneath the output directory", () => {
const writes: Array<{ readonly path: string; readonly content: string }> = []
const output: Output = {
operations: [],
files: [{ path: "session.ts", content: "export const session = {}" }],
}
return Effect.gen(function* () {
yield* write(output, "/generated")
expect(writes).toEqual([
{ path: "/generated/session.ts", content: "export const session = {}\n" },
{ path: "/generated/.httpapi-codegen.json", content: '[\n "session.ts"\n]\n' },
])
}).pipe(
Effect.provideService(
FileSystem.FileSystem,
FileSystem.makeNoop({
exists: () => Effect.succeed(false),
makeDirectory: () => Effect.void,
writeFileString: (path, content) => {
writes.push({ path, content })
return Effect.void
},
}),
),
)
})
it.effect("removes only stale files owned by the previous manifest", () => {
const removed: Array<string> = []
return write(
{
operations: [],
files: [{ path: "session.ts", content: "" }],
},
"/generated",
).pipe(
Effect.provideService(
FileSystem.FileSystem,
FileSystem.makeNoop({
exists: (path) => Effect.succeed(path.endsWith(".httpapi-codegen.json")),
makeDirectory: () => Effect.void,
readFileString: () => Effect.succeed('["old.ts", "session.ts"]'),
remove: (path) => {
removed.push(path)
return Effect.void
},
writeFileString: () => Effect.void,
}),
),
Effect.tap(() => Effect.sync(() => expect(removed).toEqual(["/generated/old.ts"]))),
)
})
it.effect("rejects unsafe and duplicate output paths before writing", () => {
const writes: Array<string> = []
return Effect.gen(function* () {
const error = yield* write(
{
operations: [],
files: [
{ path: "../outside.ts", content: "" },
{ path: "client.ts", content: "" },
{ path: "CLIENT.ts", content: "" },
],
},
"/generated",
).pipe(Effect.flip)
expect(error._tag).toBe("GenerationError")
expect(writes).toEqual([])
}).pipe(
Effect.provideService(
FileSystem.FileSystem,
FileSystem.makeNoop({
writeFileString: (path) => {
writes.push(path)
return Effect.void
},
}),
),
)
})
it.effect("rejects case-insensitive duplicate output paths", () => {
const writes: Array<string> = []
return Effect.gen(function* () {
const error = yield* write(
{
operations: [],
files: [
{ path: "client.ts", content: "" },
{ path: "CLIENT.ts", content: "" },
],
},
"/generated",
).pipe(Effect.flip)
expect(error._tag).toBe("GenerationError")
expect(error.reason).toBe("Duplicate output path: CLIENT.ts")
expect(writes).toEqual([])
}).pipe(
Effect.provideService(
FileSystem.FileSystem,
FileSystem.makeNoop({
writeFileString: (path) => {
writes.push(path)
return Effect.void
},
}),
),
)
})
it.effect("reserves the private manifest path", () =>
write({ operations: [], files: [{ path: ".httpapi-codegen.json", content: "" }] }, "/generated").pipe(
Effect.flip,
Effect.tap((error) => Effect.sync(() => expect(error.reason).toContain("Unsafe output path"))),
Effect.provideService(FileSystem.FileSystem, FileSystem.makeNoop({})),
),
)
it.effect("rejects existing symbolic-link output targets", () =>
write({ operations: [], files: [{ path: "session.ts", content: "" }] }, "/generated").pipe(
Effect.flip,
Effect.tap((error) => Effect.sync(() => expect(error.reason).toBe("Unsafe output path: session.ts"))),
Effect.provideService(
FileSystem.FileSystem,
FileSystem.makeNoop({
exists: (path) => Effect.succeed(path.endsWith("session.ts")),
makeDirectory: () => Effect.void,
stat: () =>
Effect.succeed({
type: "SymbolicLink",
mtime: Option.none(),
atime: Option.none(),
birthtime: Option.none(),
dev: 0,
ino: Option.none(),
mode: 0,
nlink: Option.none(),
uid: Option.none(),
gid: Option.none(),
rdev: Option.none(),
size: FileSystem.Size(0),
blksize: Option.none(),
blocks: Option.none(),
}),
}),
),
),
)
})