| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- 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(),
- }),
- }),
- ),
- ),
- )
- })
|