From 75d2ea7fb7a5c87761b71e6cb1504c7c30211154 Mon Sep 17 00:00:00 2001 From: mastertyko <11311479+mastertyko@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:53:02 +0100 Subject: [PATCH] test(web): isolate onboarding contract env from host machine (#2119) --- src/tests/web-onboarding-contract.test.ts | 70 ++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/tests/web-onboarding-contract.test.ts b/src/tests/web-onboarding-contract.test.ts index 9a18f37e9..d757d9f6a 100644 --- a/src/tests/web-onboarding-contract.test.ts +++ b/src/tests/web-onboarding-contract.test.ts @@ -15,6 +15,59 @@ const onboardingRoute = await import("../../web/app/api/onboarding/route.ts"); const commandRoute = await import("../../web/app/api/session/command/route.ts"); const { AuthStorage } = await import("@gsd/pi-coding-agent"); +const ONBOARDING_ENV_KEYS = [ + "GITHUB_TOKEN", + "GH_TOKEN", + "COPILOT_GITHUB_TOKEN", + "ANTHROPIC_OAUTH_TOKEN", + "ANTHROPIC_API_KEY", + "OPENAI_API_KEY", + "AZURE_OPENAI_API_KEY", + "GEMINI_API_KEY", + "GOOGLE_APPLICATION_CREDENTIALS", + "GOOGLE_CLOUD_PROJECT", + "GCLOUD_PROJECT", + "GOOGLE_CLOUD_LOCATION", + "GROQ_API_KEY", + "CEREBRAS_API_KEY", + "XAI_API_KEY", + "OPENROUTER_API_KEY", + "AI_GATEWAY_API_KEY", + "ZAI_API_KEY", + "MISTRAL_API_KEY", + "MINIMAX_API_KEY", + "MINIMAX_CN_API_KEY", + "HF_TOKEN", + "OPENCODE_API_KEY", + "KIMI_API_KEY", + "ALIBABA_API_KEY", + "AWS_PROFILE", + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_BEARER_TOKEN_BEDROCK", + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", + "AWS_CONTAINER_CREDENTIALS_FULL_URI", + "AWS_WEB_IDENTITY_TOKEN_FILE", +] as const; + +const ORIGINAL_ONBOARDING_ENV = Object.fromEntries( + ONBOARDING_ENV_KEYS.map((key) => [key, process.env[key]]), +) as Record<(typeof ONBOARDING_ENV_KEYS)[number], string | undefined>; + +function clearOnboardingEnv(): void { + for (const key of ONBOARDING_ENV_KEYS) { + delete process.env[key]; + } +} + +function restoreOnboardingEnv(): void { + for (const key of ONBOARDING_ENV_KEYS) { + const value = ORIGINAL_ONBOARDING_ENV[key]; + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } +} + class FakeRpcChild extends EventEmitter { stdin = new PassThrough(); stdout = new PassThrough(); @@ -239,7 +292,6 @@ function configureBridgeFixture(fixture: { projectCwd: string; sessionsDir: stri bridge.configureBridgeServiceForTests({ env: { - ...process.env, GSD_WEB_PROJECT_CWD: fixture.projectCwd, GSD_WEB_PROJECT_SESSIONS_DIR: fixture.sessionsDir, GSD_WEB_PACKAGE_ROOT: repoRoot, @@ -254,6 +306,7 @@ function configureBridgeFixture(fixture: { projectCwd: string; sessionsDir: stri test("boot and onboarding routes expose locked required state plus explicitly skippable optional setup when auth is missing", async () => { const fixture = makeWorkspaceFixture(); + clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); configureBridgeFixture(fixture, "sess-missing-auth"); onboarding.configureOnboardingServiceForTests({ authStorage, getEnvApiKey: noEnvApiKey }); @@ -299,12 +352,14 @@ test("boot and onboarding routes expose locked required state plus explicitly sk } finally { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); + restoreOnboardingEnv(); fixture.cleanup(); } }); test("runtime env-backed auth unlocks boot onboarding state and reports the environment source", async () => { const fixture = makeWorkspaceFixture(); + clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); const previousGithubToken = process.env.GITHUB_TOKEN; process.env.GITHUB_TOKEN = "ghu_runtime_env_token"; @@ -338,12 +393,14 @@ test("runtime env-backed auth unlocks boot onboarding state and reports the envi } onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); + restoreOnboardingEnv(); fixture.cleanup(); } }); test("failed API-key validation stays locked, redacts the error, and is reflected in boot state without persisting auth", async () => { const fixture = makeWorkspaceFixture(); + clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); configureBridgeFixture(fixture, "sess-validation-failure"); onboarding.configureOnboardingServiceForTests({ @@ -389,12 +446,14 @@ test("failed API-key validation stays locked, redacts the error, and is reflecte } finally { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); + restoreOnboardingEnv(); fixture.cleanup(); } }); test("direct prompt commands cannot bypass onboarding while required setup is still locked", async () => { const fixture = makeWorkspaceFixture(); + clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); const harness = configureBridgeFixture(fixture, "sess-command-locked"); onboarding.configureOnboardingServiceForTests({ authStorage, getEnvApiKey: noEnvApiKey }); @@ -430,12 +489,14 @@ test("direct prompt commands cannot bypass onboarding while required setup is st } finally { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); + restoreOnboardingEnv(); fixture.cleanup(); } }); test("bridge auth refresh failures remain inspectable and keep the workspace locked after credentials validate", async () => { const fixture = makeWorkspaceFixture(); + clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); configureBridgeFixture(fixture, "sess-refresh-failure"); onboarding.configureOnboardingServiceForTests({ @@ -478,12 +539,14 @@ test("bridge auth refresh failures remain inspectable and keep the workspace loc } finally { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); + restoreOnboardingEnv(); fixture.cleanup(); } }); test("successful API-key validation persists the credential and unlocks onboarding", async () => { const fixture = makeWorkspaceFixture(); + clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); const harness = configureBridgeFixture(fixture, "sess-validation-success"); onboarding.configureOnboardingServiceForTests({ @@ -527,12 +590,14 @@ test("successful API-key validation persists the credential and unlocks onboardi } finally { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); + restoreOnboardingEnv(); fixture.cleanup(); } }); test("logout_provider removes saved auth, refreshes the bridge, and relocks onboarding when it was the only provider", async () => { const fixture = makeWorkspaceFixture(); + clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({ openai: { type: "api_key", key: "sk-saved-logout" }, } as any); @@ -574,12 +639,14 @@ test("logout_provider removes saved auth, refreshes the bridge, and relocks onbo } finally { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); + restoreOnboardingEnv(); fixture.cleanup(); } }); test("logout_provider fails clearly for environment-backed auth that the browser cannot remove", async () => { const fixture = makeWorkspaceFixture(); + clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); const previousGithubToken = process.env.GITHUB_TOKEN; process.env.GITHUB_TOKEN = "ghu_env_only_token"; @@ -620,6 +687,7 @@ test("logout_provider fails clearly for environment-backed auth that the browser } onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); + restoreOnboardingEnv(); fixture.cleanup(); } });