From dd019cf36f12e662295f64ff6b1d552470fc585b Mon Sep 17 00:00:00 2001 From: bendo-eXX Date: Thu, 30 Jul 2026 13:44:32 +0200 Subject: [PATCH 1/3] feat(CSAF2.1): add informativeTest_6_3_8.js --- csaf_2_1/informativeTests.js | 2 +- .../informativeTests/informativeTest_6_3_8.js | 188 ++++++++++++++++++ tests/csaf_2_1/informativeTest_6_3_8.js | 132 ++++++++++++ 3 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 csaf_2_1/informativeTests/informativeTest_6_3_8.js create mode 100644 tests/csaf_2_1/informativeTest_6_3_8.js diff --git a/csaf_2_1/informativeTests.js b/csaf_2_1/informativeTests.js index 22d3a07d..67378ecc 100644 --- a/csaf_2_1/informativeTests.js +++ b/csaf_2_1/informativeTests.js @@ -2,7 +2,6 @@ export { informativeTest_6_3_3, informativeTest_6_3_6, informativeTest_6_3_7, - informativeTest_6_3_8, informativeTest_6_3_9, informativeTest_6_3_10, informativeTest_6_3_11, @@ -11,6 +10,7 @@ export { informativeTest_6_3_1 } from './informativeTests/informativeTest_6_3_1. export { informativeTest_6_3_2 } from './informativeTests/informativeTest_6_3_2.js' export { informativeTest_6_3_4 } from './informativeTests/informativeTest_6_3_4.js' export { informativeTest_6_3_5 } from './informativeTests/informativeTest_6_3_5.js' +export { informativeTest_6_3_8 } from './informativeTests/informativeTest_6_3_8.js' export { informativeTest_6_3_12 } from './informativeTests/informativeTest_6_3_12.js' export { informativeTest_6_3_18 } from './informativeTests/informativeTest_6_3_18.js' export { informativeTest_6_3_21_1 } from './informativeTests/informativeTest_6_3_21_1.js' diff --git a/csaf_2_1/informativeTests/informativeTest_6_3_8.js b/csaf_2_1/informativeTests/informativeTest_6_3_8.js new file mode 100644 index 00000000..edd25612 --- /dev/null +++ b/csaf_2_1/informativeTests/informativeTest_6_3_8.js @@ -0,0 +1,188 @@ +import { Ajv } from 'ajv/dist/jtd.js' +import { execFile } from 'node:child_process' +import bcp47 from 'bcp47' +import { walkPath } from '../../lib/walkPaths.js' + +const ajv = new Ajv() + +const inputSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + document: { + additionalProperties: true, + properties: { + lang: { type: 'string' }, + }, + }, + }, +}) +const validateInput = ajv.compile(inputSchema) + +// Profile categories defined in CSAF 2.1 ยง4 that are exempt from +// document/category spell-checking (they are machine-readable identifiers, +// not natural-language text). +const PROFILE_CATEGORIES = new Set([ + 'csaf_base', + 'csaf_deprecated_security_advisory', + 'csaf_informational_advisory', + 'csaf_security_advisory', + 'csaf_security_incident_response', + 'csaf_superseded', + 'csaf_vex', + 'csaf_withdrawn', +]) + +// Spell-checking regexes for hunspell output. +const HUNSPELL_SUGGESTION_RE = /^& (\S+)/ +const HUNSPELL_MISS_RE = /^# (\S+)/ + +/** + * @param {any} doc + * @param {object} [params] + * @param {typeof runHunspell} params.hunspell + */ +export async function informativeTest_6_3_8( + doc, + params = { hunspell: runHunspell } +) { + const ctx = { + infos: /** @type {Array<{ message: string; instancePath: string }>} */ ([]), + } + + if (!validateInput(doc)) { + return ctx + } + + const lang = bcp47.parse(doc.document.lang) + if (!lang?.langtag.language.language) return ctx + const dictionary = `${lang.langtag.language.language}${ + typeof lang.langtag.region === 'string' ? `_${lang.langtag.region}` : '' + }` + + try { + await params.hunspell({ dictionary, input: '' }) + } catch { + ctx.infos.push({ + instancePath: '/document/lang', + message: `language "${doc.document.lang}" is not supported`, + }) + return ctx + } + + for (const path of [ + '/document/acknowledgments[]/names[]', + '/document/acknowledgments[]/organization', + '/document/acknowledgments[]/summary', + '/document/aggregate_severity/text', + '/document/distribution/text', + '/document/notes[]/audience', + '/document/notes[]/text', + '/document/notes[]/title', + '/document/publisher/issuing_authority', + '/document/publisher/name', + '/document/references[]/summary', + '/document/title', + '/document/tracking/aliases[]', + '/document/tracking/generator/engine/name', + '/document/tracking/revision_history[]/summary', + '/product_tree/branches[*]/name', + '/product_tree/branches[*]/product/name', + '/product_tree/full_product_names[]/name', + '/product_tree/product_groups[]/summary', + '/product_tree/product_paths[]/full_product_name/name', + '/vulnerabilities[]/acknowledgments[]/names[]', + '/vulnerabilities[]/acknowledgments[]/organization', + '/vulnerabilities[]/acknowledgments[]/summary', + '/vulnerabilities[]/involvements[]/summary', + '/vulnerabilities[]/notes[]/audience', + '/vulnerabilities[]/notes[]/text', + '/vulnerabilities[]/notes[]/title', + '/vulnerabilities[]/references[]/summary', + '/vulnerabilities[]/remediations[]/details', + '/vulnerabilities[]/remediations[]/entitlements[]', + '/vulnerabilities[]/remediations[]/restart_required/details', + '/vulnerabilities[]/threats[]/details', + '/vulnerabilities[]/title', + ]) { + await walkPath(doc, path, async (instancePath, value) => { + if (typeof value === 'string') { + await checkField(instancePath, value) + } + }) + } + + if ( + typeof doc.document?.category === 'string' && + !PROFILE_CATEGORIES.has(doc.document.category) + ) { + await checkField('/document/category', doc.document.category) + } + + /** + * Check a single text field for spelling mistakes and add an info message to + * the context if any mistakes are found. + * @param {string} instancePath + * @param {string} text + */ + async function checkField(instancePath, text) { + const result = await spellCheckString({ + text, + dictionary, + hunspell: params.hunspell, + }) + if (!result.ok) { + ctx.infos.push({ + instancePath, + message: `there are spelling mistakes in: ${result.mistakes + .map((m) => m.word) + .join(', ')}`, + }) + } + } + + return ctx +} + +/** + * Spell-check a string using hunspell and return the list of mistakes. + * @param {object} params + * @param {(params: { dictionary: string; input: string }) => Promise} params.hunspell + * @param {string} params.text + * @param {string} params.dictionary + */ +async function spellCheckString({ text, dictionary, hunspell }) { + /** @type {string} */ + const result = await hunspell({ dictionary, input: text }) + const lines = result.split('\n').slice(1) + const errors = lines + .filter((l) => l.startsWith('# ') || l.startsWith('& ')) + .map((l) => { + if (l.startsWith('& ')) { + const regexR = HUNSPELL_SUGGESTION_RE.exec(l) + if (!regexR) throw new Error('Error while parsing hunspell output') + return { word: regexR[1] } + } else { + const regexR = HUNSPELL_MISS_RE.exec(l) + if (!regexR) throw new Error('Error while parsing hunspell output') + return { word: regexR[1] } + } + }) + return { mistakes: errors, ok: !errors.length } +} + +/** + * Spell-check a string using hunspell and return the raw output. + * @param {object} params + * @param {string} params.dictionary + * @param {string} params.input + * @returns + */ +async function runHunspell({ dictionary, input }) { + return await new Promise((resolve, reject) => { + const child = execFile('hunspell', ['-d', dictionary], (err, stdout) => { + if (err) return reject(err) + resolve(stdout) + }) + child.stdin?.end(input) + }) +} diff --git a/tests/csaf_2_1/informativeTest_6_3_8.js b/tests/csaf_2_1/informativeTest_6_3_8.js new file mode 100644 index 00000000..a10f7e35 --- /dev/null +++ b/tests/csaf_2_1/informativeTest_6_3_8.js @@ -0,0 +1,132 @@ +import assert from 'node:assert' +import { informativeTest_6_3_8 } from '../../csaf_2_1/informativeTests/informativeTest_6_3_8.js' + +describe('informativeTest_6_3_8', function () { + it('only runs on relevant documents', async function () { + const result = await informativeTest_6_3_8({ document: 'mydoc' }) + assert.equal(result.infos.length, 0) + }) + + it('skips documents without a resolvable language tag', async function () { + const result = await informativeTest_6_3_8({ + document: { lang: 'not-a-valid-tag!!' }, + }) + assert.equal(result.infos.length, 0) + }) + + it('skips document/category when it is a known profile category', async function () { + const result = await informativeTest_6_3_8( + { + document: { + lang: 'en', + category: 'csaf_base', + }, + }, + { + hunspell: async () => 'Hunspell v1\n\n# ignored 0', + } + ) + assert.equal(result.infos.length, 0) + }) + + it('checks document/category when it is not a known profile category and reports misspellings without suggestions', async function () { + const result = await informativeTest_6_3_8( + { + document: { + lang: 'en', + category: 'my_custom_category', + }, + }, + { + hunspell: async () => 'Hunspell v1\n\n# custm 0', + } + ) + assert.equal(result.infos.length, 1) + assert.equal(result.infos[0].instancePath, '/document/category') + assert.match(result.infos[0].message, /custm/) + }) + + it('reports misspelled words that have suggestions', async function () { + const result = await informativeTest_6_3_8( + { + document: { + lang: 'en', + category: 'my_custom_category', + }, + }, + { + hunspell: async () => 'Hunspell v1\n\n& custm 1 0: custom', + } + ) + assert.equal(result.infos.length, 1) + assert.equal(result.infos[0].instancePath, '/document/category') + assert.match(result.infos[0].message, /custm/) + }) + + it('builds the dictionary name from language and region subtags', async function () { + /** @type {string[]} */ + const dictionaries = [] + await informativeTest_6_3_8( + { + document: { + lang: 'en-US', + category: 'my_custom_category', + }, + }, + { + hunspell: async ({ dictionary }) => { + dictionaries.push(dictionary) + return 'Hunspell v1\n\n*' + }, + } + ) + assert.ok(dictionaries.length > 0) + assert.ok(dictionaries.every((d) => d === 'en_US')) + }) + + it('throws when a hunspell suggestion line cannot be parsed', async function () { + await assert.rejects( + informativeTest_6_3_8( + { + document: { + lang: 'en', + category: 'my_custom_category', + }, + }, + { + hunspell: async () => 'Hunspell v1\n\n& ', + } + ), + /Error while parsing hunspell output/ + ) + }) + + it('throws when a hunspell miss line cannot be parsed', async function () { + await assert.rejects( + informativeTest_6_3_8( + { + document: { + lang: 'en', + category: 'my_custom_category', + }, + }, + { + hunspell: async () => 'Hunspell v1\n\n# ', + } + ), + /Error while parsing hunspell output/ + ) + }) + + it('does not reject even with the real hunspell process when the dictionary is unknown', async function () { + const result = await informativeTest_6_3_8({ + document: { + lang: 'zz', + title: 'Some title text', + }, + }) + assert.equal(result.infos.length, 1) + assert.equal(result.infos[0].instancePath, '/document/lang') + assert.equal(result.infos[0].message, 'language "zz" is not supported') + }) +}) From 8d9ddbae65fc2c32172e9dd97df21e4bcf5d24c9 Mon Sep 17 00:00:00 2001 From: bendo-eXX Date: Wed, 12 Aug 2026 15:41:54 +0200 Subject: [PATCH 2/3] feat(csaf2.1): update to vitest --- .../informativeTests/informativeTest_6_3_8.js | 35 +++++--- tests/csaf_2_1/informativeTest_6_3_8.js | 90 ++++++++++--------- 2 files changed, 68 insertions(+), 57 deletions(-) diff --git a/csaf_2_1/informativeTests/informativeTest_6_3_8.js b/csaf_2_1/informativeTests/informativeTest_6_3_8.js index edd25612..47c83186 100644 --- a/csaf_2_1/informativeTests/informativeTest_6_3_8.js +++ b/csaf_2_1/informativeTests/informativeTest_6_3_8.js @@ -37,6 +37,8 @@ const HUNSPELL_SUGGESTION_RE = /^& (\S+)/ const HUNSPELL_MISS_RE = /^# (\S+)/ /** + * Informative test 6.3.8: Check for spelling mistakes in text fields. + * * @param {any} doc * @param {object} [params] * @param {typeof runHunspell} params.hunspell @@ -130,6 +132,13 @@ export async function informativeTest_6_3_8( dictionary, hunspell: params.hunspell, }) + if (result.parseError) { + ctx.infos.push({ + instancePath, + message: 'Error while parsing hunspell output', + }) + return + } if (!result.ok) { ctx.infos.push({ instancePath, @@ -154,20 +163,18 @@ async function spellCheckString({ text, dictionary, hunspell }) { /** @type {string} */ const result = await hunspell({ dictionary, input: text }) const lines = result.split('\n').slice(1) - const errors = lines - .filter((l) => l.startsWith('# ') || l.startsWith('& ')) - .map((l) => { - if (l.startsWith('& ')) { - const regexR = HUNSPELL_SUGGESTION_RE.exec(l) - if (!regexR) throw new Error('Error while parsing hunspell output') - return { word: regexR[1] } - } else { - const regexR = HUNSPELL_MISS_RE.exec(l) - if (!regexR) throw new Error('Error while parsing hunspell output') - return { word: regexR[1] } - } - }) - return { mistakes: errors, ok: !errors.length } + const errors = [] + for (const l of lines) { + if (!l.startsWith('# ') && !l.startsWith('& ')) continue + const regexR = l.startsWith('& ') + ? HUNSPELL_SUGGESTION_RE.exec(l) + : HUNSPELL_MISS_RE.exec(l) + if (!regexR) { + return { mistakes: [], ok: false, parseError: true } + } + errors.push({ word: regexR[1] }) + } + return { mistakes: errors, ok: !errors.length, parseError: false } } /** diff --git a/tests/csaf_2_1/informativeTest_6_3_8.js b/tests/csaf_2_1/informativeTest_6_3_8.js index a10f7e35..9c94adc2 100644 --- a/tests/csaf_2_1/informativeTest_6_3_8.js +++ b/tests/csaf_2_1/informativeTest_6_3_8.js @@ -1,17 +1,16 @@ -import assert from 'node:assert' import { informativeTest_6_3_8 } from '../../csaf_2_1/informativeTests/informativeTest_6_3_8.js' describe('informativeTest_6_3_8', function () { it('only runs on relevant documents', async function () { const result = await informativeTest_6_3_8({ document: 'mydoc' }) - assert.equal(result.infos.length, 0) + expect(result.infos.length).to.equal(0) }) it('skips documents without a resolvable language tag', async function () { const result = await informativeTest_6_3_8({ document: { lang: 'not-a-valid-tag!!' }, }) - assert.equal(result.infos.length, 0) + expect(result.infos.length).to.equal(0) }) it('skips document/category when it is a known profile category', async function () { @@ -26,7 +25,7 @@ describe('informativeTest_6_3_8', function () { hunspell: async () => 'Hunspell v1\n\n# ignored 0', } ) - assert.equal(result.infos.length, 0) + expect(result.infos.length).to.equal(0) }) it('checks document/category when it is not a known profile category and reports misspellings without suggestions', async function () { @@ -41,9 +40,9 @@ describe('informativeTest_6_3_8', function () { hunspell: async () => 'Hunspell v1\n\n# custm 0', } ) - assert.equal(result.infos.length, 1) - assert.equal(result.infos[0].instancePath, '/document/category') - assert.match(result.infos[0].message, /custm/) + expect(result.infos.length).to.equal(1) + expect(result.infos[0].instancePath).to.equal('/document/category') + expect(result.infos[0].message).to.match(/custm/) }) it('reports misspelled words that have suggestions', async function () { @@ -58,9 +57,29 @@ describe('informativeTest_6_3_8', function () { hunspell: async () => 'Hunspell v1\n\n& custm 1 0: custom', } ) - assert.equal(result.infos.length, 1) - assert.equal(result.infos[0].instancePath, '/document/category') - assert.match(result.infos[0].message, /custm/) + expect(result.infos.length).to.equal(1) + expect(result.infos[0].instancePath).to.equal('/document/category') + expect(result.infos[0].message).to.match(/custm/) + }) + + it('skips non-string values when walking text fields', async function () { + let calls = 0 + const result = await informativeTest_6_3_8( + { + document: { + lang: 'en', + title: 12345, + }, + }, + { + hunspell: async () => { + calls++ + return 'Hunspell v1\n\n' + }, + } + ) + expect(calls).to.equal(1) + expect(result.infos.length).to.equal(0) }) it('builds the dictionary name from language and region subtags', async function () { @@ -80,40 +99,25 @@ describe('informativeTest_6_3_8', function () { }, } ) - assert.ok(dictionaries.length > 0) - assert.ok(dictionaries.every((d) => d === 'en_US')) + expect(dictionaries.length).to.be.greaterThan(0) + expect(dictionaries.every((d) => d === 'en_US')).to.be.true }) - it('throws when a hunspell suggestion line cannot be parsed', async function () { - await assert.rejects( - informativeTest_6_3_8( - { - document: { - lang: 'en', - category: 'my_custom_category', - }, + it('reports an info message when a hunspell suggestion line cannot be parsed', async function () { + const result = await informativeTest_6_3_8( + { + document: { + lang: 'en', + category: 'my_custom_category', }, - { - hunspell: async () => 'Hunspell v1\n\n& ', - } - ), - /Error while parsing hunspell output/ + }, + { + hunspell: async () => 'Hunspell v1\n\n& ', + } ) - }) - - it('throws when a hunspell miss line cannot be parsed', async function () { - await assert.rejects( - informativeTest_6_3_8( - { - document: { - lang: 'en', - category: 'my_custom_category', - }, - }, - { - hunspell: async () => 'Hunspell v1\n\n# ', - } - ), + expect(result.infos.length).to.equal(1) + expect(result.infos[0].instancePath).to.equal('/document/category') + expect(result.infos[0].message).to.match( /Error while parsing hunspell output/ ) }) @@ -125,8 +129,8 @@ describe('informativeTest_6_3_8', function () { title: 'Some title text', }, }) - assert.equal(result.infos.length, 1) - assert.equal(result.infos[0].instancePath, '/document/lang') - assert.equal(result.infos[0].message, 'language "zz" is not supported') + expect(result.infos.length).to.equal(1) + expect(result.infos[0].instancePath).to.equal('/document/lang') + expect(result.infos[0].message).to.equal('language "zz" is not supported') }) }) From 7ef8f2816fb394c7e3ea999238efbe6df4867346 Mon Sep 17 00:00:00 2001 From: bendo-eXX Date: Wed, 12 Aug 2026 15:41:54 +0200 Subject: [PATCH 3/3] feat(csaf2.1): update to vitest --- .../informativeTests/informativeTest_6_3_8.js | 54 +++++------ hunspell.js | 1 + hunspell/runHunspell.js | 18 ++++ lib/informativeTests/informativeTest_6_3_8.js | 25 +----- tests/csaf_2_1/informativeTest_6_3_8.js | 90 ++++++++++--------- 5 files changed, 89 insertions(+), 99 deletions(-) create mode 100644 hunspell/runHunspell.js diff --git a/csaf_2_1/informativeTests/informativeTest_6_3_8.js b/csaf_2_1/informativeTests/informativeTest_6_3_8.js index edd25612..cadd7a55 100644 --- a/csaf_2_1/informativeTests/informativeTest_6_3_8.js +++ b/csaf_2_1/informativeTests/informativeTest_6_3_8.js @@ -1,7 +1,7 @@ import { Ajv } from 'ajv/dist/jtd.js' -import { execFile } from 'node:child_process' import bcp47 from 'bcp47' import { walkPath } from '../../lib/walkPaths.js' +import runHunspell from '../../hunspell/runHunspell.js' const ajv = new Ajv() @@ -37,6 +37,8 @@ const HUNSPELL_SUGGESTION_RE = /^& (\S+)/ const HUNSPELL_MISS_RE = /^# (\S+)/ /** + * Informative test 6.3.8: Check for spelling mistakes in text fields. + * * @param {any} doc * @param {object} [params] * @param {typeof runHunspell} params.hunspell @@ -130,6 +132,13 @@ export async function informativeTest_6_3_8( dictionary, hunspell: params.hunspell, }) + if (result.parseError) { + ctx.infos.push({ + instancePath, + message: 'Error while parsing hunspell output', + }) + return + } if (!result.ok) { ctx.infos.push({ instancePath, @@ -154,35 +163,16 @@ async function spellCheckString({ text, dictionary, hunspell }) { /** @type {string} */ const result = await hunspell({ dictionary, input: text }) const lines = result.split('\n').slice(1) - const errors = lines - .filter((l) => l.startsWith('# ') || l.startsWith('& ')) - .map((l) => { - if (l.startsWith('& ')) { - const regexR = HUNSPELL_SUGGESTION_RE.exec(l) - if (!regexR) throw new Error('Error while parsing hunspell output') - return { word: regexR[1] } - } else { - const regexR = HUNSPELL_MISS_RE.exec(l) - if (!regexR) throw new Error('Error while parsing hunspell output') - return { word: regexR[1] } - } - }) - return { mistakes: errors, ok: !errors.length } -} - -/** - * Spell-check a string using hunspell and return the raw output. - * @param {object} params - * @param {string} params.dictionary - * @param {string} params.input - * @returns - */ -async function runHunspell({ dictionary, input }) { - return await new Promise((resolve, reject) => { - const child = execFile('hunspell', ['-d', dictionary], (err, stdout) => { - if (err) return reject(err) - resolve(stdout) - }) - child.stdin?.end(input) - }) + const errors = [] + for (const l of lines) { + if (!l.startsWith('# ') && !l.startsWith('& ')) continue + const regexR = l.startsWith('& ') + ? HUNSPELL_SUGGESTION_RE.exec(l) + : HUNSPELL_MISS_RE.exec(l) + if (!regexR) { + return { mistakes: [], ok: false, parseError: true } + } + errors.push({ word: regexR[1] }) + } + return { mistakes: errors, ok: !errors.length, parseError: false } } diff --git a/hunspell.js b/hunspell.js index 6103f187..7ddaac29 100644 --- a/hunspell.js +++ b/hunspell.js @@ -1 +1,2 @@ export { default as getHunspellAvailableLangs } from './hunspell/getHunspellAvailableLangs.js' +export { default as runHunspell } from './hunspell/runHunspell.js' diff --git a/hunspell/runHunspell.js b/hunspell/runHunspell.js new file mode 100644 index 00000000..453d76e2 --- /dev/null +++ b/hunspell/runHunspell.js @@ -0,0 +1,18 @@ +/** + * Spell-check a string using hunspell and return the raw output. + * @param {object} params + * @param {string} params.dictionary + * @param {string} params.input + * @returns {Promise} + */ +export default async function runHunspell({ dictionary, input }) { + // Dynamic import: keeps this module importable outside Node. + const { execFile } = await import('node:child_process') + return await new Promise((resolve, reject) => { + const child = execFile('hunspell', ['-d', dictionary], (err, stdout) => { + if (err) return reject(err) + resolve(stdout) + }) + child.stdin?.end(input) + }) +} diff --git a/lib/informativeTests/informativeTest_6_3_8.js b/lib/informativeTests/informativeTest_6_3_8.js index 01f03ce0..db5b6c91 100644 --- a/lib/informativeTests/informativeTest_6_3_8.js +++ b/lib/informativeTests/informativeTest_6_3_8.js @@ -1,5 +1,6 @@ import { Ajv } from 'ajv/dist/jtd.js' import bcp47 from 'bcp47' +import runHunspell from '../../hunspell/runHunspell.js' const ajv = new Ajv() @@ -401,27 +402,3 @@ async function spellCheckString({ text, dictionary, hunspell }) { }) return { mistakes: errors, ok: !errors.length } } - -/** - * @param {object} params - * @param {string} params.dictionary - * @param {string} params.input - * @returns - */ -async function runHunspell({ dictionary, input }) { - // Imported dynamically (instead of a top-level static import) so this module - // - part of the informativeTests barrel export - can be imported in a browser - // without crashing. `node:child_process` has no browser equivalent, so this - // function still can't actually run there; callers are expected to skip - // invoking it outside Node (see tests/shared/isBrowserRuntime.js). - const { execFile } = await import('node:child_process') - /** @type {string} */ - const result = await new Promise((resolve, reject) => { - const child = execFile('hunspell', ['-d', dictionary], (err, stdout) => { - if (err) return reject(err) - resolve(stdout) - }) - child.stdin?.end(input) - }) - return result -} diff --git a/tests/csaf_2_1/informativeTest_6_3_8.js b/tests/csaf_2_1/informativeTest_6_3_8.js index a10f7e35..9c94adc2 100644 --- a/tests/csaf_2_1/informativeTest_6_3_8.js +++ b/tests/csaf_2_1/informativeTest_6_3_8.js @@ -1,17 +1,16 @@ -import assert from 'node:assert' import { informativeTest_6_3_8 } from '../../csaf_2_1/informativeTests/informativeTest_6_3_8.js' describe('informativeTest_6_3_8', function () { it('only runs on relevant documents', async function () { const result = await informativeTest_6_3_8({ document: 'mydoc' }) - assert.equal(result.infos.length, 0) + expect(result.infos.length).to.equal(0) }) it('skips documents without a resolvable language tag', async function () { const result = await informativeTest_6_3_8({ document: { lang: 'not-a-valid-tag!!' }, }) - assert.equal(result.infos.length, 0) + expect(result.infos.length).to.equal(0) }) it('skips document/category when it is a known profile category', async function () { @@ -26,7 +25,7 @@ describe('informativeTest_6_3_8', function () { hunspell: async () => 'Hunspell v1\n\n# ignored 0', } ) - assert.equal(result.infos.length, 0) + expect(result.infos.length).to.equal(0) }) it('checks document/category when it is not a known profile category and reports misspellings without suggestions', async function () { @@ -41,9 +40,9 @@ describe('informativeTest_6_3_8', function () { hunspell: async () => 'Hunspell v1\n\n# custm 0', } ) - assert.equal(result.infos.length, 1) - assert.equal(result.infos[0].instancePath, '/document/category') - assert.match(result.infos[0].message, /custm/) + expect(result.infos.length).to.equal(1) + expect(result.infos[0].instancePath).to.equal('/document/category') + expect(result.infos[0].message).to.match(/custm/) }) it('reports misspelled words that have suggestions', async function () { @@ -58,9 +57,29 @@ describe('informativeTest_6_3_8', function () { hunspell: async () => 'Hunspell v1\n\n& custm 1 0: custom', } ) - assert.equal(result.infos.length, 1) - assert.equal(result.infos[0].instancePath, '/document/category') - assert.match(result.infos[0].message, /custm/) + expect(result.infos.length).to.equal(1) + expect(result.infos[0].instancePath).to.equal('/document/category') + expect(result.infos[0].message).to.match(/custm/) + }) + + it('skips non-string values when walking text fields', async function () { + let calls = 0 + const result = await informativeTest_6_3_8( + { + document: { + lang: 'en', + title: 12345, + }, + }, + { + hunspell: async () => { + calls++ + return 'Hunspell v1\n\n' + }, + } + ) + expect(calls).to.equal(1) + expect(result.infos.length).to.equal(0) }) it('builds the dictionary name from language and region subtags', async function () { @@ -80,40 +99,25 @@ describe('informativeTest_6_3_8', function () { }, } ) - assert.ok(dictionaries.length > 0) - assert.ok(dictionaries.every((d) => d === 'en_US')) + expect(dictionaries.length).to.be.greaterThan(0) + expect(dictionaries.every((d) => d === 'en_US')).to.be.true }) - it('throws when a hunspell suggestion line cannot be parsed', async function () { - await assert.rejects( - informativeTest_6_3_8( - { - document: { - lang: 'en', - category: 'my_custom_category', - }, + it('reports an info message when a hunspell suggestion line cannot be parsed', async function () { + const result = await informativeTest_6_3_8( + { + document: { + lang: 'en', + category: 'my_custom_category', }, - { - hunspell: async () => 'Hunspell v1\n\n& ', - } - ), - /Error while parsing hunspell output/ + }, + { + hunspell: async () => 'Hunspell v1\n\n& ', + } ) - }) - - it('throws when a hunspell miss line cannot be parsed', async function () { - await assert.rejects( - informativeTest_6_3_8( - { - document: { - lang: 'en', - category: 'my_custom_category', - }, - }, - { - hunspell: async () => 'Hunspell v1\n\n# ', - } - ), + expect(result.infos.length).to.equal(1) + expect(result.infos[0].instancePath).to.equal('/document/category') + expect(result.infos[0].message).to.match( /Error while parsing hunspell output/ ) }) @@ -125,8 +129,8 @@ describe('informativeTest_6_3_8', function () { title: 'Some title text', }, }) - assert.equal(result.infos.length, 1) - assert.equal(result.infos[0].instancePath, '/document/lang') - assert.equal(result.infos[0].message, 'language "zz" is not supported') + expect(result.infos.length).to.equal(1) + expect(result.infos[0].instancePath).to.equal('/document/lang') + expect(result.infos[0].message).to.equal('language "zz" is not supported') }) })