diff --git a/src/Explore.Cli/PactMappingHelper.cs b/src/Explore.Cli/PactMappingHelper.cs index 82535e3..86b2b9c 100644 --- a/src/Explore.Cli/PactMappingHelper.cs +++ b/src/Explore.Cli/PactMappingHelper.cs @@ -56,7 +56,7 @@ public static string getPactVersion(string json) } - public static Connection MapPactInteractionToExploreConnection(PactV3.Interaction pactInteraction) + public static Connection MapPactV2InteractionToExploreConnection(PactV2.Interaction pactInteraction) { return new Connection() { @@ -73,7 +73,7 @@ public static Connection MapPactInteractionToExploreConnection(PactV3.Interactio Url = "" } }, - Paths = CreatePathsDictionary(pactInteraction.Request), + Paths = CreatePactV2PathsDictionary(pactInteraction.Request), }, Settings = new Settings() { @@ -84,16 +84,171 @@ public static Connection MapPactInteractionToExploreConnection(PactV3.Interactio }, }; } + public static Connection MapPactV3InteractionToExploreConnection(PactV3.Interaction pactInteraction) + { + return new Connection() + { + Type = "ConnectionRequest", + Name = "REST", + Schema = "OpenAPI", + SchemaVersion = "3.0.1", + ConnectionDefinition = new ConnectionDefinition() + { + Servers = new List() + { + new Server() + { + Url = "" + } + }, + Paths = CreatePactV3PathsDictionary(pactInteraction.Request), + }, + Settings = new Settings() + { + Type = "RestConnectionSettings", + ConnectTimeout = 30, + FollowRedirects = true, + EncodeUrl = true + }, + }; + } + + public static Connection MapPactV4InteractionToExploreConnection(PactV4.Interaction pactInteraction) + { + return new Connection() + { + Type = "ConnectionRequest", + Name = "REST", + Schema = "OpenAPI", + SchemaVersion = "3.0.1", + ConnectionDefinition = new ConnectionDefinition() + { + Servers = new List() + { + new Server() + { + Url = "" + } + }, + Paths = CreatePactV4PathsDictionary(pactInteraction.Request), + }, + Settings = new Settings() + { + Type = "RestConnectionSettings", + ConnectTimeout = 30, + FollowRedirects = true, + EncodeUrl = true + }, + }; + } + + public static Dictionary CreatePactV2PathsDictionary(PactV2.Request? request) + { + + if(request?.Path != null) + { + var pathsContent = new PathsContent() + { + Parameters = MapV2HeaderAndQueryParams(request) + }; + + // //add request body + if(request.Body != null) + { + var examplesJson = new Dictionary + { + { "examples", MapEntryBodyToContentExamples(request.Body.ToString()) } + }; + + var contentJson = new Dictionary + { + { "*/*", examplesJson } + }; + + pathsContent.RequestBody = new RequestBody() + { + Content = contentJson + }; + } + + + // add header and query params + if(request.Method != null) + { + var methodJson = new Dictionary + { + { request.Method?.ToString()?.Replace("Method", string.Empty) ?? string.Empty, pathsContent } + }; + + var json = new Dictionary + { + { request.Path, methodJson } + }; + + return json; + } + } + + return new Dictionary(); + } + + public static Dictionary CreatePactV3PathsDictionary(PactV3.Request? request) + { + + if(request?.Path != null) + { + var pathsContent = new PathsContent() + { + Parameters = MapV3HeaderAndQueryParams(request) + }; + + // //add request body + if(request.Body != null) + { + var examplesJson = new Dictionary + { + { "examples", MapEntryBodyToContentExamples(request.Body.ToString()) } + }; + + var contentJson = new Dictionary + { + { "*/*", examplesJson } + }; + pathsContent.RequestBody = new RequestBody() + { + Content = contentJson + }; + } + + + // add header and query params + if(request.Method != null) + { + var methodJson = new Dictionary + { + { request.Method?.ToString()?.Replace("Method", string.Empty) ?? string.Empty, pathsContent } + }; + + var json = new Dictionary + { + { request.Path, methodJson } + }; + + return json; + } + } - public static Dictionary CreatePathsDictionary(PactV3.Request? request) + return new Dictionary(); + } + public static Dictionary CreatePactV4PathsDictionary(PactV4.Request? request) { if(request?.Path != null) { var pathsContent = new PathsContent() { - Parameters = MapHeaderAndQueryParams(request) + Parameters = MapV4HeaderAndQueryParams(request) }; // //add request body @@ -121,7 +276,7 @@ public static Dictionary CreatePathsDictionary(PactV3.Request? r { var methodJson = new Dictionary { - { request.Method.ToString().Replace("Method", string.Empty), pathsContent } + { request.Method?.ToString()?.Replace("Method", string.Empty) ?? string.Empty, pathsContent } }; var json = new Dictionary @@ -147,7 +302,7 @@ public static Examples MapEntryBodyToContentExamples(string? rawBody) }; } - public static List MapHeaderAndQueryParams(PactV3.Request? request) + public static List MapV2HeaderAndQueryParams(PactV2.Request? request) { List parameters = new List(); @@ -197,17 +352,18 @@ public static List MapHeaderAndQueryParams(PactV3.Request? request) { if(request.Query != null) { - foreach(var param in request.Query) + + foreach(var param in request.Query.Split("&")) { parameters.Add(new Parameter() { In = "query", - Name = param.Key, + Name = param.Split("=")[0], Examples = new Examples() { Example = new Example() { - Value = param.Value.ToString() + Value = param.Split("=")[1] } } }); @@ -218,5 +374,147 @@ public static List MapHeaderAndQueryParams(PactV3.Request? request) return parameters; } + public static List MapV3HeaderAndQueryParams(PactV3.Request? request) + { + List parameters = new List(); + + if (request?.Headers != null && request.Headers.Any()) + { + // map the headers + foreach (var hdr in request.Headers) + { + parameters.Add(new Parameter() + { + In = "header", + Name = hdr.Key, + Examples = new Examples() + { + Example = new Example() + { + Value = hdr.Value.ToString() + } + } + }); + } + } + + // parse and map the query string + if (request?.Query != null) + { + if (request.Query != null) + { + foreach (var param in request.Query) + { + if (param.Value.Length > 1) + { + foreach (var value in param.Value) + { + parameters.Add(new Parameter() + { + In = "query", + Name = $"{param.Key}[]", + Examples = new Examples() + { + Example = new Example() + { + Value = value.ToString() + } + } + }); + } + } + else + { + parameters.Add(new Parameter() + { + In = "query", + Name = param.Key, + Examples = new Examples() + { + Example = new Example() + { + Value = param.Value.First().ToString() + } + } + }); + } + } + } + } + + return parameters; + } + + public static List MapV4HeaderAndQueryParams(PactV4.Request? request) + { + List parameters = new List(); + + if (request?.Headers != null && request.Headers.Any()) + { + // map the headers + foreach (var hdr in request.Headers) + { + parameters.Add(new Parameter() + { + In = "header", + Name = hdr.Key, + Examples = new Examples() + { + Example = new Example() + { + Value = string.Join(",", hdr.Value.Select(x => x.ToString())) + } + } + }); + } + } + + // parse and map the query string + if (request?.Query != null) + { + if (request.Query != null) + { + foreach (var param in request.Query) + { + if (param.Value.Length > 1) + { + foreach (var value in param.Value) + { + parameters.Add(new Parameter() + { + In = "query", + Name = $"{param.Key}[]", + Examples = new Examples() + { + Example = new Example() + { + Value = value.ToString() + } + } + }); + } + } + else + { + parameters.Add(new Parameter() + { + In = "query", + Name = param.Key, + Examples = new Examples() + { + Example = new Example() + { + Value = param.Value.First().ToString() + } + } + }); + } + } + } + } + + return parameters; + } + } diff --git a/src/Explore.Cli/PactV3Contract.cs b/src/Explore.Cli/PactV3Contract.cs index 97f1751..ed678f2 100644 --- a/src/Explore.Cli/PactV3Contract.cs +++ b/src/Explore.Cli/PactV3Contract.cs @@ -109,7 +109,7 @@ public partial class Request [JsonPropertyName("query")] - public Dictionary Query { get; set; } + public Dictionary Query { get; set; } } public partial class RequestGenerators diff --git a/src/Explore.Cli/PactV4Contract.cs b/src/Explore.Cli/PactV4Contract.cs index de1636e..b1a3b26 100644 --- a/src/Explore.Cli/PactV4Contract.cs +++ b/src/Explore.Cli/PactV4Contract.cs @@ -181,7 +181,7 @@ public partial class Request [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("headers")] - public Dictionary Headers { get; set; } + public Dictionary Headers { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("matchingRules")] @@ -197,7 +197,7 @@ public partial class Request [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("query")] - public Dictionary Query { get; set; } + public Dictionary Query { get; set; } [JsonPropertyName("contents")] public object Contents { get; set; } diff --git a/src/Explore.Cli/Program.cs b/src/Explore.Cli/Program.cs index b0ad597..66882c7 100644 --- a/src/Explore.Cli/Program.cs +++ b/src/Explore.Cli/Program.cs @@ -354,8 +354,9 @@ internal static async Task ImportPactFile(string exploreCookie, string filePath, if (!validationResult.isValid) { - Console.WriteLine($"The provided pact file does not conform to the expected schema. Errors: {validationResult.Message}"); - return; + // TODO:- schema failing on matchers - not necessary for import so can remove from schema + Console.WriteLine($"WARN: The provided pact file does not conform to the expected schema. Errors: {validationResult.Message}"); + // return; } int interactionCount = 0; switch (pactContract) @@ -364,12 +365,20 @@ internal static async Task ImportPactFile(string exploreCookie, string filePath, PactV1.Contract pactV1Contract = pactContract; interactionCount = pactV1Contract.Interactions.Count(); break; + case PactV2.Contract: + PactV2.Contract pactV2Contract = pactContract; + interactionCount = pactV2Contract.Interactions.Count(); + break; case PactV3.Contract: PactV3.Contract pactV3Contract = pactContract; interactionCount = pactV3Contract.Interactions.Count(); break; + case PactV4.Contract: + PactV4.Contract pactV4Contract = pactContract; + interactionCount = pactV4Contract.Interactions.Count(); + break; default: - Console.WriteLine($"The provided pact file does not conform to the expected schema. Errors: {validationResult.Message}"); + Console.WriteLine($"The provided pact file is unsupported."); return; }; @@ -386,33 +395,128 @@ internal static async Task ImportPactFile(string exploreCookie, string filePath, BaseAddress = new Uri("https://api.explore.swaggerhub.com") }; - // Debugging - // switch (pactContract) - // { - // case PactV1.Contract: - // Console.WriteLine($"currently unsupported"); - // return; - // case PactV3.Contract: - // PactV3.Contract pactV3Contract = pactContract; - // if (pactV3Contract != null && pactV3Contract.Interactions != null){ - // var interactions = pactV3Contract.Interactions; - // foreach (var interaction in interactions) - // { - // Console.WriteLine(interaction.Request.Body); - // } - // } - // break; - // default: - // Console.WriteLine($"currently unsupported"); - // return; - // } - // iterate over the items and import switch (pactContract) { case PactV1.Contract: Console.WriteLine($"currently unsupported"); return; + case PactV2.Contract: + PactV2.Contract pactV2Contract = pactContract; + ; if (pactV2Contract != null && pactV2Contract.Interactions != null) + { + //create an initial space to hold the collection items + var resultTable = new Table() { Title = new TableTitle(text: $"PROCESSING [green]{pactV2Contract.Consumer.Name}-{pactV2Contract.Provider.Name}[/]"), Width = 100, UseSafeBorder = true }; + resultTable.AddColumn("Result"); + resultTable.AddColumn(new TableColumn("Details").Centered()); + + var cleanedCollectionName = UtilityHelper.CleanString($"{pactV2Contract.Consumer.Name}-{pactV2Contract.Provider.Name}"); + var spaceContent = new StringContent(JsonSerializer.Serialize(new SpaceRequest() { Name = cleanedCollectionName }), Encoding.UTF8, "application/json"); + + exploreHttpClient.DefaultRequestHeaders.Clear(); + exploreHttpClient.DefaultRequestHeaders.Add("Cookie", exploreCookie); + exploreHttpClient.DefaultRequestHeaders.Add("X-Xsrf-Token", $"{UtilityHelper.ExtractXSRFTokenFromCookie(exploreCookie)}"); + var spacesResponse = await exploreHttpClient.PostAsync("/spaces-api/v1/spaces", spaceContent); + + if (spacesResponse.StatusCode == HttpStatusCode.Created) + { + var apiImportResults = new Table() { Title = new TableTitle(text: $"SPACE [green]{cleanedCollectionName}[/] CREATED"), Width = 75, UseSafeBorder = true }; + apiImportResults.AddColumn("Result"); + apiImportResults.AddColumn("API Imported"); + apiImportResults.AddColumn("Connection Imported"); + + var spaceResponse = spacesResponse.Content.ReadFromJsonAsync(); + var interactions = pactV2Contract.Interactions; + + foreach (var interaction in interactions) + { + if (interaction.Request != null) + { + //now let's create an API entry in the space + var cleanedAPIName = UtilityHelper.CleanString(interaction.Description.ToString()); + var apiContent = new StringContent(JsonSerializer.Serialize(new ApiRequest() { Name = cleanedAPIName, Type = "REST", Description = $"imported from pact file on {DateTime.UtcNow.ToShortDateString()}\nPact Specification: {pactV2Contract.Metadata.MetadataPactSpecification.Version}" }), Encoding.UTF8, "application/json"); + + exploreHttpClient.DefaultRequestHeaders.Clear(); + exploreHttpClient.DefaultRequestHeaders.Add("Cookie", exploreCookie); + exploreHttpClient.DefaultRequestHeaders.Add("X-Xsrf-Token", $"{UtilityHelper.ExtractXSRFTokenFromCookie(exploreCookie)}"); + var apiResponse = await exploreHttpClient.PostAsync($"/spaces-api/v1/spaces/{spaceResponse.Result?.Id}/apis", apiContent); + + if (apiResponse.StatusCode == HttpStatusCode.Created) + { + var createdApiResponse = apiResponse.Content.ReadFromJsonAsync(); + var connectionRequestBody = JsonSerializer.Serialize(PactMappingHelper.MapPactV2InteractionToExploreConnection(interaction)); + var connectionContent = new StringContent(connectionRequestBody, Encoding.UTF8, "application/json"); + + // //now let's do the work and import the connection + exploreHttpClient.DefaultRequestHeaders.Clear(); + exploreHttpClient.DefaultRequestHeaders.Add("Cookie", exploreCookie); + exploreHttpClient.DefaultRequestHeaders.Add("X-Xsrf-Token", $"{UtilityHelper.ExtractXSRFTokenFromCookie(exploreCookie)}"); + var connectionResponse = await exploreHttpClient.PostAsync($"/spaces-api/v1/spaces/{spaceResponse.Result?.Id}/apis/{createdApiResponse.Result?.Id}/connections", connectionContent); + + if (connectionResponse.StatusCode == HttpStatusCode.Created) + { + apiImportResults.AddRow("[green]OK[/]", $"API '{cleanedAPIName}' created", "Connection created"); + } + else + { + apiImportResults.AddRow("[orange3]OK[/]", $"API '{cleanedAPIName}' created", "[orange3]Connection NOT created[/]"); + } + } + else + { + apiImportResults.AddRow("[red]NOK[/]", $"API creation failed. StatusCode {apiResponse.StatusCode}", ""); + } + } + } + + + resultTable.AddRow(new Markup("[green]success[/]"), apiImportResults); + + if (verboseOutput == null || verboseOutput == false) + { + AnsiConsole.MarkupLine($"[green]\u2713 [/]{cleanedCollectionName}"); + } + + } + else + { + switch (spacesResponse.StatusCode) + { + case HttpStatusCode.OK: + // not expecting a 200 OK here - this would be returned for a failed auth and a redirect to SB ID + resultTable.AddRow(new Markup("[red]failure[/]"), new Markup($"[red] Auth failed connecting to SwaggerHub Explore. Please review provided cookie.[/]")); + AnsiConsole.Write(resultTable); + Console.WriteLine(""); + break; + + case HttpStatusCode.Conflict: + var apiImportResults = new Table() { Title = new TableTitle(text: $"[orange3]SPACE[/] {cleanedCollectionName} [orange3]ALREADY EXISTS[/]") }; + apiImportResults.AddColumn("Result"); + apiImportResults.AddColumn("API Imported"); + apiImportResults.AddColumn("Connection Imported"); + apiImportResults.AddRow("skipped", "", ""); + + resultTable.AddRow(new Markup("[orange3]skipped[/]"), apiImportResults); + AnsiConsole.Write(resultTable); + Console.WriteLine(""); + break; + + default: + resultTable.AddRow(new Markup("[red]failure[/]"), new Markup($"[red] StatusCode: {spacesResponse.StatusCode}, Details: {spacesResponse.ReasonPhrase}[/]")); + AnsiConsole.Write(resultTable); + Console.WriteLine(""); + break; + } + } + + if (verboseOutput != null && verboseOutput == true) + { + AnsiConsole.Write(resultTable); + } + + } + + break; case PactV3.Contract: PactV3.Contract pactV3Contract = pactContract; ; if (pactV3Contract != null && pactV3Contract.Interactions != null) @@ -439,25 +543,138 @@ internal static async Task ImportPactFile(string exploreCookie, string filePath, //parse the response var spaceResponse = spacesResponse.Content.ReadFromJsonAsync(); - - //Postman Items cant contain nested items, so we can flatten the list + // TODO - V3 reject if message interaction var interactions = pactV3Contract.Interactions; foreach (var interaction in interactions) { - // TODO - Determine if message pact, and reject + // TODO - V4 - need to discard any interactions that are not sync/http + if (interaction.Request != null) + { + + //now let's create an API entry in the space + var cleanedAPIName = UtilityHelper.CleanString(interaction.Description.ToString()); + var apiContent = new StringContent(JsonSerializer.Serialize(new ApiRequest() { Name = cleanedAPIName, Type = "REST", Description = $"imported from pact file on {DateTime.UtcNow.ToShortDateString()}\nPact Specification: {pactV3Contract.Metadata.MetadataPactSpecification.Version}" }), Encoding.UTF8, "application/json"); + + exploreHttpClient.DefaultRequestHeaders.Clear(); + exploreHttpClient.DefaultRequestHeaders.Add("Cookie", exploreCookie); + exploreHttpClient.DefaultRequestHeaders.Add("X-Xsrf-Token", $"{UtilityHelper.ExtractXSRFTokenFromCookie(exploreCookie)}"); + var apiResponse = await exploreHttpClient.PostAsync($"/spaces-api/v1/spaces/{spaceResponse.Result?.Id}/apis", apiContent); + + if (apiResponse.StatusCode == HttpStatusCode.Created) + { + var createdApiResponse = apiResponse.Content.ReadFromJsonAsync(); + var connectionRequestBody = JsonSerializer.Serialize(PactMappingHelper.MapPactV3InteractionToExploreConnection(interaction)); + var connectionContent = new StringContent(connectionRequestBody, Encoding.UTF8, "application/json"); + + // //now let's do the work and import the connection + exploreHttpClient.DefaultRequestHeaders.Clear(); + exploreHttpClient.DefaultRequestHeaders.Add("Cookie", exploreCookie); + exploreHttpClient.DefaultRequestHeaders.Add("X-Xsrf-Token", $"{UtilityHelper.ExtractXSRFTokenFromCookie(exploreCookie)}"); + var connectionResponse = await exploreHttpClient.PostAsync($"/spaces-api/v1/spaces/{spaceResponse.Result?.Id}/apis/{createdApiResponse.Result?.Id}/connections", connectionContent); + + if (connectionResponse.StatusCode == HttpStatusCode.Created) + { + apiImportResults.AddRow("[green]OK[/]", $"API '{cleanedAPIName}' created", "Connection created"); + } + else + { + apiImportResults.AddRow("[orange3]OK[/]", $"API '{cleanedAPIName}' created", "[orange3]Connection NOT created[/]"); + } + } + else + { + apiImportResults.AddRow("[red]NOK[/]", $"API creation failed. StatusCode {apiResponse.StatusCode}", ""); + } + } + } + + + resultTable.AddRow(new Markup("[green]success[/]"), apiImportResults); + + if (verboseOutput == null || verboseOutput == false) + { + AnsiConsole.MarkupLine($"[green]\u2713 [/]{cleanedCollectionName}"); + } + + } + else + { + switch (spacesResponse.StatusCode) + { + case HttpStatusCode.OK: + // not expecting a 200 OK here - this would be returned for a failed auth and a redirect to SB ID + resultTable.AddRow(new Markup("[red]failure[/]"), new Markup($"[red] Auth failed connecting to SwaggerHub Explore. Please review provided cookie.[/]")); + AnsiConsole.Write(resultTable); + Console.WriteLine(""); + break; + + case HttpStatusCode.Conflict: + var apiImportResults = new Table() { Title = new TableTitle(text: $"[orange3]SPACE[/] {cleanedCollectionName} [orange3]ALREADY EXISTS[/]") }; + apiImportResults.AddColumn("Result"); + apiImportResults.AddColumn("API Imported"); + apiImportResults.AddColumn("Connection Imported"); + apiImportResults.AddRow("skipped", "", ""); + + resultTable.AddRow(new Markup("[orange3]skipped[/]"), apiImportResults); + AnsiConsole.Write(resultTable); + Console.WriteLine(""); + break; + + default: + resultTable.AddRow(new Markup("[red]failure[/]"), new Markup($"[red] StatusCode: {spacesResponse.StatusCode}, Details: {spacesResponse.ReasonPhrase}[/]")); + AnsiConsole.Write(resultTable); + Console.WriteLine(""); + break; + } + } + + if (verboseOutput != null && verboseOutput == true) + { + AnsiConsole.Write(resultTable); + } + + } + + break; + case PactV4.Contract: + PactV4.Contract pactV4Contract = pactContract; + ; if (pactV4Contract != null && pactV4Contract.Interactions != null) + { + //create an initial space to hold the collection items + var resultTable = new Table() { Title = new TableTitle(text: $"PROCESSING [green]{pactV4Contract.Consumer.Name}-{pactV4Contract.Provider.Name}[/]"), Width = 100, UseSafeBorder = true }; + resultTable.AddColumn("Result"); + resultTable.AddColumn(new TableColumn("Details").Centered()); + + var cleanedCollectionName = UtilityHelper.CleanString($"{pactV4Contract.Consumer.Name}-{pactV4Contract.Provider.Name}"); + var spaceContent = new StringContent(JsonSerializer.Serialize(new SpaceRequest() { Name = cleanedCollectionName }), Encoding.UTF8, "application/json"); + + exploreHttpClient.DefaultRequestHeaders.Clear(); + exploreHttpClient.DefaultRequestHeaders.Add("Cookie", exploreCookie); + exploreHttpClient.DefaultRequestHeaders.Add("X-Xsrf-Token", $"{UtilityHelper.ExtractXSRFTokenFromCookie(exploreCookie)}"); + var spacesResponse = await exploreHttpClient.PostAsync("/spaces-api/v1/spaces", spaceContent); + + if (spacesResponse.StatusCode == HttpStatusCode.Created) + { + var apiImportResults = new Table() { Title = new TableTitle(text: $"SPACE [green]{cleanedCollectionName}[/] CREATED"), Width = 75, UseSafeBorder = true }; + apiImportResults.AddColumn("Result"); + apiImportResults.AddColumn("API Imported"); + apiImportResults.AddColumn("Connection Imported"); + + //parse the response + var spaceResponse = spacesResponse.Content.ReadFromJsonAsync(); + // TODO - V3 reject if message interaction + var interactions = pactV4Contract.Interactions; + + foreach (var interaction in interactions) + { + // TODO - V4 - need to discard any interactions that are not sync/http if (interaction.Request != null) { - //check if request format is supported - // if (!PostmanCollectionMappingHelper.IsItemRequestModeSupported(item.Request)) - // { - // apiImportResults.AddRow("[orange3]skipped[/]", $"Item '{item.Name}' skipped", $"Request method not supported"); - // continue; - // } //now let's create an API entry in the space var cleanedAPIName = UtilityHelper.CleanString(interaction.Description.ToString()); - var apiContent = new StringContent(JsonSerializer.Serialize(new ApiRequest() { Name = cleanedAPIName, Type = "REST", Description = $"imported from pact file on {DateTime.UtcNow.ToShortDateString()}" }), Encoding.UTF8, "application/json"); + var apiContent = new StringContent(JsonSerializer.Serialize(new ApiRequest() { Name = cleanedAPIName, Type = "REST", Description = $"imported from pact file on {DateTime.UtcNow.ToShortDateString()}\nPact Specification: {pactV4Contract.Metadata.PactSpecification.Version}" }), Encoding.UTF8, "application/json"); exploreHttpClient.DefaultRequestHeaders.Clear(); exploreHttpClient.DefaultRequestHeaders.Add("Cookie", exploreCookie); @@ -467,7 +684,7 @@ internal static async Task ImportPactFile(string exploreCookie, string filePath, if (apiResponse.StatusCode == HttpStatusCode.Created) { var createdApiResponse = apiResponse.Content.ReadFromJsonAsync(); - var connectionRequestBody = JsonSerializer.Serialize(PactMappingHelper.MapPactInteractionToExploreConnection(interaction)); + var connectionRequestBody = JsonSerializer.Serialize(PactMappingHelper.MapPactV4InteractionToExploreConnection(interaction)); var connectionContent = new StringContent(connectionRequestBody, Encoding.UTF8, "application/json"); // //now let's do the work and import the connection diff --git a/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo1.json b/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo1.json new file mode 100644 index 0000000..49f05e9 --- /dev/null +++ b/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo1.json @@ -0,0 +1,102 @@ +{ + "consumer": { + "name": "PactGoV2Consumer" + }, + "interactions": [ + { + "description": "A request to do a foo", + "providerState": "User foo exists", + "request": { + "body": { + "datetime": "2020-01-01'T'08:00:45", + "id": 27, + "lastName": "billy", + "name": "billy" + }, + "headers": { + "Authorization": "Bearer 1234", + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "type" + }, + "$.body.id": { + "match": "type" + }, + "$.body.lastName": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.header.Authorization": { + "match": "type" + }, + "$.path": { + "match": "regex", + "regex": "\\/foo.*" + }, + "$.query.baz[0]": { + "match": "regex", + "regex": "[a-z]+" + }, + "$.query.baz[1]": { + "match": "regex", + "regex": "[a-z]+" + }, + "$.query.baz[2]": { + "match": "regex", + "regex": "[a-z]+" + } + }, + "method": "POST", + "path": "/foobar", + "query": "baz=bar&baz=bat&baz=baz" + }, + "response": { + "body": { + "datetime": "2020-01-01", + "itemsMin": [ + "thereshouldbe3ofthese", + "thereshouldbe3ofthese", + "thereshouldbe3ofthese" + ], + "lastName": "Sampson", + "name": "Billy" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "regex", + "regex": "[0-9\\-]+" + }, + "$.body.itemsMin": { + "match": "type", + "min": 3 + }, + "$.header['Content-Type']": { + "match": "regex", + "regex": "application\\/json" + } + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.22", + "mockserver": "1.2.9", + "models": "1.2.2" + }, + "pactSpecification": { + "version": "2.0.0" + } + }, + "provider": { + "name": "V2Provider" + } +} \ No newline at end of file diff --git a/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo2.json b/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo2.json new file mode 100644 index 0000000..2816db5 --- /dev/null +++ b/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo2.json @@ -0,0 +1,86 @@ +{ + "consumer": { + "name": "PactGoV2ConsumerAllInOne" + }, + "interactions": [ + { + "description": "A request to do a foo", + "providerState": "User foo exists", + "request": { + "body": { + "datetime": "2020-01-01'T'08:00:45", + "id": 27, + "lastName": "billy", + "name": "billy" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "type" + }, + "$.body.id": { + "match": "type" + }, + "$.body.lastName": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.path": { + "match": "regex", + "regex": "\\/foo.*" + }, + "$.query.baz": { + "match": "regex", + "regex": "[a-zA-Z]+" + } + }, + "method": "POST", + "path": "/foobar", + "query": "baz=bat" + }, + "response": { + "body": { + "datetime": "2020-01-01", + "itemsMin": [ + "thereshouldbe3ofthese", + "thereshouldbe3ofthese", + "thereshouldbe3ofthese" + ], + "lastName": "Sampson", + "name": "Billy" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "regex", + "regex": "[0-9\\-]+" + }, + "$.body.itemsMin": { + "match": "type", + "min": 3 + } + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.22", + "mockserver": "1.2.9", + "models": "1.2.2" + }, + "pactSpecification": { + "version": "2.0.0" + } + }, + "provider": { + "name": "V2Provider" + } +} \ No newline at end of file diff --git a/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo3.json b/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo3.json new file mode 100644 index 0000000..bf15991 --- /dev/null +++ b/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo3.json @@ -0,0 +1,98 @@ +{ + "consumer": { + "name": "PactGoV2ConsumerMatch" + }, + "interactions": [ + { + "description": "A request to do a foo", + "providerState": "User foo exists", + "request": { + "body": { + "datetime": "2020-01-01'T'08:00:45,format=yyyy-MM-dd'T'HH:mm:ss,generator=datetime", + "id": 27, + "lastName": "Sampson", + "name": "Billy" + }, + "headers": { + "Authorization": "Bearer 1234", + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "type" + }, + "$.body.id": { + "match": "type" + }, + "$.body.lastName": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.header.Authorization": { + "match": "type" + }, + "$.query.baz[0]": { + "match": "regex", + "regex": "[a-z]+" + }, + "$.query.baz[1]": { + "match": "regex", + "regex": "[a-z]+" + }, + "$.query.baz[2]": { + "match": "regex", + "regex": "[a-z]+" + } + }, + "method": "POST", + "path": "/foobar", + "query": "baz=bar&baz=bat&baz=baz" + }, + "response": { + "body": { + "datetime": "2020-01-01'T'08:00:45,format=yyyy-MM-dd'T'HH:mm:ss,generator=datetime", + "id": 27, + "lastName": "Sampson", + "name": "Billy" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "type" + }, + "$.body.id": { + "match": "type" + }, + "$.body.lastName": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.header['Content-Type']": { + "match": "regex", + "regex": "application\\/json" + } + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.22", + "mockserver": "1.2.9", + "models": "1.2.2" + }, + "pactSpecification": { + "version": "2.0.0" + } + }, + "provider": { + "name": "V2ProviderMatch" + } +} \ No newline at end of file diff --git a/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo4.json b/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo4.json new file mode 100644 index 0000000..4020c8e --- /dev/null +++ b/test/Explore.Cli.Tests/fixtures/pact-v2-pactgo4.json @@ -0,0 +1,50 @@ +{ + "consumer": { + "name": "PactGoProductAPIConsumer" + }, + "interactions": [ + { + "description": "A request for Product 10", + "providerState": "A product with ID 10 exists", + "request": { + "method": "GET", + "path": "/products/10" + }, + "response": { + "body": { + "id": 10, + "name": "Billy", + "price": "23.33" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.id": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.body.price": { + "match": "type" + } + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.22", + "mockserver": "1.2.9", + "models": "1.2.2" + }, + "pactSpecification": { + "version": "2.0.0" + } + }, + "provider": { + "name": "PactGoProductAPI" + } +} \ No newline at end of file diff --git a/test/Explore.Cli.Tests/fixtures/pact-v3-pact-go.json b/test/Explore.Cli.Tests/fixtures/pact-v3-pact-go.json new file mode 100644 index 0000000..f4bf668 --- /dev/null +++ b/test/Explore.Cli.Tests/fixtures/pact-v3-pact-go.json @@ -0,0 +1,274 @@ +{ + "consumer": { + "name": "PactGoV3Consumer" + }, + "interactions": [ + { + "description": "A request to do a foo", + "providerStates": [ + { + "name": "state 1" + }, + { + "name": "User foo exists", + "params": { + "id": "foo" + } + } + ], + "request": { + "body": { + "datetime": "2020-01-01T08:00:45", + "id": 27, + "lastName": "billy", + "name": "billy" + }, + "generators": { + "body": { + "$.datetime": { + "format": "yyyy-MM-dd'T'HH:mm:ss", + "type": "DateTime" + }, + "$.name": { + "expression": "${name}", + "type": "ProviderState" + } + } + }, + "headers": { + "Authorization": "Bearer 1234", + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd'T'HH:mm:ss", + "match": "datetime" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.lastName": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.name": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "header": { + "Authorization": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "query": { + "baz": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[a-z]+" + } + ] + } + } + }, + "method": "POST", + "path": "/foobar", + "query": { + "baz": [ + "bar", + "bat", + "baz" + ] + } + }, + "response": { + "body": { + "accountBalance": 123.76, + "arrayContaining": [ + "string", + 1, + { + "foo": "bar" + } + ], + "datetime": "2020-01-01", + "equality": "a thing", + "id": 12, + "itemsMin": [ + "thereshouldbe3ofthese", + "thereshouldbe3ofthese", + "thereshouldbe3ofthese" + ], + "itemsMinMax": [ + 27, + 27, + 27, + 27, + 27 + ], + "lastName": "Sampson", + "name": "Billy", + "superstring": "foo" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.accountBalance": { + "combine": "AND", + "matchers": [ + { + "match": "decimal" + } + ] + }, + "$.arrayContaining": { + "combine": "AND", + "matchers": [ + { + "match": "arrayContains", + "variants": [ + { + "index": 0, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + { + "index": 1, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + } + } + }, + { + "index": 2, + "rules": { + "$.foo": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + } + ] + } + ] + }, + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[0-9\\-]+" + } + ] + }, + "$.equality": { + "combine": "AND", + "matchers": [ + { + "match": "equality" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + }, + "$.itemsMin": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "min": 3 + } + ] + }, + "$.itemsMinMax": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "max": 5, + "min": 3 + } + ] + }, + "$.superstring": { + "combine": "AND", + "matchers": [ + { + "match": "include", + "value": "foo" + } + ] + } + }, + "header": {} + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.22", + "mockserver": "1.2.9", + "models": "1.2.2" + }, + "pactSpecification": { + "version": "3.0.0" + } + }, + "provider": { + "name": "V3Provider" + } +} \ No newline at end of file diff --git a/pact-v3.json b/test/Explore.Cli.Tests/fixtures/pact-v3.json similarity index 100% rename from pact-v3.json rename to test/Explore.Cli.Tests/fixtures/pact-v3.json diff --git a/test/Explore.Cli.Tests/fixtures/pact-v4.json b/test/Explore.Cli.Tests/fixtures/pact-v4.json new file mode 100644 index 0000000..de7ecc9 --- /dev/null +++ b/test/Explore.Cli.Tests/fixtures/pact-v4.json @@ -0,0 +1,291 @@ +{ + "consumer": { + "name": "PactGoV4Consumer" + }, + "interactions": [ + { + "description": "A request to do a foo", + "pending": false, + "providerStates": [ + { + "name": "state 1" + }, + { + "name": "User foo exists", + "params": { + "id": "foo" + } + } + ], + "request": { + "body": { + "content": { + "datetime": "2020-01-01T08:00:45", + "id": 27, + "lastName": "billy", + "name": "billy" + }, + "contentType": "application/json", + "encoded": false + }, + "generators": { + "body": { + "$.datetime": { + "format": "yyyy-MM-dd'T'HH:mm:ss", + "type": "DateTime" + }, + "$.name": { + "expression": "${name}", + "type": "ProviderState" + } + } + }, + "headers": { + "Authorization": [ + "Bearer 1234" + ], + "Content-Type": [ + "application/json" + ] + }, + "matchingRules": { + "body": { + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd'T'HH:mm:ss", + "match": "datetime" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.lastName": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.name": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "header": { + "Authorization": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "query": { + "baz": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[a-z]+" + } + ] + } + } + }, + "method": "POST", + "path": "/foobar", + "query": { + "baz": [ + "bar", + "bat", + "baz" + ] + } + }, + "response": { + "body": { + "content": { + "accountBalance": 123.76, + "arrayContaining": [ + "string", + 1, + { + "foo": "bar" + } + ], + "datetime": "2020-01-01", + "equality": "a thing", + "id": 12, + "itemsMin": [ + "thereshouldbe3ofthese", + "thereshouldbe3ofthese", + "thereshouldbe3ofthese" + ], + "itemsMinMax": [ + 27, + 27, + 27, + 27, + 27 + ], + "lastName": "Sampson", + "name": "Billy", + "superstring": "foo" + }, + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "matchingRules": { + "body": { + "$.accountBalance": { + "combine": "AND", + "matchers": [ + { + "match": "decimal" + } + ] + }, + "$.arrayContaining": { + "combine": "AND", + "matchers": [ + { + "match": "arrayContains", + "variants": [ + { + "index": 0, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + { + "index": 1, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + } + } + }, + { + "index": 2, + "rules": { + "$.foo": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + } + ] + } + ] + }, + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[0-9\\-]+" + } + ] + }, + "$.equality": { + "combine": "AND", + "matchers": [ + { + "match": "equality" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + }, + "$.itemsMin": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "min": 3 + } + ] + }, + "$.itemsMinMax": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "max": 5, + "min": 3 + } + ] + }, + "$.superstring": { + "combine": "AND", + "matchers": [ + { + "match": "include", + "value": "foo" + } + ] + } + }, + "header": {} + }, + "status": 200 + }, + "transport": "http", + "type": "Synchronous/HTTP" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.22", + "mockserver": "1.2.9", + "models": "1.2.2" + }, + "pactSpecification": { + "version": "4.0" + } + }, + "provider": { + "name": "V4Provider" + } +} \ No newline at end of file