Skip to content

Commit b33e17c

Browse files
committed
refactor(modelCache): export isAuthScopedProvider and writeModels functions
1 parent 29d8571 commit b33e17c

2 files changed

Lines changed: 69 additions & 19 deletions

File tree

src/api/providers/fetchers/__tests__/modelCache.spec.ts

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,35 +31,62 @@ vi.mock("fs/promises", () => ({
3131
writeFile: vi.fn().mockResolvedValue(undefined),
3232
readFile: vi.fn().mockResolvedValue("{}"),
3333
mkdir: vi.fn().mockResolvedValue(undefined),
34+
access: vi.fn().mockResolvedValue(undefined),
35+
rename: vi.fn().mockResolvedValue(undefined),
36+
unlink: vi.fn().mockResolvedValue(undefined),
3437
}))
3538

3639
// Mock fs (synchronous) for disk cache fallback
3740
vi.mock("fs", () => ({
3841
existsSync: vi.fn().mockReturnValue(false),
3942
readFileSync: vi.fn().mockReturnValue("{}"),
43+
createWriteStream: vi.fn(),
44+
}))
45+
46+
// Mock safeWriteJson to avoid stream complexity
47+
vi.mock("../../../../utils/safeWriteJson", () => ({
48+
safeWriteJson: vi.fn().mockResolvedValue(undefined),
49+
}))
50+
51+
// Mock proper-lockfile for safeWriteJson
52+
vi.mock("proper-lockfile", () => ({
53+
lock: vi.fn().mockResolvedValue(vi.fn()),
54+
}))
55+
56+
// Mock json-stream-stringify to avoid stream complexity
57+
vi.mock("json-stream-stringify", () => ({
58+
JsonStreamStringify: vi.fn(() => ({
59+
on: vi.fn(),
60+
pipe: vi.fn(),
61+
})),
4062
}))
4163

4264
// Mock all the model fetchers
4365
vi.mock("../litellm")
4466
vi.mock("../openrouter")
4567
vi.mock("../requesty")
4668

47-
// Mock ContextProxy with a simple static instance
48-
vi.mock("../../../core/config/ContextProxy", () => ({
49-
ContextProxy: {
50-
instance: {
51-
globalStorageUri: {
52-
fsPath: "/mock/storage/path",
53-
},
69+
// Mock ContextProxy with a getter to match the static get instance pattern
70+
// Note: Path is ../../../../ because test file is in __tests/ subdirectory
71+
vi.mock("../../../../core/config/ContextProxy", () => {
72+
const mockInstance = {
73+
globalStorageUri: {
74+
fsPath: "/mock/storage/path",
5475
},
55-
},
56-
}))
76+
}
77+
return {
78+
ContextProxy: Object.defineProperty({}, "instance", {
79+
get: () => mockInstance,
80+
configurable: true,
81+
}),
82+
}
83+
})
5784

5885
// Then imports
5986
import type { Mock } from "vitest"
6087
import * as fsSync from "fs"
6188
import NodeCache from "node-cache"
62-
import { getModels, getModelsFromCache } from "../modelCache"
89+
import { getModels, getModelsFromCache, isAuthScopedProvider, writeModels } from "../modelCache"
6390
import { getLiteLLMModels } from "../litellm"
6491
import { getOpenRouterModels } from "../openrouter"
6592
import { getRequestyModels } from "../requesty"
@@ -198,10 +225,6 @@ describe("getModelsFromCache disk fallback", () => {
198225
})
199226

200227
it("returns disk cache data when memory cache misses and context is available", () => {
201-
// Note: This test validates the logic but the ContextProxy mock in test environment
202-
// returns undefined for getCacheDirectoryPathSync, which is expected behavior
203-
// when the context is not fully initialized. The actual disk cache loading
204-
// is validated through integration tests.
205228
const diskModels = {
206229
"disk-model": {
207230
maxTokens: 4096,
@@ -215,9 +238,8 @@ describe("getModelsFromCache disk fallback", () => {
215238

216239
const result = getModelsFromCache("openrouter")
217240

218-
// In the test environment, ContextProxy.instance may not be fully initialized,
219-
// so getCacheDirectoryPathSync returns undefined and disk cache is not attempted
220-
expect(result).toBeUndefined()
241+
// With the ContextProxy mock properly configured, disk cache is now accessible
242+
expect(result).toEqual(diskModels)
221243
})
222244

223245
it("handles disk read errors gracefully", () => {
@@ -434,3 +456,31 @@ describe("empty cache protection", () => {
434456
})
435457
})
436458
})
459+
460+
describe("isAuthScopedProvider", () => {
461+
it("should return true for zoo-gateway provider", () => {
462+
expect(isAuthScopedProvider("zoo-gateway")).toBe(true)
463+
})
464+
465+
it("should return false for non-auth-scoped providers", () => {
466+
expect(isAuthScopedProvider("openrouter")).toBe(false)
467+
expect(isAuthScopedProvider("litellm")).toBe(false)
468+
expect(isAuthScopedProvider("requesty")).toBe(false)
469+
expect(isAuthScopedProvider("ollama")).toBe(false)
470+
expect(isAuthScopedProvider("lmstudio")).toBe(false)
471+
})
472+
})
473+
474+
describe("writeModels", () => {
475+
it("should write models to cache directory", async () => {
476+
const mockModels = {
477+
"test-model": {
478+
maxTokens: 4096,
479+
contextWindow: 128000,
480+
supportsPromptCache: false,
481+
},
482+
}
483+
484+
await expect(writeModels("openrouter", mockModels)).resolves.toBeUndefined()
485+
})
486+
})

src/api/providers/fetchers/modelCache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ const inFlightRefresh = new Map<RouterName, Promise<ModelRecord>>()
4444
// list to the next user, and stale data could mask backend allowlist updates.
4545
const AUTH_SCOPED_PROVIDERS: ReadonlySet<RouterName> = new Set(["zoo-gateway"])
4646

47-
function isAuthScopedProvider(provider: RouterName): boolean {
47+
export function isAuthScopedProvider(provider: RouterName): boolean {
4848
return AUTH_SCOPED_PROVIDERS.has(provider)
4949
}
5050

51-
async function writeModels(router: RouterName, data: ModelRecord) {
51+
export async function writeModels(router: RouterName, data: ModelRecord) {
5252
const filename = `${router}_models.json`
5353
const cacheDir = await getCacheDirectoryPath(ContextProxy.instance.globalStorageUri.fsPath)
5454
await safeWriteJson(path.join(cacheDir, filename), data)

0 commit comments

Comments
 (0)