1+ import * as nodePath from "node:path" ;
12import * as NodeServices from "@effect/platform-node/NodeServices" ;
23import { expect , it } from "@effect/vitest" ;
3- import { Effect , FileSystem , Layer } from "effect" ;
4+ import { Effect , Exit , FileSystem , Layer , PlatformError } from "effect" ;
45
5- import { ServerConfig } from "../../config.ts" ;
6+ import { ServerConfig , type ServerConfigShape } from "../../config.ts" ;
67import { ServerEnvironment } from "../Services/ServerEnvironment.ts" ;
78import { ServerEnvironmentLive } from "./ServerEnvironment.ts" ;
89
910const makeServerEnvironmentLayer = ( baseDir : string ) =>
1011 ServerEnvironmentLive . pipe ( Layer . provide ( ServerConfig . layerTest ( process . cwd ( ) , baseDir ) ) ) ;
1112
13+ const makeServerConfig = ( baseDir : string ) : ServerConfigShape => {
14+ const stateDir = nodePath . join ( baseDir , "userdata" ) ;
15+ const logsDir = nodePath . join ( stateDir , "logs" ) ;
16+ const providerLogsDir = nodePath . join ( logsDir , "provider" ) ;
17+ return {
18+ logLevel : "Error" ,
19+ traceMinLevel : "Info" ,
20+ traceTimingEnabled : true ,
21+ traceBatchWindowMs : 200 ,
22+ traceMaxBytes : 10 * 1024 * 1024 ,
23+ traceMaxFiles : 10 ,
24+ otlpTracesUrl : undefined ,
25+ otlpMetricsUrl : undefined ,
26+ otlpExportIntervalMs : 10_000 ,
27+ otlpServiceName : "t3-server" ,
28+ cwd : process . cwd ( ) ,
29+ baseDir,
30+ stateDir,
31+ dbPath : nodePath . join ( stateDir , "state.sqlite" ) ,
32+ keybindingsConfigPath : nodePath . join ( stateDir , "keybindings.json" ) ,
33+ settingsPath : nodePath . join ( stateDir , "settings.json" ) ,
34+ worktreesDir : nodePath . join ( baseDir , "worktrees" ) ,
35+ attachmentsDir : nodePath . join ( stateDir , "attachments" ) ,
36+ logsDir,
37+ serverLogPath : nodePath . join ( logsDir , "server.log" ) ,
38+ serverTracePath : nodePath . join ( logsDir , "server.trace.ndjson" ) ,
39+ providerLogsDir,
40+ providerEventLogPath : nodePath . join ( providerLogsDir , "events.log" ) ,
41+ terminalLogsDir : nodePath . join ( logsDir , "terminals" ) ,
42+ anonymousIdPath : nodePath . join ( stateDir , "anonymous-id" ) ,
43+ environmentIdPath : nodePath . join ( stateDir , "environment-id" ) ,
44+ mode : "web" ,
45+ autoBootstrapProjectFromCwd : false ,
46+ logWebSocketEvents : false ,
47+ port : 0 ,
48+ host : undefined ,
49+ authToken : undefined ,
50+ staticDir : undefined ,
51+ devUrl : undefined ,
52+ noBrowser : false ,
53+ } ;
54+ } ;
55+
1256it . layer ( NodeServices . layer ) ( "ServerEnvironmentLive" , ( it ) => {
1357 it . effect ( "persists the environment id across service restarts" , ( ) =>
1458 Effect . gen ( function * ( ) {
@@ -30,4 +74,65 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
3074 expect ( second . capabilities . repositoryIdentity ) . toBe ( true ) ;
3175 } ) ,
3276 ) ;
77+
78+ it . effect ( "fails instead of overwriting a persisted id when reading the file errors" , ( ) =>
79+ Effect . gen ( function * ( ) {
80+ const fileSystem = yield * FileSystem . FileSystem ;
81+ const baseDir = yield * fileSystem . makeTempDirectoryScoped ( {
82+ prefix : "t3-server-environment-read-error-test-" ,
83+ } ) ;
84+ const serverConfig = makeServerConfig ( baseDir ) ;
85+ const environmentIdPath = serverConfig . environmentIdPath ;
86+ yield * fileSystem . makeDirectory ( nodePath . dirname ( environmentIdPath ) , { recursive : true } ) ;
87+ yield * fileSystem . writeFileString ( environmentIdPath , "persisted-environment-id\n" ) ;
88+ const writeAttempts : string [ ] = [ ] ;
89+ const failingFileSystemLayer = FileSystem . layerNoop ( {
90+ exists : ( path ) => Effect . succeed ( path === environmentIdPath ) ,
91+ readFileString : ( path ) =>
92+ path === environmentIdPath
93+ ? Effect . fail (
94+ PlatformError . systemError ( {
95+ _tag : "PermissionDenied" ,
96+ module : "FileSystem" ,
97+ method : "readFileString" ,
98+ description : "permission denied" ,
99+ pathOrDescriptor : path ,
100+ } ) ,
101+ )
102+ : Effect . fail (
103+ PlatformError . systemError ( {
104+ _tag : "NotFound" ,
105+ module : "FileSystem" ,
106+ method : "readFileString" ,
107+ description : "not found" ,
108+ pathOrDescriptor : path ,
109+ } ) ,
110+ ) ,
111+ writeFileString : ( path ) => {
112+ writeAttempts . push ( path ) ;
113+ return Effect . void ;
114+ } ,
115+ } ) ;
116+
117+ const exit = yield * Effect . gen ( function * ( ) {
118+ const serverEnvironment = yield * ServerEnvironment ;
119+ return yield * serverEnvironment . getDescriptor ;
120+ } ) . pipe (
121+ Effect . provide (
122+ ServerEnvironmentLive . pipe (
123+ Layer . provide (
124+ Layer . merge ( Layer . succeed ( ServerConfig , serverConfig ) , failingFileSystemLayer ) ,
125+ ) ,
126+ ) ,
127+ ) ,
128+ Effect . exit ,
129+ ) ;
130+
131+ expect ( Exit . isFailure ( exit ) ) . toBe ( true ) ;
132+ expect ( writeAttempts ) . toEqual ( [ ] ) ;
133+ expect ( yield * fileSystem . readFileString ( environmentIdPath ) ) . toBe (
134+ "persisted-environment-id\n" ,
135+ ) ;
136+ } ) ,
137+ ) ;
33138} ) ;
0 commit comments