new_pull
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { statSync } from "node:fs";
|
||||
import {
|
||||
createProvider,
|
||||
InMemoryModelsStore,
|
||||
@@ -25,7 +24,7 @@ function model(id: string): Model<"openai-completions"> {
|
||||
};
|
||||
}
|
||||
|
||||
function testProvider(localCatalogUrl?: URL) {
|
||||
function testProvider(localGeneratedAt?: number) {
|
||||
return withRemoteCatalog(
|
||||
createProvider({
|
||||
id: "test-provider",
|
||||
@@ -41,7 +40,7 @@ function testProvider(localCatalogUrl?: URL) {
|
||||
},
|
||||
}),
|
||||
"https://pi.dev",
|
||||
localCatalogUrl,
|
||||
localGeneratedAt,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,19 +79,18 @@ describe("remote catalog provider", () => {
|
||||
});
|
||||
|
||||
it("prefers the newer of the generated and remote catalogs", async () => {
|
||||
const localCatalogUrl = new URL(import.meta.url);
|
||||
const localMtime = statSync(localCatalogUrl).mtimeMs;
|
||||
const newerHeader = new Date(localMtime + 60_000).toUTCString();
|
||||
const localGeneratedAt = Date.parse("2026-07-23T10:00:00.000Z");
|
||||
const newerHeader = new Date(localGeneratedAt + 60_000).toUTCString();
|
||||
const responses = [
|
||||
new Response(JSON.stringify({ old: model("old") }), {
|
||||
headers: { "last-modified": new Date(localMtime - 60_000).toUTCString() },
|
||||
headers: { "last-modified": new Date(localGeneratedAt - 60_000).toUTCString() },
|
||||
}),
|
||||
new Response(JSON.stringify({ newer: model("newer") }), {
|
||||
headers: { "last-modified": newerHeader },
|
||||
}),
|
||||
];
|
||||
vi.spyOn(globalThis, "fetch").mockImplementation(async () => responses.shift() as Response);
|
||||
const provider = testProvider(localCatalogUrl);
|
||||
const provider = testProvider(localGeneratedAt);
|
||||
const store = new InMemoryModelsStore();
|
||||
const refresh = { credential: { type: "api_key" } as const, store: scopedStore(store), allowNetwork: true };
|
||||
|
||||
@@ -104,6 +102,76 @@ describe("remote catalog provider", () => {
|
||||
expect(await store.read(provider.id)).toMatchObject({ lastModified: Date.parse(newerHeader) });
|
||||
});
|
||||
|
||||
it("revalidates a stored catalog with its etag and keeps the overlay on 304", async () => {
|
||||
const responses = [
|
||||
new Response(JSON.stringify({ dynamic: model("dynamic") }), {
|
||||
headers: { "content-type": "application/json", etag: '"catalog-1"' },
|
||||
}),
|
||||
new Response(null, { status: 304, headers: { etag: '"catalog-1"' } }),
|
||||
];
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => responses.shift() as Response);
|
||||
const provider = testProvider();
|
||||
const store = new InMemoryModelsStore();
|
||||
const refresh = { credential: { type: "api_key" } as const, store: scopedStore(store), allowNetwork: true };
|
||||
|
||||
await provider.refreshModels?.(refresh);
|
||||
expect(fetchSpy.mock.calls[0]?.[1]?.headers).not.toHaveProperty("if-none-match");
|
||||
expect(await store.read(provider.id)).toMatchObject({ etag: '"catalog-1"' });
|
||||
|
||||
const checkedAt = (await store.read(provider.id))?.checkedAt;
|
||||
await provider.refreshModels?.({ ...refresh, force: true });
|
||||
|
||||
expect(fetchSpy.mock.calls[1]?.[1]?.headers).toMatchObject({ "if-none-match": '"catalog-1"' });
|
||||
expect(provider.getModels().map((entry) => entry.id)).toEqual(["static", "dynamic"]);
|
||||
const stored = await store.read(provider.id);
|
||||
expect(stored?.models.map((entry) => entry.id)).toEqual(["dynamic"]);
|
||||
expect(stored?.etag).toBe('"catalog-1"');
|
||||
expect(stored?.checkedAt).toBeGreaterThanOrEqual(checkedAt ?? 0);
|
||||
});
|
||||
|
||||
it("drops a stale etag when the overlay becomes unavailable", async () => {
|
||||
const responses = [
|
||||
new Response(JSON.stringify({ dynamic: model("dynamic") }), {
|
||||
headers: { "content-type": "application/json", etag: '"catalog-1"' },
|
||||
}),
|
||||
new Response("not implemented", { status: 501 }),
|
||||
];
|
||||
vi.spyOn(globalThis, "fetch").mockImplementation(async () => responses.shift() as Response);
|
||||
const provider = testProvider();
|
||||
const store = new InMemoryModelsStore();
|
||||
const refresh = { credential: { type: "api_key" } as const, store: scopedStore(store), allowNetwork: true };
|
||||
|
||||
await provider.refreshModels?.(refresh);
|
||||
await provider.refreshModels?.({ ...refresh, force: true });
|
||||
|
||||
expect((await store.read(provider.id))?.etag).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps the etag and overlay after a transient failure", async () => {
|
||||
const responses = [
|
||||
new Response(JSON.stringify({ dynamic: model("dynamic") }), {
|
||||
headers: { "content-type": "application/json", etag: '"catalog-1"' },
|
||||
}),
|
||||
new Response("rate limited", { status: 429 }),
|
||||
new Response(null, { status: 304, headers: { etag: '"catalog-1"' } }),
|
||||
];
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => responses.shift() as Response);
|
||||
const provider = testProvider();
|
||||
const store = new InMemoryModelsStore();
|
||||
const refresh = { credential: { type: "api_key" } as const, store: scopedStore(store), allowNetwork: true };
|
||||
|
||||
await provider.refreshModels?.(refresh);
|
||||
await expect(provider.refreshModels?.({ ...refresh, force: true })).rejects.toThrow(/429/);
|
||||
|
||||
const stored = await store.read(provider.id);
|
||||
expect(stored?.etag).toBe('"catalog-1"');
|
||||
expect(stored?.models.map((entry) => entry.id)).toEqual(["dynamic"]);
|
||||
|
||||
await provider.refreshModels?.({ ...refresh, force: true });
|
||||
expect(fetchSpy.mock.calls[2]?.[1]?.headers).toMatchObject({ "if-none-match": '"catalog-1"' });
|
||||
expect(provider.getModels().map((entry) => entry.id)).toEqual(["static", "dynamic"]);
|
||||
});
|
||||
|
||||
it("treats unimplemented pi.dev catalog routes as an unavailable overlay", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("not implemented", { status: 501 }));
|
||||
const provider = testProvider();
|
||||
|
||||
Reference in New Issue
Block a user