From a2330938d6f3c2090f4af3dfc964efceca8956a3 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 26 Feb 2026 18:05:12 -0800 Subject: [PATCH] Normalize Git and PTY adapter errors across services - Use domain errors directly in GitManager tests (`GitHubCliError`, `TextGenerationError`) - Remove fixture-only GH error normalization in test scaffolding - Introduce shared `PtyAdapterInitializationError` and use it in NodePTY adapter init --- apps/server/src/git/Layers/GitManager.test.ts | 58 ++++++------------- apps/server/src/terminal/Layers/NodePTY.ts | 20 +++---- 2 files changed, 26 insertions(+), 52 deletions(-) diff --git a/apps/server/src/git/Layers/GitManager.test.ts b/apps/server/src/git/Layers/GitManager.test.ts index 3417b58c2a274e86e3201966db177c49d7cc2b41..56c7839246d471cd636931eb5ae24aaeb63e01e4 100644 --- a/apps/server/src/git/Layers/GitManager.test.ts +++ b/apps/server/src/git/Layers/GitManager.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect, FileSystem, Layer, PlatformError, Schema, Scope } from "effect"; +import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect"; import { expect } from "vitest"; import { GitCommandError, GitHubCliError, TextGenerationError } from "../Errors.ts"; @@ -19,16 +19,11 @@ import { GitService } from "../Services/GitService.ts"; import { GitCoreLive } from "./GitCore.ts"; import { makeGitManager } from "./GitManager.ts"; -class GitFixtureError extends Schema.TaggedErrorClass()("GitFixtureError", { - message: Schema.String, - cause: Schema.optional(Schema.Defect), -}) {} - interface FakeGhScenario { prListSequence?: string[]; createdPrUrl?: string; defaultBranch?: string; - failWith?: GitFixtureError; + failWith?: GitHubCliError; } interface FakeGitTextGeneration { @@ -37,7 +32,7 @@ interface FakeGitTextGeneration { branch: string | null; stagedSummary: string; stagedPatch: string; - }) => Effect.Effect<{ subject: string; body: string }, GitFixtureError>; + }) => Effect.Effect<{ subject: string; body: string }, TextGenerationError>; generatePrContent: (input: { cwd: string; baseBranch: string; @@ -45,7 +40,7 @@ interface FakeGitTextGeneration { commitSummary: string; diffSummary: string; diffPatch: string; - }) => Effect.Effect<{ title: string; body: string }, GitFixtureError>; + }) => Effect.Effect<{ title: string; body: string }, TextGenerationError>; } function makeTempDir( @@ -144,34 +139,6 @@ function createTextGeneration(overrides: Partial = {}): T }; } -const normalizeGhError = (error: GitFixtureError): GitHubCliError => { - if (error.message.includes("Command not found: gh")) { - return new GitHubCliError({ - operation: "execute", - detail: "GitHub CLI (`gh`) is required but not available on PATH.", - cause: error, - }); - } - const lower = error.message.toLowerCase(); - if ( - lower.includes("authentication failed") || - lower.includes("not logged in") || - lower.includes("gh auth login") || - lower.includes("no oauth token") - ) { - return new GitHubCliError({ - operation: "execute", - detail: "GitHub CLI is not authenticated. Run `gh auth login` and retry.", - cause: error, - }); - } - return new GitHubCliError({ - operation: "execute", - detail: error.message, - cause: error, - }); -}; - function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { service: GitHubCliShape; ghCalls: string[]; @@ -184,7 +151,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { ghCalls.push(args.join(" ")); if (scenario.failWith) { - return Effect.fail(normalizeGhError(scenario.failWith)); + return Effect.fail(scenario.failWith); } if (args[0] === "pr" && args[1] === "list") { @@ -378,7 +345,10 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { - failWith: new GitFixtureError({ message: "Command not found: gh" }), + failWith: new GitHubCliError({ + operation: "execute", + detail: "GitHub CLI (`gh`) is required but not available on PATH.", + }), }, }); @@ -622,7 +592,10 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { - failWith: new GitFixtureError({ message: "Command not found: gh" }), + failWith: new GitHubCliError({ + operation: "execute", + detail: "GitHub CLI (`gh`) is required but not available on PATH.", + }), }, }); @@ -648,7 +621,10 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { - failWith: new GitFixtureError({ message: "authentication failed" }), + failWith: new GitHubCliError({ + operation: "execute", + detail: "GitHub CLI is not authenticated. Run `gh auth login` and retry.", + }), }, }); diff --git a/apps/server/src/terminal/Layers/NodePTY.ts b/apps/server/src/terminal/Layers/NodePTY.ts index a45d46f23fb0d5bd74f61be229a3355f7f115d24..d31b3bea5048ae427cde69d47ef14381ed9faa99 100644 --- a/apps/server/src/terminal/Layers/NodePTY.ts +++ b/apps/server/src/terminal/Layers/NodePTY.ts @@ -1,18 +1,16 @@ import { createRequire } from "node:module"; -import { Effect, FileSystem, Layer, Path, Schema } from "effect"; -import { PtyAdapter, PtyAdapterShape, PtyExitEvent, PtyProcess } from "../Services/PTY"; +import { Effect, FileSystem, Layer, Path } from "effect"; +import { + PtyAdapter, + PtyAdapterInitializationError, + PtyAdapterShape, + PtyExitEvent, + PtyProcess, +} from "../Services/PTY"; let didEnsureSpawnHelperExecutable = false; -export class NodePtyAdapterError extends Schema.TaggedErrorClass()( - "NodePtyAdapterError", - { - message: Schema.String, - cause: Schema.optional(Schema.Defect), - }, -) {} - const resolveNodePtySpawnHelperPath = Effect.gen(function* () { const requireForNodePty = createRequire(import.meta.url); const path = yield* Path.Path; @@ -101,7 +99,7 @@ export const NodePtyAdapterLive = Layer.effect( const nodePty = yield* Effect.tryPromise({ try: () => import("node-pty"), catch: (cause) => - new NodePtyAdapterError({ + new PtyAdapterInitializationError({ message: "failed to load node-pty module", cause, }), -- 2.54.0