1import { AuthError } from "@/Auth/AuthProvider.ts";
2import { CredentialsStore, CredentialsStoreLive } from "@/Auth/Credentials.ts";
3import { expect, it } from "alchemy-test";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Layer from "effect/Layer";
7import * as PlatformError from "effect/PlatformError";
8import * as Schema from "effect/Schema";
9
10const failingDeleteLayer = CredentialsStoreLive.pipe(
11 Layer.provide(
12 FileSystem.layerNoop({
13 remove: (path) =>
14 Effect.fail(
15 PlatformError.systemError({
16 _tag: "PermissionDenied",
17 module: "FileSystem",
18 method: "remove",
19 pathOrDescriptor: path,
20 }),
21 ),
22 }),
23 ),
24);
25
26const TestCredentials = Schema.Struct({
27 type: Schema.Literal("apiKey"),
28 apiKey: Schema.String,
29});
30
31it.effect(
32 "fails read with a reconfigure hint when the stored shape is wrong",
33 () =>
34 Effect.gen(function* () {
35 const store = yield* CredentialsStore;
36 const error = yield* store
37 .read("test", "provider", TestCredentials)
38 .pipe(Effect.flip);
39
40 expect(error).toBeInstanceOf(AuthError);
41 expect(error.message).toContain("do not match the expected shape");
42 expect(error.message).toContain("--reconfigure");
43 }).pipe(
44 Effect.provide(
45 CredentialsStoreLive.pipe(
46 Layer.provide(
47 FileSystem.layerNoop({
48 // valid JSON, but not the declared credential shape
49 readFileString: () =>
50 Effect.succeed(JSON.stringify({ token: "not-the-shape" })),
51 }),
52 ),
53 ),
54 ),
55 ),
56 { tags: ["unit", "local"] },
57);
58
59it.effect(
60 "surfaces provider credential deletion failures",
61 () =>
62 Effect.gen(function* () {
63 const store = yield* CredentialsStore;
64 const error = yield* store.delete("test", "provider").pipe(Effect.flip);
65
66 expect(error).toBeInstanceOf(AuthError);
67 expect(error.message).toContain("Could not delete credentials at");
68 }).pipe(Effect.provide(failingDeleteLayer)),
69 { tags: ["unit", "local"] },
70);
71
72it.effect(
73 "surfaces profile credential deletion failures",
74 () =>
75 Effect.gen(function* () {
76 const store = yield* CredentialsStore;
77 const error = yield* store.deleteProfile("test").pipe(Effect.flip);
78
79 expect(error).toBeInstanceOf(AuthError);
80 expect(error.message).toBe(
81 "Could not delete credentials for profile 'test'.",
82 );
83 }).pipe(Effect.provide(failingDeleteLayer)),
84 { tags: ["unit", "local"] },
85);
86
87it.effect(
88 "ignores missing credential files",
89 () =>
90 Effect.gen(function* () {
91 const store = yield* CredentialsStore;
92 yield* store.delete("test", "provider");
93 yield* store.deleteProfile("test");
94 }).pipe(
95 Effect.provide(
96 CredentialsStoreLive.pipe(
97 Layer.provide(
98 FileSystem.layerNoop({
99 remove: (path) =>
100 Effect.fail(
101 PlatformError.systemError({
102 _tag: "NotFound",
103 module: "FileSystem",
104 method: "remove",
105 pathOrDescriptor: path,
106 }),
107 ),
108 }),
109 ),
110 ),
111 ),
112 ),
113 { tags: ["unit", "local"] },
114);