-
Notifications
You must be signed in to change notification settings - Fork 61.6k
/
Copy pathopenapi-schema.js
212 lines (184 loc) · 8.55 KB
/
openapi-schema.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import fs from 'fs'
import path from 'path'
import { beforeAll, describe, expect, test } from 'vitest'
import walk from 'walk-sync'
import { isPlainObject, difference } from 'lodash-es'
import { isApiVersioned, allVersions } from '#src/versions/lib/all-versions.js'
import getRest from '../lib/index.js'
import readFrontmatter from '#src/frame/lib/read-frontmatter.js'
import frontmatter from '#src/frame/lib/frontmatter.js'
import getApplicableVersions from '../../versions/lib/get-applicable-versions.js'
import { getAutomatedMarkdownFiles } from '../scripts/test-open-api-schema.js'
import { nonAutomatedRestPaths } from '../lib/config.js'
const schemasPath = 'src/rest/data'
async function getFlatListOfOperations(version) {
const flatList = []
if (isApiVersioned(version)) {
const apiVersions = allVersions[version].apiVersions
for (const apiVersion of apiVersions) {
const operations = await getRest(version, apiVersion)
flatList.push(...createCategoryList(operations))
}
} else {
const operations = await getRest(version)
flatList.push(...createCategoryList(operations))
}
return flatList
}
function createCategoryList(operations) {
const catSubCatList = []
for (const category of Object.keys(operations)) {
const subcategories = Object.keys(operations[category])
for (const subcategory of subcategories) {
catSubCatList.push(...operations[category][subcategory])
}
}
return catSubCatList
}
describe('markdown for each rest version', () => {
// Unique set of all categories across all versions of the OpenAPI schema
const allCategories = new Set()
// Entire schema including categories and subcategories
const openApiSchema = {}
// All applicable version of categories based on frontmatter in the categories index.md file
const categoryApplicableVersions = {}
function getApplicableVersionFromFile(file) {
const currentFile = fs.readFileSync(file, 'utf8')
const { data } = frontmatter(currentFile)
return getApplicableVersions(data.versions, file)
}
function getCategorySubcategory(file) {
const fileSplit = file.split('/')
const cat = fileSplit[fileSplit.length - 2]
const subCat = fileSplit[fileSplit.length - 1].replace('.md', '')
return { category: cat, subCategory: subCat }
}
beforeAll(async () => {
for (const version in allVersions) {
if (isApiVersioned(version)) {
for (const apiVersion of allVersions[version].apiVersions) {
const apiOperations = await getRest(version, apiVersion)
Object.keys(apiOperations).forEach((category) => allCategories.add(category))
openApiSchema[version] = apiOperations
}
} else {
const apiOperations = await getRest(version)
Object.keys(apiOperations).forEach((category) => allCategories.add(category))
openApiSchema[version] = apiOperations
}
}
// Read the versions from each index.md file to build a list of
// applicable versions for each category
walk('content/rest', { includeBasePath: true, directories: false })
.filter((filename) => filename.includes('index.md'))
.forEach((file) => {
const applicableVersions = getApplicableVersionFromFile(file)
const { category } = getCategorySubcategory(file)
categoryApplicableVersions[category] = applicableVersions
})
})
test('markdown file exists for every operationId prefix in all versions of the OpenAPI schema', async () => {
// List of categories derived from disk
const filenames = new Set(
getAutomatedMarkdownFiles('content/rest')
// Gets just category level files (paths directly under /rest)
.map((filename) => filename.split('/')[2])
.sort(),
)
const missingResource =
'Found a markdown file in content/rest that is not represented by an OpenAPI REST operation category.'
expect(difference([...filenames], [...allCategories]), missingResource).toEqual([])
const missingFile =
'Found an OpenAPI REST operation category that is not represented by a markdown file in content/rest.'
expect(difference([...allCategories], [...filenames]), missingFile).toEqual([])
})
test('category and subcategory exist in OpenAPI schema for every applicable version in markdown frontmatter', async () => {
const automatedFiles = getAutomatedMarkdownFiles('content/rest')
automatedFiles.forEach((file) => {
const applicableVersions = getApplicableVersionFromFile(file)
const { category, subCategory } = getCategorySubcategory(file)
for (const version of applicableVersions) {
expect(
Object.keys(openApiSchema[version][category]),
`The REST version: ${version}'s category: ${category} does not include the subcategory: ${subCategory}. Please check file: ${file}`,
).toContain(subCategory)
expect(
categoryApplicableVersions[category],
`The versions that apply to category ${category} does not contain the ${version}, as is expected. Please check the versions for file ${file} or look at the index that governs that file (in its parent directory).`,
).toContain(version)
}
})
})
})
describe('rest file structure', () => {
test('children of content/rest/index.md are in alphabetical order', async () => {
const indexContent = fs.readFileSync('content/rest/index.md', 'utf8')
const { data } = readFrontmatter(indexContent)
const nonAutomatedChildren = nonAutomatedRestPaths.map((child) => child.replace('/rest', ''))
const sortableChildren = data.children.filter((child) => !nonAutomatedChildren.includes(child))
expect(sortableChildren).toStrictEqual([...sortableChildren].sort())
})
})
describe('OpenAPI schema validation', () => {
// ensure every version defined in allVersions has a correlating static
// decorated file, while allowing decorated files to exist when a version
// is not yet defined in allVersions (e.g., a GHEC static file can exist
// even though the version is not yet supported in the docs)
test('every OpenAPI version must have a schema file in the docs', async () => {
const decoratedFilenames = walk(schemasPath).map((filename) => path.basename(filename, '.json'))
Object.values(allVersions)
.map((version) => version.openApiVersionName)
.forEach((openApiBaseName) => {
// Because the rest calendar dates now have latest, next, or calendar date attached to the name, we're
// now checking if the decorated file names now start with an openApiBaseName
expect(
decoratedFilenames.some((versionFile) => versionFile.startsWith(openApiBaseName)),
).toBe(true)
})
})
test('operations object structure organized by version, category, and subcategory', async () => {
for (const version in allVersions) {
const operations = await getFlatListOfOperations(version)
expect(operations.every((operation) => operation.verb)).toBe(true)
}
})
test('no wrongly detected AppleScript syntax highlighting in schema data', async () => {
const countAssertions = Object.keys(allVersions)
.map((version) => allVersions[version].apiVersions.length)
.reduce((prevLength, currLength) => prevLength + currLength)
expect.assertions(countAssertions)
await Promise.all(
Object.keys(allVersions).map(async (version) => {
for (const apiVersion of allVersions[version].apiVersions) {
const operations = await getRest(version, apiVersion)
expect(JSON.stringify(operations).includes('hljs language-applescript')).toBe(false)
}
}),
)
})
})
async function findOperation(version, method, path) {
const allOperations = await getFlatListOfOperations(version)
return allOperations.find((operation) => {
return operation.requestPath === path && operation.verb.toLowerCase() === method.toLowerCase()
})
}
describe('code examples are defined', () => {
test('GET', async () => {
for (const version in allVersions) {
if (version === '[email protected]' || version === '[email protected]') continue
let domain = 'https://api.github.com'
if (version.includes('enterprise-server')) {
domain = 'http(s)://HOSTNAME/api/v3'
}
const operation = await findOperation(version, 'GET', '/repos/{owner}/{repo}')
expect(operation.serverUrl).toBe(domain)
expect(isPlainObject(operation)).toBe(true)
expect(operation.codeExamples).toBeDefined()
operation.codeExamples.forEach((example) => {
expect(isPlainObject(example.request)).toBe(true)
expect(isPlainObject(example.response)).toBe(true)
})
}
})
})