Skip to content

Commit 4b079bb

Browse files
committed
test(app): tighten controller revision fs mock
1 parent 461c21a commit 4b079bb

1 file changed

Lines changed: 78 additions & 5 deletions

File tree

packages/app/tests/docker-git/controller-revision.test.ts

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SystemError } from "@effect/platform/Error"
12
import * as FileSystem from "@effect/platform/FileSystem"
23
import * as Path from "@effect/platform/Path"
34
import { describe, expect, it } from "@effect/vitest"
@@ -55,6 +56,63 @@ const memoryFileInfo = (entry: MemoryFileEntry): FileSystem.File.Info => ({
5556
uid: Option.none()
5657
})
5758

59+
/**
60+
* Builds a typed FileSystem error for the in-memory test filesystem.
61+
*
62+
* @param method - FileSystem method name that observed the invalid path.
63+
* @param requestedPath - Normalized memory path associated with the failure.
64+
* @param reason - Platform filesystem reason reported by the mock.
65+
* @param description - Human-readable failure description.
66+
* @returns Platform SystemError compatible with FileSystem effects.
67+
* @pure true
68+
* @effect none
69+
* @invariant The produced error is always scoped to the FileSystem module.
70+
* @precondition `method`, `requestedPath`, and `description` are finite strings.
71+
* @postcondition The returned error preserves the failing path in `pathOrDescriptor`.
72+
* @complexity O(1) time and space.
73+
* @throws Never
74+
*/
75+
const memoryFileSystemError = (
76+
method: string,
77+
requestedPath: string,
78+
reason: "BadResource" | "NotFound",
79+
description: string
80+
): SystemError =>
81+
new SystemError({
82+
description,
83+
method,
84+
module: "FileSystem",
85+
pathOrDescriptor: requestedPath,
86+
reason
87+
})
88+
89+
/**
90+
* Looks up an in-memory file entry with real FileSystem missing-path semantics.
91+
*
92+
* @param entries - Current memory filesystem entries.
93+
* @param requestedPath - Path requested by the FileSystem operation.
94+
* @param method - FileSystem method name for typed error reporting.
95+
* @returns Effect that succeeds with the entry or fails when the path is absent.
96+
* @pure true
97+
* @effect Effect.fail or Effect.succeed
98+
* @invariant Missing paths are represented as typed NotFound failures.
99+
* @precondition `requestedPath` is a finite path string.
100+
* @postcondition Success implies the normalized path exists in `entries`.
101+
* @complexity O(p) time and O(p) space where p = |requestedPath|.
102+
* @throws Never
103+
*/
104+
const requireMemoryEntry = (
105+
entries: ReadonlyMap<string, MemoryFileEntry>,
106+
requestedPath: string,
107+
method: string
108+
): Effect.Effect<MemoryFileEntry, SystemError> => {
109+
const normalized = normalizeMemoryPath(requestedPath)
110+
const entry = entries.get(normalized)
111+
return entry === undefined
112+
? Effect.fail(memoryFileSystemError(method, normalized, "NotFound", "Missing memory filesystem entry."))
113+
: Effect.succeed(entry)
114+
}
115+
58116
const createMemoryFileSystemLayer = () => {
59117
let entries = new Map<string, MemoryFileEntry>([
60118
["/memory", { _tag: "Directory" }]
@@ -67,8 +125,16 @@ const createMemoryFileSystemLayer = () => {
67125
entries = new Map(entries).set(normalizeMemoryPath(path), { _tag: "Directory" })
68126
}),
69127
readDirectory: (path) =>
70-
Effect.sync(() => {
128+
Effect.gen(function*(_) {
71129
const directory = normalizeMemoryPath(path)
130+
const entry = yield* _(requireMemoryEntry(entries, directory, "readDirectory"))
131+
if (entry._tag !== "Directory") {
132+
return yield* _(
133+
Effect.fail(
134+
memoryFileSystemError("readDirectory", directory, "BadResource", "Memory entry is not a directory.")
135+
)
136+
)
137+
}
72138
const prefix = directory === "/" ? "/" : `${directory}/`
73139
const names = new Set<string>()
74140
for (const candidate of entries.keys()) {
@@ -83,11 +149,18 @@ const createMemoryFileSystemLayer = () => {
83149
return [...names]
84150
}),
85151
readFileString: (path) =>
86-
Effect.sync(() => {
87-
const entry = entries.get(normalizeMemoryPath(path))
88-
return entry?._tag === "File" ? entry.contents : ""
152+
Effect.gen(function*(_) {
153+
const normalized = normalizeMemoryPath(path)
154+
const entry = yield* _(requireMemoryEntry(entries, normalized, "readFileString"))
155+
return entry._tag === "File"
156+
? entry.contents
157+
: yield* _(
158+
Effect.fail(
159+
memoryFileSystemError("readFileString", normalized, "BadResource", "Memory entry is not a file.")
160+
)
161+
)
89162
}),
90-
stat: (path) => Effect.sync(() => memoryFileInfo(entries.get(normalizeMemoryPath(path)) ?? { _tag: "Directory" })),
163+
stat: (path) => requireMemoryEntry(entries, path, "stat").pipe(Effect.map((entry) => memoryFileInfo(entry))),
91164
writeFileString: (path, contents) =>
92165
Effect.sync(() => {
93166
entries = new Map(entries).set(normalizeMemoryPath(path), { _tag: "File", contents })

0 commit comments

Comments
 (0)