fix: finish review-driven single-source cleanup

This commit is contained in:
Cita
2026-03-23 11:26:01 +08:00
parent 29c9fcbd29
commit 328b492ab3
21 changed files with 926 additions and 320 deletions
@@ -7,6 +7,7 @@ import {
class TestStandardApiProviderAdapter extends StandardApiProviderAdapterBase {
readonly platformName = 'test-standard';
fetchJsonImpl = async () => ({ data: [] as Array<{ id: string }> });
async detect(_url: string): Promise<boolean> {
return false;
@@ -15,6 +16,14 @@ class TestStandardApiProviderAdapter extends StandardApiProviderAdapterBase {
async getModels(_baseUrl: string, _token: string): Promise<string[]> {
return [];
}
protected override async fetchJson<T>(url: string, options?: Parameters<StandardApiProviderAdapterBase['fetchJson']>[1]): Promise<T> {
return this.fetchJsonImpl(url, options) as Promise<T>;
}
async fetchModelsForTest(options: Parameters<StandardApiProviderAdapterBase['fetchModelsFromStandardEndpoint']>[0]) {
return this.fetchModelsFromStandardEndpoint(options);
}
}
describe('standardApiProvider helpers', () => {
@@ -43,4 +52,33 @@ describe('standardApiProvider helpers', () => {
quota: 0,
});
});
it('does not swallow mapper bugs while still returning empty lists for network failures', async () => {
const adapter = new TestStandardApiProviderAdapter();
adapter.fetchJsonImpl = async () => ({ data: [{ id: 'gpt-5' }] });
await expect(adapter.fetchModelsForTest({
baseUrl: 'https://api.example.com',
mapResponse: () => {
throw new Error('mapper exploded');
},
})).rejects.toThrow('mapper exploded');
adapter.fetchJsonImpl = async () => {
throw new Error('network failed');
};
await expect(adapter.fetchModelsForTest({
baseUrl: 'https://api.example.com',
})).resolves.toEqual([]);
});
it('rejects invalid payload shapes instead of silently treating them as no models', async () => {
const adapter = new TestStandardApiProviderAdapter();
adapter.fetchJsonImpl = async () => ({ data: 'not-an-array' });
await expect(adapter.fetchModelsForTest({
baseUrl: 'https://api.example.com',
})).rejects.toThrow('invalid standard models payload');
});
});
@@ -60,18 +60,27 @@ export abstract class StandardApiProviderAdapterBase extends BasePlatformAdapter
? options.resolveUrl(normalizedBaseUrl)
: resolveVersionedModelsUrl(normalizedBaseUrl);
let payload: any;
try {
const payload = await this.fetchJson<any>(url, {
payload = await this.fetchJson<any>(url, {
headers: options.headers,
});
const rows = options.mapResponse
? options.mapResponse(payload)
: (payload?.data || []).map((item: any) => item?.id);
return rows
.map((item) => (typeof item === 'string' ? item.trim() : ''))
.filter((item) => item.length > 0);
} catch {
return [];
}
const rows = options.mapResponse
? options.mapResponse(payload)
: Array.isArray(payload?.data)
? payload.data.map((item: any) => item?.id)
: null;
if (!Array.isArray(rows)) {
throw new Error('invalid standard models payload');
}
return rows
.map((item) => (typeof item === 'string' ? item.trim() : ''))
.filter((item) => item.length > 0);
}
}