Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generating import file for postman #770

Closed
wants to merge 14 commits into from
Prev Previous commit
Next Next commit
divide them to another module
Signed-off-by: Tokesh <tokesh789@gmail.com>
Tokesh committed Jan 11, 2025
commit 10e35af3050c28f0d38c9a67515148654a61d7a5
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
"coverage:spec": "ts-node tools/src/coverage/coverage.ts",
"dump-cluster-spec": "ts-node tools/src/dump-cluster-spec/dump-cluster-spec.ts",
"generate-types": "ts-node tools/src/tester/_generate_story_types.ts",
"export:postman": "ts-node tools/src/exporter/export.ts",
"lint:spec": "ts-node tools/src/linter/lint.ts",
"lint": "eslint . --report-unused-disable-directives",
"lint--fix": "eslint . --fix --report-unused-disable-directives",
79 changes: 79 additions & 0 deletions tools/src/exporter/ExportChapters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

import fs from 'fs'
import { read_yaml } from '../helpers'
import { basename, resolve } from 'path'
import _ from 'lodash'
import { StoryEvaluations, StoryFile } from 'tester/types/eval.types'
import { Logger } from 'Logger'
import StoryParser from './StoryParser'
import { PostmanManager } from './PostmanManager'

export default class ExportChapters {
private readonly _story_files: Record<string, StoryFile[]> = {}
private readonly _logger: Logger
private readonly _PostmanManager: PostmanManager

constructor (logger: Logger, postman_manager: PostmanManager) {
this._PostmanManager = postman_manager
this._logger = logger
}

async run (story_path: string): Promise<{ results: StoryEvaluations, failed: boolean }> {
let failed = false
const story_files = this.story_files(story_path)
const results: StoryEvaluations = { evaluations: [] }

for (const story_file of story_files) {
for(const chapter of story_file.story.chapters) {
console.log(chapter);
this._PostmanManager.add_to_collection('url', chapter.method, chapter.path, {}, {}, 'application/json', story_file.full_path);
}
this._logger.info(`Evaluating ${story_file.display_path} ...`)
}
this._PostmanManager.save_collection()

return { results, failed }
}

story_files(story_path: string): StoryFile[] {
if (this._story_files[story_path] !== undefined) return this._story_files[story_path]
this._story_files[story_path] = this.#sort_story_files(this.#collect_story_files(resolve(story_path), '', ''))
return this._story_files[story_path]
}

#collect_story_files (folder: string, file: string, prefix: string): StoryFile[] {
const path = file === '' ? folder : `${folder}/${file}`
const next_prefix = prefix === '' ? file : `${prefix}/${file}`
if (file.startsWith('.') || file == 'docker-compose.yml' || file == 'Dockerfile' || file.endsWith('.py')) {
return []
} else if (fs.statSync(path).isFile()) {
const story = StoryParser.parse(read_yaml(path))
return [{
display_path: next_prefix === '' ? basename(path) : next_prefix,
full_path: path,
story
}]
} else {
return _.compact(fs.readdirSync(path).flatMap(next_file => {
return this.#collect_story_files(path, next_file, next_prefix)
}))
}
}

#sort_story_files (story_files: StoryFile[]): StoryFile[] {
return story_files.sort(({ display_path: a }, { display_path: b }) => {
const a_depth = a.split('/').length
const b_depth = b.split('/').length
if (a_depth !== b_depth) return a_depth - b_depth
return a.localeCompare(b)
})
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

import fs from 'fs';

export class PostmanManager {
41 changes: 41 additions & 0 deletions tools/src/exporter/StoryParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

import _ from "lodash";
import { ParsedChapter, ParsedStory } from "tester/types/parsed_story.types";
import { Chapter, Story } from "tester/types/story.types";

export default class StoryParser {
static parse(story: Story): ParsedStory {
return {
...story,
chapters: this.#expand_chapters(story.chapters),
}
}

static #chapter_methods(methods: string[] | string): string[] {
return [...(Array.isArray(methods) ? methods : [methods])]
}

static #expand_chapters(chapters?: Chapter[]): ParsedChapter[] {
if (chapters === undefined) return []
return _.flatMap(_.map(chapters, (chapter) => {
return _.map(this.#chapter_methods(chapter.method), (method) => {
let synopsis = chapter.synopsis && Array.isArray(chapter.method) ?
`${chapter.synopsis} [${method}]` :
chapter.synopsis
return {
...chapter,
synopsis,
method
}
})
})) as ParsedChapter[]
}
}
49 changes: 49 additions & 0 deletions tools/src/exporter/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

import { Logger, LogLevel } from '../Logger'
import { Command, Option } from '@commander-js/extra-typings'
import {
AWS_ACCESS_KEY_ID_OPTION,
AWS_REGION_OPTION,
AWS_SECRET_ACCESS_KEY_OPTION,
AWS_SERVICE_OPTION,
AWS_SESSION_TOKEN_OPTION,
get_opensearch_opts_from_cli,
OPENSEARCH_CERT_OPTION,
OPENSEARCH_INSECURE_OPTION,
OPENSEARCH_KEY_OPTION,
OPENSEARCH_PASSWORD_OPTION,
OPENSEARCH_URL_OPTION,
OPENSEARCH_USERNAME_OPTION,
OpenSearchHttpClient
} from '../OpenSearchHttpClient'
import * as process from 'node:process'
import ChapterReader from 'tester/ChapterReader'
import SupplementalChapterEvaluator from 'tester/SupplementalChapterEvaluator'
import StoryValidator from 'tester/StoryValidator'
import StoryEvaluator from 'tester/StoryEvaluator'
import { ConsoleResultLogger } from 'tester/ResultLogger'
import TestRunner from 'tester/TestRunner'
import ExportChapters from './ExportChapters'
import { PostmanManager } from './PostmanManager'

const command = new Command()
.description('Run test stories against the OpenSearch spec.')
.addOption(new Option('--tests, --tests-path <path>', 'path to the root folder of the tests').default('./tests/default'))
.allowExcessArguments(false)
.parse()


const opts = command.opts()
const logger = new Logger(LogLevel.warn)
const postman_manager = new PostmanManager()
const runner = new ExportChapters(logger, postman_manager)

runner.run(opts.testsPath)
8 changes: 4 additions & 4 deletions tools/src/tester/ChapterEvaluator.ts
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ export default class ChapterEvaluator {
this.logger = logger
}

async evaluate(chapter: ParsedChapter, skip: boolean, story_outputs: StoryOutputs, full_path?: string): Promise<ChapterEvaluation> {
async evaluate(chapter: ParsedChapter, skip: boolean, story_outputs: StoryOutputs): Promise<ChapterEvaluation> {
if (skip) return { title: chapter.synopsis, overall: { result: Result.SKIPPED } }

const operation = this._operation_locator.locate_operation(chapter)
@@ -48,7 +48,7 @@ export default class ChapterEvaluator {
var result: ChapterEvaluation

do {
result = await this.#evaluate(chapter, operation, story_outputs, ++retry > 1 ? retry - 1 : undefined, full_path)
result = await this.#evaluate(chapter, operation, story_outputs, ++retry > 1 ? retry - 1 : undefined)

if (result.overall.result === Result.PASSED || result.overall.result === Result.SKIPPED) {
return result
@@ -62,8 +62,8 @@ export default class ChapterEvaluator {
return result
}

async #evaluate(chapter: ParsedChapter, operation: ParsedOperation, story_outputs: StoryOutputs, retries?: number, full_path?: string): Promise<ChapterEvaluation> {
const response = await this._chapter_reader.read(chapter, story_outputs, full_path)
async #evaluate(chapter: ParsedChapter, operation: ParsedOperation, story_outputs: StoryOutputs, retries?: number): Promise<ChapterEvaluation> {
const response = await this._chapter_reader.read(chapter, story_outputs)
const params = this.#evaluate_parameters(chapter, operation, story_outputs)
const request = this.#evaluate_request(chapter, operation, story_outputs)
const status = this.#evaluate_status(chapter, response)
12 changes: 3 additions & 9 deletions tools/src/tester/ChapterReader.ts
Original file line number Diff line number Diff line change
@@ -18,20 +18,17 @@ import CBOR from 'cbor'
import SMILE from 'smile-js'
import { APPLICATION_CBOR, APPLICATION_JSON, APPLICATION_SMILE, APPLICATION_YAML, TEXT_PLAIN } from "./MimeTypes";
import _ from 'lodash'
import { PostmanManager } from './PostmanManager'

export default class ChapterReader {
private readonly _client: OpenSearchHttpClient
private readonly logger: Logger
private readonly postman_manager: PostmanManager;

constructor (client: OpenSearchHttpClient, logger: Logger, collection_path: string = './postman_collection.json') {
constructor (client: OpenSearchHttpClient, logger: Logger) {
this._client = client
this.logger = logger
this.postman_manager = new PostmanManager(collection_path);
this.logger = logger;
}

async read (chapter: ChapterRequest, story_outputs: StoryOutputs, full_path?: string): Promise<ActualResponse> {
async read (chapter: ChapterRequest, story_outputs: StoryOutputs): Promise<ActualResponse> {
const response: Record<string, any> = {}
const resolved_params = story_outputs.resolve_params(chapter.parameters ?? {})
const [url_path, params] = this.#parse_url(chapter.path, resolved_params)
@@ -41,8 +38,6 @@ export default class ChapterReader {
content_type
) : undefined

this.postman_manager.add_to_collection(this._client.get_url(), chapter.method, url_path, headers, params, request_data, content_type, full_path);

this.logger.info(`=> ${chapter.method} ${url_path} (${to_json(params)}) [${content_type}] ${_.compact([to_json(headers), to_json(request_data)]).join(' | ')}`)
await this._client.request({
url: url_path,
@@ -72,7 +67,6 @@ export default class ChapterReader {
this.logger.info(`<= ${response.status} (${response.content_type}) | ${response.payload !== undefined ? to_json(response.payload) : response.message}`)
}
})
this.postman_manager.save_collection();
return response as ActualResponse
}

6 changes: 3 additions & 3 deletions tools/src/tester/StoryEvaluator.ts
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ export default class StoryEvaluator {

const story_outputs = new StoryOutputs()
const { evaluations: prologues, has_errors: prologue_errors } = await this.#evaluate_supplemental_chapters(story.prologues ?? [], dry_run, story_outputs)
const chapters = await this.#evaluate_chapters(story.chapters, prologue_errors, dry_run, story_outputs, version, distribution, full_path)
const chapters = await this.#evaluate_chapters(story.chapters, prologue_errors, dry_run, story_outputs, version, distribution)
const { evaluations: epilogues } = await this.#evaluate_supplemental_chapters(story.epilogues ?? [], dry_run, story_outputs)

const result: StoryEvaluation = {
@@ -107,7 +107,7 @@ export default class StoryEvaluator {
}
}

async #evaluate_chapters(chapters: ParsedChapter[], has_errors: boolean, dry_run: boolean, story_outputs: StoryOutputs, version?: string, distribution?: string, full_path?: string): Promise<ChapterEvaluation[]> {
async #evaluate_chapters(chapters: ParsedChapter[], has_errors: boolean, dry_run: boolean, story_outputs: StoryOutputs, version?: string, distribution?: string): Promise<ChapterEvaluation[]> {
const evaluations: ChapterEvaluation[] = []
for (const chapter of chapters) {
const title = chapter.synopsis || `${chapter.method} ${chapter.path}`
@@ -124,7 +124,7 @@ export default class StoryEvaluator {
evaluations.push({ title, overall: { result: Result.IGNORED, message: chapter.pending } })
continue
} else {
const evaluation = await this._chapter_evaluator.evaluate(chapter, has_errors, story_outputs, full_path)
const evaluation = await this._chapter_evaluator.evaluate(chapter, has_errors, story_outputs)
has_errors = has_errors || evaluation.overall.result === Result.ERROR
if (evaluation.output !== undefined && chapter.id !== undefined) {
story_outputs.set_chapter_output(chapter.id, evaluation.output)