test(web): isolate onboarding contract env from host machine (#2119)
This commit is contained in:
parent
06901f1c76
commit
75d2ea7fb7
1 changed files with 69 additions and 1 deletions
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue