Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 2 additions & 1 deletion ai-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"debug": "nodemon dist/index.js",
"dev:docker": "nodemon .",
"dev": "tsc -w",
"start": "node dist/index.js"
"start": "node dist/index.js",
"test": "mocha tests/**/*.test.mjs --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ai-service.xml --exit"
},
"devDependencies": {
"@types/glob": "^8.1.0",
Expand Down
42 changes: 42 additions & 0 deletions ai-service/tests/api-response.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import assert from 'node:assert/strict';
import { ApiResponse, ApiResponseSubscribe } from '../dist/helpers/api-response.js';
import { AISuggestionService } from '../dist/helpers/suggestions.js';

const origRegister = AISuggestionService.prototype.registerListener;
const origSubscribe = AISuggestionService.prototype.subscribe;

afterEach(() => {
AISuggestionService.prototype.registerListener = origRegister;
AISuggestionService.prototype.subscribe = origSubscribe;
});

describe('ApiResponse', () => {
it('registers a listener whose wrapper delegates to the handler', async () => {
let captured = null;
AISuggestionService.prototype.registerListener = function (event, cb) {
captured = { event, cb };
};
const handler = async (msg) => ({ echoed: msg });
ApiResponse('EVENT_A', handler);
assert.equal(captured.event, 'EVENT_A');
const result = await captured.cb({ payload: 1 });
assert.deepEqual(result, { echoed: { payload: 1 } });
});
});

describe('ApiResponseSubscribe', () => {
it('subscribes with a wrapper that awaits the handler', async () => {
let captured = null;
let handled = null;
AISuggestionService.prototype.subscribe = function (event, cb) {
captured = { event, cb };
};
const handler = async (msg) => {
handled = msg;
};
ApiResponseSubscribe('EVENT_B', handler);
assert.equal(captured.event, 'EVENT_B');
await captured.cb({ value: 2 });
assert.deepEqual(handled, { value: 2 });
});
});
8 changes: 8 additions & 0 deletions ai-service/tests/config.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import assert from 'node:assert/strict';

describe('config bootstrap', () => {
it('loads without throwing and applies dotenv', async () => {
const mod = await import('../dist/config.js');
assert.ok(mod);
});
});
177 changes: 177 additions & 0 deletions ai-service/tests/files-manager-helper.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import assert from 'node:assert/strict';
import { FilesManager } from '../dist/helpers/files-manager-helper.js';

const policy = (overrides = {}) => ({
_id: { toString: () => 'p1' },
name: 'Methodology Alpha',
topicDescription: undefined,
description: undefined,
typicalProjects: undefined,
applicabilityConditions: undefined,
importantParameters: undefined,
categories: [],
...overrides,
});

describe('FilesManager.wordsCount', () => {
it('counts whitespace-separated words', () => {
assert.equal(FilesManager.wordsCount('one two three'), 3);
});

it('returns 0 for an empty/whitespace-only string', () => {
assert.equal(FilesManager.wordsCount(''), 0);
assert.equal(FilesManager.wordsCount(' '), 0);
});

it('collapses multiple separators', () => {
assert.equal(FilesManager.wordsCount('a b\t\nc'), 3);
});
});

describe('FilesManager.getFileName', () => {
it('joins dir + name with .txt suffix', () => {
assert.equal(FilesManager.getFileName('/tmp/out', 'M1'), '/tmp/out/M1.txt');
});
});

describe('FilesManager.getNameByCategoryType', () => {
it('maps known category types to human labels', () => {
assert.equal(
FilesManager.getNameByCategoryType('APPLIED_TECHNOLOGY_TYPE'),
'Categorization Methodologies by Applied Technology Type/Measure'
);
assert.equal(
FilesManager.getNameByCategoryType('SECTORAL_SCOPE'),
'Methodologies Sectoral Scope Name'
);
assert.equal(
FilesManager.getNameByCategoryType('SUB_TYPE'),
'Categorization Methodologies by Sub Type'
);
});

it('returns empty string for unknown types', () => {
assert.equal(FilesManager.getNameByCategoryType('NOT_A_TYPE'), '');
assert.equal(FilesManager.getNameByCategoryType(undefined), '');
});
});

describe('FilesManager.getCategoryRowByType', () => {
const cats = [
{ id: 'c1', type: 'PROJECT_SCALE', name: 'Small' },
{ id: 'c2', type: 'SECTORAL_SCOPE', name: 'Energy' },
];

it('returns a formatted row when the policy has a matching category', () => {
const row = FilesManager.getCategoryRowByType(
policy({ categories: ['c1'] }),
cats,
'PROJECT_SCALE',
'methodology by scale type'
);
assert.equal(row, '\n Methodology Alpha methodology by scale type: Small \n');
});

it('returns "" when policy has no categories', () => {
const row = FilesManager.getCategoryRowByType(policy(), cats, 'PROJECT_SCALE', 'x');
assert.equal(row, '');
});

it('returns "" when no category of the requested type matches', () => {
const row = FilesManager.getCategoryRowByType(
policy({ categories: ['c2'] }),
cats,
'PROJECT_SCALE',
'x'
);
assert.equal(row, '');
});
});

describe('FilesManager.getFileData', () => {
it('returns "" for a policy with no descriptive content', () => {
const result = FilesManager.getFileData(policy(), [], []);
assert.equal(result, '');
});

it('prepends "Methodology name: <name>" when there is content', () => {
const result = FilesManager.getFileData(
policy({ description: 'About methodology' }),
[],
[]
);
assert.ok(result.startsWith('Methodology name: Methodology Alpha\n'));
assert.ok(result.includes('About methodology'));
});

it('appends typicalProjects, applicabilityConditions, and importantParameters', () => {
const result = FilesManager.getFileData(
policy({
typicalProjects: 'forestry',
applicabilityConditions: 'permit',
importantParameters: { atValidation: 'X', monitored: 'Y' },
}),
[],
[]
);
assert.ok(result.includes('Typical projects:'));
assert.ok(result.includes('forestry'));
assert.ok(result.includes('Important conditions'));
assert.ok(result.includes('At validation:'));
assert.ok(result.includes('Monitored:'));
});

it('includes the description block', () => {
const result = FilesManager.getFileData(
policy({ description: 'Some description' }),
[],
['Extra description with enough words to satisfy the helper.']
);
assert.ok(result.includes('Some description'));
assert.ok(result.includes('Extra description'));
});

it('parenthesizes the topicDescription on the methodology name line', () => {
const result = FilesManager.getFileData(
policy({ description: 'd', topicDescription: 'subtopic' }),
[],
[]
);
assert.ok(result.startsWith('Methodology name: Methodology Alpha (subtopic)'));
});
});

describe('FilesManager.getMetadataContent', () => {
const categories = [
{ id: 'c1', type: 'PROJECT_SCALE', name: 'Small' },
{ id: 'c2', type: 'PROJECT_SCALE', name: 'Large' },
{ id: 'c3', type: 'SECTORAL_SCOPE', name: 'Energy' },
];

it('returns "" when no policies match any category', () => {
const result = FilesManager.getMetadataContent([], categories);
assert.equal(result, '');
});

it('groups policies under their category headers', () => {
const policies = [
policy({ name: 'Pa', categories: ['c1'] }),
policy({ name: 'Pb', categories: ['c2'] }),
policy({ name: 'Pc', categories: ['c3'] }),
];
const result = FilesManager.getMetadataContent(policies, categories);
assert.ok(result.includes('Categorization Methodologies by Scale'));
assert.ok(result.includes('Methodologies Sectoral Scope Name'));
assert.ok(result.includes('Small: Pa'));
assert.ok(result.includes('Large: Pb'));
assert.ok(result.includes('Energy: Pc'));
});

it('skips categories that no policy maps to', () => {
const policies = [policy({ name: 'Pa', categories: ['c1'] })];
const result = FilesManager.getMetadataContent(policies, categories);
assert.ok(result.includes('Small: Pa'));
assert.ok(!result.includes('Large:'));
assert.ok(!result.includes('Energy:'));
});
});
Loading
Loading