From 0b8edd9be945c0558316aefb17a7fd073dab0c76 Mon Sep 17 00:00:00 2001 From: Elena Izaguirre Date: Wed, 13 Oct 2021 16:21:12 +0200 Subject: [PATCH] fix: openapi validation for keychain-google-sm plugin Includes tests for endpoints setKeychainEntry, getKeychainEntryV1, hasKeychainEntryV1 and deleteKeychainEntryV1, each of them with test cases: - Right request - Request including an invalid parameter - Request without a required parameter Relationed with #847 Signed-off-by: Elena Izaguirre --- .../src/main/json/openapi.json | 36 +-- .../openapi/openapi-validation.test.ts | 280 ++++++++++++++++++ 2 files changed, 298 insertions(+), 18 deletions(-) create mode 100644 packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/openapi/openapi-validation.test.ts diff --git a/packages/cactus-plugin-keychain-google-sm/src/main/json/openapi.json b/packages/cactus-plugin-keychain-google-sm/src/main/json/openapi.json index 994e8b59bf0..00ae4eed9ab 100644 --- a/packages/cactus-plugin-keychain-google-sm/src/main/json/openapi.json +++ b/packages/cactus-plugin-keychain-google-sm/src/main/json/openapi.json @@ -26,23 +26,23 @@ "summary": "Retrieves the contents of a keychain entry from the backend.", "parameters": [], "requestBody": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/requestBodies/keychain_get_entry_request_body" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/requestBodies/keychain_get_entry_request_body" }, "responses": { "200": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_200" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_200" }, "400": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_400" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_400" }, "401": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_401" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_401" }, "404": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_404" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_404" }, "500": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_500" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_get_entry_500" } } } @@ -59,20 +59,20 @@ "summary": "Sets a value under a key on the keychain backend.", "parameters": [], "requestBody": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/requestBodies/keychain_set_entry_request_body" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/requestBodies/keychain_set_entry_request_body" }, "responses": { "200": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_set_entry_200" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_set_entry_200" }, "400": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_set_entry_400" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_set_entry_400" }, "401": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_set_entry_401" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_set_entry_401" }, "500": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_set_entry_500" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_set_entry_500" } } } @@ -89,11 +89,11 @@ "summary": "Deletes an entry under a key on the keychain backend.", "parameters": [], "requestBody": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/requestBodies/keychain_delete_entry_request_body" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/requestBodies/keychain_delete_entry_request_body" }, "responses": { "200": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_delete_entry_200" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_delete_entry_200" } } } @@ -110,20 +110,20 @@ "summary": "Checks that an entry exists under a key on the keychain backend.", "parameters": [], "requestBody": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/requestBodies/keychain_has_entry_request_body" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/requestBodies/keychain_has_entry_request_body" }, "responses": { "200": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_has_entry_200" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_has_entry_200" }, "400": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_has_entry_400" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_has_entry_400" }, "401": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_has_entry_401" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_has_entry_401" }, "500": { - "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v0.10.0/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_has_entry_500" + "$ref": "https://raw.githubusercontent.com/hyperledger/cactus/v1.0.0-rc.1/packages/cactus-core-api/src/main/json/openapi.json#/components/responses/keychain_has_entry_500" } } } diff --git a/packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/openapi/openapi-validation.test.ts b/packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/openapi/openapi-validation.test.ts new file mode 100644 index 00000000000..8f157f02f4b --- /dev/null +++ b/packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/openapi/openapi-validation.test.ts @@ -0,0 +1,280 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import http from "http"; +import type { AddressInfo } from "net"; +import express from "express"; +import bodyParser from "body-parser"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import { + Configuration, + DefaultApi as KeychainGoogleSmApi, + DeleteKeychainEntryRequestV1, + GetKeychainEntryRequestV1, + HasKeychainEntryRequestV1, + IPluginKeychainGoogleSmOptions, + PluginKeychainGoogleSm, + SetKeychainEntryRequestV1, +} from "../../../../main/typescript/public-api"; + +import { SecretManagerServiceClientMock } from "../../mock/plugin-keychain-google-sm-mock"; + +import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core"; +import OAS from "../../../../main/json/openapi.json"; + +const logLevel: LogLevelDesc = "TRACE"; +const testCase = "Test cactus-plugin-keychain-azure-kv openapi validation"; + +test(testCase, async (t: Test) => { + const options: IPluginKeychainGoogleSmOptions = { + instanceId: uuidv4(), + keychainId: uuidv4(), + logLevel: logLevel, + backend: new SecretManagerServiceClientMock({ + logLevel: logLevel, + }), + }; + const plugin = new PluginKeychainGoogleSm(options); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + const configuration = new Configuration({ basePath: apiHost }); + const apiClient = new KeychainGoogleSmApi(configuration); + + await installOpenapiValidationMiddleware({ + logLevel, + app: expressApp, + apiSpec: OAS, + }); + + await plugin.getOrCreateWebServices(); + await plugin.registerWebServices(expressApp); + + const key = `${uuidv4()}?${uuidv4()}`; + const value = uuidv4(); + + const fSet = "setKeychainEntryV1"; + const fGet = "getKeychainEntryV1"; + const fHas = "hasKeychainEntryV1"; + const fDelete = "deleteKeychainEntryV1"; + const cOk = "without bad request error"; + const cWithoutParams = "not sending all required parameters"; + const cInvalidParams = "sending invalid parameters"; + + test(`${testCase} - ${fSet} - ${cOk}`, async (t2: Test) => { + const res = await apiClient.setKeychainEntryV1({ + key, + value, + }); + t2.equal(res.status, 200, `Endpoint ${fSet}: response.status === 200 OK`); + t2.end(); + }); + + test(`${testCase} - ${fGet} - ${cOk}`, async (t2: Test) => { + const res = await apiClient.getKeychainEntryV1({ key }); + t2.equal(res.status, 200, `Endpoint ${fGet}: response.status === 200 OK`); + t2.end(); + }); + + test(`${testCase} - ${fHas} - ${cOk}`, async (t2: Test) => { + const res = await apiClient.hasKeychainEntryV1({ key }); + t2.equal(res.status, 200, `Endpoint ${fHas}: response.status === 200 OK`); + t2.end(); + }); + + test(`${testCase} - ${fDelete} - ${cOk}`, async (t2: Test) => { + const res = await apiClient.deleteKeychainEntryV1({ key }); + t2.equal( + res.status, + 200, + `Endpoint ${fDelete}: response.status === 200 OK`, + ); + t2.end(); + }); + + test(`${testCase} - ${fSet} - ${cWithoutParams}`, async (t2: Test) => { + try { + await apiClient.setKeychainEntryV1(({ + value, + } as any) as SetKeychainEntryRequestV1); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fSet} without required key: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok(fields.includes("key"), "Rejected because key is required"); + } + t2.end(); + }); + + test(`${testCase} - ${fGet} - ${cWithoutParams}`, async (t2: Test) => { + try { + await apiClient.getKeychainEntryV1( + ({} as any) as GetKeychainEntryRequestV1, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fGet} without required key: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok(fields.includes("key"), "Rejected because key is required"); + } + t2.end(); + }); + + test(`${testCase} - ${fHas} - ${cWithoutParams}`, async (t2: Test) => { + try { + await apiClient.hasKeychainEntryV1( + ({} as any) as HasKeychainEntryRequestV1, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fHas} without required key: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok(fields.includes("key"), "Rejected because key is required"); + } + t2.end(); + }); + + test(`${testCase} - ${fDelete} - ${cWithoutParams}`, async (t2: Test) => { + try { + await apiClient.deleteKeychainEntryV1( + ({} as any) as DeleteKeychainEntryRequestV1, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDelete} without required key: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok(fields.includes("key"), "Rejected because key is required"); + } + t2.end(); + }); + + test(`${testCase} - ${fSet} - ${cInvalidParams}`, async (t2: Test) => { + try { + await apiClient.setKeychainEntryV1(({ + key, + value, + fake: 4, + } as any) as SetKeychainEntryRequestV1); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fSet} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + t2.end(); + }); + + test(`${testCase} - ${fGet} - ${cInvalidParams}`, async (t2: Test) => { + try { + await apiClient.getKeychainEntryV1(({ + key, + fake: 4, + } as any) as GetKeychainEntryRequestV1); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fGet} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + t2.end(); + }); + + test(`${testCase} - ${fHas} - ${cInvalidParams}`, async (t2: Test) => { + try { + await apiClient.hasKeychainEntryV1(({ + key, + fake: 4, + } as any) as HasKeychainEntryRequestV1); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fHas} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + t2.end(); + }); + + test(`${testCase} - ${fDelete} - ${cInvalidParams}`, async (t2: Test) => { + try { + await apiClient.deleteKeychainEntryV1(({ + key, + fake: 4, + } as any) as DeleteKeychainEntryRequestV1); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDelete} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + t2.end(); + }); + + t.end(); +});