/ tests / agents / context-loader.test.js
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  });