Makelore 2.0 initial clean snapshot

This commit is contained in:
inman
2026-07-29 17:22:35 +08:00
commit b8ca3f8eea
694 changed files with 139782 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const mockFetchProviderSnapshot = vi.fn();
const mockHostApiFetch = vi.fn();
vi.mock('@/lib/provider-accounts', () => ({
fetchProviderSnapshot: (...args: unknown[]) => mockFetchProviderSnapshot(...args),
isHostApiRouteMissing: (value: unknown): boolean => {
if (!value || typeof value !== 'object') return false;
const record = value as Record<string, unknown>;
if (record.success !== false) return false;
const error = record.error;
return typeof error === 'string' && /no\s+route\s+for/i.test(error);
},
}));
vi.mock('@/lib/host-api', () => ({
hostApiFetch: (...args: unknown[]) => mockHostApiFetch(...args),
}));
import { useProviderStore } from '@/stores/providers';
describe('useProviderStore validateAccountApiKey()', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('trims API keys before sending provider validation requests', async () => {
mockHostApiFetch.mockResolvedValueOnce({ valid: true });
const result = await useProviderStore.getState().validateAccountApiKey('custom', ' sk-lm-test \n', {
baseUrl: 'http://127.0.0.1:1234/v1',
apiProtocol: 'openai-completions',
});
expect(result).toEqual({ valid: true });
expect(mockHostApiFetch).toHaveBeenCalledWith('/api/provider-accounts/validate', {
method: 'POST',
body: JSON.stringify({
accountId: 'custom',
vendorId: 'custom',
providerId: 'custom',
apiKey: 'sk-lm-test',
options: {
baseUrl: 'http://127.0.0.1:1234/v1',
apiProtocol: 'openai-completions',
},
}),
});
});
it('falls back to legacy /api/providers/validate when the new route throws a 404', async () => {
// The browser-fallback path of `hostApiFetch` (used in non-Electron
// environments and surfaced by some IPC error normalisations) throws
// on non-2xx HTTP. Make sure the renderer treats those as missing-route.
mockHostApiFetch.mockRejectedValueOnce(new Error('404 Not Found'));
mockHostApiFetch.mockResolvedValueOnce({ valid: true });
const result = await useProviderStore.getState().validateAccountApiKey('custom', 'sk-lm-test', {
baseUrl: 'http://127.0.0.1:1234/v1',
});
expect(result).toEqual({ valid: true });
expect(mockHostApiFetch).toHaveBeenNthCalledWith(1, '/api/provider-accounts/validate', expect.any(Object));
expect(mockHostApiFetch).toHaveBeenNthCalledWith(2, '/api/providers/validate', {
method: 'POST',
body: JSON.stringify({
providerId: 'custom',
apiKey: 'sk-lm-test',
options: { baseUrl: 'http://127.0.0.1:1234/v1' },
}),
});
});
it('falls back to legacy /api/providers/validate when the new route returns a route-not-found body', async () => {
// The Electron IPC proxy never throws on HTTP 404 — it surfaces the
// JSON body. Older Host API builds without the new validate route
// therefore return `{ success: false, error: "No route for ..." }`.
// The renderer must detect that body shape via `isHostApiRouteMissing`
// and replay the request against the legacy route. This is the path
// that actually runs in production today.
mockHostApiFetch.mockResolvedValueOnce({
success: false,
error: 'No route for POST /api/provider-accounts/validate',
});
mockHostApiFetch.mockResolvedValueOnce({ valid: true });
const result = await useProviderStore.getState().validateAccountApiKey('custom', 'sk-lm-test', {
baseUrl: 'http://127.0.0.1:1234/v1',
});
expect(result).toEqual({ valid: true });
expect(mockHostApiFetch).toHaveBeenCalledTimes(2);
expect(mockHostApiFetch).toHaveBeenNthCalledWith(1, '/api/provider-accounts/validate', expect.any(Object));
expect(mockHostApiFetch).toHaveBeenNthCalledWith(2, '/api/providers/validate', {
method: 'POST',
body: JSON.stringify({
providerId: 'custom',
apiKey: 'sk-lm-test',
options: { baseUrl: 'http://127.0.0.1:1234/v1' },
}),
});
});
it('does NOT fall back when the new route returns a real validation failure', async () => {
// `{ valid: false, error: ... }` is a legitimate validation result —
// it must NOT be confused with a missing-route body (whose discriminator
// is `success: false`). Otherwise we would silently retry against the
// legacy route and double-charge the upstream provider.
mockHostApiFetch.mockResolvedValueOnce({ valid: false, error: 'API key is rejected' });
const result = await useProviderStore.getState().validateAccountApiKey('custom', 'sk-lm-test');
expect(result).toEqual({ valid: false, error: 'API key is rejected' });
expect(mockHostApiFetch).toHaveBeenCalledTimes(1);
expect(mockHostApiFetch).toHaveBeenNthCalledWith(1, '/api/provider-accounts/validate', expect.any(Object));
});
});
describe('useProviderStore getAccountApiKey()', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('reads the key from the new account-namespaced endpoint by default', async () => {
mockHostApiFetch.mockResolvedValueOnce({ apiKey: 'sk-stored-key' });
const apiKey = await useProviderStore.getState().getAccountApiKey('openai-account-1');
expect(apiKey).toBe('sk-stored-key');
expect(mockHostApiFetch).toHaveBeenCalledTimes(1);
expect(mockHostApiFetch).toHaveBeenCalledWith('/api/provider-accounts/openai-account-1/api-key');
});
it('falls back to legacy /api/providers/:id/api-key when the new route throws a 404', async () => {
// Browser-fallback path: thrown 404.
mockHostApiFetch.mockRejectedValueOnce(new Error('404 Not Found'));
mockHostApiFetch.mockResolvedValueOnce({ apiKey: 'sk-legacy-key' });
const apiKey = await useProviderStore.getState().getAccountApiKey('openai-account-1');
expect(apiKey).toBe('sk-legacy-key');
expect(mockHostApiFetch).toHaveBeenCalledTimes(2);
expect(mockHostApiFetch).toHaveBeenNthCalledWith(1, '/api/provider-accounts/openai-account-1/api-key');
expect(mockHostApiFetch).toHaveBeenNthCalledWith(2, '/api/providers/openai-account-1/api-key');
});
it('falls back to legacy /api/providers/:id/api-key when the new route returns a route-not-found body', async () => {
// Electron IPC proxy path: 404 surfaces as a "No route" body.
mockHostApiFetch.mockResolvedValueOnce({
success: false,
error: 'No route for GET /api/provider-accounts/openai-account-1/api-key',
});
mockHostApiFetch.mockResolvedValueOnce({ apiKey: 'sk-legacy-key' });
const apiKey = await useProviderStore.getState().getAccountApiKey('openai-account-1');
expect(apiKey).toBe('sk-legacy-key');
expect(mockHostApiFetch).toHaveBeenCalledTimes(2);
expect(mockHostApiFetch).toHaveBeenNthCalledWith(1, '/api/provider-accounts/openai-account-1/api-key');
expect(mockHostApiFetch).toHaveBeenNthCalledWith(2, '/api/providers/openai-account-1/api-key');
});
it('returns null when the legacy fallback also reports no key', async () => {
mockHostApiFetch.mockResolvedValueOnce({
success: false,
error: 'No route for GET /api/provider-accounts/missing-account/api-key',
});
mockHostApiFetch.mockResolvedValueOnce({ apiKey: null });
const apiKey = await useProviderStore.getState().getAccountApiKey('missing-account');
expect(apiKey).toBeNull();
expect(mockHostApiFetch).toHaveBeenCalledTimes(2);
});
it('encodes the account id so colons and slashes survive the request', async () => {
mockHostApiFetch.mockResolvedValueOnce({ apiKey: 'sk-stored-key' });
await useProviderStore.getState().getAccountApiKey('vendor:weird/id');
expect(mockHostApiFetch).toHaveBeenCalledWith('/api/provider-accounts/vendor%3Aweird%2Fid/api-key');
});
});
describe('useProviderStore importUserModelConfig()', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('imports current user model config through the Host API and refreshes the provider snapshot', async () => {
mockHostApiFetch.mockResolvedValueOnce({
success: true,
account: {
id: 'niancode-user-models',
vendorId: 'custom',
label: 'Makelore Models',
authMode: 'api_key',
model: 'gpt-4.1-mini',
fallbackModels: ['gpt-4o-mini'],
enabled: true,
isDefault: true,
createdAt: '2026-06-19T00:00:00.000Z',
updatedAt: '2026-06-19T00:00:00.000Z',
},
importedModels: ['gpt-4.1-mini', 'gpt-4o-mini'],
});
mockFetchProviderSnapshot.mockResolvedValueOnce({
statuses: [],
accounts: [],
vendors: [],
defaultAccountId: 'niancode-user-models',
});
const result = await useProviderStore.getState().importUserModelConfig('access-token');
expect(result.importedModels).toEqual(['gpt-4.1-mini', 'gpt-4o-mini']);
expect(mockHostApiFetch).toHaveBeenCalledWith('/api/provider-accounts/import-user-model-config', {
method: 'POST',
body: JSON.stringify({ accessToken: 'access-token' }),
});
expect(mockFetchProviderSnapshot).toHaveBeenCalledOnce();
});
});