test: добавить тесты для matcher.js и aiAdapter.js

This commit is contained in:
zhumabekovmerei 2026-09-16 15:31:05 +00:00
parent f07ae80874
commit f6ca766531

172
test.js Normal file
View File

@ -0,0 +1,172 @@
/**
* Тесты для matcher.js и aiAdapter.js
* Запуск: node test.js
*/
const aiAdapter = require('./aiAdapter.js');
const matcher = require('./matcher.js');
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(`✓ ${name}`);
passed++;
} catch (error) {
console.error(`✗ ${name}`);
console.error(` ${error.message}`);
failed++;
}
}
function assert(condition, message) {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
console.log('Тесты aiAdapter.js:\n');
test('Извлечение требований из текста вакансии', () => {
const text = 'Требуется JavaScript разработчик с опытом от 3 лет. Знание React обязательно.';
const result = aiAdapter.generate('extractCriteria', { vacancyText: text });
assert(result.criteria.length > 0, 'Критерии не извлечены');
assert(result.criteria.some(c => c.label.toLowerCase().includes('javascript')), 'Не найдено JavaScript');
assert(result.criteria.some(c => c.label.toLowerCase().includes('react')), 'Не найдено React');
});
test('Пустой текст вакансии', () => {
const result = aiAdapter.generate('extractCriteria', { vacancyText: '' });
assert(result.error, 'Нет ошибки для пустого текста');
});
test('Сопоставление резюме с критериями', () => {
const criteria = [
{ id: 'js', label: 'JavaScript', required: true, weight: 5, synonyms: ['js'], category: 'frontend' }
];
const resume = 'Опыт работы с JavaScript 3 года. Владею JS, React.';
const result = aiAdapter.generate('matchResume', { criteria, resumeText: resume });
assert(result.results.length === 1, 'Неверное количество результатов');
assert(result.results[0].status === 'confirmed', 'JavaScript не подтверждён');
assert(result.results[0].evidence.length > 0, 'Нет цитаты');
});
test('Отрицание не считается подтверждением', () => {
const criteria = [
{ id: 'react', label: 'React', required: true, weight: 5, synonyms: [], category: 'frontend' }
];
const resume = 'Без опыта работы с React. Не владею React.';
const result = aiAdapter.generate('matchResume', { criteria, resumeText: resume });
assert(result.results[0].status !== 'confirmed', 'Отрицание проигнорировано');
});
test('Синонимы работают', () => {
const criteria = [
{ id: 'js', label: 'JavaScript', required: true, weight: 5, synonyms: ['js', 'ecmascript'], category: 'frontend' }
];
const resume = 'Опыт коммерческой разработки на JS 2 года.';
const result = aiAdapter.generate('matchResume', { criteria, resumeText: resume });
assert(result.results[0].status === 'confirmed', 'Синоним JS не сработал');
});
test('Генерация вопросов для интервью', () => {
const unmatched = [
{ criterionId: 'nodejs', label: 'Node.js', status: 'clarify', required: true, weight: 3 }
];
const result = aiAdapter.generate('generateQuestions', { unmatchedCriteria: unmatched });
assert(result.questions.length > 0, 'Вопросы не сгенерированы');
assert(result.questions[0].question.includes('Node.js'), 'Вопрос не содержит название навыка');
});
console.log('\nТесты matcher.js:\n');
test('Расчёт покрытия', () => {
const results = [
{ status: 'confirmed', weight: 5 },
{ status: 'confirmed', weight: 3 },
{ status: 'notfound', weight: 2 }
];
const coverage = matcher.calculateCoverage(results);
assert(coverage === 80, `Неверное покрытие: ${coverage} (ожидалось 80)`);
});
test('Покрытие при пустом списке', () => {
const coverage = matcher.calculateCoverage([]);
assert(coverage === 0, `Неверное покрытие пустого списка: ${coverage}`);
});
test('Статусы сопоставления', () => {
const results = [
{ status: 'confirmed' },
{ status: 'clarify' },
{ status: 'clarify' },
{ status: 'notfound' }
];
const stats = matcher.getStatusStats(results);
assert(stats.confirmed === 1, 'Неверно confirmed');
assert(stats.clarify === 2, 'Неверно clarify');
assert(stats.notfound === 1, 'Неверно notfound');
assert(stats.total === 4, 'Неверно total');
});
test('Добавление критерия', () => {
const criteria = [];
const newCriteria = matcher.addCustomCriterion(criteria, 'Тест', true, 4, ['синоним1', 'синоним2']);
assert(newCriteria.length === 1, 'Критерий не добавлен');
assert(newCriteria[0].label === 'Тест', 'Неверная метка');
assert(newCriteria[0].weight === 4, 'Неверный вес');
assert(newCriteria[0].synonyms.length === 2, 'Неверное количество синонимов');
});
test('Удаление критерия', () => {
const criteria = [
{ id: '1', label: 'First' },
{ id: '2', label: 'Second' }
];
const result = matcher.removeCriterion(criteria, '1');
assert(result.length === 1, 'Критерий не удалён');
assert(result[0].id === '2', 'Неверный критерий удалён');
});
test('Обновление статуса', () => {
const results = [
{ criterionId: '1', status: 'notfound', note: '' }
];
const updated = matcher.updateCriterionStatus(results, '1', 'clarify', 'Нужно уточнить');
assert(updated[0].status === 'clarify', 'Статус не обновлён');
assert(updated[0].note === 'Нужно уточнить', 'Примечание не обновлено');
});
test('Экспорт сводки', () => {
const summary = matcher.exportSummary(
'Текст вакансии',
'Текст резюме',
[{ id: '1', label: 'Требование' }],
{ results: [{ label: 'Требование', required: true, weight: 5, status: 'confirmed', evidence: ['цитата'] }] },
{ questions: [{ label: 'Требование', question: 'Вопрос?', followUp: [] }] }
);
assert(summary.coverage.percentage === 100, 'Неверное покрытие');
assert(summary.results.length > 0, 'Нет результатов');
assert(summary.interviewQuestions.length > 0, 'Нет вопросов');
});
test('Форматирование экспорта в текст', () => {
const summary = {
exportedAt: new Date().toISOString(),
coverage: { percentage: 80, confirmed: 4, toClarify: 1, notFound: 0, total: 5 },
results: [{ requirement: 'JavaScript', type: 'Обязательное', weight: 5, status: 'Подтверждено', evidence: 'Опыт 3 года' }],
interviewQuestions: []
};
const text = matcher.formatExportText(summary);
assert(text.includes('HR CANDIDATE MATCHER'), 'Нет заголовка');
assert(text.includes('80%'), 'Нет процента покрытия');
assert(text.includes('JavaScript'), 'Нет требования');
});
console.log(`\n${passed} passed, ${failed} failed`);
if (failed > 0) {
process.exit(1);
}