open-design/apps/web/tests/providers/registry.test.ts
marco 5dd70b5016
Some checks failed
ci / Validate workspace (push) Successful in 12m32s
landing-page-ci / Validate landing page (push) Successful in 9m41s
landing-page-deploy / Deploy landing page (push) Failing after 5m23s
github-metrics / Generate repository metrics SVG (push) Failing after 2m3s
refresh-contributors-wall / Refresh contributors wall cache bust (push) Failing after 11s
Initial import: open-design source for helix-mind.ai distribution
This repository contains the open-design daemon CLI source code, built
and packaged at https://helix-mind.ai/cli/open-design/latest.tgz for use
by the HelixMind /design slash command.

Licenses: Apache-2.0 (root) + MIT (skills/*)
2026-05-06 20:50:24 +02:00

191 lines
6.1 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import {
fetchAppVersionInfo,
fetchConnectorDiscovery,
fetchProjectFileText,
uploadProjectFiles,
} from '../../src/providers/registry';
describe('fetchAppVersionInfo', () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it('returns version info from the daemon response', async () => {
vi.stubGlobal(
'fetch',
vi.fn(async () => new Response(JSON.stringify({
version: { version: '1.2.3', channel: 'beta', packaged: true, platform: 'darwin', arch: 'arm64' },
}), { status: 200 })),
);
await expect(fetchAppVersionInfo()).resolves.toEqual({
version: '1.2.3',
channel: 'beta',
packaged: true,
platform: 'darwin',
arch: 'arm64',
});
});
it('returns null when version info is unavailable or malformed', async () => {
vi.stubGlobal(
'fetch',
vi.fn(async () => new Response(JSON.stringify({ version: { version: '1.2.3' } }), { status: 200 })),
);
await expect(fetchAppVersionInfo()).resolves.toBeNull();
});
});
describe('fetchProjectFileText', () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it('can bypass caches when fetching source text', async () => {
const fetchMock = vi.fn(async () => new Response('<svg />', { status: 200 }));
vi.stubGlobal('fetch', fetchMock);
await expect(
fetchProjectFileText('project-1', 'diagram.svg', {
cache: 'no-store',
cacheBustKey: '1710000000-2',
}),
).resolves.toBe('<svg />');
expect(fetchMock).toHaveBeenCalledWith(
'/api/projects/project-1/raw/diagram.svg?cacheBust=1710000000-2',
{ cache: 'no-store' },
);
});
it('logs HTTP failure context before returning null', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
vi.stubGlobal('fetch', vi.fn(async () => new Response('missing', { status: 404, statusText: 'Not Found' })));
await expect(fetchProjectFileText('project-1', 'missing.svg')).resolves.toBeNull();
expect(warn).toHaveBeenCalledWith(
'[fetchProjectFileText] failed:',
expect.objectContaining({
name: 'missing.svg',
projectId: 'project-1',
status: 404,
statusText: 'Not Found',
url: '/api/projects/project-1/raw/missing.svg',
}),
);
});
it('logs thrown fetch errors before returning null', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const error = new Error('network down');
vi.stubGlobal('fetch', vi.fn(async () => {
throw error;
}));
await expect(fetchProjectFileText('project-1', 'diagram.svg')).resolves.toBeNull();
expect(warn).toHaveBeenCalledWith(
'[fetchProjectFileText] failed:',
expect.objectContaining({
error,
name: 'diagram.svg',
projectId: 'project-1',
url: '/api/projects/project-1/raw/diagram.svg',
}),
);
});
});
describe('fetchConnectorDiscovery', () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it('caches connector discovery after a successful fetch', async () => {
const fetchMock = vi.fn(async () => new Response(JSON.stringify({
connectors: [{ id: 'github', name: 'GitHub', tools: [{ name: 'issues' }] }],
}), { status: 200 }));
vi.stubGlobal('fetch', fetchMock);
await expect(fetchConnectorDiscovery({ refresh: true })).resolves.toEqual([
{ id: 'github', name: 'GitHub', tools: [{ name: 'issues' }] },
]);
await expect(fetchConnectorDiscovery()).resolves.toEqual([
{ id: 'github', name: 'GitHub', tools: [{ name: 'issues' }] },
]);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock).toHaveBeenCalledWith('/api/connectors/discovery?refresh=true');
});
});
describe('uploadProjectFiles', () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it('treats every response entry as a success regardless of originalName drift', async () => {
// Simulates an encoding edge case: the browser File.name carries a
// composed CJK name (NFC) but multer round-trips it through latin1 and
// returns a slightly different decoded form. The old name-equality
// matching marked these as failed even though the server stored them.
const composed = '测试.pdf';
const decomposed = '测试.pdf'; // pretend the server returned a normalized variant
const file = new File(['hello'], composed, { type: 'application/pdf' });
vi.stubGlobal(
'fetch',
vi.fn(async () => new Response(JSON.stringify({
files: [
{
name: 'mxk7-test.pdf',
path: 'mxk7-test.pdf',
size: 5,
originalName: decomposed,
},
],
}), { status: 200 })),
);
const result = await uploadProjectFiles('project-1', [file]);
expect(result.failed).toEqual([]);
expect(result.uploaded).toHaveLength(1);
expect(result.uploaded[0]).toMatchObject({
path: 'mxk7-test.pdf',
name: decomposed,
size: 5,
});
});
it('marks the unmatched tail as failed when the server drops files mid-flight', async () => {
const a = new File(['a'], 'a.txt', { type: 'text/plain' });
const b = new File(['b'], 'b.txt', { type: 'text/plain' });
const c = new File(['c'], 'c.txt', { type: 'text/plain' });
vi.stubGlobal(
'fetch',
vi.fn(async () => new Response(JSON.stringify({
files: [
{ name: 't1-a.txt', path: 't1-a.txt', size: 1, originalName: 'a.txt' },
{ name: 't2-b.txt', path: 't2-b.txt', size: 1, originalName: 'b.txt' },
],
}), { status: 200 })),
);
const result = await uploadProjectFiles('project-1', [a, b, c]);
expect(result.uploaded).toHaveLength(2);
expect(result.failed).toHaveLength(1);
expect(result.failed[0]).toMatchObject({ name: 'c.txt' });
});
});