1import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
2import {
3 AuthSessionId,
4 EnvironmentAuthenticatedAuth,
5 EnvironmentAuthenticatedPrincipal,
6 EnvironmentHttpApi,
7 type AuthBrowserSessionRequest,
8 type AuthBrowserSessionResult,
9 type AuthCreatePairingCredentialInput,
10 type AuthEnvironmentScope,
11 type AuthPairingCredentialResult,
12 type AuthSessionState,
13 type ExecutionEnvironmentDescriptor,
14 type EnvironmentAuthInvalidError,
15} from "@t3tools/contracts";
16import * as DateTime from "effect/DateTime";
17import type * as Context from "effect/Context";
18import * as Effect from "effect/Effect";
19import * as Layer from "effect/Layer";
20import * as ManagedRuntime from "effect/ManagedRuntime";
21import { HttpApiTest } from "effect/http-api";
22import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
23
24import { PrimaryEnvironmentHttpClient } from "../src/environments/primary/httpClient";
25import { __setPrimaryHttpRunnerForTests } from "../src/lib/runtime";
26
27type BrowserSessionHandler = (
28 payload: AuthBrowserSessionRequest,
29) => Effect.Effect<AuthBrowserSessionResult, EnvironmentAuthInvalidError>;
30
31interface EnvironmentHttpTestScenario {
32 readonly descriptor?: () => Effect.Effect<ExecutionEnvironmentDescriptor>;
33 readonly session?: () => Effect.Effect<AuthSessionState>;
34 readonly browserSession?: BrowserSessionHandler;
35 readonly pairingCredential?: (
36 payload: AuthCreatePairingCredentialInput,
37 ) => Effect.Effect<AuthPairingCredentialResult>;
38}
39
40export interface EnvironmentHttpTestCalls {
41 descriptor: number;
42 session: number;
43 browserSession: Array<AuthBrowserSessionRequest>;
44 pairingCredential: Array<AuthCreatePairingCredentialInput>;
45}
46
47const unexpectedEndpoint = (endpoint: string) =>
48 Effect.die(new Error(`Unexpected environment HTTP endpoint: ${endpoint}`));
49
50const authenticatedAuth: Context.Service.Shape<typeof EnvironmentAuthenticatedAuth> = (
51 httpEffect,
52) =>
53 httpEffect.pipe(
54 Effect.provideService(EnvironmentAuthenticatedPrincipal, {
55 sessionId: AuthSessionId.make("test-session"),
56 subject: "test-client",
57 method: "browser-session-cookie",
58 scopes: new Set<AuthEnvironmentScope>(),
59 expiresAt: DateTime.makeUnsafe("2026-05-01T12:00:00.000Z"),
60 }),
61 );
62
63export async function installEnvironmentHttpTest(scenario: EnvironmentHttpTestScenario) {
64 const calls: EnvironmentHttpTestCalls = {
65 descriptor: 0,
66 session: 0,
67 browserSession: [],
68 pairingCredential: [],
69 };
70
71 const client = await Effect.runPromise(
72 HttpApiTest.groups(EnvironmentHttpApi, ["metadata", "auth"]).pipe(
73 Effect.provide([
74 NodeHttpServer.layerHttpServices,
75 HttpApiBuilder.group(EnvironmentHttpApi, "metadata", (handlers) =>
76 handlers.handle(
77 "descriptor",
78 Effect.fn("test.environment.metadata.descriptor")(function* () {
79 calls.descriptor += 1;
80 return yield* scenario.descriptor?.() ?? unexpectedEndpoint("metadata.descriptor");
81 }),
82 ),
83 ),
84 HttpApiBuilder.group(EnvironmentHttpApi, "auth", (handlers) =>
85 handlers
86 .handle(
87 "session",
88 Effect.fn("test.environment.auth.session")(function* () {
89 calls.session += 1;
90 return yield* scenario.session?.() ?? unexpectedEndpoint("auth.session");
91 }),
92 )
93 .handle(
94 "browserSession",
95 Effect.fn("test.environment.auth.browserSession")(function* ({ payload }) {
96 calls.browserSession.push(payload);
97 return yield* (
98 scenario.browserSession?.(payload) ?? unexpectedEndpoint("auth.browserSession")
99 );
100 }),
101 )
102 .handle("delegatedSession", () => unexpectedEndpoint("auth.delegatedSession"))
103 .handle("token", () => unexpectedEndpoint("auth.token"))
104 .handle("webSocketTicket", () => unexpectedEndpoint("auth.webSocketTicket"))
105 .handle(
106 "pairingCredential",
107 Effect.fn("test.environment.auth.pairingCredential")(function* ({ payload }) {
108 calls.pairingCredential.push(payload);
109 return yield* (
110 scenario.pairingCredential?.(payload) ??
111 unexpectedEndpoint("auth.pairingCredential")
112 );
113 }),
114 )
115 .handle("pairingLinks", () => unexpectedEndpoint("auth.pairingLinks"))
116 .handle("revokePairingLink", () => unexpectedEndpoint("auth.revokePairingLink"))
117 .handle("clients", () => unexpectedEndpoint("auth.clients"))
118 .handle("revokeClient", () => unexpectedEndpoint("auth.revokeClient"))
119 .handle("revokeOtherClients", () => unexpectedEndpoint("auth.revokeOtherClients")),
120 ),
121 ]),
122 Effect.provideService(EnvironmentAuthenticatedAuth, authenticatedAuth),
123 Effect.scoped,
124 ),
125 );
126
127 const runtime = ManagedRuntime.make(Layer.succeed(PrimaryEnvironmentHttpClient, client));
128 __setPrimaryHttpRunnerForTests((effect) => runtime.runPromise(effect));
129
130 return {
131 calls,
132 async dispose() {
133 __setPrimaryHttpRunnerForTests();
134 await runtime.dispose();
135 },
136 };
137}