From 631b4b16a7c23019dac45f4e7199969bedc5d90b Mon Sep 17 00:00:00 2001 From: Chinmay Shewale Date: Wed, 19 Jul 2023 16:05:10 +0530 Subject: [PATCH] test: local server for mocking tests (#625) Co-authored-by: Lukasz Gornicki --- test/commands/convert.test.ts | 19 ++- test/commands/generate/models.test.ts | 13 +- test/commands/optimize.test.ts | 15 +- test/commands/validate.test.ts | 46 +++--- test/dummyspec/dummySpec.yml | 209 ++++++++++++++++++++++++++ test/testHelper.ts | 53 ++++++- 6 files changed, 319 insertions(+), 36 deletions(-) create mode 100644 test/dummyspec/dummySpec.yml diff --git a/test/commands/convert.test.ts b/test/commands/convert.test.ts index f0bfaf71d23..ece81b24945 100644 --- a/test/commands/convert.test.ts +++ b/test/commands/convert.test.ts @@ -1,9 +1,8 @@ import path from 'path'; import { test } from '@oclif/test'; import { NO_CONTEXTS_SAVED } from '../../src/errors/context-error'; -import TestHelper from '../testHelper'; +import TestHelper, { createMockServer, stopMockServer } from '../testHelper'; import fs from 'fs-extra'; -import { unlink, unlinkSync } from 'fs'; const testHelper = new TestHelper(); const filePath = './test/specification.yml'; @@ -19,6 +18,14 @@ describe('convert', () => { testHelper.deleteDummyContextFile(); }); + beforeAll(() => { + createMockServer(); + }); + + afterAll(() => { + stopMockServer(); + }); + test .stderr() .stdout() @@ -42,9 +49,9 @@ describe('convert', () => { test .stderr() .stdout() - .command(['convert', 'https://bit.ly/asyncapi']) + .command(['convert', 'http://localhost:8080/dummySpec.yml']) .it('works when url is passed', (ctx, done) => { - expect(ctx.stdout).toContain('URL https://bit.ly/asyncapi successfully converted!\n'); + expect(ctx.stdout).toContain('URL http://localhost:8080/dummySpec.yml successfully converted!\n'); expect(ctx.stderr).toEqual(''); done(); }); @@ -95,7 +102,7 @@ describe('convert', () => { } } }); - + test .stderr() .stdout() @@ -170,4 +177,4 @@ describe('convert', () => { done(); }); }); -}); +}); diff --git a/test/commands/generate/models.test.ts b/test/commands/generate/models.test.ts index 191faa30980..ff0be8ec940 100644 --- a/test/commands/generate/models.test.ts +++ b/test/commands/generate/models.test.ts @@ -2,14 +2,21 @@ /* eslint-disable sonarjs/no-identical-functions */ import path from 'path'; import { test } from '@oclif/test'; +import { createMockServer, stopMockServer } from '../../testHelper'; const generalOptions = ['generate:models']; const outputDir = './test/commands/generate/models'; describe('models', () => { + beforeAll(() => { + createMockServer(); + }); + afterAll(() => { + stopMockServer(); + }); test .stderr() .stdout() - .command([...generalOptions, 'typescript', 'http://bit.ly/asyncapi']) + .command([...generalOptions, 'typescript', 'http://localhost:8080/dummySpec.yml']) .it('works with remote AsyncAPI files', (ctx, done) => { expect(ctx.stderr).toEqual(''); expect(ctx.stdout).toMatchSnapshot(); @@ -355,10 +362,10 @@ describe('models', () => { test .stderr() .stdout() - .command([...generalOptions, 'typescript', 'http://bit.ly/asyncapi', '--log-diagnostics']) + .command([...generalOptions, 'typescript', 'http://localhost:8080/dummySpec.yml', '--log-diagnostics']) .it('works with remote AsyncAPI files', (ctx, done) => { expect(ctx.stderr).toEqual(''); - expect(ctx.stdout).toMatch('URL http://bit.ly/asyncapi is valid but has (itself and/or referenced documents) governance issues.'); + expect(ctx.stdout).toMatch('URL http://localhost:8080/dummySpec.yml is valid but has (itself and/or referenced documents) governance issues.'); done(); }); }); diff --git a/test/commands/optimize.test.ts b/test/commands/optimize.test.ts index d5a94878d79..b4d774093f1 100644 --- a/test/commands/optimize.test.ts +++ b/test/commands/optimize.test.ts @@ -1,7 +1,6 @@ import path from 'path'; import { test } from '@oclif/test'; -import { NO_CONTEXTS_SAVED } from '../../src/errors/context-error'; -import TestHelper from '../testHelper'; +import TestHelper, { createMockServer, stopMockServer } from '../testHelper'; import inquirer from 'inquirer'; import {Optimizations, Outputs} from '../../src/commands/optimize'; @@ -20,6 +19,14 @@ describe('optimize', () => { testHelper.deleteDummyContextFile(); }); + beforeAll(() => { + createMockServer(); + }); + + afterAll(() => { + stopMockServer(); + }); + test .stderr() .stdout() @@ -43,9 +50,9 @@ describe('optimize', () => { test .stderr() .stdout() - .command(['optimize', 'https://bit.ly/asyncapi']) + .command(['optimize', 'http://localhost:8080/dummySpec.yml']) .it('works when url is passed', (ctx, done) => { - expect(ctx.stdout).toContain('No optimization has been applied since https://bit.ly/asyncapi looks optimized!'); + expect(ctx.stdout).toContain('No optimization has been applied since http://localhost:8080/dummySpec.yml looks optimized!'); expect(ctx.stderr).toEqual(''); done(); }); diff --git a/test/commands/validate.test.ts b/test/commands/validate.test.ts index 3f34eb5ef8c..585cfae6c94 100644 --- a/test/commands/validate.test.ts +++ b/test/commands/validate.test.ts @@ -3,7 +3,7 @@ import path from 'path'; import { test } from '@oclif/test'; import { NO_CONTEXTS_SAVED } from '../../src/errors/context-error'; -import TestHelper from '../testHelper'; +import TestHelper, { createMockServer, stopMockServer } from '../testHelper'; const testHelper = new TestHelper(); @@ -12,11 +12,19 @@ describe('validate', () => { beforeEach(() => { testHelper.createDummyContextFile(); }); - + afterEach(() => { testHelper.deleteDummyContextFile(); }); + beforeAll(() => { + createMockServer(); + }); + + afterAll(() => { + stopMockServer(); + }); + test .stderr() .stdout() @@ -26,7 +34,7 @@ describe('validate', () => { expect(ctx.stderr).toEqual(''); done(); }); - + test .stderr() .stdout() @@ -52,9 +60,9 @@ describe('validate', () => { test .stderr() .stdout() - .command(['validate', 'https://bit.ly/asyncapi']) + .command(['validate', 'http://localhost:8080/dummySpec.yml']) .it('works when url is passed', (ctx, done) => { - expect(ctx.stdout).toMatch('URL https://bit.ly/asyncapi is valid but has (itself and/or referenced documents) governance issues.\n\nhttps://bit.ly/asyncapi'); + expect(ctx.stdout).toMatch('URL http://localhost:8080/dummySpec.yml is valid but has (itself and/or referenced documents) governance issues.\n\nhttp://localhost:8080/dummySpec.yml'); expect(ctx.stderr).toEqual(''); done(); }); @@ -69,16 +77,16 @@ describe('validate', () => { done(); }); }); - + describe('with context names', () => { beforeEach(() => { testHelper.createDummyContextFile(); }); - + afterEach(() => { testHelper.deleteDummyContextFile(); }); - + test .stderr() .stdout() @@ -89,7 +97,7 @@ describe('validate', () => { expect(ctx.stderr).toEqual(''); done(); }); - + test .stderr() .stdout() @@ -100,7 +108,7 @@ describe('validate', () => { done(); }); }); - + describe('with no arguments', () => { beforeEach(() => { testHelper.createDummyContextFile(); @@ -110,7 +118,7 @@ describe('validate', () => { testHelper.setCurrentContext('home'); testHelper.deleteDummyContextFile(); }); - + test .stderr() .stdout() @@ -121,7 +129,7 @@ describe('validate', () => { expect(ctx.stderr).toEqual(''); done(); }); - + test .stderr() .stdout() @@ -147,7 +155,7 @@ describe('validate', () => { } } }); - + test .stderr() .stdout() @@ -163,11 +171,11 @@ describe('validate', () => { beforeEach(() => { testHelper.createDummyContextFile(); }); - + afterEach(() => { testHelper.deleteDummyContextFile(); }); - + test .stderr() .stdout() @@ -193,11 +201,11 @@ describe('validate', () => { beforeEach(() => { testHelper.createDummyContextFile(); }); - + afterEach(() => { testHelper.deleteDummyContextFile(); }); - + test .stderr() .stdout() @@ -223,11 +231,11 @@ describe('validate', () => { beforeEach(() => { testHelper.createDummyContextFile(); }); - + afterEach(() => { testHelper.deleteDummyContextFile(); }); - + test .stderr() .stdout() diff --git a/test/dummyspec/dummySpec.yml b/test/dummyspec/dummySpec.yml new file mode 100644 index 00000000000..20b400aa28f --- /dev/null +++ b/test/dummyspec/dummySpec.yml @@ -0,0 +1,209 @@ +asyncapi: '2.5.0' +info: + title: Dummy example used in tests related to fetching AsyncAPI from URL + version: '1.0.0' + description: | + The Dummy API allows you to remotely manage the city lights. + + ### Check out its awesome features: + + * Turn a specific streetlight on/off 🌃 + * Dim a specific streetlight 😎 + * Receive real-time information about environmental lighting conditions 📈 + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 + +servers: + production: + url: test.mosquitto.org:{port} + protocol: mqtt + description: Test broker + variables: + port: + description: Secure connection (TLS) is available through port 8883. + default: '1883' + enum: + - '1883' + - '8883' + security: + - apiKey: [] + - supportedOauthFlows: + - streetlights:on + - streetlights:off + - streetlights:dim + - openIdConnectWellKnown: [] + +defaultContentType: application/json + +channels: + smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured: + description: The topic on which measured values may be produced and consumed. + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + publish: + summary: Inform about environmental lighting conditions of a particular streetlight. + operationId: receiveLightMeasurement + traits: + - $ref: '#/components/operationTraits/kafka' + message: + $ref: '#/components/messages/lightMeasured' + + smartylighting/streetlights/1/0/action/{streetlightId}/turn/on: + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + subscribe: + operationId: turnOn + traits: + - $ref: '#/components/operationTraits/kafka' + message: + $ref: '#/components/messages/turnOnOff' + + smartylighting/streetlights/1/0/action/{streetlightId}/turn/off: + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + subscribe: + operationId: turnOff + traits: + - $ref: '#/components/operationTraits/kafka' + message: + $ref: '#/components/messages/turnOnOff' + + smartylighting/streetlights/1/0/action/{streetlightId}/dim: + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + subscribe: + operationId: dimLight + traits: + - $ref: '#/components/operationTraits/kafka' + message: + $ref: '#/components/messages/dimLight' + +components: + messages: + lightMeasured: + name: lightMeasured + title: Light measured + summary: Inform about environmental lighting conditions of a particular streetlight. + contentType: application/json + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: "#/components/schemas/lightMeasuredPayload" + turnOnOff: + name: turnOnOff + title: Turn on/off + summary: Command a particular streetlight to turn the lights on or off. + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: "#/components/schemas/turnOnOffPayload" + dimLight: + name: dimLight + title: Dim light + summary: Command a particular streetlight to dim the lights. + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: "#/components/schemas/dimLightPayload" + + schemas: + lightMeasuredPayload: + type: object + properties: + lumens: + type: integer + minimum: 0 + description: Light intensity measured in lumens. + sentAt: + $ref: "#/components/schemas/sentAt" + turnOnOffPayload: + type: object + properties: + command: + type: string + enum: + - on + - off + description: Whether to turn on or off the light. + sentAt: + $ref: "#/components/schemas/sentAt" + dimLightPayload: + type: object + properties: + percentage: + type: integer + description: Percentage to which the light should be dimmed to. + minimum: 0 + maximum: 100 + sentAt: + $ref: "#/components/schemas/sentAt" + sentAt: + type: string + format: date-time + description: Date and time when the message was sent. + + securitySchemes: + apiKey: + type: apiKey + in: user + description: Provide your API key as the user and leave the password empty. + supportedOauthFlows: + type: oauth2 + description: Flows to support OAuth 2.0 + flows: + implicit: + authorizationUrl: 'https://authserver.example/auth' + scopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + password: + tokenUrl: 'https://authserver.example/token' + scopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + clientCredentials: + tokenUrl: 'https://authserver.example/token' + scopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + authorizationCode: + authorizationUrl: 'https://authserver.example/auth' + tokenUrl: 'https://authserver.example/token' + refreshUrl: 'https://authserver.example/refresh' + scopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + openIdConnectWellKnown: + type: openIdConnect + openIdConnectUrl: 'https://authserver.example/.well-known' + + parameters: + streetlightId: + description: The ID of the streetlight. + schema: + type: string + + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + + operationTraits: + kafka: + bindings: + kafka: + clientId: my-app-id diff --git a/test/testHelper.ts b/test/testHelper.ts index 8659e1f0e5f..c23949d5f02 100644 --- a/test/testHelper.ts +++ b/test/testHelper.ts @@ -1,11 +1,15 @@ -import { existsSync, writeFileSync, unlinkSync, rmSync, mkdirSync } from 'fs'; +import { existsSync, writeFileSync, unlinkSync, rmSync, mkdirSync , promises as fs } from 'fs'; import * as path from 'path'; import { IContextFile, DEFAULT_CONTEXT_FILE_PATH } from '../src/models/Context'; import SpecificationFile from '../src/models/SpecificationFile'; +import http from 'http'; const ASYNCAPI_FILE_PATH = path.resolve(process.cwd(), 'specification.yaml'); +const SERVER_DIRECTORY= path.join(__dirname, 'dummyspec'); export const PROJECT_DIRECTORY_PATH = path.join(process.cwd(), 'test-project'); +let server: http.Server; + export default class ContextTestingHelper { private _context: IContextFile; constructor() { @@ -28,10 +32,10 @@ export default class ContextTestingHelper { createDummyContextFile(): void { writeFileSync(DEFAULT_CONTEXT_FILE_PATH, JSON.stringify(this._context), { encoding: 'utf-8' }); } - + deleteDummyContextFile(): void { unlinkSync(DEFAULT_CONTEXT_FILE_PATH); - } + } unsetCurrentContext(): void { delete this._context.current; @@ -71,7 +75,7 @@ export default class ContextTestingHelper { createDummyProjectDirectory(): void { mkdirSync(PROJECT_DIRECTORY_PATH); } - + deleteDummyProjectDirectory(): void { rmSync(PROJECT_DIRECTORY_PATH, {recursive: true}); } @@ -80,3 +84,44 @@ export default class ContextTestingHelper { export function fileCleanup(filepath: string) { unlinkSync(filepath); } + +export function createMockServer (port = 8080) { + server = http.createServer(async (req,res) => { + if (req.method ==='GET') { + const filePath= path.join(SERVER_DIRECTORY, req.url || '/'); + try { + const content = await fs.readFile(filePath, {encoding: 'utf8'}); + res.writeHead(200, {'Content-Type': getContentType(filePath)}); + res.end(content); + } catch (error) { + if (error.code === 'ENOENT') { + res.writeHead(404); + res.end('404 NOT FOUND'); + } else { + res.writeHead(500); + res.end('Internal Server Error'); + } + } + } + }); + server.listen(port); +} + +export function stopMockServer() { + server.close(); +} + +function getContentType(filePath:string):string { + const extname = path.extname(filePath); + switch (extname) { + case '.json': + return 'application/json'; + case '.yml': + case '.yaml': + return 'application/yaml'; + default: + // Any other suggestion? + return 'application/octet-stream'; + } +} +