-
Notifications
You must be signed in to change notification settings - Fork 9
feat: add informative test 6.3.16 #444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a better way to name that or a better location? In my perception, people think, when they find a docker file in the root of the repo that this is for the repo itself, not a dev dependency... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| services: | ||
| languagetool: | ||
| image: collabora/languagetool | ||
| ports: | ||
| - 8010:8010 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /** | ||
| * @typedef {object} Context | ||
| * @property {string} languageToolUrl The url to the language tool | ||
| */ | ||
|
|
||
| /** | ||
| * This is the context that is used to execute the tests. Modify it when | ||
| * initializing the library to change settings. | ||
| * | ||
| * @type {Context} | ||
| */ | ||
| export const context = { languageToolUrl: 'http://localhost:8010' } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like this is falling out of the sky. I didn't find TCP 8010 in the official documentation. What did I overlook? |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| /* | ||
| This test depends on the languagetool server to be available. See | ||
| https://languagetool.org/de. A `compose.yml` file is available in the | ||
| repository root to start an instance. | ||
| */ | ||
|
|
||
| import Ajv from 'ajv/dist/jtd.js' | ||
| import bcp47 from 'bcp47' | ||
| import { context } from '../../context.js' | ||
|
|
||
| const ajv = new Ajv() | ||
|
|
||
| const inputSchema = /** @type {const} */ ({ | ||
| additionalProperties: true, | ||
| optionalProperties: { | ||
| document: { | ||
| additionalProperties: true, | ||
| optionalProperties: { | ||
| lang: { type: 'string' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| const validateInput = ajv.compile(inputSchema) | ||
|
|
||
| /** | ||
| * If the document language is given it MUST be tested that a grammar check for | ||
| * the given language does not find any mistakes. The test SHALL be skipped if | ||
| * the document language is not set. It SHALL fail if the given language is not | ||
| * supported. | ||
| * | ||
| * @param {unknown} doc | ||
| * @returns | ||
| */ | ||
| export async function informativeTest_6_3_16(doc) { | ||
| const ctx = { | ||
| infos: /** @type {Array<{ message: string; instancePath: string }>} */ ([]), | ||
| } | ||
|
|
||
| if (!validateInput(doc)) { | ||
| return ctx | ||
| } | ||
|
|
||
| const lang = | ||
| (doc.document?.lang && | ||
| bcp47.parse(doc.document.lang)?.langtag.language.language) ?? | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we extracting the just the language and not using the whole string? |
||
| 'en' | ||
|
Comment on lines
+45
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we setting |
||
|
|
||
| /* | ||
| Check if the language is supported by the languagetool server. | ||
| */ | ||
| { | ||
| /** | ||
| * @typedef {object} Language | ||
| * @property {string} code | ||
| */ | ||
|
|
||
| /** @typedef {Language[]} Response */ | ||
|
|
||
| const res = await fetch(new URL('/v2/languages', context.languageToolUrl), { | ||
| headers: { | ||
| accept: 'application/json', | ||
| }, | ||
| }) | ||
| if (!res.ok) throw new Error('request to languagetool failed') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add into the error message here on which port / url you tried to connected to languageTool |
||
|
|
||
| const json = /** @type {Response} */ (await res.json()) | ||
|
|
||
| if (!json.some((l) => l.code === lang)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we check against |
||
| ctx.infos.push({ | ||
| instancePath: '/document/lang', | ||
| message: 'language is not supported', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add the language into the error message |
||
| }) | ||
| } | ||
| } | ||
|
|
||
| for (const path of [ | ||
| '/document/acknowledgments[]/summary', | ||
| '/document/aggregate_severity/text', | ||
| '/document/distribution/text', | ||
| '/document/notes[]/audience', | ||
| '/document/notes[]/text', | ||
| '/document/notes[]/title', | ||
| '/document/publisher/issuing_authority', | ||
| '/document/references[]/summary', | ||
| '/document/title', | ||
| '/document/tracking/revision_history[]/summary', | ||
| '/product_tree/product_groups[]/summary', | ||
| '/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 checkPath( | ||
| [], | ||
| path.split('/').slice(1), | ||
| doc, | ||
| async (instancePath, text) => { | ||
| if (typeof text !== 'string') return | ||
| const result = await checkString(text, lang) | ||
| if (result.length) { | ||
| ctx.infos.push({ | ||
| instancePath, | ||
| message: result.map((r) => r.message).join(' '), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should open an issue as a reminder to improve the output and expose all information language tool provides us... (This will be helpful for viewers to display and provide correction suggestions... |
||
| }) | ||
| } | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| return ctx | ||
| } | ||
|
|
||
| /** | ||
| * Checks the value behind `path` using the given `onCheck` function. This is a | ||
| * recursive helper function to loop through the list of paths in the spec. | ||
| * | ||
| * @param {string[]} reminder | ||
| * @param {string[]} path | ||
| * @param {unknown} value | ||
| * @param {(instancePath: string, value: string) => Promise<void>} onCheck | ||
| */ | ||
| async function checkPath(reminder, path, value, onCheck) { | ||
| if (value == null) return | ||
| const currentSegment = path.at(0) | ||
|
|
||
| if (!currentSegment) { | ||
| // We've reached the end. Now the `onCheck` function can be called to check | ||
| // the actual value. | ||
| if (typeof value === 'string') { | ||
| await onCheck('/' + reminder.join('/'), value) | ||
| } | ||
| } else if (currentSegment.endsWith('[]')) { | ||
| // The value is supposed to be an array for which every element needs to be | ||
| // checked ... | ||
| const arrayName = currentSegment.split('[')[0] | ||
| const array = Reflect.get(value, arrayName) | ||
|
|
||
| if (Array.isArray(array)) { | ||
| // ... But only if it's really an array. | ||
| for (const [elementIndex, element] of array.entries() ?? []) { | ||
| await checkPath( | ||
| [...reminder, arrayName, String(elementIndex)], | ||
| [...path.slice(1)], | ||
| element, | ||
| onCheck | ||
| ) | ||
| } | ||
| } | ||
| } else { | ||
| // Otherwise it's something object-ish which we traverse recursively. | ||
| await checkPath( | ||
| [...reminder, currentSegment], | ||
| path.slice(1), | ||
| Reflect.get(value, currentSegment), | ||
| onCheck | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check the given string using the languagetool server. | ||
| * | ||
| * @param {string} str | ||
| * @param {string} lng | ||
| * @returns | ||
| */ | ||
| async function checkString(str, lng) { | ||
| /** | ||
| * @typedef {object} Match | ||
| * @property {string} message | ||
| */ | ||
|
|
||
| /** | ||
| * @typedef {object} Response | ||
| * @property {Match[]} matches | ||
| */ | ||
|
|
||
| const res = await fetch(new URL('/v2/check', context.languageToolUrl), { | ||
| method: 'POST', | ||
| body: new URLSearchParams([ | ||
| ['language', lng], | ||
| ['text', str], | ||
| ]), | ||
| }) | ||
| if (!res.ok) throw new Error('request to languagetool failed') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
|
|
||
| const json = /** @type {Response} */ (await res.json()) | ||
| return json.matches | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import assert from 'node:assert' | ||
| import { informativeTest_6_3_16 } from '../../csaf_2_1/informativeTests.js' | ||
| import { expect } from 'chai' | ||
|
|
||
| describe('informativeTest_6_3_16', function () { | ||
| it('only runs on relevant documents', async function () { | ||
| assert.equal( | ||
| (await informativeTest_6_3_16({ document: 'mydoc' })).infos.length, | ||
| 0 | ||
| ) | ||
| }) | ||
|
|
||
| it('fails if the language is not known', async function () { | ||
| const result = await informativeTest_6_3_16({ | ||
| document: { | ||
| lang: 'zz', | ||
| }, | ||
| }) | ||
| expect(result.infos.length).to.eq(1) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,7 +59,6 @@ const excluded = [ | |
| '6.3.13', | ||
| '6.3.14', | ||
| '6.3.15', | ||
| '6.3.16', | ||
| '6.3.17', | ||
| ] | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not mention in the Readme that languagetool is required (and at which port).