context-loader.test.js
1 /** 2 * Tests for src/agents/utils/context-loader.js 3 * 4 * Covers: 5 * - loadContextFiles: validation, happy path, ENOENT error, other errors 6 * - getContextSize: bytes 7 * - getContextSizeKB: kilobytes 8 * - loadContextWithMetadata: returns context + size + files 9 */ 10 11 import { test, describe } from 'node:test'; 12 import assert from 'node:assert/strict'; 13 import { 14 loadContextFiles, 15 getContextSize, 16 getContextSizeKB, 17 loadContextWithMetadata, 18 } from '../../src/agents/utils/context-loader.js'; 19 20 describe('context-loader', () => { 21 describe('loadContextFiles', () => { 22 test('throws for non-array input', async () => { 23 await assert.rejects(loadContextFiles('base.md'), /contextFiles must be a non-empty array/); 24 }); 25 26 test('throws for empty array', async () => { 27 await assert.rejects(loadContextFiles([]), /contextFiles must be a non-empty array/); 28 }); 29 30 test('loads a single real context file', async () => { 31 const result = await loadContextFiles(['base.md']); 32 assert.ok(typeof result === 'string', 'should return a string'); 33 assert.ok(result.length > 0, 'should have content'); 34 }); 35 36 test('loads multiple context files and merges with separator', async () => { 37 const result = await loadContextFiles(['base.md', 'developer.md']); 38 assert.ok(result.includes('\n\n---\n\n'), 'should contain separator between files'); 39 }); 40 41 test('throws ENOENT for missing context file', async () => { 42 await assert.rejects( 43 loadContextFiles(['nonexistent-file-xyz.md']), 44 /Context file not found: nonexistent-file-xyz\.md/ 45 ); 46 }); 47 }); 48 49 describe('getContextSize', () => { 50 test('returns byte count for ASCII string', () => { 51 const size = getContextSize('hello'); 52 assert.equal(size, 5); 53 }); 54 55 test('returns byte count for UTF-8 string with multibyte chars', () => { 56 // '€' is 3 bytes in UTF-8 57 const size = getContextSize('€'); 58 assert.equal(size, 3); 59 }); 60 61 test('returns 0 for empty string', () => { 62 assert.equal(getContextSize(''), 0); 63 }); 64 }); 65 66 describe('getContextSizeKB', () => { 67 test('returns size in KB rounded to 1 decimal', () => { 68 // 1024 bytes = 1.0 KB 69 const str = 'a'.repeat(1024); 70 assert.equal(getContextSizeKB(str), 1.0); 71 }); 72 73 test('returns 0.0 for empty string', () => { 74 assert.equal(getContextSizeKB(''), 0); 75 }); 76 77 test('rounds to 1 decimal place', () => { 78 // 1536 bytes = 1.5 KB 79 const str = 'a'.repeat(1536); 80 assert.equal(getContextSizeKB(str), 1.5); 81 }); 82 }); 83 84 describe('loadContextWithMetadata', () => { 85 test('returns context, size, sizeKB, and files array', async () => { 86 const result = await loadContextWithMetadata(['base.md']); 87 assert.ok(typeof result.context === 'string', 'should have context string'); 88 assert.ok(typeof result.size === 'number', 'should have size'); 89 assert.ok(typeof result.sizeKB === 'number', 'should have sizeKB'); 90 assert.deepEqual(result.files, ['base.md']); 91 assert.ok(result.size > 0, 'size should be > 0'); 92 }); 93 94 test('merges multiple files and returns combined size', async () => { 95 const single = await loadContextWithMetadata(['base.md']); 96 const merged = await loadContextWithMetadata(['base.md', 'qa.md']); 97 assert.ok(merged.size > single.size, 'merged context should be larger'); 98 assert.equal(merged.files.length, 2); 99 }); 100 101 test('throws for invalid input (propagated from loadContextFiles)', async () => { 102 await assert.rejects(loadContextWithMetadata([]), /contextFiles must be a non-empty array/); 103 }); 104 }); 105 });