Skip to content

Commit 3d4c56a

Browse files
feat(CSAF2.1): #403 add mandatory test 6.2.39.4
1 parent 2c8a07f commit 3d4c56a

File tree

6 files changed

+164
-1
lines changed

6 files changed

+164
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ export const recommendedTest_6_2_18: DocumentTest
462462
export const recommendedTest_6_2_22: DocumentTest
463463
export const recommendedTest_6_2_23: DocumentTest
464464
export const recommendedTest_6_2_39_2: DocumentTest
465+
export const recommendedTest_6_2_39_4: DocumentTest
465466
```
466467
467468
[(back to top)](#bsi-csaf-validator-lib)

csaf_2_1/recommendedTests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ export { recommendedTest_6_2_28 } from './recommendedTests/recommendedTest_6_2_2
3333
export { recommendedTest_6_2_29 } from './recommendedTests/recommendedTest_6_2_29.js'
3434
export { recommendedTest_6_2_38 } from './recommendedTests/recommendedTest_6_2_38.js'
3535
export { recommendedTest_6_2_39_2 } from './recommendedTests/recommendedTest_6_2_39_2.js'
36+
export { recommendedTest_6_2_39_4 } from './recommendedTests/recommendedTest_6_2_39_4.js'
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
import {
3+
existsReferenceWithSummaryAndCategory,
4+
getTranslationInDocumentLang,
5+
isLangSpecifiedAndNotEnglish,
6+
} from '../../lib/shared/languageSpecificTranslation.js'
7+
8+
const ajv = new Ajv()
9+
10+
/*
11+
This is the jtd schema that needs to match the input document so that the
12+
test is activated. If this schema doesn't match it normally means that the input
13+
document does not validate against the csaf json schema or optional fields that
14+
the test checks are not present.
15+
*/
16+
const inputSchema = /** @type {const} */ ({
17+
additionalProperties: true,
18+
properties: {
19+
document: {
20+
additionalProperties: true,
21+
properties: {
22+
category: { type: 'string' },
23+
},
24+
optionalProperties: {
25+
lang: {
26+
type: 'string',
27+
},
28+
references: {
29+
elements: {
30+
additionalProperties: true,
31+
optionalProperties: {
32+
category: {
33+
type: 'string',
34+
},
35+
references: {
36+
type: 'string',
37+
},
38+
},
39+
},
40+
},
41+
},
42+
},
43+
},
44+
})
45+
46+
const validateSchema = ajv.compile(inputSchema)
47+
48+
/**
49+
* If the document language is specified but not English, it MUST be tested that at least one item in document
50+
* references exists that starts with the language-specific translation of the term Superseding Document as summary.
51+
* The category of this item MUST be external. If no language-specific translation has been recorded,
52+
* the test MUST be skipped and output an information to the user that no such translation is known.
53+
*
54+
* @param {unknown} doc
55+
*/
56+
export function recommendedTest_6_2_39_4(doc) {
57+
/*
58+
The `ctx` variable holds the state that is accumulated during the test run and is
59+
finally returned by the function.
60+
*/
61+
const ctx = {
62+
warnings:
63+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
64+
}
65+
66+
const referenceCategory = 'external'
67+
68+
if (!validateSchema(doc) || doc.document.category !== 'csaf_superseded') {
69+
return ctx
70+
}
71+
72+
const supersedingInDocLang = getTranslationInDocumentLang(
73+
doc,
74+
'superseding_document'
75+
)
76+
if (!supersedingInDocLang) {
77+
ctx.warnings.push({
78+
instancePath: '/document/references',
79+
message:
80+
'no language specific translation for "Superseding Document" has been recorded',
81+
})
82+
return ctx
83+
}
84+
85+
if (isLangSpecifiedAndNotEnglish(doc.document.lang)) {
86+
const references = doc.document.references
87+
if (
88+
!references ||
89+
!existsReferenceWithSummaryAndCategory(
90+
references,
91+
supersedingInDocLang,
92+
referenceCategory
93+
)
94+
) {
95+
ctx.warnings.push({
96+
instancePath: '/document/references',
97+
message:
98+
`for document category "csaf_superseded" at least one references must exist ` +
99+
`with reference category "${referenceCategory}" and whose summary begins with ${supersedingInDocLang}`,
100+
})
101+
}
102+
}
103+
104+
return ctx
105+
}

lib/shared/languageSpecificTranslation.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@ export function containsOneNoteWithTitleAndCategory(
3232
)
3333
}
3434

35+
/**
36+
* test whether at least one item in document references exists that starts with the given summary
37+
* and has the given category.
38+
* @param {({} & { category?: string | undefined; summary?: string | undefined; } & Record<string, unknown>)[]} references
39+
* @param {string} summaryStartsWith
40+
* @param {string} category
41+
* @returns {boolean} True if the reference was found, false otherwise
42+
*/
43+
export function existsReferenceWithSummaryAndCategory(
44+
references,
45+
summaryStartsWith,
46+
category
47+
) {
48+
return (
49+
references.filter(
50+
(reference) =>
51+
reference.category === category &&
52+
reference.summary &&
53+
reference.summary.startsWith(summaryStartsWith)
54+
).length > 0
55+
)
56+
}
57+
3558
/**
3659
* Get the language specific translation of the given i18nKey
3760
* @param {{ document: { lang?: string; }; }} doc

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ const excluded = [
4949
'6.2.37',
5050
'6.2.39.1',
5151
'6.2.39.3',
52-
'6.2.39.4',
5352
'6.2.40',
5453
'6.2.41',
5554
'6.2.42',
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { recommendedTest_6_2_39_4 } from '../../csaf_2_1/recommendedTests/recommendedTest_6_2_39_4.js'
2+
import { expect } from 'chai'
3+
import assert from 'node:assert'
4+
import { getTranslationInDocumentLang } from '../../lib/shared/languageSpecificTranslation.js'
5+
6+
describe('recommendedTest_6_2_39_4', function () {
7+
it('only runs on relevant documents', function () {
8+
assert.equal(recommendedTest_6_2_39_4({}).warnings.length, 0)
9+
})
10+
11+
it('only runs on valid language', function () {
12+
assert.equal(
13+
recommendedTest_6_2_39_4({
14+
document: { lang: '123', license_expression: 'MIT' },
15+
}).warnings.length,
16+
0
17+
)
18+
})
19+
20+
it('check get superseding_document in document lang', function () {
21+
expect(
22+
getTranslationInDocumentLang(
23+
{ document: { lang: 'de' } },
24+
'superseding_document'
25+
)
26+
).to.eq('Ersetzendes Dokument')
27+
expect(
28+
getTranslationInDocumentLang({ document: { lang: 'es' } }, 'v')
29+
).to.eq(undefined)
30+
expect(
31+
getTranslationInDocumentLang({ document: {} }, 'superseding_document')
32+
).to.eq(undefined)
33+
})
34+
})

0 commit comments

Comments
 (0)