-
Notifications
You must be signed in to change notification settings - Fork 66.9k
Expand file tree
/
Copy pathrest-transformer.ts
More file actions
309 lines (240 loc) · 12 KB
/
rest-transformer.ts
File metadata and controls
309 lines (240 loc) · 12 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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
import { beforeAll, describe, expect, test } from 'vitest'
import { get } from '@/tests/helpers/e2etest'
const makeURL = (pathname: string, apiVersion?: string): string => {
const params = new URLSearchParams({ pathname })
if (apiVersion) {
params.set('apiVersion', apiVersion)
}
return `/api/article/body?${params}`
}
describe('REST transformer', { timeout: 10000 }, () => {
beforeAll(() => {
if (!process.env.ROOT) {
console.warn(
'WARNING: The REST transformer tests require the ROOT environment variable to be set to the fixture root',
)
}
})
test('REST page renders with markdown structure', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
expect(res.headers['content-type']).toContain('text/markdown')
// Check for the main heading
expect(res.body).toContain('# GitHub Actions Artifacts')
// Check for intro (using fixture's prodname_actions which is 'HubGit Actions')
expect(res.body).toContain('Use the REST API to interact with artifacts in HubGit Actions.')
// Check for manual content section heading
expect(res.body).toContain('## About artifacts in HubGit Actions')
})
test('REST operations are formatted correctly', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check for operation heading
expect(res.body).toContain('## List artifacts for a repository')
// Check for HTTP method and endpoint
expect(res.body).toContain('GET /repos/{owner}/{repo}/actions/artifacts')
// Check for operation description
expect(res.body).toContain('Lists all artifacts for a repository.')
})
test('Parameters section includes headers', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check for parameters heading
expect(res.body).toContain('### Parameters')
// Check for headers section
expect(res.body).toContain('#### Headers')
// Check for accept header
expect(res.body).toContain('**`accept`** (string)')
expect(res.body).toContain('Setting to `application/vnd.github+json` is recommended.')
})
test('Path and query parameters are listed', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check for path and query parameters section
expect(res.body).toContain('#### Path and query parameters')
// Check for specific parameters
expect(res.body).toContain('**`owner`** (string) (required)')
expect(res.body).toContain('The account owner of the repository.')
expect(res.body).toContain('**`repo`** (string) (required)')
expect(res.body).toContain('**`per_page`** (integer)')
expect(res.body).toContain('Default: `30`')
})
test('Status codes are formatted correctly', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check for status codes section
expect(res.body).toContain('### HTTP response status codes')
// Check for specific status code
expect(res.body).toContain('**200**')
expect(res.body).toContain('OK')
})
test('Code examples include curl with proper formatting', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check for code examples section
expect(res.body).toContain('### Code examples')
// Check for request/response labels
expect(res.body).toContain('**Request:**')
expect(res.body).toContain('**Response schema:**')
// Check for curl code block
expect(res.body).toContain('```curl')
expect(res.body).toContain('curl -L \\')
expect(res.body).toContain('-X GET \\')
expect(res.body).toContain('https://api.github.com/repos/OWNER/REPO/actions/artifacts \\')
expect(res.body).toContain('-H "Accept: application/vnd.github.v3+json" \\')
expect(res.body).toContain('-H "Authorization: Bearer <YOUR-TOKEN>"')
})
test('Code examples include X-GitHub-Api-Version header by default', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check for API version header in curl example
expect(res.body).toContain('-H "X-GitHub-Api-Version: 2022-11-28"')
})
test('Code examples include specified API version', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts', '2022-11-28'))
expect(res.statusCode).toBe(200)
// Check for the specified API version header
expect(res.body).toContain('-H "X-GitHub-Api-Version: 2022-11-28"')
})
test('Liquid tags are rendered in intro', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Liquid tags should be rendered, not shown as raw tags (fixture uses 'HubGit Actions')
expect(res.body).toContain('HubGit Actions')
expect(res.body).not.toContain('{% data variables.product.prodname_actions %}')
// Check in both the intro and the manual content section
expect(res.body).toMatch(/Use the REST API to interact with artifacts in HubGit Actions/)
expect(res.body).toMatch(/About artifacts in HubGit Actions/)
})
test('AUTOTITLE links are resolved', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check that AUTOTITLE has been resolved to actual link text
// The link should have the actual page title, not "AUTOTITLE"
expect(res.body).toContain('[Storing workflow data as artifacts]')
expect(res.body).toContain('(/en/actions/using-workflows/storing-workflow-data-as-artifacts)')
// Make sure the raw AUTOTITLE tag is not present
expect(res.body).not.toContain('[AUTOTITLE]')
// Verify the link appears in the manual content section
expect(res.body).toMatch(
/About artifacts in HubGit Actions[\s\S]*Storing workflow data as artifacts/,
)
})
test('Markdown links are preserved in descriptions', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check that markdown links are preserved
expect(res.body).toMatch(/\[.*?\]\(\/en\/.*?\)/)
})
test('Response schema is formatted correctly', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check for JSON code block with schema label
expect(res.body).toContain('**Response schema:**')
expect(res.body).toContain('```json')
expect(res.body).toContain('Status: 200')
// Verify schema structure is present (not an example)
expect(res.body).toContain('"type":')
expect(res.body).toContain('"properties":')
// Check for common schema keywords
const schemaMatch = res.body.match(/```json\s+Status: 200\s+([\s\S]*?)```/)
expect(schemaMatch).toBeTruthy()
if (schemaMatch) {
const schemaContent = schemaMatch[1]
const schema = JSON.parse(schemaContent)
// Verify it's a valid OpenAPI/JSON schema structure
expect(schema).toHaveProperty('type')
expect(schema.type).toBe('object')
expect(schema).toHaveProperty('properties')
// Verify it has expected properties for artifacts response
expect(schema.properties).toHaveProperty('total_count')
expect(schema.properties).toHaveProperty('artifacts')
}
})
test('Non-REST pages return appropriate error', async () => {
const res = await get(makeURL('/en/get-started/start-your-journey/hello-world'))
expect(res.statusCode).toBe(200)
// Regular article pages should still work, they just won't use the transformer
expect(res.body).toContain('## Introduction')
})
test('Invalid apiVersion returns 400 error', async () => {
// An invalid API version should return a validation error with 400 status
const res = await get(makeURL('/en/rest/actions/artifacts', 'invalid-version'))
// Returns 400 because the apiVersion is invalid (client error)
expect(res.statusCode).toBe(400)
const parsed = JSON.parse(res.body)
expect(parsed.error).toContain("Invalid apiVersion 'invalid-version'")
expect(parsed.error).toContain('Valid API versions are:')
expect(parsed.error).toContain('2022-11-28')
})
test('Multiple apiVersion query parameters returns 400 error', async () => {
// Multiple apiVersion parameters should be rejected
const res = await get(
'/api/article/body?pathname=/en/rest/actions/artifacts&apiVersion=2022-11-28&apiVersion=2023-01-01',
)
expect(res.statusCode).toBe(400)
const parsed = JSON.parse(res.body)
expect(parsed.error).toBe("Multiple 'apiVersion' keys")
})
test('Valid apiVersion passes validation', async () => {
// A valid API version should work
const res = await get(makeURL('/en/rest/actions/artifacts', '2022-11-28'))
expect(res.statusCode).toBe(200)
expect(res.body).toContain('-H "X-GitHub-Api-Version: 2022-11-28"')
})
test('Missing apiVersion defaults to latest', async () => {
// When no apiVersion is provided, it should default to the latest version
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Should include the default API version header
expect(res.body).toContain('-H "X-GitHub-Api-Version: 2022-11-28"')
})
test('Multiple operations on a page are all rendered', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Check for multiple operation headings
expect(res.body).toContain('## List artifacts for a repository')
expect(res.body).toContain('## Get an artifact')
expect(res.body).toContain('## Delete an artifact')
})
test('Body parameters are formatted correctly for POST/PUT operations', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// For operations with body parameters, check formatting
// (artifacts endpoint is mostly GET/DELETE, but structure should be there)
// The transformer handles body parameters when present
})
test('Content-type header is included for operations that need it', async () => {
const res = await get(makeURL('/en/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// Content-type header appears for operations that require it
// The REST transformer adds this based on the operation data
})
test('Non-English language paths work correctly', async () => {
// Note: This test may fail in dev mode with ENABLED_LANGUAGES=en
// but the transformer itself should handle any language path
const res = await get(makeURL('/ja/rest/actions/artifacts'))
expect(res.statusCode).toBe(200)
// The transformer should work regardless of language prefix
// because it looks for 'rest' in the path and gets the category/subcategory after it
// e.g. /ja/rest/actions/artifacts should work the same as /en/rest/actions/artifacts
// Verify the operation content is present (in English, since REST data is not translated)
expect(res.body).toContain('## List artifacts for a repository')
expect(res.body).toContain('GET /repos/{owner}/{repo}/actions/artifacts')
// Check what language is actually being served by examining the response
// If Japanese translations are loaded, the title will be in Japanese
// Otherwise, it falls back to English
const hasJapaneseTitle = res.body.includes('# GitHub Actions アーティファクト')
const hasEnglishTitle = res.body.includes('# GitHub Actions Artifacts')
// One of them must be present
expect(hasJapaneseTitle || hasEnglishTitle).toBe(true)
// Verify the appropriate content based on which language was served
if (hasJapaneseTitle) {
// If Japanese is loaded, expect Japanese intro text
expect(res.body).toContain('アーティファクト')
} else {
// If Japanese is not loaded, expect English fallback
expect(res.body).toContain('Use the REST API to interact with artifacts in HubGit Actions')
}
})
})