unleash-server
Version:
Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.
166 lines • 7.35 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const test_helper_1 = require("../../helpers/test-helper");
const database_init_1 = __importDefault(require("../../helpers/database-init"));
const no_logger_1 = __importDefault(require("../../../fixtures/no-logger"));
const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
const openapi_enforcer_1 = __importDefault(require("openapi-enforcer"));
const semver_1 = __importDefault(require("semver"));
const openapi_tags_1 = require("../../../../lib/openapi/util/openapi-tags");
let app;
let db;
beforeAll(async () => {
db = await (0, database_init_1.default)('openapi', no_logger_1.default);
app = await (0, test_helper_1.setupApp)(db.stores);
});
afterAll(async () => {
await app.destroy();
await db.destroy();
});
test('should serve the OpenAPI UI', async () => {
return app.request
.get('/docs/openapi/')
.expect('Content-Type', /html/)
.expect(200)
.expect((res) => expect(res.text).toMatchSnapshot());
});
test('should serve the OpenAPI spec', async () => {
return app.request
.get('/docs/openapi.json')
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
// Don't use the version field in snapshot tests. Having the version
// listed in automated testing causes issues when trying to deploy
// new versions of the API (due to mismatch between new tag versions etc).
delete res.body.info.version;
// This test will fail whenever there's a change to the API spec.
// If the change is intended, update the snapshot with `jest -u`.
expect(res.body).toMatchSnapshot();
});
});
test('should serve the OpenAPI spec with a `version` property', async () => {
return app.request
.get('/docs/openapi.json')
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
const { version } = res.body.info;
// ensure there's no whitespace or leading `v`
expect(semver_1.default.clean(version)).toStrictEqual(version);
// ensure the version listed is valid semver
expect(semver_1.default.parse(version, { loose: false })).toBeTruthy();
});
});
test('the generated OpenAPI spec is valid', async () => {
const { body } = await app.request
.get('/docs/openapi.json')
.expect('Content-Type', /json/)
.expect(200);
// this throws if the swagger parser can't parse it correctly
// also parses examples, but _does_ do some string coercion in examples
try {
await swagger_parser_1.default.validate(body);
}
catch (err) {
console.error(err);
// there's an error here, so let's exit after showing it in the console.
expect(true).toBe(false);
}
const [, enforcerError, enforcerWarning] = await (0, openapi_enforcer_1.default)(body, {
fullResult: true,
componentOptions: {
exceptionSkipCodes: [
// allow non-standard formats for strings (including 'uri')
'WSCH001',
// Schemas with an indeterminable type cannot serialize,
// deserialize, or validate values. [WSCH005]
//
// This allows specifying the 'any' type for schemas (such as the
// patchSchema)
'WSCH005',
],
},
});
if (enforcerWarning !== undefined) {
console.warn(enforcerWarning);
}
if (enforcerError !== undefined) {
console.error(enforcerError);
}
expect(enforcerWarning ?? enforcerError).toBe(undefined);
});
test('all root-level tags are "approved tags"', async () => {
const { body: spec } = await app.request
.get('/docs/openapi.json')
.expect('Content-Type', /json/)
.expect(200);
const specTags = spec.tags;
const approvedTags = openapi_tags_1.openApiTags;
// expect spec tags to be a subset of the approved tags
expect(approvedTags).toEqual(expect.arrayContaining(specTags));
});
// All tags that are used for OpenAPI path operations must also be listed in the
// OpenAPI root-level tags list. For us, there's two immediate things that make
// this important:
//
// 1. Swagger UI groups operations by tags. To make sure that endpoints are
// listed where users would expect to find them, they should be given an
// appropriate tag.
//
// 2. The OpenAPI/docusaurus integration we use does not generate documentation
// for paths whose tags are not listed in the root-level tags list.
//
// If none of the official tags seem appropriate for an endpoint, consider
// creating a new tag.
test('all tags are listed in the root "tags" list', async () => {
const { body: spec } = await app.request
.get('/docs/openapi.json')
.expect('Content-Type', /json/)
.expect(200);
const rootLevelTagNames = new Set(spec.tags.map((tag) => tag.name));
// dictionary of all invalid tags found in the spec
let invalidTags = {};
for (const [path, data] of Object.entries(spec.paths)) {
for (const [operation, opData] of Object.entries(data)) {
// ensure that the list of tags for every operation is a subset of
// the list of tags defined on the root level
// check each tag for this operation
for (const tag of opData.tags) {
if (!rootLevelTagNames.has(tag)) {
// store other invalid tags that already exist on this
// operation
const preExistingTags = (invalidTags[path] ?? {})[operation]?.invalidTags ?? [];
// add information about the invalid tag to the invalid tags
// dict.
invalidTags = {
...invalidTags,
[path]: {
...invalidTags[path],
[operation]: {
operationId: opData.operationId,
invalidTags: [...preExistingTags, tag],
},
},
};
}
}
}
}
if (Object.keys(invalidTags).length) {
// create a human-readable list of invalid tags per operation
const msgs = Object.entries(invalidTags).flatMap(([path, data]) => Object.entries(data).map(([operation, opData]) => `${operation.toUpperCase()} ${path} (operation id: ${opData.operationId}) has the following invalid tags: ${opData.invalidTags
.map((tag) => `"${tag}"`)
.join(', ')}`));
// format message
const errorMessage = `The OpenAPI spec contains path-level tags that are not listed in the root-level tags object. The relevant paths, operation ids, and tags are as follows:\n\n${msgs.join('\n\n')}\n\nFor reference, the root-level tags are: ${spec.tags
.map((tag) => `"${tag.name}"`)
.join(', ')}`;
console.error(errorMessage);
}
expect(invalidTags).toStrictEqual({});
});
//# sourceMappingURL=openapi.e2e.test.js.map