UNPKG

@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.

328 lines (274 loc) 11.3 kB
// writefile.test.js // Integration tests for WriteFile tool import test from 'ava'; import serverFactory from '../../../../index.js'; import { callPathway } from '../../../../lib/pathwayTools.js'; import { loadFileCollection } from '../../../../lib/fileUtils.js'; let testServer; test.before(async () => { const { server, startServer } = await serverFactory(); if (startServer) { await startServer(); } testServer = server; }); test.after.always('cleanup', async () => { if (testServer) { await testServer.stop(); } }); // Helper to create a test context const createTestContext = () => { const contextId = `test-writefile-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; return contextId; }; // Helper to clean up test data const cleanup = async (contextId, contextKey = null) => { try { const { getRedisClient } = await import('../../../../lib/fileUtils.js'); const redisClient = await getRedisClient(); if (redisClient) { const contextMapKey = `FileStoreMap:ctx:${contextId}`; await redisClient.del(contextMapKey); } } catch (e) { // Ignore cleanup errors } }; test('WriteFile: Write and upload text file', async t => { const contextId = createTestContext(); try { const content = 'Hello, world!\nThis is a test file.'; const filename = 'test.txt'; const result = await callPathway('sys_tool_writefile', { contextId, content, filename, userMessage: 'Writing test file' }); const parsed = JSON.parse(result); // Skip test if file handler is not configured if (!parsed.success && parsed.error?.includes('WHISPER_MEDIA_API_URL')) { t.log('Test skipped - file handler URL not configured'); t.pass(); return; } t.is(parsed.success, true); t.is(parsed.filename, filename); t.truthy(parsed.url); t.is(parsed.size, Buffer.byteLength(content, 'utf8')); t.true(parsed.message.includes('written') && parsed.message.includes('uploaded')); // Verify it was added to file collection const collection = await loadFileCollection(contextId, { useCache: false }); t.is(collection.length, 1); // Use displayFilename (user-friendly name) with fallback to filename (CFH-managed) t.is(collection[0].displayFilename || collection[0].filename, filename); t.is(collection[0].url, parsed.url); t.truthy(collection[0].hash); } finally { await cleanup(contextId); } }); test('WriteFile: Write JSON file with tags and notes', async t => { const contextId = createTestContext(); try { const content = JSON.stringify({ name: 'Test', value: 42 }, null, 2); const filename = 'data.json'; const tags = ['data', 'test']; const notes = 'Test JSON file'; const result = await callPathway('sys_tool_writefile', { contextId, content, filename, tags, notes, userMessage: 'Writing JSON file' }); const parsed = JSON.parse(result); // Skip test if file handler is not configured if (!parsed.success && parsed.error?.includes('WHISPER_MEDIA_API_URL')) { t.log('Test skipped - file handler URL not configured'); t.pass(); return; } t.is(parsed.success, true); t.is(parsed.filename, filename); t.truthy(parsed.url); t.truthy(parsed.hash); t.is(parsed.size, Buffer.byteLength(content, 'utf8')); // Verify it was added to file collection with metadata const collection = await loadFileCollection(contextId, { useCache: false }); t.is(collection.length, 1); // Use displayFilename (user-friendly name) with fallback to filename (CFH-managed) t.is(collection[0].displayFilename || collection[0].filename, filename); t.deepEqual(collection[0].tags, tags); t.is(collection[0].notes, notes); } finally { await cleanup(contextId); } }); test('WriteFile: Write file without contextId (no collection)', async t => { try { const content = 'Standalone file content'; const filename = 'standalone.txt'; const result = await callPathway('sys_tool_writefile', { content, filename, userMessage: 'Writing standalone file' }); const parsed = JSON.parse(result); // This test may fail if WHISPER_MEDIA_API_URL is not set if (!parsed.success && parsed.error?.includes('WHISPER_MEDIA_API_URL')) { t.log('Test skipped - file handler URL not configured'); t.pass(); } else { t.is(parsed.success, true); t.is(parsed.filename, filename); t.truthy(parsed.url); t.is(parsed.fileId, null); // Should be null since no contextId } } catch (error) { // This is expected if WHISPER_MEDIA_API_URL is not set in test environment t.log('Test skipped - file handler URL not configured'); t.pass(); } }); test('WriteFile: Error handling - missing content', async t => { const contextId = createTestContext(); try { const result = await callPathway('sys_tool_writefile', { contextId, filename: 'test.txt', userMessage: 'Missing content' }); const parsed = JSON.parse(result); t.is(parsed.success, false); t.true(parsed.error?.includes('content is required') || parsed.error?.includes('required')); } finally { await cleanup(contextId); } }); test('WriteFile: Error handling - missing filename', async t => { const contextId = createTestContext(); try { const result = await callPathway('sys_tool_writefile', { contextId, content: 'Some content', userMessage: 'Missing filename' }); const parsed = JSON.parse(result); t.is(parsed.success, false); t.true(parsed.error?.includes('filename is required') || parsed.error?.includes('required')); } finally { await cleanup(contextId); } }); test('WriteFile: Different file types and MIME types', async t => { const contextId = createTestContext(); try { const testCases = [ { content: 'console.log("hello");', filename: 'script.js', expectedMime: 'application/javascript' }, { content: 'def hello(): pass', filename: 'script.py', expectedMime: 'text/x-python' }, { content: '# Hello', filename: 'readme.md', expectedMime: 'text/markdown' }, { content: '<html></html>', filename: 'page.html', expectedMime: 'text/html' }, { content: 'name,value\nTest,42', filename: 'data.csv', expectedMime: 'text/csv' } ]; let successCount = 0; for (const testCase of testCases) { const result = await callPathway('sys_tool_writefile', { contextId, content: testCase.content, filename: testCase.filename, userMessage: `Writing ${testCase.filename}` }); const parsed = JSON.parse(result); // Skip test if file handler is not configured if (!parsed.success && parsed.error?.includes('WHISPER_MEDIA_API_URL')) { t.log('Test skipped - file handler URL not configured'); t.pass(); return; } t.is(parsed.success, true); t.is(parsed.filename, testCase.filename); t.truthy(parsed.url); successCount++; } // Verify all files were added const collection = await loadFileCollection(contextId, { useCache: false }); t.is(collection.length, successCount); } finally { await cleanup(contextId); } }); test('WriteFile: Large content', async t => { const contextId = createTestContext(); try { // Create a large content string (100KB) const largeContent = 'A'.repeat(100 * 1024); const filename = 'large.txt'; const result = await callPathway('sys_tool_writefile', { contextId, content: largeContent, filename, userMessage: 'Writing large file' }); const parsed = JSON.parse(result); // Skip test if file handler is not configured if (!parsed.success && parsed.error?.includes('WHISPER_MEDIA_API_URL')) { t.log('Test skipped - file handler URL not configured'); t.pass(); return; } t.is(parsed.success, true); t.is(parsed.filename, filename); t.is(parsed.size, Buffer.byteLength(largeContent, 'utf8')); t.truthy(parsed.url); t.truthy(parsed.hash); } finally { await cleanup(contextId); } }); test('WriteFile: Duplicate content (same hash)', async t => { const contextId = createTestContext(); try { const content = 'Duplicate test content'; const filename1 = 'file1.txt'; const filename2 = 'file2.txt'; // Write first file const result1 = await callPathway('sys_tool_writefile', { contextId, content, filename: filename1, userMessage: 'Writing first file' }); const parsed1 = JSON.parse(result1); // Skip test if file handler is not configured if (!parsed1.success && parsed1.error?.includes('WHISPER_MEDIA_API_URL')) { t.log('Test skipped - file handler URL not configured'); t.pass(); return; } t.is(parsed1.success, true); t.truthy(parsed1.hash); const firstHash = parsed1.hash; // Write second file with same content (should reuse hash) const result2 = await callPathway('sys_tool_writefile', { contextId, content, filename: filename2, userMessage: 'Writing duplicate file' }); const parsed2 = JSON.parse(result2); t.is(parsed2.success, true); t.is(parsed2.hash, firstHash); // Should have same hash // Both files with same hash should result in one entry (same content, CFH will find it) // The second file will update the existing entry with the new displayFilename const collection = await loadFileCollection(contextId, { useCache: false }); t.is(collection.length, 1); // Same hash = one entry t.is(collection[0].hash, firstHash); // Same hash // The displayFilename should be from the most recent write t.is(collection[0].displayFilename || collection[0].filename, filename2); } finally { await cleanup(contextId); } });