@devilsdev/rag-pipeline-utils 1.0.1 โ†’ 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.codecov.yml +21 -0
  2. package/.github/workflows/ci.yml +18 -0
  3. package/.husky/pre-commit +5 -0
  4. package/CHANGELOG.md +33 -0
  5. package/__tests__/fixtures/.ragrc.json +8 -0
  6. package/__tests__/fixtures/invalid-ragrc.json +4 -0
  7. package/__tests__/fixtures/src/mocks/openai-embedder.js +16 -0
  8. package/__tests__/fixtures/src/mocks/openai-llm.js +9 -0
  9. package/__tests__/fixtures/src/mocks/pdf-loader.js +17 -0
  10. package/__tests__/fixtures/src/mocks/pinecone-retriever.js +26 -0
  11. package/__tests__/fixtures/valid-ragrc.json +16 -0
  12. package/__tests__/integration/cli/config-flow.test.js +84 -15
  13. package/__tests__/integration/config/load-config.test.js +45 -30
  14. package/__tests__/unit/config/validate-schema.test.js +121 -0
  15. package/__tests__/unit/reranker/llm-reranker.test.js +9 -33
  16. package/__tests__/unit/reranker/reranker.enriched.test.js +19 -0
  17. package/__tests__/unit/reranker/reranker.fallback.test.js +15 -0
  18. package/__tests__/unit/reranker/reranker.snapshot.test.js +9 -11
  19. package/__tests__/unit/reranker/reranker.structured-output.test.js +17 -0
  20. package/bin/cli.js +49 -155
  21. package/package.json +17 -2
  22. package/plugin.config.json +14 -0
  23. package/scripts/banner-injector.js +89 -0
  24. package/scripts/ci-runner.js +51 -0
  25. package/scripts/repair-fixtures.js +30 -0
  26. package/scripts/setup.js +26 -2
  27. package/scripts/verify-fixture-mocks.js +45 -0
  28. package/scripts/verify-fixtures.js +36 -0
  29. package/src/config/load-config.js +19 -21
  30. package/src/config/load-plugin-config.js +56 -0
  31. package/src/config/validate-plugin-schema.js +28 -0
  32. package/src/config/validate-schema.js +88 -30
  33. package/src/core/create-pipeline.js +40 -26
  34. package/src/core/plugin-contracts.js +21 -0
  35. package/src/core/plugin-registry.js +4 -17
  36. package/src/mocks/openai-embedder.js +30 -0
  37. package/src/mocks/openai-llm.js +18 -0
  38. package/src/mocks/pdf-loader.js +17 -0
  39. package/src/mocks/pinecone-retriever.js +25 -0
  40. package/src/reranker/llm-reranker.js +28 -43
  41. package/src/utils/ci/diagnostic-reporter.js +36 -0
  42. package/src/utils/validate-plugin-contract.js +36 -0
  43. package/.github/workflows/release.yml +0 -20
package/.codecov.yml ADDED
@@ -0,0 +1,21 @@
1
+ # .codecov.yml
2
+
3
+ codecov:
4
+ notify:
5
+ after_n_builds: 1
6
+
7
+ coverage:
8
+ status:
9
+ project:
10
+ default:
11
+ target: 90%
12
+ threshold: 5%
13
+ patch:
14
+ default:
15
+ target: 90%
16
+ threshold: 5%
17
+
18
+ comment:
19
+ layout: "reach, diff, flags, files"
20
+ behavior: default
21
+ require_changes: false
@@ -44,3 +44,21 @@ jobs:
44
44
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
45
45
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # ๐Ÿ‘ˆ critical for npm publi
46
46
  run: npx semantic-release
47
+
48
+ - name: ๐Ÿงช Run Tests with Coverage
49
+ run: npm test
50
+
51
+ - name: Validate Fixtures
52
+ run: npm run validate-fixtures
53
+
54
+ - name: Lint code
55
+ run: npm run lint
56
+
57
+
58
+ - name: ๐Ÿ“Š Upload coverage to Codecov
59
+ uses: codecov/codecov-action@v5
60
+ with:
61
+ token: ${{ secrets.CODECOV_TOKEN }}
62
+ slug: DevilsDev/rag-pipeline-utils
63
+
64
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ npm run validate-fixtures && npm run lint
5
+ npx lint-staged
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ # [2.0.0](https://github.com/DevilsDev/rag-pipeline-utils/compare/v1.0.1...v2.0.0) (2025-04-17)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **ci:** correct .ragrc.json plugin paths and CLI test cwd to resolve plugin loading in CI ([911c23b](https://github.com/DevilsDev/rag-pipeline-utils/commit/911c23b1d3ddb9e2d938b37f4422a5d81995e97f))
9
+ * **ci:** resolve plugin path error in .ragrc.json for GitHub Actions compatibility ([affc739](https://github.com/DevilsDev/rag-pipeline-utils/commit/affc739d5c4ff2070b1a1ca0ccf3f9137d6cee5a))
10
+ * **ci:** resolve plugin path failures in .ragrc.json during CLI integration tests ([04b977b](https://github.com/DevilsDev/rag-pipeline-utils/commit/04b977b3b745fa3d3ab47c84b2aeee3285896068))
11
+ * **cli:** correct path to load-plugin-config.js after moving to /src/config ([f743498](https://github.com/DevilsDev/rag-pipeline-utils/commit/f743498ec32e03151eda9ba7fe5886bda8da792f))
12
+ * **config:** use default export when importing plugin registry ([ef8dce1](https://github.com/DevilsDev/rag-pipeline-utils/commit/ef8dce12bfab62bc9faec764198b98c0c356472e))
13
+ * **test:** ensure .ragrc.json copied to project root for CLI fallback in CI ([31bd2b8](https://github.com/DevilsDev/rag-pipeline-utils/commit/31bd2b844279fb0404056d9929e5f28f9127eb12))
14
+ * **tests:** define __dirname manually for ESM compatibility in CLI integration test ([18b8dad](https://github.com/DevilsDev/rag-pipeline-utils/commit/18b8dadabb852243dda2b78de048c71106a759fc))
15
+
16
+
17
+ ### Features
18
+
19
+ * **ci:** integrate full fixture validation, schema contracts, and CLI test flow ([2b57fe9](https://github.com/DevilsDev/rag-pipeline-utils/commit/2b57fe90e64a806815c1f66f5e2a745780270ce6))
20
+ * **cli:** dynamic plugin registration with config fallback and middleware injection ([cc24b7e](https://github.com/DevilsDev/rag-pipeline-utils/commit/cc24b7e74d0a2630eb1c6d2def69b370329dcc2b))
21
+ * **plugins:** enable dynamic plugin registration from JSON config ([d81d6d7](https://github.com/DevilsDev/rag-pipeline-utils/commit/d81d6d7d5f2d21b5a46b379b530c162ba9c30398))
22
+ * **testing:** integrate full fixture and validation setup for CLI + reranker ([ab96037](https://github.com/DevilsDev/rag-pipeline-utils/commit/ab9603765594e7718e2688b80797e1f5e8afe86f))
23
+
24
+
25
+ ### BREAKING CHANGES
26
+
27
+ * **cli:** now requires named options for middleware injection; existing usage must be updated.
28
+
29
+ ## [1.0.2] - 2025-04-15
30
+ ### Fixed
31
+ - Corrected plugin path resolution in `.ragrc.json` fixture for CI compatibility.
32
+ - Resolved `ERR_MODULE_NOT_FOUND` during CLI integration tests in GitHub Actions.
33
+ - Ensured consistent module resolution across environments (Windows, Linux, CI).
34
+
35
+
3
36
  ## [1.0.1](https://github.com/DevilsDev/rag-pipeline-utils/compare/v1.0.0...v1.0.1) (2025-04-14)
4
37
 
5
38
 
@@ -0,0 +1,8 @@
1
+ {
2
+ "loader": { "pdf": "./src/mocks/pdf-loader.js" },
3
+ "embedder": { "openai": "./src/mocks/openai-embedder.js" },
4
+ "retriever": { "pinecone": "./src/mocks/pinecone-retriever.js" },
5
+ "llm": { "openai": "./src/mocks/openai-llm.js" },
6
+ "namespace": "cli-config-test",
7
+ "pipeline": ["loader", "embedder", "retriever"]
8
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "loader": "./src/mocks/pdf-loader.js",
3
+ "namespace": 12345
4
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Mock OpenAI Embedder Plugin
3
+ * Implements: embedder.embed(docs), embedder.embedQuery(query)
4
+ */
5
+ export default class OpenAIEmbedder {
6
+ embed(documents) {
7
+ return documents.map(doc => ({
8
+ id: doc.id,
9
+ values: [0.1, 0.2, 0.3],
10
+ }));
11
+ }
12
+
13
+ embedQuery(query) {
14
+ return [0.1, 0.2, 0.3];
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Mock OpenAI LLM Plugin
3
+ * Implements: llm.ask(prompt)
4
+ */
5
+ export default class OpenAILLM {
6
+ ask(prompt) {
7
+ return `Answer: Mocked response to "${prompt}"`;
8
+ }
9
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Mock PDF Loader Plugin
3
+ * Implements: loader.load(filePath)
4
+ */
5
+ export default class PdfLoader {
6
+ /**
7
+ * Loads a file and returns mock document chunks.
8
+ * @param {string} filePath
9
+ * @returns {Array<{ id: string, content: string }>}
10
+ */
11
+ load(filePath) {
12
+ return [
13
+ { id: 'doc1', content: `Loaded content from ${filePath}` },
14
+ { id: 'doc2', content: 'Second document content' },
15
+ ];
16
+ }
17
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Mock Pinecone Retriever Plugin
3
+ * Implements: retriever.store(vectors), retriever.search(queryVector)
4
+ */
5
+ export default class PineconeRetriever {
6
+ constructor() {
7
+ this._store = [];
8
+ }
9
+
10
+ /**
11
+ * Stores embedded vectors.
12
+ * @param {Array<{ id: string, values: number[] }>} vectors
13
+ */
14
+ store(vectors) {
15
+ this._store = vectors;
16
+ }
17
+
18
+ /**
19
+ * Searches mock memory and returns matching docs.
20
+ * @param {number[]} queryVector
21
+ * @returns {Array<{ id: string, score: number }>}
22
+ */
23
+ search(queryVector) {
24
+ return this._store.map(v => ({ id: v.id, score: 0.99 }));
25
+ }
26
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "loader": {
3
+ "pdf": "./src/mocks/pdf-loader.js"
4
+ },
5
+ "embedder": {
6
+ "openai": "./src/mocks/openai-embedder.js"
7
+ },
8
+ "retriever": {
9
+ "pinecone": "./src/mocks/pinecone-retriever.js"
10
+ },
11
+ "llm": {
12
+ "openai": "./src/mocks/openai-llm.js"
13
+ },
14
+ "namespace": "cli-config-test",
15
+ "pipeline": ["loader", "embedder", "retriever"]
16
+ }
@@ -1,29 +1,98 @@
1
1
  /**
2
- * Version: 0.1.3
3
- * Description: Integration test for CLI with config fallback
2
+ * Version: 1.1.0
3
+ * Description: Final CLI config fallback integration test with fixture mock copying and validation
4
4
  * Author: Ali Kahwaji
5
5
  */
6
6
 
7
7
  import { execSync } from 'child_process';
8
- import path from 'path';
8
+ import {
9
+ mkdirSync,
10
+ rmSync,
11
+ writeFileSync,
12
+ copyFileSync,
13
+ existsSync,
14
+ readdirSync,
15
+ readFileSync
16
+ } from 'fs';
17
+ import { join, resolve } from 'path';
18
+ import { fileURLToPath } from 'url';
9
19
 
10
- const CLI_PATH = path.resolve('bin/cli.js');
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = resolve(__filename, '..');
22
+
23
+ const CLI_PATH = resolve('bin/cli.js');
24
+ const TEMP_TEST_DIR = resolve('__tests__/__temp__/cli-config-fallback');
25
+ const FIXTURE_CONFIG = join(TEMP_TEST_DIR, '.ragrc.json');
26
+ const FIXTURE_PDF = resolve('__tests__/fixtures/sample.pdf');
27
+
28
+ // Fixture sources
29
+ const FIXTURE_SRC = resolve('__tests__/fixtures/src/mocks');
30
+ const PDF_LOADER_SRC = join(FIXTURE_SRC, 'pdf-loader.js');
31
+ const EMBEDDER_SRC = join(FIXTURE_SRC, 'openai-embedder.js');
32
+ const RETRIEVER_SRC = join(FIXTURE_SRC, 'pinecone-retriever.js');
33
+ const LLM_SRC = join(FIXTURE_SRC, 'openai-llm.js');
34
+
35
+ const FIXTURE_CONFIG_OBJECT = {
36
+ loader: { pdf: './src/mocks/pdf-loader.js' },
37
+ embedder: { openai: './src/mocks/openai-embedder.js' },
38
+ retriever: { pinecone: './src/mocks/pinecone-retriever.js' },
39
+ llm: { openai: './src/mocks/openai-llm.js' },
40
+ namespace: 'cli-config-test',
41
+ pipeline: ['loader', 'embedder', 'retriever']
42
+ };
11
43
 
12
44
  describe('CLI integration with .ragrc.json config fallback', () => {
45
+ beforeAll(() => {
46
+ rmSync(TEMP_TEST_DIR, { recursive: true, force: true });
47
+ mkdirSync(join(TEMP_TEST_DIR, 'src/mocks'), { recursive: true });
48
+
49
+ // Assert existence
50
+ [FIXTURE_PDF, PDF_LOADER_SRC, EMBEDDER_SRC, RETRIEVER_SRC, LLM_SRC].forEach(file => {
51
+ if (!existsSync(file)) throw new Error(`[FATAL] Missing required fixture: ${file}`);
52
+ });
53
+
54
+ // Copy fixture PDF
55
+ copyFileSync(FIXTURE_PDF, join(TEMP_TEST_DIR, 'sample.pdf'));
56
+
57
+ // Copy mock plugins into temp dir
58
+ copyFileSync(PDF_LOADER_SRC, join(TEMP_TEST_DIR, 'src/mocks/pdf-loader.js'));
59
+ copyFileSync(EMBEDDER_SRC, join(TEMP_TEST_DIR, 'src/mocks/openai-embedder.js'));
60
+ copyFileSync(RETRIEVER_SRC, join(TEMP_TEST_DIR, 'src/mocks/pinecone-retriever.js'));
61
+ copyFileSync(LLM_SRC, join(TEMP_TEST_DIR, 'src/mocks/openai-llm.js'));
62
+
63
+ // Write valid .ragrc.json
64
+ writeFileSync(FIXTURE_CONFIG, JSON.stringify(FIXTURE_CONFIG_OBJECT, null, 2));
65
+
66
+ console.debug('[DEBUG] .ragrc.json contents:\n', JSON.stringify(FIXTURE_CONFIG_OBJECT, null, 2));
67
+ console.debug('[SETUP] Temp dir:', TEMP_TEST_DIR);
68
+ console.debug('[SETUP] Contents:', readdirSync(TEMP_TEST_DIR));
69
+ });
70
+
71
+ afterAll(() => {
72
+ rmSync(TEMP_TEST_DIR, { recursive: true, force: true });
73
+ });
74
+
75
+ test('validates plugin sections contain resolvable names', () => {
76
+ const config = JSON.parse(readFileSync(FIXTURE_CONFIG, 'utf-8'));
77
+ ['loader', 'embedder', 'retriever', 'llm'].forEach(type => {
78
+ expect(typeof config[type]).toBe('object');
79
+ expect(Object.keys(config[type]).length).toBeGreaterThan(0);
80
+ });
81
+ });
82
+
13
83
  test('executes CLI ingest using config fallback', () => {
14
- const result = execSync(
15
- `node ${CLI_PATH} ingest ./__tests__/fixtures/sample.pdf --loader pdf --embedder openai --retriever pinecone`,
16
- { encoding: 'utf-8' }
17
- );
18
- expect(result).toContain('Ingestion complete');
84
+ const result = execSync(`node ${CLI_PATH} ingest sample.pdf`, {
85
+ cwd: TEMP_TEST_DIR,
86
+ encoding: 'utf-8'
87
+ });
88
+ expect(result).toMatch(/Ingestion complete/);
19
89
  });
20
90
 
21
91
  test('executes CLI query using config fallback', () => {
22
- const result = execSync(
23
- `node ${CLI_PATH} query "What is this test about?" --embedder openai --retriever pinecone --llm openai-gpt-4`,
24
- { encoding: 'utf-8' }
25
- );
26
- // Adjust expectation to check for a known part of the generated answer
27
- expect(result).toContain('Generated answer using: Chunk about pine trees');
92
+ const result = execSync(`node ${CLI_PATH} query "What is this test about?"`, {
93
+ cwd: TEMP_TEST_DIR,
94
+ encoding: 'utf-8'
95
+ });
96
+ expect(result).toMatch(/Answer:/);
28
97
  });
29
98
  });
@@ -1,52 +1,67 @@
1
1
  /**
2
- * Version: 0.1.0
3
- * Path: /__tests__/integration/config/load-config.test.js
4
- * Description: Integration tests for loading and validating .ragrc.json config
2
+ * Version: 1.1.1
3
+ * Description: Integration tests for loading and validating .ragrc.json via loadRagConfig()
5
4
  * Author: Ali Kahwaji
5
+ * File: __tests__/integration/config/load-config.test.js
6
6
  */
7
7
 
8
8
  import fs from 'fs';
9
- import path from 'path';
9
+ import { copyFileSync, mkdirSync, rmSync, existsSync } from 'fs';
10
+ import { join, resolve } from 'path';
11
+ import { fileURLToPath } from 'url';
10
12
  import { loadRagConfig } from '../../../src/config/load-config.js';
11
13
 
12
- const FIXTURE_PATH = path.resolve(process.cwd(), '.ragrc.json');
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = resolve(__filename, '..');
13
16
 
14
- const validConfig = {
15
- loader: 'pdf',
16
- embedder: 'openai',
17
- retriever: 'pinecone',
18
- llm: 'openai-gpt-4',
19
- chunk: {
20
- strategy: 'semantic',
21
- size: 500
22
- }
23
- };
17
+ // Fixture paths
18
+ const FIXTURE_DIR = resolve(__dirname, '../../fixtures');
19
+ const VALID_FIXTURE = join(FIXTURE_DIR, 'valid-ragrc.json');
20
+ const INVALID_FIXTURE = join(FIXTURE_DIR, 'invalid-ragrc.json');
24
21
 
25
- const invalidConfig = {
26
- loader: 'pdf',
27
- chunk: { size: 'large' } // Missing required keys + invalid type
28
- };
22
+ // Temp directory used during tests
23
+ const TEMP_DIR = resolve('__tests__', '__temp__', 'load-config');
24
+ const TEMP_CONFIG = join(TEMP_DIR, '.ragrc.json');
29
25
 
30
- describe('loadRagConfig()', () => {
31
- afterEach(() => {
32
- if (fs.existsSync(FIXTURE_PATH)) {
33
- fs.unlinkSync(FIXTURE_PATH);
26
+ // Ensure fixtures are available before copying
27
+ function assertFixturesExist() {
28
+ const required = [VALID_FIXTURE, INVALID_FIXTURE];
29
+ required.forEach(file => {
30
+ if (!existsSync(file)) {
31
+ throw new Error(`โŒ Required test fixture missing: ${file}`);
34
32
  }
35
33
  });
34
+ }
35
+
36
+ describe('loadRagConfig()', () => {
37
+ beforeAll(() => {
38
+ rmSync(TEMP_DIR, { recursive: true, force: true });
39
+ mkdirSync(TEMP_DIR, { recursive: true });
40
+
41
+ assertFixturesExist();
42
+ });
43
+
44
+ afterAll(() => {
45
+ rmSync(TEMP_DIR, { recursive: true, force: true });
46
+ });
36
47
 
37
48
  test('loads a valid .ragrc.json config file successfully', () => {
38
- fs.writeFileSync(FIXTURE_PATH, JSON.stringify(validConfig, null, 2));
39
- const result = loadRagConfig();
40
- expect(result).toMatchObject(validConfig);
49
+ copyFileSync(VALID_FIXTURE, TEMP_CONFIG);
50
+
51
+ const config = loadRagConfig(TEMP_CONFIG);
52
+ expect(config).toBeDefined();
53
+ expect(config.namespace).toBe('cli-config-test');
54
+ expect(config.loader?.pdf).toBe('./src/mocks/pdf-loader.js');
55
+ expect(config.pipeline).toEqual(expect.arrayContaining(['loader', 'embedder', 'retriever']));
41
56
  });
42
57
 
43
58
  test('throws validation error for invalid .ragrc.json config', () => {
44
- fs.writeFileSync(FIXTURE_PATH, JSON.stringify(invalidConfig, null, 2));
45
- expect(() => loadRagConfig()).toThrow(/Invalid config/);
59
+ copyFileSync(INVALID_FIXTURE, TEMP_CONFIG);
60
+ expect(() => loadRagConfig(TEMP_CONFIG)).toThrow(/Config validation failed/);
46
61
  });
47
62
 
48
63
  test('throws error if config file does not exist', () => {
49
- expect(() => loadRagConfig()).toThrow(/Config file not found/);
64
+ const NON_EXISTENT = join(TEMP_DIR, 'missing.json');
65
+ expect(() => loadRagConfig(NON_EXISTENT)).toThrow(/Config file not found/);
50
66
  });
51
67
  });
52
-
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Version: 1.1.0
3
+ * Description: Schema validation tests for .ragrc.json using AJV and external fixtures
4
+ * Author: Ali Kahwaji
5
+ * File: __tests__/unit/config/validate-schema.test.js
6
+ */
7
+
8
+ import { readFileSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { validateRagrcSchema, validatePluginSchema } from '../../../src/config/validate-schema.js';
11
+
12
+ const readFixture = (name) => {
13
+ const filePath = resolve('__tests__/fixtures',name.replace('.ragrc', '-ragrc'));
14
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
15
+ };
16
+
17
+ describe('validateRagrcSchema', () => {
18
+ test('passes with valid structured plugin schema', () => {
19
+ const config = readFixture('valid-ragrc.json');
20
+ const result = validateRagrcSchema(config);
21
+ expect(result.valid).toBe(true);
22
+ expect(result.errors).toBeUndefined(); // updated: null => undefined
23
+ });
24
+
25
+ test('fails with missing pipeline', () => {
26
+ const config = {
27
+ loader: { pdf: './src/mocks/pdf-loader.js' },
28
+ namespace: 'no-pipeline'
29
+ };
30
+ const result = validateRagrcSchema(config);
31
+ expect(result.valid).toBe(false);
32
+ expect(result.errors).toEqual(
33
+ expect.arrayContaining([
34
+ expect.objectContaining({
35
+ keyword: 'required',
36
+ params: expect.objectContaining({ missingProperty: 'pipeline' })
37
+ })
38
+ ])
39
+ );
40
+ });
41
+
42
+ test('fails with non-object plugin sections', () => {
43
+ const config = {
44
+ loader: './src/invalid.js', // should be object
45
+ embedder: { openai: './src/mocks/openai-embedder.js' },
46
+ retriever: { pinecone: './src/mocks/pinecone-retriever.js' },
47
+ llm: { openai: './src/mocks/openai-llm.js' },
48
+ pipeline: ['loader'],
49
+ namespace: 'invalid-section'
50
+ };
51
+ const result = validateRagrcSchema(config);
52
+ expect(result.valid).toBe(false);
53
+ expect(result.errors).toEqual(
54
+ expect.arrayContaining([
55
+ expect.objectContaining({ instancePath: '/loader' })
56
+ ])
57
+ );
58
+ });
59
+
60
+ test('fails with non-string namespace', () => {
61
+ const config = {
62
+ loader: { pdf: './src/mocks/pdf-loader.js' },
63
+ embedder: { openai: './src/mocks/openai-embedder.js' },
64
+ retriever: { pinecone: './src/mocks/pinecone-retriever.js' },
65
+ llm: { openai: './src/mocks/openai-llm.js' },
66
+ pipeline: ['loader'],
67
+ namespace: 12345
68
+ };
69
+ const result = validateRagrcSchema(config);
70
+ expect(result.valid).toBe(false);
71
+ expect(result.errors).toEqual(
72
+ expect.arrayContaining([
73
+ expect.objectContaining({ instancePath: '/namespace' })
74
+ ])
75
+ );
76
+ });
77
+ });
78
+
79
+ describe('validatePluginSchema (subset)', () => {
80
+ test('passes with plugin-only valid structure', () => {
81
+ const config = {
82
+ loader: { pdf: './src/mocks/pdf-loader.js' },
83
+ embedder: { openai: './src/mocks/openai-embedder.js' },
84
+ retriever: { pinecone: './src/mocks/pinecone-retriever.js' },
85
+ llm: { openai: './src/mocks/openai-llm.js' }
86
+ };
87
+ const result = validatePluginSchema(config);
88
+ expect(result.valid).toBe(true);
89
+ });
90
+
91
+ test('fails when plugin section is missing', () => {
92
+ const config = {
93
+ embedder: { openai: './mock.js' },
94
+ retriever: { pinecone: './mock.js' },
95
+ llm: { openai: './mock.js' }
96
+ };
97
+ const result = validatePluginSchema(config);
98
+ expect(result.valid).toBe(false);
99
+ expect(result.errors).toEqual(
100
+ expect.arrayContaining([
101
+ expect.objectContaining({ instancePath: '', message: expect.stringContaining('must have required property') })
102
+ ])
103
+ );
104
+ });
105
+
106
+ test('fails when plugin paths are not strings', () => {
107
+ const config = {
108
+ loader: { pdf: 12345 },
109
+ embedder: { openai: './mock.js' },
110
+ retriever: { pinecone: './mock.js' },
111
+ llm: { openai: './mock.js' }
112
+ };
113
+ const result = validatePluginSchema(config);
114
+ expect(result.valid).toBe(false);
115
+ expect(result.errors).toEqual(
116
+ expect.arrayContaining([
117
+ expect.objectContaining({ instancePath: '/loader/pdf' })
118
+ ])
119
+ );
120
+ });
121
+ });
@@ -1,42 +1,18 @@
1
1
  /**
2
- * Version: 0.1.0
3
- * Path: /__tests__/unit/reranker/llm-reranker.test.js
4
- * Description: Unit tests for LLM-based context reranker
5
- * Author: Ali Kahwaji
2
+ * File: __tests__/unit/reranker/llm-reranker.test.js
3
+ * Version: 1.0.0
4
+ * Description: Validates core behavior of the LLMReranker module
5
+ * Author: @devilsdev
6
6
  */
7
7
 
8
8
  import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
9
9
 
10
10
  describe('LLMReranker', () => {
11
- const mockDocs = [
12
- { text: 'context about math' },
13
- { text: 'context about vectors' },
14
- { text: 'context about nothing' }
15
- ];
16
-
17
11
  test('returns reranked documents based on mock LLM JSON output', async () => {
18
- const mockLLM = {
19
- async generate() {
20
- return '[1, 0, 2]';
21
- }
22
- };
23
- const reranker = new LLMReranker({ llm: mockLLM });
24
- const result = await reranker.rerank('What are vectors?', mockDocs);
25
- expect(result.map(d => d.text)).toEqual([
26
- 'context about vectors',
27
- 'context about math',
28
- 'context about nothing'
29
- ]);
30
- });
31
-
32
- test('gracefully falls back if LLM returns invalid JSON', async () => {
33
- const mockLLM = {
34
- async generate() {
35
- return 'this is not JSON';
36
- }
37
- };
12
+ const mockLLM = { async generate() { return '[1, 0, 2]'; } };
13
+ const docs = [{ text: 'A' }, { text: 'B' }, { text: 'C' }];
38
14
  const reranker = new LLMReranker({ llm: mockLLM });
39
- const result = await reranker.rerank('Explain.', mockDocs);
40
- expect(result.length).toBe(0);
15
+ const result = await reranker.rerank('What is A?', docs);
16
+ expect(result.map(d => d.text)).toEqual(['B', 'A', 'C']);
41
17
  });
42
- });
18
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * File: __tests__/unit/reranker/reranker.enriched.test.js
3
+ * Version: 1.0.0
4
+ * Description: Tests support for extended document structures
5
+ * Author: @devilsdev
6
+ */
7
+ import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
8
+
9
+ test('supports documents with metadata', async () => {
10
+ const mockLLM = { async generate() { return '[2, 0, 1]'; } };
11
+ const docs = [
12
+ { text: 'One', source: 'alpha' },
13
+ { text: 'Two', source: 'beta' },
14
+ { text: 'Three', source: 'gamma' }
15
+ ];
16
+ const reranker = new LLMReranker({ llm: mockLLM });
17
+ const result = await reranker.rerank('Choose the most complete', docs);
18
+ expect(result[0]).toHaveProperty('source', 'gamma');
19
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * File: __tests__/unit/reranker/reranker.fallback.test.js
3
+ * Version: 1.0.0
4
+ * Description: Validates fallback handling for invalid LLM output
5
+ * Author: @devilsdev
6
+ */
7
+ import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
8
+
9
+ test('gracefully handles invalid LLM output', async () => {
10
+ const mockLLM = { async generate() { return 'INVALID_JSON'; } };
11
+ const docs = [{ text: 'X' }, { text: 'Y' }, { text: 'Z' }];
12
+ const reranker = new LLMReranker({ llm: mockLLM });
13
+ const result = await reranker.rerank('Prompt?', docs);
14
+ expect(result.map(d => d.text)).toEqual(['X', 'Y', 'Z']);
15
+ });
@@ -1,17 +1,15 @@
1
+ /**
2
+ * File: __tests__/unit/reranker/reranker.snapshot.test.js
3
+ * Version: 1.0.0
4
+ * Description: Captures a snapshot baseline of reranked results
5
+ * Author: @devilsdev
6
+ */
1
7
  import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
2
8
 
3
9
  test('LLMReranker output snapshot', async () => {
4
- const mockLLM = {
5
- async generate() {
6
- return '[1, 0, 2]';
7
- }
8
- };
9
- const mockDocs = [
10
- { text: 'A' },
11
- { text: 'B' },
12
- { text: 'C' }
13
- ];
10
+ const mockLLM = { async generate() { return '[1, 0, 2]'; } };
11
+ const mockDocs = [{ text: 'A' }, { text: 'B' }, { text: 'C' }];
14
12
  const reranker = new LLMReranker({ llm: mockLLM });
15
13
  const result = await reranker.rerank('What is A?', mockDocs);
16
14
  expect(result).toMatchSnapshot();
17
- });
15
+ });