| 1 | /** |
| 2 | * A `SourceControlHost.SourceControlHost` for provider tests. Settings start from the defaults |
| 3 | * or what the test supplies, and `TestSourceControlHostSettings` patches them mid-test the way a |
| 4 | * client's settings write would. Process runs and git operations go to what the test supplies; |
| 5 | * anything else dies, so an unexpected CLI or git call is visible. |
| 6 | * |
| 7 | * @module source-control-testing/TestSourceControlHost |
| 8 | */ |
| 9 | import { |
| 10 | DEFAULT_SERVER_SETTINGS, |
| 11 | type ServerSettings, |
| 12 | type ServerSettingsPatch, |
| 13 | } from "@t3tools/contracts"; |
| 14 | import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings"; |
| 15 | import * as SourceControlHost from "@t3tools/source-control-core/server/SourceControlHost"; |
| 16 | import * as Context from "effect/Context"; |
| 17 | import * as Effect from "effect/Effect"; |
| 18 | import * as Layer from "effect/Layer"; |
| 19 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 20 | import * as Ref from "effect/Ref"; |
| 21 | |
| 22 | export interface TestSourceControlHostOptions { |
| 23 | readonly settings?: ServerSettings; |
| 24 | readonly process?: Partial<SourceControlHost.SourceControlHost["Service"]["process"]>; |
| 25 | readonly git?: Partial<SourceControlHost.SourceControlHost["Service"]["git"]>; |
| 26 | } |
| 27 | |
| 28 | /** Lets a test change the settings its `layer` reports. */ |
| 29 | export class TestSourceControlHostSettings extends Context.Service< |
| 30 | TestSourceControlHostSettings, |
| 31 | { readonly update: (patch: ServerSettingsPatch) => Effect.Effect<void> } |
| 32 | >()("@t3tools/source-control-testing/TestSourceControlHost/TestSourceControlHostSettings") {} |
| 33 | |
| 34 | type Git = SourceControlHost.SourceControlHost["Service"]["git"]; |
| 35 | |
| 36 | const unexpectedGit = (operation: string) => () => |
| 37 | Effect.die(`Unexpected git ${operation} in a test that supplied none.`); |
| 38 | |
| 39 | const failingGit: Git = { |
| 40 | execute: unexpectedGit("execute"), |
| 41 | resolveCommit: unexpectedGit("resolveCommit"), |
| 42 | remotes: unexpectedGit("remotes"), |
| 43 | readConfigValue: unexpectedGit("readConfigValue"), |
| 44 | resolvePrimaryRemoteName: unexpectedGit("resolvePrimaryRemoteName"), |
| 45 | ensureRemote: unexpectedGit("ensureRemote"), |
| 46 | listLocalBranchNames: unexpectedGit("listLocalBranchNames"), |
| 47 | fetchRemoteBranch: unexpectedGit("fetchRemoteBranch"), |
| 48 | fetchRemoteTrackingBranch: unexpectedGit("fetchRemoteTrackingBranch"), |
| 49 | setBranchUpstream: unexpectedGit("setBranchUpstream"), |
| 50 | switchRef: unexpectedGit("switchRef"), |
| 51 | }; |
| 52 | |
| 53 | export const layer = ( |
| 54 | options: TestSourceControlHostOptions = {}, |
| 55 | ): Layer.Layer<SourceControlHost.SourceControlHost | TestSourceControlHostSettings> => |
| 56 | Layer.effectContext( |
| 57 | Effect.gen(function* () { |
| 58 | const settings = yield* Ref.make(options.settings ?? DEFAULT_SERVER_SETTINGS); |
| 59 | const host = SourceControlHost.SourceControlHost.of({ |
| 60 | settings: { get: Ref.get(settings) }, |
| 61 | process: { |
| 62 | run: |
| 63 | options.process?.run ?? |
| 64 | ((input) => Effect.die(`Unexpected ${input.command} run in ${input.operation}.`)), |
| 65 | }, |
| 66 | git: { ...failingGit, ...options.git }, |
| 67 | }); |
| 68 | return Context.make(SourceControlHost.SourceControlHost, host).pipe( |
| 69 | Context.add(TestSourceControlHostSettings, { |
| 70 | update: (patch) => |
| 71 | Ref.update(settings, (current) => applyServerSettingsPatch(current, patch)), |
| 72 | }), |
| 73 | ); |
| 74 | }), |
| 75 | ); |
| 76 | |
| 77 | /** A successful run's output, for tests that script CLI responses. */ |
| 78 | export const processOutput = ( |
| 79 | stdout: string, |
| 80 | options?: { |
| 81 | readonly stderr?: string; |
| 82 | readonly exitCode?: ChildProcessSpawner.ExitCode; |
| 83 | }, |
| 84 | ): SourceControlHost.SourceControlProcessOutput => ({ |
| 85 | exitCode: options?.exitCode ?? ChildProcessSpawner.ExitCode(0), |
| 86 | stdout, |
| 87 | stderr: options?.stderr ?? "", |
| 88 | stdoutTruncated: false, |
| 89 | stderrTruncated: false, |
| 90 | }); |