-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathapi-content-quality.test.ts
More file actions
116 lines (99 loc) · 3.59 KB
/
Copy pathapi-content-quality.test.ts
File metadata and controls
116 lines (99 loc) · 3.59 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
import { readdirSync, readFileSync } from 'node:fs';
import { join, relative } from 'node:path';
import { describe, expect, it } from 'vitest';
const PROJECT_ROOT = process.cwd();
const API_REFERENCE_DIR = join(PROJECT_ROOT, 'api-reference');
const FRONTMATTER_API_PATTERN = /^api:\s*"([A-Z]+) ([^"]+)"/mu;
const UNIX_TIMESTAMP_FILTER_ENDPOINTS: ReadonlySet<string> = new Set([
'GET /x/lists/{id}/tweets',
'GET /x/tweets/{id}/quotes',
'GET /x/tweets/{id}/replies',
'GET /x/users/{id}/mentions',
] as const);
const FORBIDDEN_PUBLIC_ENDPOINT_SNIPPETS = [
['shared', 'read', 'pool'].join(' '),
['read', 'pool'].join(' '),
['participant', 'session'].join(' '),
['session', 'reads', 'the', 'conversation'].join(' '),
['whose', 'session', 'reads'].join(' '),
'A transient upstream issue occurred. Safe to retry with exponential backoff.',
'The X data source returned an error. Retry after a short delay.',
'The best-practice response contract can return 424 when the upstream X data source fails.',
'Returned when you opt into the normalized v1 response contract and the upstream dependency fails.',
'The X data source is temporarily unavailable. Retry with exponential backoff.',
] as const;
interface ContentFinding {
readonly file: string;
readonly issue: string;
}
function listApiReferenceFiles(dir: string): readonly string[] {
const files: string[] = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...listApiReferenceFiles(fullPath));
continue;
}
if (entry.name.endsWith('.mdx')) {
files.push(relative(PROJECT_ROOT, fullPath));
}
}
return files.sort();
}
function isApiEndpointPage(source: string): boolean {
return FRONTMATTER_API_PATTERN.test(source);
}
function operationKey(source: string): string | undefined {
const match = FRONTMATTER_API_PATTERN.exec(source);
if (match?.[1] === undefined || match[2] === undefined) {
return undefined;
}
return `${match[1]} ${match[2]}`;
}
function collectContentFindings(): readonly ContentFinding[] {
const findings: ContentFinding[] = [];
for (const file of listApiReferenceFiles(API_REFERENCE_DIR)) {
const source = readFileSync(join(PROJECT_ROOT, file), 'utf8');
if (!isApiEndpointPage(source)) {
continue;
}
if (!source.includes('<CodeGroup>')) {
findings.push({ file, issue: 'Missing copy-ready code examples.' });
}
if (!/^## Headers\b/mu.test(source)) {
findings.push({ file, issue: 'Missing headers section.' });
}
if (!/^## Response\b/mu.test(source)) {
findings.push({ file, issue: 'Missing response section.' });
}
if (!/<Tab title="2\d\d\b/u.test(source)) {
findings.push({ file, issue: 'Missing successful response tab.' });
}
const key = operationKey(source);
for (const snippet of FORBIDDEN_PUBLIC_ENDPOINT_SNIPPETS) {
if (source.includes(snippet)) {
findings.push({
file,
issue: `Public endpoint page exposes internal wording "${snippet}".`,
});
}
}
if (
key !== undefined &&
UNIX_TIMESTAMP_FILTER_ENDPOINTS.has(key) &&
source.includes('ISO 8601 timestamp')
) {
findings.push({
file,
issue: 'sinceTime/untilTime must document Unix timestamp filters.',
});
}
}
return findings;
}
describe('API endpoint content quality', (): void => {
it('keeps endpoint pages useful beyond generated signatures', (): void => {
expect.assertions(1);
expect(collectContentFindings()).toStrictEqual([]);
});
});