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