mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
feat(memoro/audio-server): add vitest setup and 25 API + config tests
- Health endpoint, service key auth, 404 handler tests - Transcribe and append endpoint validation tests - Azure speech service config tests (getAvailableSpeechServices, pickRandomService) - Export app for testability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cb0e67ddd2
commit
c582f164ba
7 changed files with 562 additions and 52 deletions
|
|
@ -7,34 +7,36 @@ const app = new Hono();
|
|||
// ─── Service key middleware ───────────────────────────────────────────────────
|
||||
|
||||
function serviceKeyMiddleware(): MiddlewareHandler {
|
||||
return async (c, next) => {
|
||||
const expectedKey = process.env.SERVICE_KEY;
|
||||
return async (c, next) => {
|
||||
const expectedKey = process.env.SERVICE_KEY;
|
||||
|
||||
if (!expectedKey) {
|
||||
console.error('[Auth] SERVICE_KEY env var is not configured');
|
||||
return c.json({ error: 'Server misconfiguration' }, 500);
|
||||
}
|
||||
if (!expectedKey) {
|
||||
console.error('[Auth] SERVICE_KEY env var is not configured');
|
||||
return c.json({ error: 'Server misconfiguration' }, 500);
|
||||
}
|
||||
|
||||
const providedKey = c.req.header('X-Service-Key');
|
||||
const providedKey = c.req.header('X-Service-Key');
|
||||
|
||||
if (!providedKey || providedKey !== expectedKey) {
|
||||
console.warn(`[Auth] Unauthorized request to ${c.req.path} — invalid or missing X-Service-Key`);
|
||||
return c.json({ error: 'Unauthorized' }, 401);
|
||||
}
|
||||
if (!providedKey || providedKey !== expectedKey) {
|
||||
console.warn(
|
||||
`[Auth] Unauthorized request to ${c.req.path} — invalid or missing X-Service-Key`
|
||||
);
|
||||
return c.json({ error: 'Unauthorized' }, 401);
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
await next();
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Health check (no auth) ───────────────────────────────────────────────────
|
||||
|
||||
app.get('/health', (c) => {
|
||||
return c.json({
|
||||
status: 'ok',
|
||||
service: 'memoro-audio-server',
|
||||
version: '1.0.0',
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
return c.json({
|
||||
status: 'ok',
|
||||
service: 'memoro-audio-server',
|
||||
version: '1.0.0',
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Protected routes ─────────────────────────────────────────────────────────
|
||||
|
|
@ -47,14 +49,14 @@ app.route('/api/v1/transcribe', transcribeRoutes);
|
|||
// ─── 404 handler ─────────────────────────────────────────────────────────────
|
||||
|
||||
app.notFound((c) => {
|
||||
return c.json({ error: `Not found: ${c.req.method} ${c.req.path}` }, 404);
|
||||
return c.json({ error: `Not found: ${c.req.method} ${c.req.path}` }, 404);
|
||||
});
|
||||
|
||||
// ─── Error handler ────────────────────────────────────────────────────────────
|
||||
|
||||
app.onError((err, c) => {
|
||||
console.error(`[Error] Unhandled error on ${c.req.method} ${c.req.path}:`, err);
|
||||
return c.json({ error: 'Internal server error' }, 500);
|
||||
console.error(`[Error] Unhandled error on ${c.req.method} ${c.req.path}:`, err);
|
||||
return c.json({ error: 'Internal server error' }, 500);
|
||||
});
|
||||
|
||||
// ─── Start ────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -63,7 +65,8 @@ const port = parseInt(process.env.PORT ?? '3016', 10);
|
|||
|
||||
console.log(`[Server] Memoro Audio Server starting on port ${port}`);
|
||||
|
||||
export { app };
|
||||
export default {
|
||||
port,
|
||||
fetch: app.fetch,
|
||||
port,
|
||||
fetch: app.fetch,
|
||||
};
|
||||
|
|
|
|||
109
apps/memoro/apps/audio-server/src/lib/azure.test.ts
Normal file
109
apps/memoro/apps/audio-server/src/lib/azure.test.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* Tests for Azure Speech config utilities.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { getAvailableSpeechServices, pickRandomService, BATCH_ENDPOINT_BASE } from './azure';
|
||||
import type { SpeechServiceConfig } from './azure';
|
||||
|
||||
describe('BATCH_ENDPOINT_BASE', () => {
|
||||
it('points to swedencentral', () => {
|
||||
expect(BATCH_ENDPOINT_BASE).toBe(
|
||||
'https://swedencentral.api.cognitive.microsoft.com/speechtotext'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvailableSpeechServices', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
// Clear all Azure keys
|
||||
delete process.env.AZURE_SPEECH_KEY;
|
||||
delete process.env.AZURE_SPEECH_KEY_1;
|
||||
delete process.env.AZURE_SPEECH_KEY_2;
|
||||
delete process.env.AZURE_SPEECH_KEY_3;
|
||||
delete process.env.AZURE_SPEECH_KEY_4;
|
||||
delete process.env.AZURE_SPEECH_REGION;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it('throws if no keys configured', () => {
|
||||
expect(() => getAvailableSpeechServices()).toThrow('No Azure Speech credentials configured');
|
||||
});
|
||||
|
||||
it('uses single AZURE_SPEECH_KEY as fallback', () => {
|
||||
process.env.AZURE_SPEECH_KEY = 'single-key';
|
||||
|
||||
const services = getAvailableSpeechServices();
|
||||
expect(services).toHaveLength(1);
|
||||
expect(services[0].key).toBe('single-key');
|
||||
expect(services[0].name).toBe('azure-speech-default');
|
||||
});
|
||||
|
||||
it('uses numbered keys when available', () => {
|
||||
process.env.AZURE_SPEECH_KEY_1 = 'key-1';
|
||||
process.env.AZURE_SPEECH_KEY_2 = 'key-2';
|
||||
|
||||
const services = getAvailableSpeechServices();
|
||||
expect(services).toHaveLength(2);
|
||||
expect(services[0].name).toBe('azure-speech-1');
|
||||
expect(services[1].name).toBe('azure-speech-2');
|
||||
});
|
||||
|
||||
it('prefers numbered keys over single key', () => {
|
||||
process.env.AZURE_SPEECH_KEY = 'fallback-key';
|
||||
process.env.AZURE_SPEECH_KEY_1 = 'key-1';
|
||||
|
||||
const services = getAvailableSpeechServices();
|
||||
expect(services).toHaveLength(1);
|
||||
expect(services[0].key).toBe('key-1');
|
||||
});
|
||||
|
||||
it('uses custom region', () => {
|
||||
process.env.AZURE_SPEECH_KEY = 'key';
|
||||
process.env.AZURE_SPEECH_REGION = 'westeurope';
|
||||
|
||||
const services = getAvailableSpeechServices();
|
||||
expect(services[0].region).toBe('westeurope');
|
||||
expect(services[0].endpoint).toContain('westeurope');
|
||||
});
|
||||
|
||||
it('defaults to swedencentral region', () => {
|
||||
process.env.AZURE_SPEECH_KEY = 'key';
|
||||
|
||||
const services = getAvailableSpeechServices();
|
||||
expect(services[0].region).toBe('swedencentral');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pickRandomService', () => {
|
||||
it('throws for empty array', () => {
|
||||
expect(() => pickRandomService([])).toThrow('No speech services available');
|
||||
});
|
||||
|
||||
it('returns the only service for single-element array', () => {
|
||||
const service: SpeechServiceConfig = {
|
||||
key: 'key-1',
|
||||
endpoint: 'https://example.com',
|
||||
region: 'swedencentral',
|
||||
name: 'azure-speech-1',
|
||||
};
|
||||
|
||||
expect(pickRandomService([service])).toBe(service);
|
||||
});
|
||||
|
||||
it('returns a service from the array', () => {
|
||||
const services: SpeechServiceConfig[] = [
|
||||
{ key: 'key-1', endpoint: 'https://example.com', region: 'swedencentral', name: 'svc-1' },
|
||||
{ key: 'key-2', endpoint: 'https://example.com', region: 'swedencentral', name: 'svc-2' },
|
||||
];
|
||||
|
||||
const result = pickRandomService(services);
|
||||
expect(services).toContainEqual(result);
|
||||
});
|
||||
});
|
||||
224
apps/memoro/apps/audio-server/src/routes/transcribe.test.ts
Normal file
224
apps/memoro/apps/audio-server/src/routes/transcribe.test.ts
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/**
|
||||
* Tests for audio-server transcription routes and health check.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { app } from '../index';
|
||||
|
||||
// ── Mocks ────────────────────────────────────────────────────────────────────
|
||||
|
||||
vi.mock('../lib/supabase.ts', () => ({
|
||||
downloadAudioFromStorage: vi.fn().mockResolvedValue(Buffer.from('fake-audio')),
|
||||
}));
|
||||
|
||||
vi.mock('../services/transcription.ts', () => {
|
||||
class MockTranscriptionService {
|
||||
transcribeWithFallback = vi.fn().mockResolvedValue({ transcript: 'Hello world' });
|
||||
}
|
||||
return { TranscriptionService: MockTranscriptionService };
|
||||
});
|
||||
|
||||
const SERVICE_KEY = 'test-service-key';
|
||||
|
||||
function post(path: string, body: unknown, headers?: Record<string, string>) {
|
||||
return app.request(path, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
// ── Health ───────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('GET /health', () => {
|
||||
it('returns 200 with service info', async () => {
|
||||
const res = await app.request('/health');
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.status).toBe('ok');
|
||||
expect(data.service).toBe('memoro-audio-server');
|
||||
expect(data.version).toBe('1.0.0');
|
||||
expect(data.timestamp).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Auth ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('Service key authentication', () => {
|
||||
it('rejects requests without X-Service-Key', async () => {
|
||||
const res = await post('/api/v1/transcribe', {
|
||||
audioPath: 'test.m4a',
|
||||
memoId: 'memo-1',
|
||||
userId: 'user-1',
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.error).toBe('Unauthorized');
|
||||
});
|
||||
|
||||
it('rejects requests with wrong service key', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe',
|
||||
{ audioPath: 'test.m4a', memoId: 'memo-1', userId: 'user-1' },
|
||||
{ 'X-Service-Key': 'wrong-key' }
|
||||
);
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('accepts requests with valid service key', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe',
|
||||
{ audioPath: 'test.m4a', memoId: 'memo-1', userId: 'user-1' },
|
||||
{ 'X-Service-Key': SERVICE_KEY }
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
// ── POST /api/v1/transcribe ──────────────────────────────────────────────────
|
||||
|
||||
describe('POST /api/v1/transcribe', () => {
|
||||
it('starts transcription with valid input', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe',
|
||||
{ audioPath: 'user/recording.m4a', memoId: 'memo-1', userId: 'user-1' },
|
||||
{ 'X-Service-Key': SERVICE_KEY }
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.success).toBe(true);
|
||||
expect(data.memoId).toBe('memo-1');
|
||||
expect(data.message).toBe('Transcription started');
|
||||
});
|
||||
|
||||
it('accepts optional fields', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe',
|
||||
{
|
||||
audioPath: 'user/recording.m4a',
|
||||
memoId: 'memo-1',
|
||||
userId: 'user-1',
|
||||
spaceId: 'space-1',
|
||||
recordingLanguages: ['de-DE', 'en-US'],
|
||||
enableDiarization: true,
|
||||
recordingIndex: 0,
|
||||
},
|
||||
{ 'X-Service-Key': SERVICE_KEY }
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it('rejects missing audioPath', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe',
|
||||
{ memoId: 'memo-1', userId: 'user-1' },
|
||||
{ 'X-Service-Key': SERVICE_KEY }
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.error).toContain('Missing required fields');
|
||||
});
|
||||
|
||||
it('rejects missing memoId', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe',
|
||||
{ audioPath: 'test.m4a', userId: 'user-1' },
|
||||
{ 'X-Service-Key': SERVICE_KEY }
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('rejects missing userId', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe',
|
||||
{ audioPath: 'test.m4a', memoId: 'memo-1' },
|
||||
{ 'X-Service-Key': SERVICE_KEY }
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('rejects invalid JSON', async () => {
|
||||
const res = await app.request('/api/v1/transcribe', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Service-Key': SERVICE_KEY,
|
||||
},
|
||||
body: 'not json',
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.error).toBe('Invalid JSON body');
|
||||
});
|
||||
});
|
||||
|
||||
// ── POST /api/v1/transcribe/append ───────────────────────────────────────────
|
||||
|
||||
describe('POST /api/v1/transcribe/append', () => {
|
||||
it('starts append transcription with valid input', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe/append',
|
||||
{
|
||||
audioPath: 'user/append.m4a',
|
||||
memoId: 'memo-1',
|
||||
userId: 'user-1',
|
||||
recordingIndex: 1,
|
||||
},
|
||||
{ 'X-Service-Key': SERVICE_KEY }
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.success).toBe(true);
|
||||
expect(data.memoId).toBe('memo-1');
|
||||
expect(data.message).toBe('Append transcription started');
|
||||
});
|
||||
|
||||
it('rejects missing required fields', async () => {
|
||||
const res = await post(
|
||||
'/api/v1/transcribe/append',
|
||||
{ audioPath: 'test.m4a' },
|
||||
{ 'X-Service-Key': SERVICE_KEY }
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('rejects invalid JSON', async () => {
|
||||
const res = await app.request('/api/v1/transcribe/append', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Service-Key': SERVICE_KEY,
|
||||
},
|
||||
body: '{invalid',
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
// ── 404 ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('404 handler', () => {
|
||||
it('returns 404 for unknown routes', async () => {
|
||||
const res = await app.request('/nonexistent');
|
||||
expect(res.status).toBe(404);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.error).toContain('Not found');
|
||||
});
|
||||
|
||||
it('returns 404 for unknown API routes', async () => {
|
||||
const res = await app.request('/api/v1/unknown', {
|
||||
headers: { 'X-Service-Key': SERVICE_KEY },
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
3
apps/memoro/apps/audio-server/src/test-setup.ts
Normal file
3
apps/memoro/apps/audio-server/src/test-setup.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
process.env.SERVICE_KEY = 'test-service-key';
|
||||
process.env.MEMORO_SERVER_URL = 'http://localhost:3015';
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ESNext"],
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"outDir": "dist",
|
||||
"types": ["bun-types", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ESNext"],
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"outDir": "dist",
|
||||
"types": ["bun-types", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "vitest.config.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
|||
11
apps/memoro/apps/audio-server/vitest.config.ts
Normal file
11
apps/memoro/apps/audio-server/vitest.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
include: ['src/**/*.test.ts'],
|
||||
setupFiles: ['./src/test-setup.ts'],
|
||||
clearMocks: true,
|
||||
},
|
||||
});
|
||||
182
pnpm-lock.yaml
generated
182
pnpm-lock.yaml
generated
|
|
@ -2897,6 +2897,9 @@ importers:
|
|||
typescript:
|
||||
specifier: ^5.5.0
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@20.19.25)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
apps/memoro/apps/landing:
|
||||
dependencies:
|
||||
|
|
@ -3196,9 +3199,6 @@ importers:
|
|||
|
||||
apps/memoro/apps/server:
|
||||
dependencies:
|
||||
'@manacore/notify-client':
|
||||
specifier: workspace:^
|
||||
version: link:../../../../packages/notify-client
|
||||
'@manacore/shared-hono':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-hono
|
||||
|
|
@ -14453,6 +14453,9 @@ packages:
|
|||
'@vitest/expect@4.1.1':
|
||||
resolution: {integrity: sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==}
|
||||
|
||||
'@vitest/expect@4.1.2':
|
||||
resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==}
|
||||
|
||||
'@vitest/mocker@2.1.9':
|
||||
resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
|
||||
peerDependencies:
|
||||
|
|
@ -14519,6 +14522,17 @@ packages:
|
|||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/mocker@4.1.2':
|
||||
resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@2.1.9':
|
||||
resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
|
||||
|
||||
|
|
@ -14537,6 +14551,9 @@ packages:
|
|||
'@vitest/pretty-format@4.1.1':
|
||||
resolution: {integrity: sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==}
|
||||
|
||||
'@vitest/pretty-format@4.1.2':
|
||||
resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==}
|
||||
|
||||
'@vitest/runner@1.6.1':
|
||||
resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==}
|
||||
|
||||
|
|
@ -14558,6 +14575,9 @@ packages:
|
|||
'@vitest/runner@4.1.1':
|
||||
resolution: {integrity: sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==}
|
||||
|
||||
'@vitest/runner@4.1.2':
|
||||
resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==}
|
||||
|
||||
'@vitest/snapshot@1.6.1':
|
||||
resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
|
||||
|
||||
|
|
@ -14579,6 +14599,9 @@ packages:
|
|||
'@vitest/snapshot@4.1.1':
|
||||
resolution: {integrity: sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==}
|
||||
|
||||
'@vitest/snapshot@4.1.2':
|
||||
resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==}
|
||||
|
||||
'@vitest/spy@1.6.1':
|
||||
resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==}
|
||||
|
||||
|
|
@ -14600,6 +14623,9 @@ packages:
|
|||
'@vitest/spy@4.1.1':
|
||||
resolution: {integrity: sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==}
|
||||
|
||||
'@vitest/spy@4.1.2':
|
||||
resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==}
|
||||
|
||||
'@vitest/ui@3.2.4':
|
||||
resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==}
|
||||
peerDependencies:
|
||||
|
|
@ -14631,6 +14657,9 @@ packages:
|
|||
'@vitest/utils@4.1.1':
|
||||
resolution: {integrity: sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==}
|
||||
|
||||
'@vitest/utils@4.1.2':
|
||||
resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==}
|
||||
|
||||
'@volar/kit@2.4.26':
|
||||
resolution: {integrity: sha512-shgNg7PbV8SIxxQLOQh5zMr8KV0JvdG9If0MwJb5L1HMrBU91jBxR0ANi2OJPMMme6/l1vIYm4hCaO6W2JaEcQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -23801,6 +23830,10 @@ packages:
|
|||
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyrainbow@3.1.0:
|
||||
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyspy@2.2.1:
|
||||
resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
|
@ -24905,6 +24938,41 @@ packages:
|
|||
jsdom:
|
||||
optional: true
|
||||
|
||||
vitest@4.1.2:
|
||||
resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==}
|
||||
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 4.1.2
|
||||
'@vitest/browser-preview': 4.1.2
|
||||
'@vitest/browser-webdriverio': 4.1.2
|
||||
'@vitest/ui': 4.1.2
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser-playwright':
|
||||
optional: true
|
||||
'@vitest/browser-preview':
|
||||
optional: true
|
||||
'@vitest/browser-webdriverio':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vlq@1.0.1:
|
||||
resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==}
|
||||
|
||||
|
|
@ -37170,7 +37238,7 @@ snapshots:
|
|||
magic-string: 0.30.21
|
||||
sirv: 3.0.2
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
ws: 8.18.3
|
||||
optionalDependencies:
|
||||
playwright: 1.57.0
|
||||
|
|
@ -37201,11 +37269,11 @@ snapshots:
|
|||
- vite
|
||||
optional: true
|
||||
|
||||
'@vitest/browser@3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)':
|
||||
'@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
|
||||
'@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@vitest/utils': 3.2.4
|
||||
magic-string: 0.30.21
|
||||
sirv: 3.0.2
|
||||
|
|
@ -37221,6 +37289,26 @@ snapshots:
|
|||
- vite
|
||||
optional: true
|
||||
|
||||
'@vitest/browser@3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
|
||||
'@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@vitest/utils': 3.2.4
|
||||
magic-string: 0.30.21
|
||||
sirv: 3.0.2
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
ws: 8.18.3
|
||||
optionalDependencies:
|
||||
playwright: 1.57.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- msw
|
||||
- utf-8-validate
|
||||
- vite
|
||||
optional: true
|
||||
|
||||
'@vitest/coverage-v8@4.0.14(vitest@4.0.14)':
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
|
|
@ -37295,6 +37383,15 @@ snapshots:
|
|||
chai: 6.2.2
|
||||
tinyrainbow: 3.0.3
|
||||
|
||||
'@vitest/expect@4.1.2':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/spy': 4.1.2
|
||||
'@vitest/utils': 4.1.2
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.25)(lightningcss@1.30.2)(terser@5.44.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.9
|
||||
|
|
@ -37335,13 +37432,13 @@ snapshots:
|
|||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
'@vitest/mocker@3.2.4(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vite: 7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
optional: true
|
||||
|
||||
'@vitest/mocker@4.0.14(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
|
|
@ -37392,6 +37489,14 @@ snapshots:
|
|||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
'@vitest/mocker@4.1.2(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.2
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
'@vitest/pretty-format@2.1.9':
|
||||
dependencies:
|
||||
tinyrainbow: 1.2.0
|
||||
|
|
@ -37416,6 +37521,10 @@ snapshots:
|
|||
dependencies:
|
||||
tinyrainbow: 3.0.3
|
||||
|
||||
'@vitest/pretty-format@4.1.2':
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/runner@1.6.1':
|
||||
dependencies:
|
||||
'@vitest/utils': 1.6.1
|
||||
|
|
@ -37453,6 +37562,11 @@ snapshots:
|
|||
'@vitest/utils': 4.1.1
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/runner@4.1.2':
|
||||
dependencies:
|
||||
'@vitest/utils': 4.1.2
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@1.6.1':
|
||||
dependencies:
|
||||
magic-string: 0.30.21
|
||||
|
|
@ -37497,6 +37611,13 @@ snapshots:
|
|||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@4.1.2':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.2
|
||||
'@vitest/utils': 4.1.2
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/spy@1.6.1':
|
||||
dependencies:
|
||||
tinyspy: 2.2.1
|
||||
|
|
@ -37517,6 +37638,8 @@ snapshots:
|
|||
|
||||
'@vitest/spy@4.1.1': {}
|
||||
|
||||
'@vitest/spy@4.1.2': {}
|
||||
|
||||
'@vitest/ui@3.2.4(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@vitest/utils': 3.2.4
|
||||
|
|
@ -37526,7 +37649,7 @@ snapshots:
|
|||
sirv: 3.0.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
optional: true
|
||||
|
||||
'@vitest/ui@4.0.14(vitest@4.0.14)':
|
||||
|
|
@ -37581,6 +37704,12 @@ snapshots:
|
|||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.0.3
|
||||
|
||||
'@vitest/utils@4.1.2':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.2
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@volar/kit@2.4.26(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@volar/language-service': 2.4.26
|
||||
|
|
@ -52302,6 +52431,8 @@ snapshots:
|
|||
|
||||
tinyrainbow@3.0.3: {}
|
||||
|
||||
tinyrainbow@3.1.0: {}
|
||||
|
||||
tinyspy@2.2.1: {}
|
||||
|
||||
tinyspy@3.0.2: {}
|
||||
|
|
@ -53690,7 +53821,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/debug': 4.1.12
|
||||
'@types/node': 20.19.25
|
||||
'@vitest/browser': 3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)
|
||||
'@vitest/browser': 3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)
|
||||
'@vitest/ui': 3.2.4(vitest@3.2.4)
|
||||
jsdom: 29.0.1(@noble/hashes@2.0.1)
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -53780,7 +53911,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/debug': 4.1.12
|
||||
'@types/node': 24.10.1
|
||||
'@vitest/browser': 3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)
|
||||
'@vitest/browser': 3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)
|
||||
'@vitest/ui': 3.2.4(vitest@3.2.4)
|
||||
jsdom: 29.0.1(@noble/hashes@2.0.1)
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -54050,6 +54181,35 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@20.19.25)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.2
|
||||
'@vitest/mocker': 4.1.2(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@vitest/pretty-format': 4.1.2
|
||||
'@vitest/runner': 4.1.2
|
||||
'@vitest/snapshot': 4.1.2
|
||||
'@vitest/spy': 4.1.2
|
||||
'@vitest/utils': 4.1.2
|
||||
es-module-lexer: 2.0.0
|
||||
expect-type: 1.3.0
|
||||
magic-string: 0.30.21
|
||||
obug: 2.1.1
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.3
|
||||
std-env: 4.0.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 1.0.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 3.1.0
|
||||
vite: 7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@types/node': 20.19.25
|
||||
jsdom: 29.0.1(@noble/hashes@2.0.1)
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
vlq@1.0.1: {}
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue