Skip to content

Commit 0d7f8e0

Browse files
authored
use ajv for release notes test schema (#35300)
1 parent e77b7bf commit 0d7f8e0

File tree

4 files changed

+62
-17
lines changed

4 files changed

+62
-17
lines changed

tests/helpers/schemas.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// lightly format the schema errors object returned from ajv to connect the
2+
// error message to where the problem is -- for example, if a top level 'date'
3+
// property isn't correctly formatted as a date we return:
4+
//
5+
// at 'date': must match format "date"
6+
//
7+
// if sections > features has an array of objects that must have a 'notes'
8+
// property and we misspell the property name in the first item:
9+
//
10+
// at 'sections > features > item 0': must have required property 'notes'
11+
export const formatAjvErrors = (errors = []) => {
12+
return errors
13+
.map((errorObj) => {
14+
// ajv instancePath tells us in the data we're checking where there was a
15+
// schema error -- for release notes looks like this for example
16+
// `/sections/features/0` if the error is in the first feature under sections.
17+
const split = errorObj.instancePath.split('/')
18+
split.shift()
19+
20+
if (split.length === 0) {
21+
return `at '/' (top-level): ${errorObj.message}`
22+
}
23+
24+
const schemaErrorPath = split
25+
.map((item) => {
26+
if (!isNaN(item)) {
27+
return `item ${item}`
28+
} else {
29+
return item
30+
}
31+
})
32+
.join(' > ')
33+
34+
return `at '${schemaErrorPath}': ${errorObj.message}`
35+
})
36+
.join('\n ')
37+
}

tests/helpers/schemas/release-notes-schema.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@ const section = {
77
},
88
{
99
type: 'array',
10+
minItems: 1,
1011
items: {
1112
type: 'object',
13+
required: ['heading', 'notes'],
14+
additionalProperties: false,
1215
properties: {
1316
heading: {
1417
type: 'string',
15-
required: true,
1618
},
1719
notes: {
1820
type: 'array',
1921
items: { type: 'string' },
20-
required: true,
2122
minItems: 1,
2223
},
2324
},
@@ -27,14 +28,15 @@ const section = {
2728
}
2829

2930
export default {
31+
type: 'object',
32+
required: ['date', 'sections'],
3033
properties: {
3134
intro: {
3235
type: 'string',
3336
},
3437
date: {
3538
type: 'string',
3639
format: 'date',
37-
required: true,
3840
},
3941
release_candidate: {
4042
type: 'boolean',
@@ -45,9 +47,9 @@ export default {
4547
default: false,
4648
},
4749
sections: {
48-
required: true,
4950
type: 'object',
5051
minProperties: 1,
52+
additionalProperties: false,
5153
properties: [
5254
'bugs',
5355
'known_issues',

tests/linting/lint-files.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import walk from 'walk-sync'
55
import { zip } from 'lodash-es'
66
import yaml from 'js-yaml'
77
import revalidator from 'revalidator'
8+
import Ajv from 'ajv'
9+
import addFormats from 'ajv-formats'
810
import { fromMarkdown } from 'mdast-util-from-markdown'
911
import { visit } from 'unist-util-visit'
1012
import fs from 'fs/promises'
@@ -18,6 +20,7 @@ import getApplicableVersions from '../../lib/get-applicable-versions.js'
1820
import { allVersions } from '../../lib/all-versions.js'
1921
import { jest } from '@jest/globals'
2022
import { getDiffFiles } from '../helpers/diff-files.js'
23+
import { formatAjvErrors } from '../helpers/schemas.js'
2124

2225
jest.useFakeTimers({ legacyFakeTimers: true })
2326

@@ -345,6 +348,11 @@ if (
345348
})
346349
}
347350

351+
// ajv for schema validation tests
352+
const ajv = new Ajv({ allErrors: true })
353+
addFormats(ajv)
354+
const ghesValidate = ajv.compile(releaseNotesSchema)
355+
348356
describe('lint markdown content', () => {
349357
if (mdToLint.length < 1) return
350358

@@ -878,11 +886,14 @@ describe('lint GHES release notes', () => {
878886
})
879887

880888
it('matches the schema', () => {
881-
const { errors } = revalidator.validate(dictionary, releaseNotesSchema)
882-
const errorMessage = errors
883-
.map((error) => `- [${error.property}]: ${error.actual}, ${error.message}`)
884-
.join('\n')
885-
expect(errors.length, errorMessage).toBe(0)
889+
const valid = ghesValidate(dictionary)
890+
let errors
891+
892+
if (!valid) {
893+
errors = formatAjvErrors(ghesValidate.errors)
894+
}
895+
896+
expect(valid, errors).toBe(true)
886897
})
887898

888899
it('contains valid liquid', () => {

tests/unit/products.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import Ajv from 'ajv'
22
import { productMap } from '../../lib/all-products.js'
3+
import { formatAjvErrors } from '../helpers/schemas.js'
34
import schema from '../helpers/schemas/products-schema.js'
45

5-
const ajv = new Ajv()
6+
const ajv = new Ajv({ allErrors: true })
67
const validate = ajv.compile(schema)
78

89
describe('products module', () => {
@@ -17,14 +18,8 @@ describe('products module', () => {
1718
let errors
1819

1920
if (!valid) {
20-
errors = validate.errors
21-
.map((errorObj) => {
22-
return errorObj.message
23-
})
24-
.join(', ')
25-
errors = `'${product.name}' product schema errors: ${errors}`
21+
errors = formatAjvErrors(valid.errors)
2622
}
27-
2823
expect(valid, errors).toBe(true)
2924
})
3025
})

0 commit comments

Comments
 (0)