1import { AuthProviders, getAuthProvider } from "@/Auth/AuthProvider";
2import { ProfileStore } from "@/Auth/Profile";
3import * as Interaction from "@/Interaction.ts";
4import {
5 PRISMA_AUTH_PROVIDER_NAME,
6 PrismaAuth,
7 type PrismaAuthConfig,
8 type PrismaResolvedCredentials,
9} from "@/Prisma/AuthProvider";
10import { describe, expect, it } from "alchemy-test";
11import * as NodeServices from "@effect/platform-node/NodeServices";
12import * as ConfigProvider from "effect/ConfigProvider";
13import * as Effect from "effect/Effect";
14import * as Layer from "effect/Layer";
15import * as Redacted from "effect/Redacted";
16import { makeFakeProfileStore } from "./fakes.ts";
17
18const fakeProfile = makeFakeProfileStore();
19
20const testLayer = (config: Record<string, string> = {}) => {
21 const authProviders: AuthProviders["Service"] = {};
22 const authRegistry = Layer.succeed(AuthProviders, authProviders);
23 const base = Layer.mergeAll(
24 NodeServices.layer,
25 authRegistry,
26 Layer.succeed(ProfileStore, fakeProfile),
27 ConfigProvider.layer(ConfigProvider.fromUnknown(config)),
28 Interaction.layerNonInteractive(),
29 );
30 return PrismaAuth.pipe(Layer.provideMerge(base));
31};
32
33const prismaAuthProvider = getAuthProvider<
34 PrismaAuthConfig,
35 PrismaResolvedCredentials
36>(PRISMA_AUTH_PROVIDER_NAME);
37
38const readStoredCredentials = Effect.gen(function* () {
39 const auth = yield* prismaAuthProvider;
40 return yield* auth.read("default", {
41 method: "stored",
42 serviceToken: "stored-token",
43 });
44});
45
46const readEnvironmentCredentials = Effect.gen(function* () {
47 const auth = yield* prismaAuthProvider;
48 if (auth.readEnvironment === undefined) {
49 return yield* Effect.die(
50 "Prisma does not expose CI environment credentials",
51 );
52 }
53 return yield* auth.readEnvironment;
54});
55
56describe(
57 "Prisma auth provider",
58 { tags: ["unit", "provider:prisma", "local"] },
59 () => {
60 it.effect("reads PRISMA_SERVICE_TOKEN for CI", () =>
61 Effect.gen(function* () {
62 const credentials = yield* readEnvironmentCredentials;
63 expect(credentials.source).toEqual({
64 type: "env",
65 details: "PRISMA_SERVICE_TOKEN",
66 });
67 expect(Redacted.value(credentials.serviceToken)).toBe("service-token");
68 }).pipe(
69 Effect.provide(testLayer({ PRISMA_SERVICE_TOKEN: "service-token" })),
70 ),
71 );
72
73 it.effect("falls back to PRISMA_API_TOKEN for CI", () =>
74 Effect.gen(function* () {
75 const credentials = yield* readEnvironmentCredentials;
76 expect(credentials.source).toEqual({
77 type: "env",
78 details: "PRISMA_API_TOKEN",
79 });
80 expect(Redacted.value(credentials.serviceToken)).toBe("api-token");
81 }).pipe(Effect.provide(testLayer({ PRISMA_API_TOKEN: "api-token" }))),
82 );
83
84 it.effect("reads inline stored Prisma service tokens", () =>
85 Effect.gen(function* () {
86 const credentials = yield* readStoredCredentials;
87
88 expect(credentials.type).toBe("serviceToken");
89 expect(credentials.source).toEqual({ type: "stored" });
90 expect(Redacted.value(credentials.serviceToken)).toBe("stored-token");
91 }).pipe(Effect.provide(testLayer())),
92 );
93
94 it.effect("returns redacted details for stored credentials", () =>
95 Effect.gen(function* () {
96 const auth = yield* prismaAuthProvider;
97 const details = yield* auth.details("default", {
98 method: "stored",
99 serviceToken: "stored-token",
100 });
101
102 expect(details.lines).toEqual([
103 { key: "serviceToken", value: "stor****" },
104 ]);
105 }).pipe(Effect.provide(testLayer())),
106 );
107
108 it.effect("rejects whitespace-only service tokens in configureWith", () =>
109 Effect.gen(function* () {
110 const auth = yield* prismaAuthProvider;
111 if (auth.configureWith === undefined) {
112 return yield* Effect.die(
113 "Prisma does not expose flag-driven configuration",
114 );
115 }
116 const error = yield* auth
117 .configureWith("default", {
118 method: "stored",
119 values: { serviceToken: " " },
120 })
121 .pipe(Effect.flip);
122
123 expect(error._tag).toBe("AuthError");
124 expect(error.message).toContain("invalid 'serviceToken'");
125 }).pipe(Effect.provide(testLayer())),
126 );
127
128 it.effect("declares the stored configure method", () =>
129 Effect.gen(function* () {
130 const auth = yield* prismaAuthProvider;
131
132 expect(auth.configureMethods).toEqual([
133 {
134 method: "stored",
135 fields: [
136 expect.objectContaining({
137 name: "serviceToken",
138 label: "Prisma Service Token",
139 secret: true,
140 }),
141 ],
142 },
143 ]);
144 }).pipe(Effect.provide(testLayer())),
145 );
146 },
147);