-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Thomas Farr <[email protected]>
- Loading branch information
Showing
5 changed files
with
154 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import fs from 'fs' | ||
import { type OpenAPIV3 } from 'openapi-types' | ||
import { HTTP_METHODS, read_yaml } from '../../helpers' | ||
|
||
export default class CoverageCalculator { | ||
private readonly _cluster_spec: OpenAPIV3.Document | ||
private readonly _input_spec: OpenAPIV3.Document | ||
private readonly _output_path: string | ||
|
||
constructor (cluster_spec_path: string, input_spec_path: string, output_path: string) { | ||
this._cluster_spec = read_yaml(cluster_spec_path) as OpenAPIV3.Document | ||
this._input_spec = read_yaml(input_spec_path) as OpenAPIV3.Document | ||
this._output_path = output_path | ||
} | ||
|
||
calculate (): void { | ||
type Endpoints = Record<string, Set<OpenAPIV3.HttpMethods>> | ||
const collect = (document: OpenAPIV3.Document): Endpoints => | ||
Object.fromEntries( | ||
Object.entries(document.paths) | ||
.map(([path, path_item]): [string, Set<OpenAPIV3.HttpMethods>] => { | ||
// Sanitize path params to ignore naming of params in route templates | ||
path = path.replaceAll(/\{[^}]+}/g, '{}') | ||
if (path_item == null) return [path, new Set()] | ||
return [path, new Set(HTTP_METHODS.filter(method => path_item[method] != null))] | ||
}) | ||
) | ||
const count = (endpoints: Endpoints): number => | ||
Object.values(endpoints).map(methods => methods.size).reduce((acc, v) => acc + v, 0) | ||
const prune = (endpoints: Endpoints): Endpoints => | ||
Object.fromEntries(Object.entries(endpoints).filter(([_, methods]) => methods.size > 0)) | ||
|
||
const uncovered = collect(this._cluster_spec) | ||
const specified_but_not_provided = collect(this._input_spec) | ||
const covered: Endpoints = {} | ||
|
||
for (const [path, methods] of Object.entries(uncovered)) { | ||
if (specified_but_not_provided[path] === undefined) continue | ||
|
||
for (const method of [...methods]) { | ||
if (!specified_but_not_provided[path].delete(method)) continue | ||
|
||
if (covered[path] === undefined) covered[path] = new Set() | ||
covered[path].add(method) | ||
uncovered[path].delete(method) | ||
} | ||
} | ||
|
||
const uncovered_count = count(uncovered) | ||
const covered_count = count(covered) | ||
const total_count = uncovered_count + covered_count | ||
|
||
const json = JSON.stringify( | ||
{ | ||
$description: { | ||
uncovered: 'Endpoints provided by the OpenSearch cluster but DO NOT exist in the specification', | ||
covered: 'Endpoints both provided by the OpenSearch cluster and exist in the specification', | ||
specified_but_not_provided: 'Endpoints NOT provided by the OpenSearch cluster but exist in the specification' | ||
}, | ||
counts: { | ||
uncovered: uncovered_count, | ||
uncovered_pct: Math.round(uncovered_count / total_count * 100 * 100) / 100, | ||
covered: covered_count, | ||
covered_pct: Math.round(covered_count / total_count * 100 * 100) / 100, | ||
specified_but_not_provided: count(specified_but_not_provided) | ||
}, | ||
endpoints: { | ||
uncovered: prune(uncovered), | ||
covered: prune(covered), | ||
specified_but_not_provided: prune(specified_but_not_provided) | ||
} | ||
}, | ||
(_, value) => { | ||
if (value instanceof Set) return [...value] | ||
return value | ||
}, | ||
2) | ||
|
||
fs.writeFileSync(this._output_path, json) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Command, Option } from '@commander-js/extra-typings' | ||
import CoverageCalculator from './CoverageCalculator' | ||
import { resolve } from 'path' | ||
|
||
const command = new Command() | ||
.description('Calculates the coverage of a specification against an OpenSearch clusters generated specification.') | ||
.addOption(new Option('--cluster <path>', 'path to the cluster\'s generated specification.').makeOptionMandatory()) | ||
.addOption(new Option('--specification <path>', 'path to the specification to calculate coverage of.').makeOptionMandatory()) | ||
.addOption(new Option('-o, --output <path>', 'path to the output file.').default(resolve(__dirname, '../../../build/coverage.json'))) | ||
.allowExcessArguments(false) | ||
.parse() | ||
|
||
const opts = command.opts() | ||
const calculator = new CoverageCalculator(opts.cluster, opts.specification, opts.output) | ||
calculator.calculate() |