From aeef0545d558485dffb29a2621649d393be12dd2 Mon Sep 17 00:00:00 2001 From: mefellows Date: Sun, 6 Mar 2022 21:50:47 +1100 Subject: [PATCH] feat: add plugin consumer native methods --- native/addon.cc | 3 + native/consumer.cc | 168 ++++++++++++++++++++++++++++++++++++++++++++- native/consumer.h | 4 ++ src/ffi/types.ts | 31 ++++++++- 4 files changed, 203 insertions(+), 3 deletions(-) diff --git a/native/addon.cc b/native/addon.cc index 338c7895..56cbdb9c 100644 --- a/native/addon.cc +++ b/native/addon.cc @@ -29,6 +29,9 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) { exports.Set(Napi::String::New(env, "pactffiWithBinaryFile"), Napi::Function::New(env, PactffiWithBinaryFile)); exports.Set(Napi::String::New(env, "pactffiWithMultipartFile"), Napi::Function::New(env, PactffiWithMultipartFile)); exports.Set(Napi::String::New(env, "pactffiResponseStatus"), Napi::Function::New(env, PactffiResponseStatus)); + exports.Set(Napi::String::New(env, "pactFfiUsingPlugin"), Napi::Function::New(env, PactFfiUsingPlugin)); + exports.Set(Napi::String::New(env, "pactFfiCleanupPlugins"), Napi::Function::New(env, PactFfiCleanupPlugins)); + exports.Set(Napi::String::New(env, "pactFfiPluginInteractionContents"), Napi::Function::New(env, PactFfiPluginInteractionContents)); // Provider exports.Set(Napi::String::New(env, "pactffiVerifierNewForApplication"), Napi::Function::New(env, PactffiVerifierNewForApplication)); diff --git a/native/consumer.cc b/native/consumer.cc index 201ba5a8..57fb145f 100644 --- a/native/consumer.cc +++ b/native/consumer.cc @@ -501,7 +501,7 @@ Napi::Value PactffiGivenWithParam(const Napi::CallbackInfo& info) { } if (!info[3].IsString()) { - throw Napi::Error::New(env, "PactffiGivenWithParam(arg 2) expected a string"); + throw Napi::Error::New(env, "PactffiGivenWithParam(arg 3) expected a string"); } InteractionHandle interaction = info[0].As().Uint32Value(); @@ -1112,4 +1112,168 @@ Napi::Value PactffiNewSyncMessageInteraction(const Napi::CallbackInfo& info) { return info.Env().Undefined(); } -*/ \ No newline at end of file +*/ + +/** + * Extracts the verification result as a JSON document. The returned string will need to be + * freed with the `free_string` function call to avoid leaking memory. + * + * Will return a NULL pointer if the handle is invalid. + * + * C interface: + * + * const char *pactffi_verifier_json(const VerifierHandle *handle); + */ + +/** + * Add a plugin to be used by the test. The plugin needs to be installed correctly for this + * function to work. + * + * * `plugin_name` is the name of the plugin to load. + * * `plugin_version` is the version of the plugin to load. It is optional, and can be NULL. + * + * Returns zero on success, and a positive integer value on failure. + * + * Note that plugins run as separate processes, so will need to be cleaned up afterwards by + * calling `pactffi_cleanup_plugins` otherwise you have plugin processes left running. + * + * # Safety + * + * `plugin_name` must be a valid pointer to a NULL terminated string. `plugin_version` may be null, + * and if not NULL must also be a valid pointer to a NULL terminated string. + * + * # Errors + * + * * `1` - A general panic was caught. + * * `2` - Failed to load the plugin. + * * `3` - Pact Handle is not valid. + * + * When an error errors, LAST_ERROR will contain the error message. + * + * C interface: + * + * unsigned int pactffi_using_plugin(PactHandle pact, + * const char *plugin_name, + * const char *plugin_version); + */ +Napi::Value PactFfiUsingPlugin(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() < 3) { + throw Napi::Error::New(env, "PactFfiUsingPlugin received < 3 arguments"); + } + + if (!info[0].IsNumber()) { + throw Napi::Error::New(env, "PactFfiUsingPlugin(arg 0) expected a PactHandle (uint16_t)"); + } + + if (!info[1].IsString()) { + throw Napi::Error::New(env, "PactFfiUsingPlugin(arg 1) expected a string"); + } + + if (!info[2].IsString()) { + throw Napi::Error::New(env, "PactFfiUsingPlugin(arg 2) expected a string"); + } + + PactHandle pact = info[0].As().Int32Value(); + std::string name = info[1].As().Utf8Value(); + std::string version = info[2].As().Utf8Value(); + + uint16_t result = pactffi_using_plugin(pact, name.c_str(), version.c_str()); + + return Number::New(env, result); +} +/** + * Decrement the access count on any plugins that are loaded for the Pact. This will shutdown + * any plugins that are no longer required (access count is zero). + * + * C interface: + * + * void pactffi_cleanup_plugins(PactHandle pact); + */ +Napi::Value PactFfiCleanupPlugins(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() < 1) { + throw Napi::Error::New(env, "PactFfiUsingPlugin received < 1 arguments"); + } + + if (!info[0].IsNumber()) { + throw Napi::Error::New(env, "PactFfiUsingPlugin(arg 0) expected a PactHandle (uint16_t)"); + } + + PactHandle pact = info[0].As().Int32Value(); + + pactffi_cleanup_plugins(pact); + + return env.Undefined(); +} + +/** + * Setup the interaction part using a plugin. The contents is a JSON string that will be passed on to + * the plugin to configure the interaction part. Refer to the plugin documentation on the format + * of the JSON contents. + * + * Returns zero on success, and a positive integer value on failure. + * + * * `interaction` - Handle to the interaction to configure. + * * `part` - The part of the interaction to configure (request or response). It is ignored for messages. + * * `content_type` - NULL terminated C string of the content type of the part. + * * `contents` - NULL terminated C string of the JSON contents that gets passed to the plugin. + * + * # Safety + * + * `content_type` and `contents` must be a valid pointers to NULL terminated strings. + * + * # Errors + * + * * `1` - A general panic was caught. + * * `2` - The mock server has already been started. + * * `3` - The interaction handle is invalid. + * * `4` - The content type is not valid. + * * `5` - The contents JSON is not valid JSON. + * * `6` - The plugin returned an error. + * + * When an error errors, LAST_ERROR will contain the error message. + * + * C interface: + * + * unsigned int pactffi_interaction_contents(InteractionHandle interaction, + * InteractionPart part, + * const char *content_type, + * const char *contents); + */ +Napi::Value PactFfiPluginInteractionContents(const Napi::CallbackInfo& info) { + // return: bool + Napi::Env env = info.Env(); + + if (info.Length() < 4) { + throw Napi::Error::New(env, "PactFfiUsingPlugin received < 4 arguments"); + } + + if (!info[0].IsNumber()) { + throw Napi::Error::New(env, "PactFfiUsingPlugin(arg 0) expected a InteractionHandle (uint32_t)"); + } + + if (!info[1].IsNumber()) { + throw Napi::Error::New(env, "PactFfiUsingPlugin(arg 1) expected an InteractionPart (uint32_t)"); + } + + if (!info[2].IsString()) { + throw Napi::Error::New(env, "PactFfiUsingPlugin(arg 2) expected a string"); + } + + if (!info[3].IsString()) { + throw Napi::Error::New(env, "PactFfiUsingPlugin(arg 3) expected a string"); + } + + InteractionHandle interaction = info[0].As().Uint32Value(); + uint32_t partNumber = info[1].As().Uint32Value(); + InteractionPart part = integerToInteractionPart(env, partNumber); + std::string contentType = info[2].As().Utf8Value(); + std::string contents = info[3].As().Utf8Value(); + + bool res = pactffi_interaction_contents(interaction, part, contentType.c_str(), contents.c_str()); + + return Napi::Boolean::New(env, res); +} \ No newline at end of file diff --git a/native/consumer.h b/native/consumer.h index 5a66f714..6881a5b0 100644 --- a/native/consumer.h +++ b/native/consumer.h @@ -36,6 +36,10 @@ Napi::Value PactffiMessageSetDescription(const Napi::CallbackInfo& info); Napi::Value PactffiMessageWithContents(const Napi::CallbackInfo& info); Napi::Value PactffiMessageWithMetadata(const Napi::CallbackInfo& info); +// Plugins +Napi::Value PactFfiUsingPlugin(const Napi::CallbackInfo& info); +Napi::Value PactFfiCleanupPlugins(const Napi::CallbackInfo& info); +Napi::Value PactFfiPluginInteractionContents(const Napi::CallbackInfo& info); // Unimplemented Napi::Value PactffiConsumerGetName(const Napi::CallbackInfo& info); diff --git a/src/ffi/types.ts b/src/ffi/types.ts index 7c1c3140..65131392 100644 --- a/src/ffi/types.ts +++ b/src/ffi/types.ts @@ -23,6 +23,33 @@ export const FfiWritePactResponse: Record = { MOCK_SERVER_NOT_FOUND: 3, }; +export type FfiConfigurePluginResponse = 0 | 1 | 2 | 3; + +export const FfiConfigurePluginResponse: Record< + string, + FfiConfigurePluginResponse +> = { + SUCCESS: 0, + GENERAL_PANIC: 1, + FAILED_TO_LOAD_PLUGIN: 2, + PACT_HANDLE_INVALID: 3, +}; + +export type FfiConfigurePluginInteraction = 0 | 1 | 2 | 3 | 4 | 5 | 6; + +export const FfiConfigurePluginInteraction: Record< + string, + FfiConfigurePluginInteraction +> = { + SUCCESS: 0, + A_GENERAL_PANIC_WAS_CAUGHT: 1, + MOCK_SERVER_HAS_ALREADY_BEEN_STARTED: 2, + INTERACTION_HANDLE_IS_INVALID: 3, + CONTENT_TYPE_IS_NOT_VALID: 4, + CONTENTS_JSON_IS_NOT_VALID_JSON: 5, + PLUGIN_RETURNED_AN_ERROR: 6, +}; + export type FfiInteractionPart = 0 | 1; export const INTERACTION_PART_REQUEST: FfiInteractionPart = 0; @@ -159,7 +186,9 @@ export type Ffi = { pactffiLogToStdout(level: FfiLogLevelFilter): number; pactffiLogToFile(fileName: string, level: FfiLogLevelFilter): number; pactffiFetchLogBuffer(logId: number): string; - + pactFfiUsingPlugin(handle: FfiPactHandle): number; + pactFfiCleanupPlugins(handle: FfiPactHandle): void; + pactFfiPluginInteractionContents(): number; pactffiVerifierNewForApplication( libraryName: string, version: string