@@ -6,6 +6,8 @@ import type { PlatformError } from "@effect/platform/Error"
66import { NodeContext } from "@effect/platform-node"
77import { describe , expect , it } from "@effect/vitest"
88import { Effect } from "effect"
9+ import type * as ParseResult from "effect/ParseResult"
10+ import * as Schema from "effect/Schema"
911import * as Scope from "effect/Scope"
1012
1113import { makeRouter } from "../src/http.js"
@@ -20,6 +22,29 @@ const requestApiRoute = (path: string) =>
2022 catch : ( cause ) => new Error ( String ( cause ) )
2123 } )
2224
25+ const HealthResponseSchema = Schema . Struct ( {
26+ cwd : Schema . String ,
27+ ok : Schema . Boolean ,
28+ projectsRoot : Schema . String ,
29+ revision : Schema . NullOr ( Schema . String )
30+ } )
31+
32+ type HealthResponse = Schema . Schema . Type < typeof HealthResponseSchema >
33+
34+ /**
35+ * Creates a scoped temporary directory and provides its path to the supplied effect.
36+ *
37+ * @param use - Effect factory that receives the temporary directory path.
38+ * @returns Effect that yields the factory result and finalizes the temporary directory scope.
39+ *
40+ * @pure false
41+ * @effect FileSystem service for scoped directory allocation and cleanup
42+ * @invariant the temporary directory lifetime is bounded by the returned Effect scope
43+ * @precondition FileSystem service is available in the environment
44+ * @postcondition the temporary directory scope is finalized after success or failure
45+ * @complexity O(1) allocation; cleanup is O(n) in created filesystem entries
46+ * @throws Never - filesystem and user errors are represented in the Effect error channel
47+ */
2348const withTempDir = < A , E , R > (
2449 use : ( tempDir : string ) => Effect . Effect < A , E , R >
2550) : Effect . Effect < A , E | PlatformError , FileSystem . FileSystem | Exclude < R , Scope . Scope > > =>
@@ -35,6 +60,22 @@ const withTempDir = <A, E, R>(
3560 } )
3661 )
3762
63+ /**
64+ * Temporarily sets or unsets an environment variable for the duration of an effect.
65+ *
66+ * @param key - Environment variable name to modify.
67+ * @param value - Temporary value, or undefined to remove the variable.
68+ * @param effect - Effect evaluated while the temporary environment binding is active.
69+ * @returns Effect that yields the supplied effect result and restores the previous binding.
70+ *
71+ * @pure false
72+ * @effect process environment mutation inside acquire/release
73+ * @invariant the previous environment value is restored exactly once during finalization
74+ * @precondition key is a non-empty environment variable name accepted by the runtime
75+ * @postcondition process.env[key] equals its previous value after the scope finalizes
76+ * @complexity O(1) time and space
77+ * @throws Never - user effect errors are represented in the Effect error channel
78+ */
3879const withEnvVar = < A , E , R > (
3980 key : string ,
4081 value : string | undefined ,
@@ -62,14 +103,11 @@ const withEnvVar = <A, E, R>(
62103 ) . pipe ( Effect . flatMap ( ( ) => effect ) )
63104 )
64105
65- const readResponseJson = ( response : Response ) =>
106+ const readHealthResponse = ( response : Response ) : Effect . Effect < HealthResponse , Error | ParseResult . ParseError > =>
66107 Effect . tryPromise ( {
67108 try : ( ) => response . json ( ) ,
68109 catch : ( cause ) => new Error ( String ( cause ) )
69- } )
70-
71- const objectOrNull = ( value : unknown ) : object | null =>
72- typeof value === "object" && value !== null && ! Array . isArray ( value ) ? value : null
110+ } ) . pipe ( Effect . flatMap ( Schema . decodeUnknown ( HealthResponseSchema ) ) )
73111
74112describe ( "api console routes" , ( ) => {
75113 it . effect ( "does not serve the legacy built-in API console" , ( ) =>
@@ -90,12 +128,10 @@ describe("api console routes", () => {
90128 const response = yield * _ (
91129 withEnvVar ( "DOCKER_GIT_PROJECTS_ROOT" , projectsRoot , requestApiRoute ( "/health" ) )
92130 )
93- const payload = yield * _ ( readResponseJson ( response ) )
94- const objectPayload = objectOrNull ( payload )
131+ const payload = yield * _ ( readHealthResponse ( response ) )
95132
96133 expect ( response . status ) . toBe ( 200 )
97- expect ( objectPayload ) . not . toBeNull ( )
98- expect ( Reflect . get ( objectPayload ?? { } , "projectsRoot" ) ) . toBe ( projectsRoot )
134+ expect ( payload . projectsRoot ) . toBe ( projectsRoot )
99135 } )
100136 ) . pipe ( Effect . provide ( NodeContext . layer ) ) )
101137} )
0 commit comments