159 lines · 5.4 KB · TypeScript
RawDownload
import { FileFinder } from "@ff-labs/fff-node";import { afterEach, expect, it } from "@effect/vitest";import * as Cause from "effect/Cause";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import { vi } from "vite-plus/test"; import * as WorkspaceSearchIndex from "./WorkspaceSearchIndex.ts"; afterEach(() => {  vi.restoreAllMocks();}); it.effect("preserves unexpected FileFinder creation failures", () =>  Effect.gen(function* () {    const cause = new Error("native initialization failed");    vi.spyOn(FileFinder, "create").mockImplementationOnce(() => {      throw cause;    });     const error = yield* Effect.flip(      Effect.scoped(WorkspaceSearchIndex.make("/workspace/project")),    );     expect(error).toMatchObject({      _tag: "WorkspaceSearchIndexCreateFailed",      cwd: "/workspace/project",      reason: "FileFinder.create threw unexpectedly.",      cause,    });  }),); it.effect("keeps returned FileFinder creation diagnostics out of the cause chain", () =>  Effect.gen(function* () {    vi.spyOn(FileFinder, "create").mockReturnValueOnce({      ok: false,      error: "native index rejected the directory",    });     const error = yield* Effect.flip(      Effect.scoped(WorkspaceSearchIndex.make("/workspace/project")),    );     expect(error).toMatchObject({      _tag: "WorkspaceSearchIndexCreateFailed",      cwd: "/workspace/project",      reason: "native index rejected the directory",    });    expect(error.cause).toBeUndefined();  }),); it.effect("preserves FileFinder destroy failures as structured defects", () =>  Effect.gen(function* () {    const cause = new Error("native destroy failed");    const finder = {      destroy: vi.fn(() => {        throw cause;      }),      isScanning: vi.fn(() => false),    } as unknown as FileFinder;    vi.spyOn(FileFinder, "create").mockReturnValueOnce({ ok: true, value: finder });     const exit = yield* Effect.scoped(WorkspaceSearchIndex.make("/workspace/project")).pipe(      Effect.exit,    );     expect(Exit.isFailure(exit)).toBe(true);    if (Exit.isFailure(exit)) {      expect(Cause.hasDies(exit.cause)).toBe(true);      const error = Cause.squash(exit.cause);      expect(error).toBeInstanceOf(WorkspaceSearchIndex.WorkspaceSearchIndexDestroyFailed);      expect(error).toMatchObject({        _tag: "WorkspaceSearchIndexDestroyFailed",        cwd: "/workspace/project",        cause,      });    }  }),); it.effect("preserves search and refresh failures with operation context", () =>  Effect.scoped(    Effect.gen(function* () {      const searchCause = new Error("native search failed");      const refreshCause = new Error("native scan failed");      const finder = {        destroy: vi.fn(),        isScanning: vi.fn(() => false),        mixedSearch: vi.fn(() => {          throw searchCause;        }),        scanFiles: vi.fn(() => {          throw refreshCause;        }),      } as unknown as FileFinder;      vi.spyOn(FileFinder, "create").mockReturnValueOnce({ ok: true, value: finder });       const searchIndex = yield* WorkspaceSearchIndex.make("/workspace/project");      const query = "authorization: Bearer secret-token";      const searchError = yield* Effect.flip(searchIndex.search(query, 3));      const refreshError = yield* Effect.flip(searchIndex.refresh());       expect(searchError).toMatchObject({        _tag: "WorkspaceSearchIndexSearchFailed",        cwd: "/workspace/project",        queryLength: query.length,        pageSize: 4,        reason: "FileFinder.mixedSearch threw unexpectedly.",        cause: searchCause,      });      expect(searchError).not.toHaveProperty("query");      expect(searchError.message).not.toMatch(/Bearer|secret-token/);      expect(refreshError).toMatchObject({        _tag: "WorkspaceSearchIndexRefreshFailed",        cwd: "/workspace/project",        reason: "FileFinder.scanFiles threw unexpectedly.",        cause: refreshCause,      });    }),  ),); it.effect("keeps returned search diagnostics out of the cause chain", () =>  Effect.scoped(    Effect.gen(function* () {      const finder = {        destroy: vi.fn(),        isScanning: vi.fn(() => false),        mixedSearch: vi.fn(() => ({ ok: false, error: "native query rejected" })),        scanFiles: vi.fn(() => ({ ok: false, error: "native refresh rejected" })),      } as unknown as FileFinder;      vi.spyOn(FileFinder, "create").mockReturnValueOnce({ ok: true, value: finder });       const searchIndex = yield* WorkspaceSearchIndex.make("/workspace/project");      const query = "authorization: Bearer secret-token";      const searchError = yield* Effect.flip(searchIndex.search(query, 3));      const refreshError = yield* Effect.flip(searchIndex.refresh());       expect(searchError).toMatchObject({        _tag: "WorkspaceSearchIndexSearchFailed",        cwd: "/workspace/project",        queryLength: query.length,        pageSize: 4,        reason: "native query rejected",      });      expect(searchError).not.toHaveProperty("query");      expect(searchError.message).not.toMatch(/Bearer|secret-token/);      expect(searchError.cause).toBeUndefined();      expect(refreshError).toMatchObject({        _tag: "WorkspaceSearchIndexRefreshFailed",        cwd: "/workspace/project",        reason: "native refresh rejected",      });      expect(refreshError.cause).toBeUndefined();    }),  ),);