diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6ce25893..1dc5af360 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,6 +139,9 @@ jobs: - name: Run unit tests run: npm run test:unit + - name: Run package tests + run: npm run test:packages + - name: Run integration tests run: npm run test:integration @@ -170,3 +173,6 @@ jobs: - name: Run unit tests run: npm run test:unit + + - name: Run package tests + run: npm run test:packages diff --git a/package.json b/package.json index 463246933..7ee1cfb45 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "copy-themes": "node scripts/copy-themes.cjs", "copy-export-html": "node scripts/copy-export-html.cjs", "test:unit": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts", + "test:packages": "node --test packages/pi-coding-agent/dist/core/*.test.js", "test:marketplace": "GSD_TEST_CLONE_MARKETPLACES=1 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/claude-import-tui.test.ts src/resources/extensions/gsd/tests/plugin-importer-live.test.ts src/tests/marketplace-discovery.test.ts", "test:coverage": "c8 --reporter=text --reporter=lcov --exclude='src/resources/extensions/gsd/tests/**' --exclude='src/tests/**' --exclude='scripts/**' --exclude='native/**' --exclude='node_modules/**' --check-coverage --statements=50 --lines=50 --branches=20 --functions=20 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts", "test:integration": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*integration*.test.ts src/tests/integration/*.test.ts", diff --git a/packages/pi-coding-agent/src/core/auth-storage.test.ts b/packages/pi-coding-agent/src/core/auth-storage.test.ts index dc601cf06..7961edb73 100644 --- a/packages/pi-coding-agent/src/core/auth-storage.test.ts +++ b/packages/pi-coding-agent/src/core/auth-storage.test.ts @@ -266,7 +266,7 @@ describe("AuthStorage — areAllCredentialsBackedOff", () => { // ─── mismatched oauth credential for non-OAuth provider (#2083) ─────────────── describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () => { - it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async () => { + it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async (t) => { // Simulates the bug: OpenRouter credential stored as type:"oauth" // but OpenRouter is not a registered OAuth provider. const storage = inMemory({ @@ -278,12 +278,25 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () = }, }); + // Isolate from any real OPENROUTER_API_KEY in the environment so the + // fall-through to env / fallback finds nothing and returns undefined. + const origEnv = process.env.OPENROUTER_API_KEY; + delete process.env.OPENROUTER_API_KEY; + t.after(() => { + if (origEnv === undefined) { + delete process.env.OPENROUTER_API_KEY; + } else { + process.env.OPENROUTER_API_KEY = origEnv; + } + }); + // Before the fix, getApiKey returns undefined because // resolveCredentialApiKey calls getOAuthProvider("openrouter") → null → undefined. // The key in the oauth credential is never extracted. const key = await storage.getApiKey("openrouter"); // After the fix, the oauth credential with an unrecognised provider // should be skipped, and getApiKey should fall through to env / fallback. + // With no env var and no fallback resolver configured, the result is undefined. assert.equal(key, undefined); }); @@ -312,7 +325,7 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () = assert.equal(key, "sk-or-v1-env-key"); }); - it("falls through to fallback resolver when openrouter has type:oauth credential", async () => { + it("falls through to fallback resolver when openrouter has type:oauth credential", async (t) => { const storage = inMemory({ openrouter: { type: "oauth", @@ -322,6 +335,18 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () = }, }); + // Isolate from any real OPENROUTER_API_KEY so env fallback is skipped + // and the fallback resolver is reached. + const origEnv = process.env.OPENROUTER_API_KEY; + delete process.env.OPENROUTER_API_KEY; + t.after(() => { + if (origEnv === undefined) { + delete process.env.OPENROUTER_API_KEY; + } else { + process.env.OPENROUTER_API_KEY = origEnv; + } + }); + storage.setFallbackResolver((provider) => provider === "openrouter" ? "sk-or-v1-fallback" : undefined, );