forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrest.js
More file actions
235 lines (209 loc) · 10.4 KB
/
rest.js
File metadata and controls
235 lines (209 loc) · 10.4 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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import { jest, test } from '@jest/globals'
import { slug } from 'github-slugger'
import { readdirSync, readFileSync } from 'fs'
import path from 'path'
import { get, getDOM } from '../helpers/e2etest.js'
import getRest, {
categoriesWithoutSubcategories,
REST_DATA_DIR,
REST_SCHEMA_FILENAME,
} from '../../src/rest/lib/index.js'
import { getEnabledForApps } from '../../src/github-apps/lib/index.js'
import { isApiVersioned, allVersions } from '../../lib/all-versions.js'
import { getDiffOpenAPIContentRest } from '../../src/rest/scripts/test-open-api-schema.js'
describe('REST references docs', () => {
jest.setTimeout(3 * 60 * 1000)
test('all category and subcategory REST pages render for free-pro-team', async () => {
// This currently just grabs the 'free-pro-team' schema, but ideally, we'd
// get a list of all categories across all versions.
const freeProTeamVersion = readdirSync(REST_DATA_DIR)
.filter((file) => file.startsWith('fpt'))
.shift()
const freeProTeamSchema = JSON.parse(
readFileSync(path.join(REST_DATA_DIR, freeProTeamVersion, REST_SCHEMA_FILENAME), 'utf8')
)
const restCategories = Object.entries(freeProTeamSchema)
.map(([key, subCategory]) => {
const subCategoryKeys = Object.keys(subCategory)
if (subCategoryKeys.length === 1) {
return key
} else {
return subCategoryKeys.map((elem) => `${key}/${elem}`)
}
})
.flat()
const statusCodes = await Promise.all(
restCategories.map(async (page) => {
const url = `/en/rest/${page}`
const res = await get(url)
return [url, res.statusCode]
})
)
for (const [url, status] of statusCodes) {
expect(status, url).toBe(200)
}
expect.assertions(restCategories.length)
})
// Checks that every version of the /rest/checks
// page has every operation defined in the openapi schema.
test('loads schema data for all versions', async () => {
for (const version in allVersions) {
const calendarDate = allVersions[version].latestApiVersion
const checksRestOperations = await getRest(version, calendarDate, 'checks', 'runs')
const $ = await getDOM(`/en/${version}/rest/checks/runs?restVersion=${calendarDate}`)
const domH2Ids = $('h2')
.map((i, h2) => $(h2).attr('id'))
.get()
const schemaSlugs = checksRestOperations.map((operation) => slug(operation.title))
expect(schemaSlugs.every((slug) => domH2Ids.includes(slug))).toBe(true)
}
})
// Checks every version of the
// /rest/overview/endpoints-available-for-github-apps page
// and ensures that all sections in the openapi schema
// are present in the page.
test('loads operations enabled for GitHub Apps', async () => {
const flatMapping = getFlatMappingWithCalendarDates()
for (const [version, versionValue] of Object.entries(flatMapping)) {
const schemaSlugs = []
const enabledForApps = await getEnabledForApps(version, versionValue.apiVersion)
// using the static file, generate the expected slug for each operation
for (const [key, value] of Object.entries(enabledForApps)) {
schemaSlugs.push(
...value.map(
(item) =>
`/en${
versionValue.url === '' ? versionValue.url : `/${versionValue.url}`
}/rest/${key}${
categoriesWithoutSubcategories.includes(key) ? '' : '/' + item.subcategory
}#${item.slug}`
)
)
}
// get all of the href attributes in the anchor tags
const $ = await getDOM(
`/en/${versionValue.url}/rest/overview/endpoints-available-for-github-apps${
versionValue.apiVersion ? `?apiVersion=${versionValue.apiVersion}` : ''
}`
)
const domH3Ids = $('#article-contents a')
.map((i, a) => $(a).attr('href'))
.get()
expect(schemaSlugs.every((slug) => domH3Ids.includes(slug))).toBe(true)
}
})
test('falls back when unsupported calendar version provided', async () => {
const res = await get(
`/en/rest/overview/endpoints-available-for-github-apps?${new URLSearchParams({
apiVersion: 'junk',
})}`
)
expect(res.statusCode).toBe(200)
})
test('test the latest version of the OpenAPI schema categories/subcategories to see if it matches the content/rest directory', async () => {
const differences = await getDiffOpenAPIContentRest()
const errorMessage = formatErrors(differences)
expect(Object.keys(differences).length, errorMessage).toBe(0)
})
test('REST reference pages have DOM markers needed for extracting search content', async () => {
// Pick an arbitrary REST reference page that is build from React
const $ = await getDOM('/en/rest/actions/artifacts')
const rootSelector = '[data-search=article-body]'
const $root = $(rootSelector)
expect($root.length).toBe(1)
// Within that, should expect a "lead" text.
// Note! Not all REST references pages have a lead. The one in this
// test does.
const leadSelector = '[data-search=lead] p'
const $lead = $root.find(leadSelector)
expect($lead.length).toBe(1)
})
test('REST pages show the correct versions in the api version picker', async () => {
for (const version in allVersions) {
if (isApiVersioned(version)) {
for (const apiVersion of allVersions[version].apiVersions) {
const $ = await getDOM(`/en/${version}/rest?apiVersion=${apiVersion}`)
const versionName = $('[data-testid=api-version-picker] [data-testid=version]')
.text()
.trim()
if (apiVersion === allVersions[version].latestApiVersion) {
expect(versionName).toBe(apiVersion + ' (latest)')
} else {
expect(versionName).toBe(apiVersion)
}
}
} else {
const $ = await getDOM(`/en/${version}/rest`)
expect($('[data-testid=api-version-picker] button span').text()).toBe('')
}
}
})
describe('headings', () => {
test('rest pages do not render any headings with duplicate text', async () => {
const $ = await getDOM('/en/rest/actions/artifacts')
const headingText = $('body')
.find('h2, h3, h4, h5, h6')
.map((i, el) => $(el).text())
.get()
.sort()
const dupes = headingText.filter((item, index) => headingText.indexOf(item) !== index)
const message = `The following duplicate heading texts were found: ${dupes.join(', ')}`
expect(dupes.length, message).toBe(0)
})
test('rest pages do not render any headings with duplicate ids', async () => {
const $ = await getDOM('/en/rest/actions/artifacts')
const headingIDs = $('body')
.find('h2, h3, h4, h5, h6')
.map((i, el) => $(el).attr('id'))
.get()
.sort()
const dupes = headingIDs.filter((item, index) => headingIDs.indexOf(item) !== index)
const message = `The following duplicate heading IDs were found: ${dupes.join(', ')}`
expect(dupes.length, message).toBe(0)
})
})
})
function formatErrors(differences) {
let errorMessage = 'There are differences in Categories/Subcategories in:\n'
for (const schema in differences) {
errorMessage += 'Version: ' + schema + '\n'
for (const category in differences[schema]) {
errorMessage += 'Category: ' + category + '\nSubcategories: \n'
errorMessage +=
' - content/rest directory: ' + differences[schema][category].contentDir + '\n'
errorMessage += ' - OpenAPI Schema: ' + differences[schema][category].openAPI + '\n'
errorMessage += '---\n'
}
}
errorMessage += `
This test checks that the categories and subcategories in the content/rest directory matches the decorated schemas in src/rest/data for each version of the REST API.
If you have made changes to the categories or subcategories in the content/rest directory, either in the frontmatter or the structure of the directory, you will need to ensure that it matches the operations in the OpenAPI description. For example, if an operation is available in GHAE, the frontmatter versioning in the relevant docs category and subcategory files also need to be versioned for GHAE. If you are adding category or subcategory files to the content/rest directory, the OpenAPI dereferenced files must have at least one operation that will be shown for the versions in the category or subcategory files. If this is the case, it is likely that the description files have not been updated from github/github yet.
If you come across this error in an Update OpenAPI Descriptions PR it's likely that a category/subcategory has been added or removed and our content/rest directory no longer in sync with our OpenAPI Descriptions. First, please check for an open docs-internal PR that updates the content/rest directory. If you find one, merge that PR into the Update OpenAPI Descriptions PR to fix this failure. Otherwise, follow the link in the Update OpenAPI Descriptions PR body to find the author of the PR that introduced this change. Verify that the new operations are ready to be published. If yes, ask them to follow these instructions to open a docs-internal PR: https://thehub.github.com/epd/engineering/products-and-services/public-apis/rest/openapi/openapi-in-the-docs/#adding-or-changing-category-or-subcategory. If no, ask them to open a github/github PR to unpublish the operations.
If you have any questions contact #docs-engineering, #docs-content, or #docs-apis-and-events if you need help.`
return errorMessage
}
// This gets a flat mapping for all REST versions (versioned and unversioned) and creates a mapping between
// the full name of the version including calendar dates to its url name and apiVersion if it exists.
// Example:
// {
// free-pro-team@latest: { url: '', apiVersion: 2022-08-09 }
// free-pro-team@latest: { url: '', apiVersion: 2022-11-14 }
// enterprise-cloud@latest: { url: 'enterprise-cloud@latest, apiVersion: 2022-11-14 }
// enterprise-server@3.6: { url: enterprise-server@3.6 }
// }
function getFlatMappingWithCalendarDates() {
const flatMapping = {}
for (const version in allVersions) {
if (isApiVersioned(version)) {
for (const apiVersion of allVersions[version].apiVersions) {
flatMapping[allVersions[version].version] = {
url: `${version === 'free-pro-team@latest' ? '' : allVersions[version].version}`,
apiVersion,
}
}
} else {
flatMapping[allVersions[version].version] = { url: allVersions[version].version }
}
}
return flatMapping
}