@aj-archipelago/cortex
Version:
Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.
309 lines (248 loc) • 10.4 kB
JavaScript
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import fs from 'fs/promises';
import os from 'os';
import { dirname, join } from 'path';
import { performance } from 'perf_hooks';
import { fileURLToPath } from 'url';
import test from 'ava';
import nock from 'nock';
import { splitMediaFile, downloadFile } from '../src/fileChunker.js';
import { createTestMediaFile } from './testUtils.helper.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
// Setup: Create test files and mock external services
test.before(async (t) => {
// Check if ffmpeg is available
try {
execSync('ffmpeg -version', { stdio: 'ignore' });
} catch (error) {
console.error(
'ffmpeg is not installed. Please install it to run these tests.',
);
process.exit(1);
}
const testDir = join(__dirname, 'test-files');
await fs.mkdir(testDir, { recursive: true });
try {
// Create test files of different durations
const testFile1s = join(testDir, 'test-1s.mp3');
const testFile10s = join(testDir, 'test-10s.mp3');
const testFile600s = join(testDir, 'test-600s.mp3');
await createTestMediaFile(testFile1s, 1);
await createTestMediaFile(testFile10s, 10);
await createTestMediaFile(testFile600s, 600);
// Create large test files
const testFile1h = join(testDir, 'test-1h.mp3');
const testFile4h = join(testDir, 'test-4h.mp3');
console.log('\nCreating large test files (this may take a while)...');
await createTestMediaFile(testFile1h, 3600);
await createTestMediaFile(testFile4h, 14400);
t.context = {
testDir,
testFile1s,
testFile10s,
testFile600s,
testFile1h,
testFile4h,
};
// Setup nock for URL tests with proper headers
nock('https://example.com')
.get('/media/test.mp3')
.replyWithFile(200, testFile10s, {
'Content-Type': 'audio/mpeg',
'Content-Length': (await fs.stat(testFile10s)).size.toString(),
})
.persist();
} catch (error) {
console.error('Error during test setup:', error);
// Clean up any partially created files
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch (cleanupError) {
console.error('Error during cleanup:', cleanupError);
}
throw error;
}
});
// Cleanup: Remove test files
test.after.always(async (t) => {
// Clean up test files
if (t.context.testDir) {
try {
await fs.rm(t.context.testDir, { recursive: true, force: true });
console.log('Test files cleaned up successfully');
} catch (error) {
console.error('Error cleaning up test files:', error);
}
}
// Clean up nock
nock.cleanAll();
});
// Test successful chunking of a short file
test('successfully chunks short media file', async (t) => {
const { chunkPromises, chunkOffsets, uniqueOutputPath } =
await splitMediaFile(t.context.testFile1s);
t.true(Array.isArray(chunkPromises), 'Should return array of promises');
t.true(Array.isArray(chunkOffsets), 'Should return array of offsets');
t.true(typeof uniqueOutputPath === 'string', 'Should return output path');
// Should only create one chunk for 1s file
t.is(chunkPromises.length, 1, 'Should create single chunk for short file');
// Wait for chunks to process
const chunkPaths = await Promise.all(chunkPromises);
// Verify chunk exists
t.true(existsSync(chunkPaths[0]), 'Chunk file should exist');
// Cleanup
await fs.rm(uniqueOutputPath, { recursive: true, force: true });
});
// Test chunking of a longer file
test('correctly chunks longer media file', async (t) => {
const { chunkPromises, chunkOffsets, uniqueOutputPath } =
await splitMediaFile(t.context.testFile600s);
// For 600s file with 500s chunks, should create 2 chunks
t.is(chunkPromises.length, 2, 'Should create correct number of chunks');
t.is(chunkOffsets.length, 2, 'Should create correct number of offsets');
// Verify offsets
t.is(chunkOffsets[0], 0, 'First chunk should start at 0');
t.is(chunkOffsets[1], 500, 'Second chunk should start at 500s');
// Wait for chunks to process
const chunkPaths = await Promise.all(chunkPromises);
// Verify all chunks exist
for (const chunkPath of chunkPaths) {
t.true(existsSync(chunkPath), 'Each chunk file should exist');
}
// Cleanup
await fs.rm(uniqueOutputPath, { recursive: true, force: true });
});
// Test custom chunk duration
test('respects custom chunk duration', async (t) => {
const customDuration = 5; // 5 seconds
const { chunkPromises, chunkOffsets } = await splitMediaFile(
t.context.testFile10s,
customDuration,
);
// For 10s file with 5s chunks, should create 2 chunks
t.is(
chunkPromises.length,
2,
'Should create correct number of chunks for custom duration',
);
t.deepEqual(chunkOffsets, [0, 5], 'Should have correct offset points');
});
// Test URL-based file processing
test('processes media file from URL', async (t) => {
const url = 'https://example.com/media/test.mp3';
const { chunkPromises, uniqueOutputPath } = await splitMediaFile(url);
// Wait for chunks to process
const chunkPaths = await Promise.all(chunkPromises);
// Verify chunks were created
for (const chunkPath of chunkPaths) {
t.true(
existsSync(chunkPath),
'Chunk files should exist for URL-based media',
);
}
// Cleanup
await fs.rm(uniqueOutputPath, { recursive: true, force: true });
});
// Test error handling for invalid files
test('handles invalid media files gracefully', async (t) => {
const invalidFile = join(t.context.testDir, 'invalid.mp3');
await fs.writeFile(invalidFile, 'not a valid mp3 file');
await t.throwsAsync(async () => splitMediaFile(invalidFile), {
message: /Error processing media file/,
});
});
// Test error handling for non-existent files
test('handles non-existent files gracefully', async (t) => {
const nonExistentFile = join(t.context.testDir, 'non-existent.mp3');
await t.throwsAsync(async () => splitMediaFile(nonExistentFile), {
message: /Error processing media file/,
});
});
// Test file download functionality
test('successfully downloads file from URL', async (t) => {
const url = 'https://example.com/media/test.mp3';
const outputPath = join(os.tmpdir(), 'downloaded-test.mp3');
await downloadFile(url, outputPath);
t.true(existsSync(outputPath), 'Downloaded file should exist');
// Cleanup
await fs.unlink(outputPath);
});
// Test error handling for invalid URLs in download
test('handles invalid URLs in download gracefully', async (t) => {
const invalidUrl = 'https://invalid-url-that-does-not-exist.com/test.mp3';
const outputPath = join(os.tmpdir(), 'should-not-exist.mp3');
await t.throwsAsync(async () => downloadFile(invalidUrl, outputPath));
});
// Helper to format duration nicely
function formatDuration(ms) {
if (ms < 1000) return `${ms}ms`;
const seconds = ms / 1000;
if (seconds < 60) return `${seconds.toFixed(2)}s`;
const minutes = seconds / 60;
if (minutes < 60) return `${minutes.toFixed(2)}m`;
const hours = minutes / 60;
return `${hours.toFixed(2)}h`;
}
// Test performance with 1-hour file
test('performance test - 1 hour file', async (t) => {
const start = performance.now();
const { chunkPromises, uniqueOutputPath } = await splitMediaFile(
t.context.testFile1h,
);
// Wait for all chunks to complete
const chunkPaths = await Promise.all(chunkPromises);
const end = performance.now();
const duration = end - start;
console.log(`\n1 hour file processing stats:
- Total time: ${formatDuration(duration)}
- Chunks created: ${chunkPaths.length}
- Average time per chunk: ${formatDuration(duration / chunkPaths.length)}
- Processing speed: ${(3600 / (duration / 1000)).toFixed(2)}x realtime`);
t.true(chunkPaths.length > 0, 'Should create chunks');
t.true(duration > 0, 'Should measure time');
// Cleanup
await fs.rm(uniqueOutputPath, { recursive: true, force: true });
});
// Test performance with 4-hour file
test('performance test - 4 hour file', async (t) => {
const start = performance.now();
const { chunkPromises, uniqueOutputPath } = await splitMediaFile(
t.context.testFile4h,
);
// Wait for all chunks to complete
const chunkPaths = await Promise.all(chunkPromises);
const end = performance.now();
const duration = end - start;
console.log(`\n4 hour file processing stats:
- Total time: ${formatDuration(duration)}
- Chunks created: ${chunkPaths.length}
- Average time per chunk: ${formatDuration(duration / chunkPaths.length)}
- Processing speed: ${(14400 / (duration / 1000)).toFixed(2)}x realtime`);
t.true(chunkPaths.length > 0, 'Should create chunks');
t.true(duration > 0, 'Should measure time');
// Cleanup
await fs.rm(uniqueOutputPath, { recursive: true, force: true });
});
// Test memory usage during large file processing
test('memory usage during large file processing', async (t) => {
const initialMemory = process.memoryUsage().heapUsed;
let peakMemory = initialMemory;
const interval = setInterval(() => {
const used = process.memoryUsage().heapUsed;
peakMemory = Math.max(peakMemory, used);
}, 100);
const { chunkPromises, uniqueOutputPath } = await splitMediaFile(
t.context.testFile4h,
);
await Promise.all(chunkPromises);
clearInterval(interval);
const memoryIncrease = (peakMemory - initialMemory) / 1024 / 1024; // Convert to MB
console.log(`\nMemory usage stats:
- Initial memory: ${(initialMemory / 1024 / 1024).toFixed(2)}MB
- Peak memory: ${(peakMemory / 1024 / 1024).toFixed(2)}MB
- Memory increase: ${memoryIncrease.toFixed(2)}MB`);
t.true(memoryIncrease >= 0, 'Should track memory usage');
// Cleanup
await fs.rm(uniqueOutputPath, { recursive: true, force: true });
});