diff --git a/README.md b/README.md index 2de1744..dc46052 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Want to see this extension in action? Check out our [sample application](https:/ Latest released version is `0.2.6`. -Current development version is `0.2.7`. +Current development version is `0.3.0`. #### Fossa license and security scans @@ -140,6 +140,8 @@ expect(testResult.testCaseResults[0].testStepResults[0].message).toContain("obje The `TestResult` gives you access to all details regarding success of failure on different test cases. +In addition, you can use the `getMessagesForTestCase()` method to retrieve the messages exchanged during the test. + A comprehensive NestJS demo application illustrating both usages is available here: [nest-order-service](https://github.com/microcks/api-lifecycle/tree/master/shift-left-demo/nest-order-service). ### Using authentication Secrets @@ -278,3 +280,5 @@ let testResultPromise: Promise = ensemble.getMicrocksContainer().tes let testResult = await testResultPromise; expect(testResult.success).toBe(true); ``` + +In addition, you can use the `getEventMessagesForTestCase()` method to retrieve the events received during the test. diff --git a/src/microcks-container.test.ts b/src/microcks-container.test.ts index bdfc423..09cecc2 100644 --- a/src/microcks-container.test.ts +++ b/src/microcks-container.test.ts @@ -130,6 +130,19 @@ describe("MicrocksContainer", () => { expect(testResult.testCaseResults.length).toBe(3); expect(testResult.testCaseResults[0].testStepResults[0].message).toContain("object has missing required properties"); + // Retrieve messages for the failing test case. + const messages = await container.getMessagesForTestCase(testResult, "GET /pastries"); + expect(messages.length).toBe(3); + messages.forEach(message => { + expect(message.request).not.toBeNull(); + expect(message.response).not.toBeNull(); + expect(message.request.content).not.toBeNull(); + // Check these are the correct requests. + expect(message.request.queryParameters).not.toBeNull(); + expect(message.request.queryParameters.length).toBe(1); + expect(message.request.queryParameters[0].name).toBe('size'); + }); + testRequest = { serviceId: "API Pastries:0.0.1", runnerType: TestRunnerType.OPEN_API_SCHEMA, diff --git a/src/microcks-container.ts b/src/microcks-container.ts index 8e0d429..1984d4d 100644 --- a/src/microcks-container.ts +++ b/src/microcks-container.ts @@ -229,6 +229,54 @@ export enum TestRunnerType { GRAPHQL_SCHEMA = "GRAPHQL_SCHEMA" } +export interface MicrocksHeader { + name: string; + values: string[]; +} +export interface MicrocksParameter { + name: string; + value: string; +} + +export interface Message { + name: string; + content: string; + operationId: string; + testCaseId: string; + sourceArtifact: string; + headers: MicrocksHeader[]; +} + +export interface MicrocksRequest extends Message { + id: string; + responseId: string; + queryParameters: MicrocksParameter[]; +} + +export interface MicrocksResponse extends Message { + id: string; + status: string; + mediaType: string; + dispatchCriteria: string; + isFault: boolean; +} + +export interface EventMessage extends Message { + id: string; + mediaType: string; + dispatchCriteria: string; +} + +export interface RequestResponsePair { + request: MicrocksRequest; + response: MicrocksResponse; +} + +export interface UnidirectionalEvent { + eventMessage: EventMessage; +} + + export class StartedMicrocksContainer extends AbstractStartedContainer { private readonly httpPort: number; private readonly grpcPort: number; @@ -387,6 +435,56 @@ export class StartedMicrocksContainer extends AbstractStartedContainer { throw new Error("Couldn't launch on new test on Microcks. Please check Microcks container logs."); } + /** + * Retrieve messages exchanged during a test on an endpoint. + * @param {TestResult} testResult The TestResult containing information on success/failure as well as details on test cases. + * @param {string} operationName The name of the operation to get messages corresponding to test case + * @returns A list of RequestResponsePairs containing the request/response of test + */ + public async getMessagesForTestCase(testResult: TestResult, operationName: string): Promise { + // Build the test case identfier. + const operation = this.encode(operationName) + const testCaseId = `${testResult.id}-${testResult.testNumber}-${operation}`; + // Request the test case messages. + const response = await fetch(this.getHttpEndpoint() + "/api/tests/" + testResult.id + "/messages/" + testCaseId, { + method: 'GET', + headers: { + 'Accept': 'application/json' + }, + }); + + if (response.status == 200) { + const responseJson = await response.json(); + return responseJson as RequestResponsePair[]; + } + throw new Error("Couldn't retrieve messages on test on Microcks. Please check Microcks container logs"); + } + + /** + * Retrieve event messages received during a test on an endpoint. + * @param {TestResult} testResult The TestResult containing information on success/failure as well as details on test cases. + * @param {string} operationName The name of the operation to get messages corresponding to test case + * @returns A list of RequestResponsePairs containing the request/response of test + */ + public async getEventMessagesForTestCase(testResult: TestResult, operationName: string): Promise { + // Build the test case identfier. + const operation = this.encode(operationName) + const testCaseId = `${testResult.id}-${testResult.testNumber}-${operation}`; + // Request the test case event messages. + const response = await fetch(this.getHttpEndpoint() + "/api/tests/" + testResult.id + "/events/" + testCaseId, { + method: 'GET', + headers: { + 'Accept': 'application/json' + }, + }); + + if (response.status == 200) { + const responseJson = await response.json(); + return responseJson as UnidirectionalEvent[]; + } + throw new Error("Couldn't retrieve event messages on test on Microcks. Please check Microcks container logs"); + } + private async importArtifact(artifactPath: string, mainArtifact: boolean): Promise { const isFile = await this.isFile(artifactPath); @@ -496,4 +594,9 @@ export class StartedMicrocksContainer extends AbstractStartedContainer { setTimeout(resolve, ms); }); } + + private encode(operation: string): string { + operation = operation.replace(/\//g, '!'); + return encodeURIComponent(operation); + } } \ No newline at end of file diff --git a/src/microcks-containers-ensemble.test.ts b/src/microcks-containers-ensemble.test.ts index 0bab37d..83670e1 100644 --- a/src/microcks-containers-ensemble.test.ts +++ b/src/microcks-containers-ensemble.test.ts @@ -198,6 +198,21 @@ describe("MicrocksContainersEnsemble", () => { expect(testResult.testCaseResults.length).toBeGreaterThan(0); expect(testResult.testCaseResults[0].testStepResults[0].message).toContain("object has missing required properties"); + // Retrieve event messages for the failing test case. + const events = await ensemble.getMicrocksContainer().getEventMessagesForTestCase(testResult, + "SUBSCRIBE pastry/orders"); + + // We should have at least 1 event. + expect(events.length).toBeGreaterThan(0); + events.forEach(message => { + expect(message.eventMessage).not.toBeNull(); + expect(message.eventMessage.content).not.toBeNull(); + + // Check these are the correct content. + const content = JSON.parse(message.eventMessage.content); + expect(content['productQuantities'].length).toBe(2); + }); + testRequest = { serviceId: "Pastry orders API:0.1.0", runnerType: TestRunnerType.ASYNC_API_SCHEMA, @@ -341,7 +356,7 @@ describe("MicrocksContainersEnsemble", () => { const network = await new Network().start(); // Start ensemble, load artifacts and start other containers. - const localstack = await new LocalstackContainer("localstack/localstack:latest") + const localstack = await new LocalstackContainer("localstack/localstack:4.0.3") .withNetwork(network) .withNetworkAliases("localstack") .withEnvironment({ @@ -426,7 +441,7 @@ describe("MicrocksContainersEnsemble", () => { const network = await new Network().start(); // Start ensemble, load artifacts and start other containers. - const localstack = await new LocalstackContainer("localstack/localstack:latest") + const localstack = await new LocalstackContainer("localstack/localstack:4.0.3") .withNetwork(network) .withNetworkAliases("localstack") .start(); @@ -499,6 +514,9 @@ describe("MicrocksContainersEnsemble", () => { expect(testResult.success).toBe(false); expect(testResult.testedEndpoint).toBe("sqs://us-east-1/pastry-orders?overrideUrl=http://localstack:4566"); + + console.log("TestResult: " + JSON.stringify(testResult)); + expect(testResult.testCaseResults.length).toBeGreaterThan(0); expect(testResult.testCaseResults[0].testStepResults[0].message).toContain("object has missing required properties"); testResult.testCaseResults.forEach(tcr => {