docs-v2/api-docs/scripts/dist/test-post-process-specs.js

490 lines
20 KiB
JavaScript

#!/usr/bin/env node
"use strict";
/**
* Tests for post-process-specs.ts
*
* Standalone test script — no test runner required.
*
* Usage:
* node api-docs/scripts/dist/test-post-process-specs.js
*
* Creates temporary fixtures in $TMPDIR, runs the compiled script against
* them via child_process, and reports pass/fail for each case.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const child_process_1 = require("child_process");
const yaml = __importStar(require("js-yaml"));
// ---------------------------------------------------------------------------
// Paths
// ---------------------------------------------------------------------------
const SCRIPT = path.resolve(__dirname, 'post-process-specs.js');
function makeSpec(tags, operationTags, overrides) {
return {
openapi: '3.0.0',
info: { title: 'Test', version: '1.0.0' },
tags,
paths: {
'/test': {
get: {
operationId: 'testOp',
tags: operationTags,
responses: {},
},
},
},
...overrides,
};
}
function createTmpRoot() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'post-process-test-'));
const productDir = path.join(root, 'influxdb3', 'core');
const specDir = path.join(productDir, 'v3');
const specPath = path.join(specDir, 'openapi.yaml');
const buildSpecPath = path.join(root, '_build', 'influxdb3', 'core', 'v3', 'openapi.yaml');
fs.mkdirSync(specDir, { recursive: true });
const config = {
apis: {
data: {
root: 'v3/openapi.yaml',
},
},
};
fs.writeFileSync(path.join(productDir, '.config.yml'), yaml.dump(config));
return { root, productDir, specDir, specPath, buildSpecPath };
}
function writeYaml(filePath, data) {
fs.writeFileSync(filePath, yaml.dump(data, { lineWidth: -1 }), 'utf8');
}
function readYaml(filePath) {
return yaml.load(fs.readFileSync(filePath, 'utf8'));
}
function runScript(root, productFilter) {
const scriptArgs = ['--root', root];
if (productFilter)
scriptArgs.push(productFilter);
const result = (0, child_process_1.spawnSync)('node', [SCRIPT, ...scriptArgs], {
encoding: 'utf8',
timeout: 10_000,
});
return {
stdout: result.stdout ?? '',
stderr: result.stderr ?? '',
exitCode: result.status ?? 1,
};
}
function cleanup(root) {
fs.rmSync(root, { recursive: true, force: true });
}
// ---------------------------------------------------------------------------
// Test runner
// ---------------------------------------------------------------------------
let passed = 0;
let failed = 0;
const failures = [];
function pass(name) {
console.log(` PASS ${name}`);
passed++;
}
function fail(name, reason) {
console.log(` FAIL ${name}`);
console.log(` ${reason}`);
failed++;
failures.push(`${name}: ${reason}`);
}
function assert(name, condition, reason) {
if (condition) {
pass(name);
}
else {
fail(name, reason);
}
}
// ---------------------------------------------------------------------------
// Tag config tests
// ---------------------------------------------------------------------------
function testDescriptionSetting() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': { description: 'Write line protocol data to InfluxDB.' },
},
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('1a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
const tag = spec.tags?.find((t) => t.name === 'Write data');
assert('1b. description applied to tag', tag?.description === 'Write line protocol data to InfluxDB.', `description was: ${tag?.description}`);
}
finally {
cleanup(root);
}
}
function testTagRename() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Cache data' }], ['Cache data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Cache data': { rename: 'Cache distinct values' },
},
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('2a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
const oldTag = spec.tags?.find((t) => t.name === 'Cache data');
assert('2b. old tag name gone from tags[]', !oldTag, 'old tag still present in tags[]');
const newTag = spec.tags?.find((t) => t.name === 'Cache distinct values');
assert('2c. new tag name in tags[]', !!newTag, 'renamed tag not found in tags[]');
const opTags = spec.paths?.['/test']?.['get']?.tags ?? [];
assert('2d. operation.tags[] updated', opTags.includes('Cache distinct values') &&
!opTags.includes('Cache data'), `operation tags: ${JSON.stringify(opTags)}`);
}
finally {
cleanup(root);
}
}
function testXRelated() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': {
description: 'Write data.',
'x-related': [
{ title: 'Write data guide', href: '/influxdb3/core/write-data/' },
],
},
},
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('3a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
const tag = spec.tags?.find((t) => t.name === 'Write data');
const related = tag?.['x-related'];
assert('3b. x-related present', Array.isArray(related) && related.length === 1, `x-related: ${JSON.stringify(related)}`);
assert('3c. x-related entry correct', related?.[0]?.title === 'Write data guide' &&
related?.[0]?.href === '/influxdb3/core/write-data/', `entry: ${JSON.stringify(related?.[0])}`);
}
finally {
cleanup(root);
}
}
function testStaleConfigWarning() {
const { root, specDir, specPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': { description: 'Write data.' },
'Ghost tag': { description: 'This tag does not exist in the spec.' },
},
});
const { stderr, exitCode } = runScript(root, 'influxdb3/core');
assert('4a. exits 0 (warnings are not errors)', exitCode === 0, `exit code was ${exitCode}`);
assert('4b. stale config warning emitted', stderr.includes("config tag 'Ghost tag' not found in spec operations"), `stderr: ${stderr}`);
}
finally {
cleanup(root);
}
}
function testUncoveredTagWarning() {
const { root, specDir, specPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }, { name: 'Query data' }], ['Write data', 'Query data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': { description: 'Write data.' },
},
});
const { stderr, exitCode } = runScript(root, 'influxdb3/core');
assert('5a. exits 0 (warnings are not errors)', exitCode === 0, `exit code was ${exitCode}`);
assert('5b. uncovered tag warning emitted', stderr.includes("spec tag 'Query data' has no config entry in tags.yml"), `stderr: ${stderr}`);
}
finally {
cleanup(root);
}
}
function testNoTagsYmlSilentSkip() {
const { root, specPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
const { stderr, exitCode } = runScript(root, 'influxdb3/core');
assert('6a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
assert('6b. no error output', !stderr.includes('ERROR'), `unexpected error in stderr: ${stderr}`);
}
finally {
cleanup(root);
}
}
function testMalformedYamlFails() {
const { root, specDir, specPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
fs.writeFileSync(path.join(specDir, 'tags.yml'), 'tags:\n Write data:\n description: [\n bad yaml here', 'utf8');
const { exitCode } = runScript(root, 'influxdb3/core');
assert('7a. exits 1 on malformed YAML', exitCode === 1, `exit code was ${exitCode}`);
}
finally {
cleanup(root);
}
}
// ---------------------------------------------------------------------------
// Content overlay tests
// ---------------------------------------------------------------------------
// 8. Info overlay — API-specific content/info.yml
function testInfoOverlay() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([], [], {
info: { title: 'Original Title', version: '0.0.0' },
}));
// Create API-specific content/info.yml
const contentDir = path.join(specDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
writeYaml(path.join(contentDir, 'info.yml'), {
title: 'Overridden Title',
version: '2.0.0',
'x-influxdata-short-title': 'Short',
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('8a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('8b. title overridden', spec.info.title === 'Overridden Title', `title: ${spec.info.title}`);
assert('8c. version overridden', spec.info.version === '2.0.0', `version: ${spec.info.version}`);
assert('8d. x-influxdata-short-title applied', spec.info['x-influxdata-short-title'] ===
'Short', `x-influxdata-short-title: ${spec.info['x-influxdata-short-title']}`);
}
finally {
cleanup(root);
}
}
// 9. Info overlay — product-level fallback
function testInfoOverlayProductFallback() {
const { root, productDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([], [], {
info: { title: 'Original', version: '1.0.0' },
}));
// Create product-level content/info.yml (NOT in specDir/content/)
const contentDir = path.join(productDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
writeYaml(path.join(contentDir, 'info.yml'), {
title: 'Product-Level Title',
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('9a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('9b. title from product-level', spec.info.title === 'Product-Level Title', `title: ${spec.info.title}`);
assert('9c. version preserved', spec.info.version === '1.0.0', `version: ${spec.info.version}`);
}
finally {
cleanup(root);
}
}
// 10. Servers overlay
function testServersOverlay() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([], [], {
servers: [{ url: 'https://old.example.com' }],
}));
const contentDir = path.join(specDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
writeYaml(path.join(contentDir, 'servers.yml'), [
{
url: 'https://{baseurl}',
description: 'InfluxDB API',
variables: {
baseurl: {
enum: ['localhost:8181'],
default: 'localhost:8181',
description: 'InfluxDB URL',
},
},
},
]);
const { exitCode } = runScript(root, 'influxdb3/core');
assert('10a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('10b. servers replaced', spec.servers?.length === 1, `server count: ${spec.servers?.length}`);
assert('10c. server URL correct', spec.servers?.[0]?.url === 'https://{baseurl}', `url: ${spec.servers?.[0]?.url}`);
assert('10d. server variables present', spec.servers?.[0]?.variables !== undefined, 'variables missing');
}
finally {
cleanup(root);
}
}
// 11. Info overlay preserves fields not in overlay
function testInfoOverlayPreservesFields() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([], [], {
info: {
title: 'Original Title',
version: '3.0.0',
description: 'Original description.',
license: { name: 'MIT', url: 'https://opensource.org/licenses/MIT' },
},
}));
const contentDir = path.join(specDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
// Overlay only sets x-* fields, no title/version/description
writeYaml(path.join(contentDir, 'info.yml'), {
'x-influxdata-short-title': 'InfluxDB 3 API',
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('11a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('11b. title preserved', spec.info.title === 'Original Title', `title: ${spec.info.title}`);
assert('11c. version preserved', spec.info.version === '3.0.0', `version: ${spec.info.version}`);
assert('11d. description preserved', spec.info.description === 'Original description.', `desc: ${spec.info.description}`);
assert('11e. x-influxdata-short-title added', spec.info['x-influxdata-short-title'] ===
'InfluxDB 3 API', 'x-influxdata-short-title missing');
}
finally {
cleanup(root);
}
}
// 12. No content overlays — spec unchanged
function testNoOverlaysNoWrite() {
const { root, specPath, buildSpecPath } = createTmpRoot();
try {
const original = makeSpec([{ name: 'Write data' }], ['Write data']);
writeYaml(specPath, original);
const mtime = fs.statSync(specPath).mtimeMs;
// Small delay to detect mtime changes
const start = Date.now();
while (Date.now() - start < 50) {
/* busy wait */
}
const { exitCode } = runScript(root, 'influxdb3/core');
assert('12a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const built = readYaml(buildSpecPath);
assert('12b. build output matches input when no overlays/tags', JSON.stringify(built) === JSON.stringify(original), 'build output differed from source');
assert('12c. source file untouched', fs.statSync(specPath).mtimeMs === mtime, 'source spec modified');
}
finally {
cleanup(root);
}
}
// 13. Combined: info + servers + tags applied together
function testCombinedOverlaysAndTags() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data'], {
info: { title: 'Original', version: '1.0.0' },
servers: [{ url: 'https://old.example.com' }],
}));
const contentDir = path.join(specDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
writeYaml(path.join(contentDir, 'info.yml'), {
title: 'New Title',
'x-influxdata-short-title': 'Short',
});
writeYaml(path.join(contentDir, 'servers.yml'), [
{ url: 'https://new.example.com', description: 'New Server' },
]);
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': {
description: 'Write line protocol data.',
'x-related': [{ title: 'Guide', href: '/guide/' }],
},
},
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('13a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('13b. info title updated', spec.info.title === 'New Title', `title: ${spec.info.title}`);
assert('13c. info version preserved', spec.info.version === '1.0.0', `version: ${spec.info.version}`);
assert('13d. x-influxdata-short-title set', spec.info['x-influxdata-short-title'] ===
'Short', 'missing');
assert('13e. servers replaced', spec.servers?.[0]?.url === 'https://new.example.com', `url: ${spec.servers?.[0]?.url}`);
const tag = spec.tags?.find((t) => t.name === 'Write data');
assert('13f. tag description set', tag?.description === 'Write line protocol data.', `desc: ${tag?.description}`);
assert('13g. tag x-related set', Array.isArray(tag?.['x-related']) && tag['x-related'].length === 1, `x-related: ${JSON.stringify(tag?.['x-related'])}`);
}
finally {
cleanup(root);
}
}
// ---------------------------------------------------------------------------
// Run all tests
// ---------------------------------------------------------------------------
const tests = [
// Tag config tests (carried forward)
['1. Tag description setting', testDescriptionSetting],
['2. Tag rename (tags[] and operation.tags[])', testTagRename],
['3. x-related links', testXRelated],
['4. Warning: stale config reference', testStaleConfigWarning],
['5. Warning: uncovered spec tag', testUncoveredTagWarning],
['6. No tags.yml — silent skip', testNoTagsYmlSilentSkip],
['7. Malformed YAML — exit 1', testMalformedYamlFails],
// Content overlay tests (new)
['8. Info overlay — API-specific', testInfoOverlay],
['9. Info overlay — product-level fallback', testInfoOverlayProductFallback],
['10. Servers overlay', testServersOverlay],
[
'11. Info overlay preserves fields not in overlay',
testInfoOverlayPreservesFields,
],
['12. No overlays or tags — build mirrors source', testNoOverlaysNoWrite],
['13. Combined: info + servers + tags', testCombinedOverlaysAndTags],
];
console.log('\npost-process-specs tests\n');
for (const [name, fn] of tests) {
console.log(name);
try {
fn();
}
catch (err) {
fail(name, `threw: ${err.message}`);
}
}
console.log(`\n${passed + failed} tests: ${passed} passed, ${failed} failed`);
if (failures.length > 0) {
console.log('\nFailed:');
for (const f of failures)
console.log(` - ${f}`);
process.exit(1);
}
//# sourceMappingURL=test-post-process-specs.js.map