Skip to content

Commit 63c4128

Browse files
authored
Increasing test coverage back to over 90% (#207)
1 parent f2f0c27 commit 63c4128

File tree

13 files changed

+2130
-0
lines changed

13 files changed

+2130
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dist
1212
dist-ssr
1313
*.local
1414
coverage
15+
tmpcov-gem
1516

1617
# Editor directories and files
1718
.vscode/*

plugins/antigravity/plugin.test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,4 +1291,66 @@ describe("antigravity plugin", () => {
12911291
expect(calls.filter((u) => u.includes("fetchAvailableModels")).length).toBe(0)
12921292
expect(calls.filter((u) => u.includes("oauth2.googleapis.com")).length).toBe(0)
12931293
})
1294+
1295+
it("throws when Cloud Code returns no models", async () => {
1296+
const ctx = makeCtx()
1297+
const futureExpiry = Math.floor(Date.now() / 1000) + 3600
1298+
setupSqliteMock(ctx, makeAuthStatusJson(), makeProtobufBase64(ctx, "ya29.test-token", "1//refresh", futureExpiry))
1299+
ctx.host.ls.discover.mockReturnValue(null)
1300+
ctx.host.http.request.mockImplementation((opts) => {
1301+
if (String(opts.url).includes("fetchAvailableModels")) {
1302+
return { status: 200, bodyText: JSON.stringify({}) }
1303+
}
1304+
return { status: 500, bodyText: "" }
1305+
})
1306+
1307+
const plugin = await loadPlugin()
1308+
expect(() => plugin.probe(ctx)).toThrow("Start Antigravity and try again.")
1309+
})
1310+
1311+
it("handles refresh response missing access_token", async () => {
1312+
const ctx = makeCtx()
1313+
const futureExpiry = Math.floor(Date.now() / 1000) + 3600
1314+
setupSqliteMock(ctx, makeAuthStatusJson(), makeProtobufBase64(ctx, "ya29.will-fail", "1//refresh", futureExpiry))
1315+
ctx.host.ls.discover.mockReturnValue(null)
1316+
1317+
let oauthCalls = 0
1318+
ctx.host.http.request.mockImplementation((opts) => {
1319+
const url = String(opts.url)
1320+
if (url.includes("oauth2.googleapis.com")) {
1321+
oauthCalls += 1
1322+
return { status: 200, bodyText: JSON.stringify({ expires_in: 3600 }) }
1323+
}
1324+
if (url.includes("fetchAvailableModels")) {
1325+
return { status: 401, bodyText: '{"error":"unauthorized"}' }
1326+
}
1327+
return { status: 500, bodyText: "" }
1328+
})
1329+
1330+
const plugin = await loadPlugin()
1331+
expect(() => plugin.probe(ctx)).toThrow("Start Antigravity and try again.")
1332+
expect(oauthCalls).toBe(1)
1333+
})
1334+
1335+
it("continues to next Cloud Code base URL after non-2xx response", async () => {
1336+
const ctx = makeCtx()
1337+
const futureExpiry = Math.floor(Date.now() / 1000) + 3600
1338+
setupSqliteMock(ctx, makeAuthStatusJson(), makeProtobufBase64(ctx, "ya29.test-token", "1//refresh", futureExpiry))
1339+
ctx.host.ls.discover.mockReturnValue(null)
1340+
1341+
let ccCalls = 0
1342+
ctx.host.http.request.mockImplementation((opts) => {
1343+
if (String(opts.url).includes("fetchAvailableModels")) {
1344+
ccCalls += 1
1345+
if (ccCalls === 1) return { status: 500, bodyText: "{}" }
1346+
return { status: 200, bodyText: JSON.stringify(makeCloudCodeResponse()) }
1347+
}
1348+
return { status: 500, bodyText: "" }
1349+
})
1350+
1351+
const plugin = await loadPlugin()
1352+
const result = plugin.probe(ctx)
1353+
expect(result.lines.length).toBeGreaterThan(0)
1354+
expect(ccCalls).toBe(2)
1355+
})
12941356
})

plugins/claude/plugin.test.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,117 @@ describe("claude plugin", () => {
585585
expect(() => plugin.probe(ctx)).not.toThrow()
586586
})
587587

588+
it("falls back to keychain when file oauth exists but has no access token", async () => {
589+
const ctx = makeCtx()
590+
ctx.host.fs.exists = () => true
591+
ctx.host.fs.readText = () => JSON.stringify({ claudeAiOauth: { refreshToken: "only-refresh" } })
592+
ctx.host.keychain.readGenericPassword.mockReturnValue(
593+
JSON.stringify({ claudeAiOauth: { accessToken: "keychain-token", subscriptionType: "pro" } })
594+
)
595+
ctx.host.http.request.mockReturnValue({
596+
status: 200,
597+
bodyText: JSON.stringify({
598+
five_hour: { utilization: 10, resets_at: "2099-01-01T00:00:00.000Z" },
599+
}),
600+
})
601+
602+
const plugin = await loadPlugin()
603+
const result = plugin.probe(ctx)
604+
expect(result.lines.find((line) => line.label === "Session")).toBeTruthy()
605+
})
606+
607+
it("treats keychain oauth without access token as not logged in", async () => {
608+
const ctx = makeCtx()
609+
ctx.host.fs.exists = () => false
610+
ctx.host.keychain.readGenericPassword.mockReturnValue(
611+
JSON.stringify({ claudeAiOauth: { refreshToken: "only-refresh" } })
612+
)
613+
const plugin = await loadPlugin()
614+
expect(() => plugin.probe(ctx)).toThrow("Not logged in")
615+
})
616+
617+
it("continues with existing token when refresh cannot return a usable token", async () => {
618+
const baseCreds = JSON.stringify({
619+
claudeAiOauth: {
620+
accessToken: "token",
621+
refreshToken: "refresh",
622+
expiresAt: Date.now() - 1,
623+
},
624+
})
625+
626+
const runCase = async (refreshResp) => {
627+
const ctx = makeCtx()
628+
ctx.host.fs.exists = () => true
629+
ctx.host.fs.readText = () => baseCreds
630+
ctx.host.http.request.mockImplementation((opts) => {
631+
if (String(opts.url).includes("/v1/oauth/token")) return refreshResp
632+
return {
633+
status: 200,
634+
bodyText: JSON.stringify({
635+
five_hour: { utilization: 10, resets_at: "2099-01-01T00:00:00.000Z" },
636+
}),
637+
}
638+
})
639+
640+
delete globalThis.__openusage_plugin
641+
vi.resetModules()
642+
const plugin = await loadPlugin()
643+
const result = plugin.probe(ctx)
644+
expect(result.lines.find((line) => line.label === "Session")).toBeTruthy()
645+
}
646+
647+
await runCase({ status: 500, bodyText: "" })
648+
await runCase({ status: 200, bodyText: "not-json" })
649+
await runCase({ status: 200, bodyText: JSON.stringify({}) })
650+
})
651+
652+
it("skips proactive refresh when token is not near expiry", async () => {
653+
const ctx = makeCtx()
654+
const now = 1_700_000_000_000
655+
vi.spyOn(Date, "now").mockReturnValue(now)
656+
ctx.host.fs.exists = () => true
657+
ctx.host.fs.readText = () =>
658+
JSON.stringify({
659+
claudeAiOauth: {
660+
accessToken: "token",
661+
refreshToken: "refresh",
662+
expiresAt: now + 24 * 60 * 60 * 1000,
663+
subscriptionType: "pro",
664+
},
665+
})
666+
ctx.host.http.request.mockReturnValue({
667+
status: 200,
668+
bodyText: JSON.stringify({
669+
five_hour: { utilization: 10, resets_at: "2099-01-01T00:00:00.000Z" },
670+
}),
671+
})
672+
673+
const plugin = await loadPlugin()
674+
plugin.probe(ctx)
675+
expect(
676+
ctx.host.http.request.mock.calls.some((call) => String(call[0]?.url).includes("/v1/oauth/token"))
677+
).toBe(false)
678+
})
679+
680+
it("handles malformed ccusage payload shape as runner_failed", async () => {
681+
const ctx = makeCtx()
682+
ctx.host.fs.exists = () => true
683+
ctx.host.fs.readText = () => JSON.stringify({ claudeAiOauth: { accessToken: "token", subscriptionType: " " } })
684+
ctx.host.http.request.mockReturnValue({
685+
status: 200,
686+
bodyText: JSON.stringify({
687+
five_hour: { utilization: 10, resets_at: "2099-01-01T00:00:00.000Z" },
688+
}),
689+
})
690+
ctx.host.ccusage.query = vi.fn(() => ({ status: "ok", data: {} }))
691+
692+
const plugin = await loadPlugin()
693+
const result = plugin.probe(ctx)
694+
expect(result.plan).toBeNull()
695+
expect(result.lines.find((line) => line.label === "Session")).toBeTruthy()
696+
expect(result.lines.find((line) => line.label === "Today")).toBeUndefined()
697+
})
698+
588699
it("throws usage request failed after refresh when retry errors", async () => {
589700
const ctx = makeCtx()
590701
ctx.host.fs.exists = () => true

0 commit comments

Comments
 (0)