feat: Add normalized types and utility functions for handling chat formats
- Introduced `NormalizedContentBlock`, `NormalizedUsage`, and related types for structured data handling. - Implemented utility functions for creating and merging normalized usage statistics. - Added a new module for shared transformers to manage chat formats. feat: Implement app local state management - Created functions to clear application installation state and manage local storage keys. - Added constants for theme and user profile storage keys. test: Add tests for BrandIcon component rendering - Implemented tests to ensure BrandIcon does not force a colored background for recognized brands. test: Enhance ModelAnalysisPanel tests - Added tests for rendering token summaries with compact units in ModelAnalysisPanel. test: Add tests for ModernSelect component - Implemented tests to verify icon rendering for selected options in ModernSelect. test: Add number formatting tests - Created tests for `formatCompactTokenMetric` to validate token formatting logic. feat: Implement account creation intent handling - Added logic to handle session and API key creation intents in the Accounts page. test: Add tests for dashboard performance card - Implemented tests to verify rendering of performance metrics in the dashboard. feat: Implement default connection segment resolution - Added logic to determine initial connection segments based on platform types. test: Add tests for settings factory reset functionality - Implemented tests to verify the factory reset process and local state clearing. test: Add tests for site creation redirect logic - Implemented tests to verify redirection based on site capabilities during creation. test: Add tests for token routes refresh decision action - Implemented tests to verify refresh decision actions in TokenRoutes. test: Add tests for token routes snapshot load - Implemented tests to verify loading of cached snapshot data in TokenRoutes.
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import Fastify, { type FastifyInstance } from 'fastify';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const fetchMock = vi.fn();
|
||||
const selectChannelMock = vi.fn();
|
||||
const selectNextChannelMock = vi.fn();
|
||||
const recordSuccessMock = vi.fn();
|
||||
const recordFailureMock = vi.fn();
|
||||
const refreshModelsAndRebuildRoutesMock = vi.fn();
|
||||
const reportProxyAllFailedMock = vi.fn();
|
||||
const reportTokenExpiredMock = vi.fn();
|
||||
const estimateProxyCostMock = vi.fn(async () => 0);
|
||||
const saveProxyVideoTaskMock = vi.fn();
|
||||
const getProxyVideoTaskByPublicIdMock = vi.fn();
|
||||
const deleteProxyVideoTaskByPublicIdMock = vi.fn();
|
||||
const refreshProxyVideoTaskSnapshotMock = vi.fn();
|
||||
|
||||
vi.mock('undici', async () => {
|
||||
const actual = await vi.importActual<typeof import('undici')>('undici');
|
||||
return {
|
||||
...actual,
|
||||
fetch: (...args: unknown[]) => fetchMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../services/tokenRouter.js', () => ({
|
||||
tokenRouter: {
|
||||
selectChannel: (...args: unknown[]) => selectChannelMock(...args),
|
||||
selectNextChannel: (...args: unknown[]) => selectNextChannelMock(...args),
|
||||
recordSuccess: (...args: unknown[]) => recordSuccessMock(...args),
|
||||
recordFailure: (...args: unknown[]) => recordFailureMock(...args),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../services/modelService.js', () => ({
|
||||
refreshModelsAndRebuildRoutes: (...args: unknown[]) => refreshModelsAndRebuildRoutesMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/alertService.js', () => ({
|
||||
reportProxyAllFailed: (...args: unknown[]) => reportProxyAllFailedMock(...args),
|
||||
reportTokenExpired: (...args: unknown[]) => reportTokenExpiredMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/alertRules.js', () => ({
|
||||
isTokenExpiredError: () => false,
|
||||
}));
|
||||
|
||||
vi.mock('../../services/modelPricingService.js', () => ({
|
||||
estimateProxyCost: (arg: any) => estimateProxyCostMock(arg),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/proxyRetryPolicy.js', () => ({
|
||||
shouldRetryProxyRequest: () => false,
|
||||
}));
|
||||
|
||||
vi.mock('../../services/proxyVideoTaskStore.js', () => ({
|
||||
saveProxyVideoTask: (...args: unknown[]) => saveProxyVideoTaskMock(...args),
|
||||
getProxyVideoTaskByPublicId: (...args: unknown[]) => getProxyVideoTaskByPublicIdMock(...args),
|
||||
deleteProxyVideoTaskByPublicId: (...args: unknown[]) => deleteProxyVideoTaskByPublicIdMock(...args),
|
||||
refreshProxyVideoTaskSnapshot: (...args: unknown[]) => refreshProxyVideoTaskSnapshotMock(...args),
|
||||
}));
|
||||
|
||||
describe('/v1/videos routes', () => {
|
||||
let app: FastifyInstance;
|
||||
|
||||
const buildMultipartBody = (boundary: string) => Buffer.from(
|
||||
`--${boundary}\r\n`
|
||||
+ `Content-Disposition: form-data; name="model"\r\n\r\n`
|
||||
+ `sora-2\r\n`
|
||||
+ `--${boundary}\r\n`
|
||||
+ `Content-Disposition: form-data; name="prompt"\r\n\r\n`
|
||||
+ `a cat walking\r\n`
|
||||
+ `--${boundary}\r\n`
|
||||
+ `Content-Disposition: form-data; name="input_reference"; filename="cat.png"\r\n`
|
||||
+ `Content-Type: image/png\r\n\r\n`
|
||||
+ `pngdata\r\n`
|
||||
+ `--${boundary}--\r\n`,
|
||||
);
|
||||
|
||||
beforeAll(async () => {
|
||||
const { videosProxyRoute } = await import('./videos.js');
|
||||
app = Fastify();
|
||||
await app.register(videosProxyRoute);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mockReset();
|
||||
selectChannelMock.mockReset();
|
||||
selectNextChannelMock.mockReset();
|
||||
recordSuccessMock.mockReset();
|
||||
recordFailureMock.mockReset();
|
||||
refreshModelsAndRebuildRoutesMock.mockReset();
|
||||
reportProxyAllFailedMock.mockReset();
|
||||
reportTokenExpiredMock.mockReset();
|
||||
estimateProxyCostMock.mockClear();
|
||||
saveProxyVideoTaskMock.mockReset();
|
||||
getProxyVideoTaskByPublicIdMock.mockReset();
|
||||
deleteProxyVideoTaskByPublicIdMock.mockReset();
|
||||
refreshProxyVideoTaskSnapshotMock.mockReset();
|
||||
|
||||
selectChannelMock.mockReturnValue({
|
||||
channel: { id: 11, routeId: 22 },
|
||||
site: { id: 44, name: 'demo-site', url: 'https://upstream.example.com', platform: 'openai' },
|
||||
account: { id: 33, username: 'demo-user' },
|
||||
tokenName: 'default',
|
||||
tokenValue: 'sk-demo',
|
||||
actualModel: 'sora-2',
|
||||
});
|
||||
selectNextChannelMock.mockReturnValue(null);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('creates an upstream video task and stores a local public id mapping', async () => {
|
||||
saveProxyVideoTaskMock.mockResolvedValue({
|
||||
publicId: 'vid_local_123',
|
||||
upstreamVideoId: 'vid_upstream_123',
|
||||
});
|
||||
fetchMock.mockResolvedValue(new Response(JSON.stringify({
|
||||
id: 'vid_upstream_123',
|
||||
object: 'video',
|
||||
status: 'queued',
|
||||
model: 'sora-2',
|
||||
prompt: 'a cat walking',
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}));
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/videos',
|
||||
payload: {
|
||||
model: 'sora-2',
|
||||
prompt: 'a cat walking',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(saveProxyVideoTaskMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
upstreamVideoId: 'vid_upstream_123',
|
||||
requestedModel: 'sora-2',
|
||||
actualModel: 'sora-2',
|
||||
lastUpstreamStatus: 200,
|
||||
statusSnapshot: expect.objectContaining({
|
||||
id: 'vid_upstream_123',
|
||||
status: 'queued',
|
||||
}),
|
||||
}));
|
||||
expect(response.json()).toMatchObject({
|
||||
id: 'vid_local_123',
|
||||
object: 'video',
|
||||
status: 'queued',
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts multipart video create requests', async () => {
|
||||
saveProxyVideoTaskMock.mockResolvedValue({
|
||||
publicId: 'vid_local_456',
|
||||
upstreamVideoId: 'vid_upstream_456',
|
||||
});
|
||||
fetchMock.mockResolvedValue(new Response(JSON.stringify({
|
||||
id: 'vid_upstream_456',
|
||||
object: 'video',
|
||||
status: 'queued',
|
||||
model: 'sora-2',
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}));
|
||||
|
||||
const boundary = 'metapi-video-boundary';
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/videos',
|
||||
headers: {
|
||||
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||
},
|
||||
payload: buildMultipartBody(boundary),
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json()).toMatchObject({
|
||||
id: 'vid_local_456',
|
||||
object: 'video',
|
||||
status: 'queued',
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves local video ids back to the upstream task on GET', async () => {
|
||||
getProxyVideoTaskByPublicIdMock.mockResolvedValue({
|
||||
publicId: 'vid_local_123',
|
||||
upstreamVideoId: 'vid_upstream_123',
|
||||
siteUrl: 'https://upstream.example.com',
|
||||
tokenValue: 'sk-demo',
|
||||
});
|
||||
fetchMock.mockResolvedValue(new Response(JSON.stringify({
|
||||
id: 'vid_upstream_123',
|
||||
object: 'video',
|
||||
status: 'running',
|
||||
model: 'sora-2',
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}));
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/videos/vid_local_123',
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const [targetUrl] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(targetUrl).toBe('https://upstream.example.com/v1/videos/vid_upstream_123');
|
||||
expect(refreshProxyVideoTaskSnapshotMock).toHaveBeenCalledWith('vid_local_123', expect.objectContaining({
|
||||
lastUpstreamStatus: 200,
|
||||
statusSnapshot: expect.objectContaining({
|
||||
id: 'vid_upstream_123',
|
||||
status: 'running',
|
||||
}),
|
||||
}));
|
||||
expect(response.json()).toMatchObject({
|
||||
id: 'vid_local_123',
|
||||
object: 'video',
|
||||
status: 'running',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the upstream task and local mapping on DELETE', async () => {
|
||||
getProxyVideoTaskByPublicIdMock.mockResolvedValue({
|
||||
publicId: 'vid_local_123',
|
||||
upstreamVideoId: 'vid_upstream_123',
|
||||
siteUrl: 'https://upstream.example.com',
|
||||
tokenValue: 'sk-demo',
|
||||
});
|
||||
fetchMock.mockResolvedValue(new Response(null, { status: 204 }));
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'DELETE',
|
||||
url: '/v1/videos/vid_local_123',
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(204);
|
||||
expect(deleteProxyVideoTaskByPublicIdMock).toHaveBeenCalledWith('vid_local_123');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user