Showing the changes to apps/server/src/sourceControl/SourceControlRepositoryService.test.ts (1 of 2 files). Show all
Unified · Split
| @@ -1,11 +1,13 @@ | |||
| 1 | import * as NodePath from "@effect/platform-node/NodePath"; | ||
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, it } from "@effect/vitest"; | 3 | import { assert, it } from "@effect/vitest"; |
| 3 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | 5 | import * as FileSystem from "effect/FileSystem"; |
| 5 | import * as Layer from "effect/Layer"; | 6 | import * as Layer from "effect/Layer"; |
| 7 | import * as PlatformError from "effect/PlatformError"; | ||
| 6 | import { ChildProcessSpawner } from "effect/unstable/process"; | 8 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 7 | 9 | ||
| 8 | import { GitCommandError, type SourceControlProviderError } from "@t3tools/contracts"; | 10 | import { GitCommandError, SourceControlProviderError } from "@t3tools/contracts"; |
| 9 | 11 | ||
| 10 | import * as ServerConfig from "../config.ts"; | 12 | import * as ServerConfig from "../config.ts"; |
| 11 | import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; | 13 | import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; |
| @@ -54,8 +56,9 @@ function processOutput(): GitVcsDriver.ExecuteGitResult { · 42 unmodified lines | |||
| 54 | function makeLayer(input: { | 56 | function makeLayer(input: { |
| 55 | readonly provider?: SourceControlProvider.SourceControlProvider["Service"]; | 57 | readonly provider?: SourceControlProvider.SourceControlProvider["Service"]; |
| 56 | readonly git?: Partial<GitVcsDriver.GitVcsDriver["Service"]>; | 58 | readonly git?: Partial<GitVcsDriver.GitVcsDriver["Service"]>; |
| 59 | readonly fileSystem?: FileSystem.FileSystem; | ||
| 57 | }) { | 60 | }) { |
| 58 | return SourceControlRepositoryService.layer.pipe( | 61 | const serviceLayer = SourceControlRepositoryService.layer.pipe( |
| 59 | Layer.provide( | 62 | Layer.provide( |
| 60 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ | 63 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 61 | get: () => Effect.succeed(input.provider ?? makeProvider()), | 64 | get: () => Effect.succeed(input.provider ?? makeProvider()), |
| @@ -75,9 +78,20 @@ function makeLayer(input: { · 13 unmodified lines | |||
| 75 | ...input.git, | 78 | ...input.git, |
| 76 | }), | 79 | }), |
| 77 | ), | 80 | ), |
| 78 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-source-control-repos-" })), | 81 | Layer.provide( |
| 79 | Layer.provideMerge(NodeServices.layer), | 82 | ServerConfig.layerTest( |
| 83 | process.cwd(), | ||
| 84 | input.fileSystem ? "/tmp/t3-source-control-repos" : { prefix: "t3-source-control-repos-" }, | ||
| 85 | ), | ||
| 86 | ), | ||
| 80 | ); | 87 | ); |
| 88 | |||
| 89 | return input.fileSystem | ||
| 90 | ? serviceLayer.pipe( | ||
| 91 | Layer.provide(Layer.succeed(FileSystem.FileSystem, input.fileSystem)), | ||
| 92 | Layer.provideMerge(NodePath.layer), | ||
| 93 | ) | ||
| 94 | : serviceLayer.pipe(Layer.provideMerge(NodeServices.layer)); | ||
| 81 | } | 95 | } |
| 82 | 96 | ||
| 83 | it.effect("looks up repositories through the requested provider without search", () => { | 97 | it.effect("looks up repositories through the requested provider without search", () => { |
| @@ -103,6 +117,39 @@ it.effect("looks up repositories through the requested provider without search", · 19 unmodified lines | |||
| 103 | }).pipe(Effect.provide(makeLayer({ provider }))); | 117 | }).pipe(Effect.provide(makeLayer({ provider }))); |
| 104 | }); | 118 | }); |
| 105 | 119 | ||
| 120 | it.effect("preserves provider failures without deriving the repository message from them", () => { | ||
| 121 | const providerCause = new SourceControlProviderError({ | ||
| 122 | provider: "github", | ||
| 123 | operation: "getRepositoryCloneUrls", | ||
| 124 | cwd: "/workspace", | ||
| 125 | repository: "octocat/t3code", | ||
| 126 | detail: "credential token abc123 was rejected", | ||
| 127 | }); | ||
| 128 | const provider = makeProvider({ | ||
| 129 | getRepositoryCloneUrls: () => Effect.fail(providerCause), | ||
| 130 | }); | ||
| 131 | |||
| 132 | return Effect.gen(function* () { | ||
| 133 | const service = yield* SourceControlRepositoryService.SourceControlRepositoryService; | ||
| 134 | const error = yield* Effect.flip( | ||
| 135 | service.lookupRepository({ | ||
| 136 | provider: "github", | ||
| 137 | repository: "octocat/t3code", | ||
| 138 | cwd: "/workspace", | ||
| 139 | }), | ||
| 140 | ); | ||
| 141 | |||
| 142 | assert.strictEqual(error.provider, "github"); | ||
| 143 | assert.strictEqual(error.operation, "lookupRepository"); | ||
| 144 | assert.strictEqual(error.detail, "The source control operation could not be completed."); | ||
| 145 | assert.strictEqual( | ||
| 146 | error.message, | ||
| 147 | "Source control repository operation lookupRepository failed for github: The source control operation could not be completed.", | ||
| 148 | ); | ||
| 149 | assert.strictEqual(error.cause, providerCause); | ||
| 150 | }).pipe(Effect.provide(makeLayer({ provider }))); | ||
| 151 | }); | ||
| 152 | |||
| 106 | it.effect("clones a looked-up repository into the requested destination", () => | 153 | it.effect("clones a looked-up repository into the requested destination", () => |
| 107 | Effect.gen(function* () { | 154 | Effect.gen(function* () { |
| 108 | const fs = yield* FileSystem.FileSystem; | 155 | const fs = yield* FileSystem.FileSystem; |
| @@ -148,6 +195,38 @@ it.effect("clones a looked-up repository into the requested destination", () => · 39 unmodified lines | |||
| 148 | }).pipe(Effect.provide(NodeServices.layer)), | 195 | }).pipe(Effect.provide(NodeServices.layer)), |
| 149 | ); | 196 | ); |
| 150 | 197 | ||
| 198 | it.effect("preserves destination probe failures instead of treating them as missing paths", () => { | ||
| 199 | const fileSystemCause = PlatformError.systemError({ | ||
| 200 | _tag: "PermissionDenied", | ||
| 201 | module: "FileSystem", | ||
| 202 | method: "exists", | ||
| 203 | pathOrDescriptor: "/restricted/t3code", | ||
| 204 | }); | ||
| 205 | |||
| 206 | return Effect.gen(function* () { | ||
| 207 | const service = yield* SourceControlRepositoryService.SourceControlRepositoryService; | ||
| 208 | const error = yield* Effect.flip( | ||
| 209 | service.cloneRepository({ | ||
| 210 | remoteUrl: CLONE_URLS.sshUrl, | ||
| 211 | destinationPath: "/restricted/t3code", | ||
| 212 | }), | ||
| 213 | ); | ||
| 214 | |||
| 215 | assert.strictEqual(error.provider, "unknown"); | ||
| 216 | assert.strictEqual(error.operation, "cloneRepository"); | ||
| 217 | assert.strictEqual(error.cause, fileSystemCause); | ||
| 218 | }).pipe( | ||
| 219 | Effect.provide( | ||
| 220 | makeLayer({ | ||
| 221 | fileSystem: FileSystem.makeNoop({ | ||
| 222 | exists: () => Effect.fail(fileSystemCause), | ||
| 223 | makeDirectory: () => Effect.void, | ||
| 224 | }), | ||
| 225 | }), | ||
| 226 | ), | ||
| 227 | ); | ||
| 228 | }); | ||
| 229 | |||
| 151 | it.effect("publishes by creating the repository, adding a remote, and pushing upstream", () => { | 230 | it.effect("publishes by creating the repository, adding a remote, and pushing upstream", () => { |
| 152 | const createCalls: Array<{ cwd: string; repository: string; visibility: string }> = []; | 231 | const createCalls: Array<{ cwd: string; repository: string; visibility: string }> = []; |
| 153 | const remoteCalls: Array<{ cwd: string; preferredName: string; url: string }> = []; | 232 | const remoteCalls: Array<{ cwd: string; preferredName: string; url: string }> = []; |