fix(web): resolve 4 pre-existing onboarding contract test failures (#2209)

Two root causes fixed:

1. Route handlers gained requireProjectCwd(request) guards after the
   contract tests were written. Test requests lacked a ?project= query
   param, causing routes to short-circuit or throw NoProjectError.

2. resolveCredentialSource's third fallback (authStorage.hasAuth) called
   the module-level getEnvApiKey import directly, bypassing the
   test-injectable getEnvApiKeyFn override. Real env vars like
   OPENROUTER_API_KEY leaked into tests expecting no auth.

Changes:
- Add projectRequest() helper to attach ?project= to all test route calls
- Add noEnvApiKey() stub and scoped getEnvApiKey overrides to isolate
  tests from real environment variables
- Replace authStorage.hasAuth() with
  authStorage.getCredentialsForProvider().length in resolveCredentialSource
  to prevent env-check duplication (env is already checked via the
  overridable getEnvApiKeyFn on the preceding line)
This commit is contained in:
Jeremy McSpadden 2026-03-23 10:50:51 -05:00 committed by GitHub
parent 97bdf3b071
commit de332ed3c8
2 changed files with 41 additions and 22 deletions

View file

@ -52,6 +52,16 @@ function attachJsonLineReader(stream: PassThrough, onLine: (line: string) => voi
});
}
function noEnvApiKey(): null {
return null;
}
function projectRequest(projectCwd: string, url: string, init?: RequestInit): Request {
const base = new URL(url, "http://localhost");
base.searchParams.set("project", projectCwd);
return new Request(base, init);
}
function makeWorkspaceFixture(): { projectCwd: string; sessionsDir: string; cleanup: () => void } {
const root = mkdtempSync(join(tmpdir(), "gsd-web-onboarding-"));
const projectCwd = join(root, "project");
@ -246,10 +256,10 @@ test("boot and onboarding routes expose locked required state plus explicitly sk
const fixture = makeWorkspaceFixture();
const authStorage = AuthStorage.inMemory({});
configureBridgeFixture(fixture, "sess-missing-auth");
onboarding.configureOnboardingServiceForTests({ authStorage });
onboarding.configureOnboardingServiceForTests({ authStorage, getEnvApiKey: noEnvApiKey });
try {
const bootResponse = await bootRoute.GET();
const bootResponse = await bootRoute.GET(projectRequest(fixture.projectCwd, "/api/boot"));
assert.equal(bootResponse.status, 200);
const bootPayload = (await bootResponse.json()) as any;
@ -281,7 +291,7 @@ test("boot and onboarding routes expose locked required state plus explicitly sk
assert.equal(anthropicProvider.supports.apiKey, true);
assert.equal(anthropicProvider.supports.oauthAvailable, true);
const onboardingResponse = await onboardingRoute.GET();
const onboardingResponse = await onboardingRoute.GET(projectRequest(fixture.projectCwd, "/api/onboarding"));
assert.equal(onboardingResponse.status, 200);
const onboardingPayload = (await onboardingResponse.json()) as any;
assert.equal(onboardingPayload.onboarding.locked, true);
@ -299,10 +309,13 @@ test("runtime env-backed auth unlocks boot onboarding state and reports the envi
const previousGithubToken = process.env.GITHUB_TOKEN;
process.env.GITHUB_TOKEN = "ghu_runtime_env_token";
configureBridgeFixture(fixture, "sess-env-auth");
onboarding.configureOnboardingServiceForTests({ authStorage });
onboarding.configureOnboardingServiceForTests({
authStorage,
getEnvApiKey: (provider: string) => (provider === "github-copilot" ? process.env.GITHUB_TOKEN : undefined),
});
try {
const bootResponse = await bootRoute.GET();
const bootResponse = await bootRoute.GET(projectRequest(fixture.projectCwd, "/api/boot"));
assert.equal(bootResponse.status, 200);
const bootPayload = (await bootResponse.json()) as any;
@ -335,6 +348,7 @@ test("failed API-key validation stays locked, redacts the error, and is reflecte
configureBridgeFixture(fixture, "sess-validation-failure");
onboarding.configureOnboardingServiceForTests({
authStorage,
getEnvApiKey: noEnvApiKey,
validateApiKey: async () => ({
ok: false,
message: "OpenAI rejected sk-test-secret-123456 because Bearer sk-test-secret-123456 is invalid",
@ -343,7 +357,7 @@ test("failed API-key validation stays locked, redacts the error, and is reflecte
try {
const validationResponse = await onboardingRoute.POST(
new Request("http://localhost/api/onboarding", {
projectRequest(fixture.projectCwd, "/api/onboarding", {
method: "POST",
body: JSON.stringify({
action: "save_api_key",
@ -366,7 +380,7 @@ test("failed API-key validation stays locked, redacts the error, and is reflecte
assert.doesNotMatch(validationPayload.onboarding.lastValidation.message, /sk-test-secret-123456/);
assert.equal(authStorage.hasAuth("openai"), false);
const bootResponse = await bootRoute.GET();
const bootResponse = await bootRoute.GET(projectRequest(fixture.projectCwd, "/api/boot"));
assert.equal(bootResponse.status, 200);
const bootPayload = (await bootResponse.json()) as any;
assert.equal(bootPayload.onboarding.locked, true);
@ -383,11 +397,11 @@ test("direct prompt commands cannot bypass onboarding while required setup is st
const fixture = makeWorkspaceFixture();
const authStorage = AuthStorage.inMemory({});
const harness = configureBridgeFixture(fixture, "sess-command-locked");
onboarding.configureOnboardingServiceForTests({ authStorage });
onboarding.configureOnboardingServiceForTests({ authStorage, getEnvApiKey: noEnvApiKey });
try {
const response = await commandRoute.POST(
new Request("http://localhost/api/session/command", {
projectRequest(fixture.projectCwd, "/api/session/command", {
method: "POST",
body: JSON.stringify({ type: "prompt", message: "hello from bypass attempt" }),
}),
@ -403,7 +417,7 @@ test("direct prompt commands cannot bypass onboarding while required setup is st
assert.equal(harness.spawnCalls, 0);
const stateResponse = await commandRoute.POST(
new Request("http://localhost/api/session/command", {
projectRequest(fixture.projectCwd, "/api/session/command", {
method: "POST",
body: JSON.stringify({ type: "get_state" }),
}),
@ -426,6 +440,7 @@ test("bridge auth refresh failures remain inspectable and keep the workspace loc
configureBridgeFixture(fixture, "sess-refresh-failure");
onboarding.configureOnboardingServiceForTests({
authStorage,
getEnvApiKey: noEnvApiKey,
validateApiKey: async () => ({ ok: true, message: "openai credentials validated" }),
refreshBridgeAuth: async () => {
throw new Error("bridge restart failed for sk-refresh-secret-123456");
@ -434,7 +449,7 @@ test("bridge auth refresh failures remain inspectable and keep the workspace loc
try {
const validationResponse = await onboardingRoute.POST(
new Request("http://localhost/api/onboarding", {
projectRequest(fixture.projectCwd, "/api/onboarding", {
method: "POST",
body: JSON.stringify({
action: "save_api_key",
@ -455,7 +470,7 @@ test("bridge auth refresh failures remain inspectable and keep the workspace loc
assert.doesNotMatch(validationPayload.onboarding.bridgeAuthRefresh.error, /sk-refresh-secret-123456/);
assert.equal(authStorage.hasAuth("openai"), true);
const bootResponse = await bootRoute.GET();
const bootResponse = await bootRoute.GET(projectRequest(fixture.projectCwd, "/api/boot"));
const bootPayload = (await bootResponse.json()) as any;
assert.equal(bootPayload.onboarding.locked, true);
assert.equal(bootPayload.onboarding.lockReason, "bridge_refresh_failed");
@ -473,12 +488,13 @@ test("successful API-key validation persists the credential and unlocks onboardi
const harness = configureBridgeFixture(fixture, "sess-validation-success");
onboarding.configureOnboardingServiceForTests({
authStorage,
getEnvApiKey: noEnvApiKey,
validateApiKey: async () => ({ ok: true, message: "openai credentials validated" }),
});
try {
const validationResponse = await onboardingRoute.POST(
new Request("http://localhost/api/onboarding", {
projectRequest(fixture.projectCwd, "/api/onboarding", {
method: "POST",
body: JSON.stringify({
action: "save_api_key",
@ -502,7 +518,7 @@ test("successful API-key validation persists the credential and unlocks onboardi
assert.equal(authStorage.hasAuth("openai"), true);
assert.equal(harness.spawnCalls, 1);
const bootResponse = await bootRoute.GET();
const bootResponse = await bootRoute.GET(projectRequest(fixture.projectCwd, "/api/boot"));
const bootPayload = (await bootResponse.json()) as any;
assert.equal(bootPayload.onboarding.locked, false);
assert.equal(bootPayload.onboarding.lockReason, null);
@ -521,17 +537,17 @@ test("logout_provider removes saved auth, refreshes the bridge, and relocks onbo
openai: { type: "api_key", key: "sk-saved-logout" },
} as any);
const harness = configureBridgeFixture(fixture, "sess-logout-success");
onboarding.configureOnboardingServiceForTests({ authStorage });
onboarding.configureOnboardingServiceForTests({ authStorage, getEnvApiKey: noEnvApiKey });
try {
const bootBefore = await bootRoute.GET();
const bootBefore = await bootRoute.GET(projectRequest(fixture.projectCwd, "/api/boot"));
const bootBeforePayload = (await bootBefore.json()) as any;
assert.equal(bootBeforePayload.onboarding.locked, false);
assert.equal(bootBeforePayload.onboarding.required.satisfiedBy.providerId, "openai");
assert.equal(harness.spawnCalls, 1);
const logoutResponse = await onboardingRoute.POST(
new Request("http://localhost/api/onboarding", {
projectRequest(fixture.projectCwd, "/api/onboarding", {
method: "POST",
body: JSON.stringify({
action: "logout_provider",
@ -549,7 +565,7 @@ test("logout_provider removes saved auth, refreshes the bridge, and relocks onbo
assert.equal(authStorage.hasAuth("openai"), false);
assert.equal(harness.spawnCalls, 2);
const bootAfter = await bootRoute.GET();
const bootAfter = await bootRoute.GET(projectRequest(fixture.projectCwd, "/api/boot"));
const bootAfterPayload = (await bootAfter.json()) as any;
assert.equal(bootAfterPayload.onboarding.locked, true);
assert.equal(bootAfterPayload.onboarding.lockReason, "required_setup");
@ -568,17 +584,20 @@ test("logout_provider fails clearly for environment-backed auth that the browser
const previousGithubToken = process.env.GITHUB_TOKEN;
process.env.GITHUB_TOKEN = "ghu_env_only_token";
configureBridgeFixture(fixture, "sess-logout-env");
onboarding.configureOnboardingServiceForTests({ authStorage });
onboarding.configureOnboardingServiceForTests({
authStorage,
getEnvApiKey: (provider: string) => (provider === "github-copilot" ? process.env.GITHUB_TOKEN : undefined),
});
try {
const bootBefore = await bootRoute.GET();
const bootBefore = await bootRoute.GET(projectRequest(fixture.projectCwd, "/api/boot"));
const bootBeforePayload = (await bootBefore.json()) as any;
assert.equal(bootBeforePayload.onboarding.locked, false);
assert.equal(bootBeforePayload.onboarding.required.satisfiedBy.providerId, "github-copilot");
assert.equal(bootBeforePayload.onboarding.required.satisfiedBy.source, "environment");
const logoutResponse = await onboardingRoute.POST(
new Request("http://localhost/api/onboarding", {
projectRequest(fixture.projectCwd, "/api/onboarding", {
method: "POST",
body: JSON.stringify({
action: "logout_provider",

View file

@ -247,7 +247,7 @@ function resolveCredentialSource(
if (getEnvApiKeyFn(providerId)) {
return "environment";
}
if (authStorage.hasAuth(providerId)) {
if (authStorage.getCredentialsForProvider(providerId).length > 0) {
return "runtime";
}
return null;