-
Notifications
You must be signed in to change notification settings - Fork 67.5k
Expand file tree
/
Copy pathopenapi-schema.ts
More file actions
221 lines (197 loc) · 9.02 KB
/
Copy pathopenapi-schema.ts
File metadata and controls
221 lines (197 loc) · 9.02 KB
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
213
214
215
216
217
218
219
220
221
import fs from 'fs'
import { beforeAll, describe, expect, test } from 'vitest'
import walk from 'walk-sync'
import { isPlainObject, difference } from 'lodash-es'
import { isApiVersioned, allVersions } from '@/versions/lib/all-versions'
import getRest, { getRestCategories, type RestOperationCategory } from '../lib/index'
import readFrontmatter from '@/frame/lib/read-frontmatter'
import frontmatter from '@/frame/lib/frontmatter'
import getApplicableVersions from '../../versions/lib/get-applicable-versions'
import { getAutomatedMarkdownFiles } from '../scripts/test-open-api-schema'
import { nonAutomatedRestPaths } from '../lib/config'
import type { Operation } from '@/rest/components/types'
const schemasPath = 'src/rest/data'
async function getFlatListOfOperations(version: string): Promise<Operation[]> {
const flatList: Operation[] = []
if (isApiVersioned(version)) {
for (const apiVersion of allVersions[version].apiVersions) {
for (const category of getRestCategories(version, apiVersion)) {
const ops = await getRest(version, apiVersion, category)
flatList.push(...Object.values(ops).flat())
}
}
} else {
for (const category of getRestCategories(version)) {
const ops = await getRest(version, undefined, category)
flatList.push(...Object.values(ops).flat())
}
}
return flatList
}
describe('markdown for each rest version', () => {
// Unique set of all categories across all versions of the OpenAPI schema
const allCategories = new Set<string>()
// Entire schema including categories and subcategories, keyed by version then category
const openApiSchema: Record<string, Record<string, RestOperationCategory>> = {}
// All applicable version of categories based on frontmatter in the categories index.md file
const categoryApplicableVersions: Record<string, string[]> = {}
function getApplicableVersionFromFile(file: string) {
const currentFile = fs.readFileSync(file, 'utf8')
const fm = frontmatter(currentFile) as unknown as {
data?: { versions?: string | Record<string, string | string[]> }
}
return getApplicableVersions(fm.data?.versions, file)
}
function getCategorySubcategory(file: string) {
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) {
openApiSchema[version] = {}
if (isApiVersioned(version)) {
for (const apiVersion of allVersions[version].apiVersions) {
for (const category of getRestCategories(version, apiVersion)) {
allCategories.add(category)
openApiSchema[version][category] = await getRest(version, apiVersion, category)
}
}
} else {
for (const category of getRestCategories(version)) {
allCategories.add(category)
openApiSchema[version][category] = await getRest(version, undefined, category)
}
}
}
// Read the versions from each index.md file to build a list of
// applicable versions for each category
for (const file of walk('content/rest', { includeBasePath: true, directories: false }).filter(
(filename) => filename.includes('index.md'),
)) {
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')
for (const file of automatedFiles) {
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 fm = readFrontmatter(indexContent) as unknown as {
data?: { children?: string[] }
}
const children = fm.data?.children
expect(Array.isArray(children)).toBe(true)
const nonAutomatedChildren = nonAutomatedRestPaths.map((child: string) =>
child.replace('/rest', ''),
)
const sortableChildren = (children as string[]).filter(
(child: string) => !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 versionDirs = fs
.readdirSync(schemasPath, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name)
const openApiBaseNames = Object.values(allVersions).map((v) => v.openApiVersionName)
for (const openApiBaseName of openApiBaseNames) {
expect(versionDirs.some((dir) => dir.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 () => {
await Promise.all(
Object.keys(allVersions).map(async (version) => {
if (isApiVersioned(version)) {
for (const apiVersion of allVersions[version].apiVersions) {
for (const category of getRestCategories(version, apiVersion)) {
const ops = await getRest(version, apiVersion, category)
expect(JSON.stringify(ops).includes('hljs language-applescript')).toBe(false)
}
}
} else {
for (const category of getRestCategories(version)) {
const ops = await getRest(version, undefined, category)
expect(JSON.stringify(ops).includes('hljs language-applescript')).toBe(false)
}
}
}),
)
})
})
async function findOperation(version: string, method: string, requestPath: string) {
const allOperations = await getFlatListOfOperations(version)
return allOperations.find((operation) => {
return (
operation.requestPath === requestPath && operation.verb.toLowerCase() === method.toLowerCase()
)
})
}
describe('code examples are defined', () => {
test('GET', async () => {
for (const version in allVersions) {
if (version === 'enterprise-server@3.2' || version === 'enterprise-server@3.1') 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).toBeDefined()
if (!operation) continue
expect(operation.serverUrl).toBe(domain)
expect(isPlainObject(operation)).toBe(true)
expect(operation.codeExamples).toBeDefined()
for (const example of operation.codeExamples) {
expect(isPlainObject(example.request)).toBe(true)
expect(isPlainObject(example.response)).toBe(true)
}
}
})
})