@86d-app/search 0.0.23 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/search/src/__tests__/admin-settings.test.js +262 -0
- package/dist/modules/search/src/__tests__/controllers.test.js +853 -0
- package/dist/modules/search/src/__tests__/embedding-provider.test.js +150 -0
- package/dist/modules/search/src/__tests__/endpoint-security.test.js +250 -0
- package/dist/modules/search/src/__tests__/meilisearch-provider.test.js +318 -0
- package/dist/modules/search/src/__tests__/service-impl.test.js +703 -0
- package/dist/modules/search/src/__tests__/store-endpoints.test.js +295 -0
- package/dist/{admin/components/index.d.ts → modules/search/src/admin/components/index.jsx} +0 -1
- package/dist/modules/search/src/admin/components/search-analytics.jsx +230 -0
- package/dist/modules/search/src/admin/endpoints/analytics.js +9 -0
- package/dist/modules/search/src/admin/endpoints/bulk-index.js +26 -0
- package/dist/modules/search/src/admin/endpoints/click-analytics.js +9 -0
- package/dist/modules/search/src/admin/endpoints/get-settings.js +97 -0
- package/dist/modules/search/src/admin/endpoints/index-manage.js +32 -0
- package/dist/modules/search/src/admin/endpoints/index.js +21 -0
- package/dist/modules/search/src/admin/endpoints/popular.js +11 -0
- package/dist/modules/search/src/admin/endpoints/synonyms.js +30 -0
- package/dist/modules/search/src/admin/endpoints/zero-results.js +11 -0
- package/dist/modules/search/src/embedding-provider.js +77 -0
- package/dist/modules/search/src/index.js +75 -0
- package/dist/modules/search/src/meilisearch-provider.js +138 -0
- package/dist/modules/search/src/schema.js +61 -0
- package/dist/modules/search/src/service-impl.js +770 -0
- package/dist/modules/search/src/service.js +1 -0
- package/dist/modules/search/src/store/components/_hooks.js +10 -0
- package/dist/modules/search/src/store/components/index.jsx +9 -0
- package/dist/modules/search/src/store/components/search-bar.jsx +91 -0
- package/dist/modules/search/src/store/components/search-page.jsx +17 -0
- package/dist/modules/search/src/store/components/search-results.jsx +51 -0
- package/dist/modules/search/src/store/endpoints/click.js +15 -0
- package/dist/modules/search/src/store/endpoints/index.js +12 -0
- package/dist/modules/search/src/store/endpoints/recent.js +18 -0
- package/dist/modules/search/src/store/endpoints/search.js +57 -0
- package/dist/modules/search/src/store/endpoints/store-search.js +33 -0
- package/dist/modules/search/src/store/endpoints/suggest.js +12 -0
- package/package.json +1 -1
- package/src/__tests__/admin-settings.test.ts +367 -0
- package/src/__tests__/store-endpoints.test.ts +392 -0
- package/src/admin/endpoints/get-settings.ts +77 -0
- package/dist/__tests__/controllers.test.d.ts +0 -2
- package/dist/__tests__/controllers.test.d.ts.map +0 -1
- package/dist/__tests__/embedding-provider.test.d.ts +0 -2
- package/dist/__tests__/embedding-provider.test.d.ts.map +0 -1
- package/dist/__tests__/endpoint-security.test.d.ts +0 -2
- package/dist/__tests__/endpoint-security.test.d.ts.map +0 -1
- package/dist/__tests__/meilisearch-provider.test.d.ts +0 -2
- package/dist/__tests__/meilisearch-provider.test.d.ts.map +0 -1
- package/dist/__tests__/service-impl.test.d.ts +0 -2
- package/dist/__tests__/service-impl.test.d.ts.map +0 -1
- package/dist/admin/components/index.d.ts.map +0 -1
- package/dist/admin/components/search-analytics.d.ts +0 -2
- package/dist/admin/components/search-analytics.d.ts.map +0 -1
- package/dist/admin/endpoints/analytics.d.ts +0 -15
- package/dist/admin/endpoints/analytics.d.ts.map +0 -1
- package/dist/admin/endpoints/bulk-index.d.ts +0 -20
- package/dist/admin/endpoints/bulk-index.d.ts.map +0 -1
- package/dist/admin/endpoints/click-analytics.d.ts +0 -7
- package/dist/admin/endpoints/click-analytics.d.ts.map +0 -1
- package/dist/admin/endpoints/get-settings.d.ts +0 -17
- package/dist/admin/endpoints/get-settings.d.ts.map +0 -1
- package/dist/admin/endpoints/index-manage.d.ts +0 -26
- package/dist/admin/endpoints/index-manage.d.ts.map +0 -1
- package/dist/admin/endpoints/index.d.ts +0 -125
- package/dist/admin/endpoints/index.d.ts.map +0 -1
- package/dist/admin/endpoints/popular.d.ts +0 -10
- package/dist/admin/endpoints/popular.d.ts.map +0 -1
- package/dist/admin/endpoints/synonyms.d.ts +0 -30
- package/dist/admin/endpoints/synonyms.d.ts.map +0 -1
- package/dist/admin/endpoints/zero-results.d.ts +0 -10
- package/dist/admin/endpoints/zero-results.d.ts.map +0 -1
- package/dist/embedding-provider.d.ts +0 -28
- package/dist/embedding-provider.d.ts.map +0 -1
- package/dist/index.d.ts +0 -23
- package/dist/index.d.ts.map +0 -1
- package/dist/meilisearch-provider.d.ts +0 -104
- package/dist/meilisearch-provider.d.ts.map +0 -1
- package/dist/schema.d.ts +0 -133
- package/dist/schema.d.ts.map +0 -1
- package/dist/service-impl.d.ts +0 -6
- package/dist/service-impl.d.ts.map +0 -1
- package/dist/service.d.ts +0 -127
- package/dist/service.d.ts.map +0 -1
- package/dist/store/components/_hooks.d.ts +0 -6
- package/dist/store/components/_hooks.d.ts.map +0 -1
- package/dist/store/components/index.d.ts +0 -10
- package/dist/store/components/index.d.ts.map +0 -1
- package/dist/store/components/search-bar.d.ts +0 -7
- package/dist/store/components/search-bar.d.ts.map +0 -1
- package/dist/store/components/search-page.d.ts +0 -4
- package/dist/store/components/search-page.d.ts.map +0 -1
- package/dist/store/components/search-results.d.ts +0 -9
- package/dist/store/components/search-results.d.ts.map +0 -1
- package/dist/store/endpoints/click.d.ts +0 -14
- package/dist/store/endpoints/click.d.ts.map +0 -1
- package/dist/store/endpoints/index.d.ts +0 -85
- package/dist/store/endpoints/index.d.ts.map +0 -1
- package/dist/store/endpoints/recent.d.ts +0 -15
- package/dist/store/endpoints/recent.d.ts.map +0 -1
- package/dist/store/endpoints/search.d.ts +0 -36
- package/dist/store/endpoints/search.d.ts.map +0 -1
- package/dist/store/endpoints/store-search.d.ts +0 -16
- package/dist/store/endpoints/store-search.d.ts.map +0 -1
- package/dist/store/endpoints/suggest.d.ts +0 -11
- package/dist/store/endpoints/suggest.d.ts.map +0 -1
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { getSettings } from "../admin/endpoints/get-settings";
|
|
3
|
+
|
|
4
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
function extractHandler(
|
|
7
|
+
ep: unknown,
|
|
8
|
+
): (ctx: Record<string, unknown>) => Promise<Record<string, unknown>> {
|
|
9
|
+
const obj = ep as Record<string, unknown>;
|
|
10
|
+
const fn = typeof obj.handler === "function" ? obj.handler : ep;
|
|
11
|
+
return fn as (
|
|
12
|
+
ctx: Record<string, unknown>,
|
|
13
|
+
) => Promise<Record<string, unknown>>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const handler = extractHandler(getSettings);
|
|
17
|
+
|
|
18
|
+
function createMockController(indexCount = 42) {
|
|
19
|
+
return { getIndexCount: vi.fn().mockResolvedValue(indexCount) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function callGetSettings(
|
|
23
|
+
opts: Record<string, unknown>,
|
|
24
|
+
controllerOverrides?: { indexCount?: number },
|
|
25
|
+
) {
|
|
26
|
+
return handler({
|
|
27
|
+
context: {
|
|
28
|
+
options: opts,
|
|
29
|
+
controllers: {
|
|
30
|
+
search: createMockController(controllerOverrides?.indexCount ?? 42),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Fetch mocks ──────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/** MeiliSearch health OK then stats OK */
|
|
39
|
+
function mockMeiliConnected(docCount = 156) {
|
|
40
|
+
const spy = vi.spyOn(globalThis, "fetch");
|
|
41
|
+
spy.mockResolvedValueOnce(
|
|
42
|
+
new Response(JSON.stringify({ status: "available" }), { status: 200 }),
|
|
43
|
+
);
|
|
44
|
+
spy.mockResolvedValueOnce(
|
|
45
|
+
new Response(
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
numberOfDocuments: docCount,
|
|
48
|
+
isIndexing: false,
|
|
49
|
+
fieldDistribution: { id: docCount, title: docCount },
|
|
50
|
+
}),
|
|
51
|
+
{ status: 200 },
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** MeiliSearch health endpoint fails */
|
|
57
|
+
function mockMeiliUnreachable() {
|
|
58
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValueOnce(
|
|
59
|
+
new Error("ECONNREFUSED"),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** MeiliSearch health then stats then embedding models endpoint */
|
|
64
|
+
function mockMeiliConnectedAndEmbeddingOk(docCount = 156) {
|
|
65
|
+
const spy = vi.spyOn(globalThis, "fetch");
|
|
66
|
+
// MeiliSearch health
|
|
67
|
+
spy.mockResolvedValueOnce(
|
|
68
|
+
new Response(JSON.stringify({ status: "available" }), { status: 200 }),
|
|
69
|
+
);
|
|
70
|
+
// MeiliSearch stats
|
|
71
|
+
spy.mockResolvedValueOnce(
|
|
72
|
+
new Response(
|
|
73
|
+
JSON.stringify({
|
|
74
|
+
numberOfDocuments: docCount,
|
|
75
|
+
isIndexing: false,
|
|
76
|
+
fieldDistribution: {},
|
|
77
|
+
}),
|
|
78
|
+
{ status: 200 },
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
// Embedding /models
|
|
82
|
+
spy.mockResolvedValueOnce(
|
|
83
|
+
new Response(JSON.stringify({ data: [{ id: "text-embedding-3-small" }] }), {
|
|
84
|
+
status: 200,
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mockEmbeddingOk() {
|
|
90
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
|
|
91
|
+
new Response(JSON.stringify({ data: [{ id: "text-embedding-3-small" }] }), {
|
|
92
|
+
status: 200,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function mockEmbeddingError(status: number) {
|
|
98
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
|
|
99
|
+
new Response(JSON.stringify({ error: { message: "Invalid API key" } }), {
|
|
100
|
+
status,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Setup ────────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
vi.restoreAllMocks();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
afterEach(() => {
|
|
112
|
+
vi.restoreAllMocks();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ── MeiliSearch connection status ────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
describe("getSettings — MeiliSearch connection", () => {
|
|
118
|
+
it('returns "connected" with document count when MeiliSearch is healthy', async () => {
|
|
119
|
+
mockMeiliConnected(250);
|
|
120
|
+
|
|
121
|
+
const result = await callGetSettings({
|
|
122
|
+
meilisearchHost: "http://meili.test:7700",
|
|
123
|
+
meilisearchApiKey: "master-key-abc123",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const meili = result.meilisearch as Record<string, unknown>;
|
|
127
|
+
expect(meili.status).toBe("connected");
|
|
128
|
+
expect(meili.documentCount).toBe(250);
|
|
129
|
+
expect(meili.configured).toBe(true);
|
|
130
|
+
expect(meili.error).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('returns "not_configured" when no MeiliSearch credentials', async () => {
|
|
134
|
+
const result = await callGetSettings({});
|
|
135
|
+
|
|
136
|
+
const meili = result.meilisearch as Record<string, unknown>;
|
|
137
|
+
expect(meili.status).toBe("not_configured");
|
|
138
|
+
expect(meili.configured).toBe(false);
|
|
139
|
+
expect(meili.documentCount).toBeUndefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('returns "not_configured" when only host is set', async () => {
|
|
143
|
+
const result = await callGetSettings({
|
|
144
|
+
meilisearchHost: "http://meili.test:7700",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const meili = result.meilisearch as Record<string, unknown>;
|
|
148
|
+
expect(meili.status).toBe("not_configured");
|
|
149
|
+
expect(meili.configured).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('returns "error" when MeiliSearch is unreachable', async () => {
|
|
153
|
+
mockMeiliUnreachable();
|
|
154
|
+
|
|
155
|
+
const result = await callGetSettings({
|
|
156
|
+
meilisearchHost: "http://meili.test:7700",
|
|
157
|
+
meilisearchApiKey: "master-key-abc123",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const meili = result.meilisearch as Record<string, unknown>;
|
|
161
|
+
expect(meili.status).toBe("error");
|
|
162
|
+
expect(meili.error).toBe("MeiliSearch instance is not reachable");
|
|
163
|
+
expect(meili.configured).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("masks the API key in the response", async () => {
|
|
167
|
+
mockMeiliConnected();
|
|
168
|
+
|
|
169
|
+
const result = await callGetSettings({
|
|
170
|
+
meilisearchHost: "http://meili.test:7700",
|
|
171
|
+
meilisearchApiKey: "master-key-abc123",
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const meili = result.meilisearch as Record<string, unknown>;
|
|
175
|
+
expect(typeof meili.apiKey).toBe("string");
|
|
176
|
+
expect(meili.apiKey).not.toContain("master-key-abc123");
|
|
177
|
+
expect((meili.apiKey as string).startsWith("mast")).toBe(true);
|
|
178
|
+
expect(meili.apiKey as string).toContain("•");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("returns configured host and index UID", async () => {
|
|
182
|
+
mockMeiliConnected();
|
|
183
|
+
|
|
184
|
+
const result = await callGetSettings({
|
|
185
|
+
meilisearchHost: "http://meili.test:7700",
|
|
186
|
+
meilisearchApiKey: "master-key-abc123",
|
|
187
|
+
meilisearchIndexUid: "products",
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const meili = result.meilisearch as Record<string, unknown>;
|
|
191
|
+
expect(meili.host).toBe("http://meili.test:7700");
|
|
192
|
+
expect(meili.indexUid).toBe("products");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("defaults index UID to 'search'", async () => {
|
|
196
|
+
mockMeiliConnected();
|
|
197
|
+
|
|
198
|
+
const result = await callGetSettings({
|
|
199
|
+
meilisearchHost: "http://meili.test:7700",
|
|
200
|
+
meilisearchApiKey: "key123",
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const meili = result.meilisearch as Record<string, unknown>;
|
|
204
|
+
expect(meili.indexUid).toBe("search");
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ── Embedding provider connection status ─────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
describe("getSettings — embedding connection", () => {
|
|
211
|
+
it('returns "connected" when OpenAI API key is valid', async () => {
|
|
212
|
+
mockEmbeddingOk();
|
|
213
|
+
|
|
214
|
+
const result = await callGetSettings({
|
|
215
|
+
openaiApiKey: "sk-test-abc123",
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
219
|
+
expect(embeddings.status).toBe("connected");
|
|
220
|
+
expect(embeddings.provider).toBe("openai");
|
|
221
|
+
expect(embeddings.configured).toBe(true);
|
|
222
|
+
expect(embeddings.error).toBeUndefined();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('returns "connected" when OpenRouter API key is valid', async () => {
|
|
226
|
+
mockEmbeddingOk();
|
|
227
|
+
|
|
228
|
+
const result = await callGetSettings({
|
|
229
|
+
openrouterApiKey: "sk-or-test-abc123",
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
233
|
+
expect(embeddings.status).toBe("connected");
|
|
234
|
+
expect(embeddings.provider).toBe("openrouter");
|
|
235
|
+
expect(embeddings.configured).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('returns "not_configured" when no embedding keys', async () => {
|
|
239
|
+
const result = await callGetSettings({});
|
|
240
|
+
|
|
241
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
242
|
+
expect(embeddings.status).toBe("not_configured");
|
|
243
|
+
expect(embeddings.provider).toBeNull();
|
|
244
|
+
expect(embeddings.configured).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('returns "error" when embedding API rejects the key', async () => {
|
|
248
|
+
mockEmbeddingError(401);
|
|
249
|
+
|
|
250
|
+
const result = await callGetSettings({
|
|
251
|
+
openaiApiKey: "sk-invalid-key",
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
255
|
+
expect(embeddings.status).toBe("error");
|
|
256
|
+
expect(embeddings.error).toContain("401");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('returns "error" when embedding API is unreachable', async () => {
|
|
260
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValueOnce(
|
|
261
|
+
new Error("Network failure"),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const result = await callGetSettings({
|
|
265
|
+
openaiApiKey: "sk-test-abc123",
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
269
|
+
expect(embeddings.status).toBe("error");
|
|
270
|
+
expect(embeddings.error).toContain("Network failure");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("returns configured embedding model", async () => {
|
|
274
|
+
mockEmbeddingOk();
|
|
275
|
+
|
|
276
|
+
const result = await callGetSettings({
|
|
277
|
+
openaiApiKey: "sk-test-abc123",
|
|
278
|
+
embeddingModel: "text-embedding-ada-002",
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
282
|
+
expect(embeddings.model).toBe("text-embedding-ada-002");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("defaults model to text-embedding-3-small", async () => {
|
|
286
|
+
const result = await callGetSettings({});
|
|
287
|
+
|
|
288
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
289
|
+
expect(embeddings.model).toBe("text-embedding-3-small");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("prefers OpenAI when both keys are set", async () => {
|
|
293
|
+
mockEmbeddingOk();
|
|
294
|
+
|
|
295
|
+
const result = await callGetSettings({
|
|
296
|
+
openaiApiKey: "sk-openai",
|
|
297
|
+
openrouterApiKey: "sk-or-key",
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
301
|
+
expect(embeddings.provider).toBe("openai");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ── Combined states ──────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
describe("getSettings — combined providers", () => {
|
|
308
|
+
it("returns both connected when both providers are healthy", async () => {
|
|
309
|
+
mockMeiliConnectedAndEmbeddingOk(100);
|
|
310
|
+
|
|
311
|
+
const result = await callGetSettings({
|
|
312
|
+
meilisearchHost: "http://meili.test:7700",
|
|
313
|
+
meilisearchApiKey: "master-key-abc123",
|
|
314
|
+
openaiApiKey: "sk-test-abc123",
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const meili = result.meilisearch as Record<string, unknown>;
|
|
318
|
+
const embeddings = result.embeddings as Record<string, unknown>;
|
|
319
|
+
expect(meili.status).toBe("connected");
|
|
320
|
+
expect(embeddings.status).toBe("connected");
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("always returns indexCount from the controller", async () => {
|
|
324
|
+
const result = await callGetSettings({}, { indexCount: 99 });
|
|
325
|
+
expect(result.indexCount).toBe(99);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("returns indexCount even when providers are not configured", async () => {
|
|
329
|
+
const result = await callGetSettings({}, { indexCount: 0 });
|
|
330
|
+
expect(result.indexCount).toBe(0);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// ── Embedding API URL routing ────────────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
describe("getSettings — embedding API URL routing", () => {
|
|
337
|
+
it("calls OpenAI models endpoint for openaiApiKey", async () => {
|
|
338
|
+
const spy = vi
|
|
339
|
+
.spyOn(globalThis, "fetch")
|
|
340
|
+
.mockResolvedValueOnce(
|
|
341
|
+
new Response(JSON.stringify({ data: [] }), { status: 200 }),
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
await callGetSettings({ openaiApiKey: "sk-test" });
|
|
345
|
+
|
|
346
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
347
|
+
const [url, opts] = spy.mock.calls[0];
|
|
348
|
+
expect(url).toBe("https://api.openai.com/v1/models");
|
|
349
|
+
expect((opts as RequestInit).headers).toEqual({
|
|
350
|
+
Authorization: "Bearer sk-test",
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("calls OpenRouter models endpoint for openrouterApiKey", async () => {
|
|
355
|
+
const spy = vi
|
|
356
|
+
.spyOn(globalThis, "fetch")
|
|
357
|
+
.mockResolvedValueOnce(
|
|
358
|
+
new Response(JSON.stringify({ data: [] }), { status: 200 }),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
await callGetSettings({ openrouterApiKey: "sk-or-test" });
|
|
362
|
+
|
|
363
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
364
|
+
const [url] = spy.mock.calls[0];
|
|
365
|
+
expect(url).toBe("https://openrouter.ai/api/v1/models");
|
|
366
|
+
});
|
|
367
|
+
});
|