├── .gitignore
├── ComPact.ConsumerTests
├── ComPact.ConsumerTests.csproj
├── Handler
│ └── RecipeAddedHandler.cs
├── MessagePactTests.cs
├── PactV2ConsumerTests.cs
├── PactV3ConsumerTests.cs
└── TestSupport
│ └── Contract.cs
├── ComPact.ProviderTests
├── ComPact.ProviderTests.csproj
├── Properties
│ └── launchSettings.json
├── ProviderTests.cs
├── TestSupport
│ ├── FakeRecipeRepository.cs
│ ├── IRecipeRepository.cs
│ ├── MessageProviderStateHandler.cs
│ ├── MessageSender.cs
│ ├── ProviderStateHandler.cs
│ ├── RecipesController.cs
│ └── TestStartup.cs
├── appsettings.Development.json
├── appsettings.json
└── pacts
│ ├── messageConsumer-messageProvider.json
│ └── recipe-consumer-recipe-service.json
├── ComPact.Tests.Shared
├── ComPact.Tests.Shared.csproj
├── FakePactBrokerMessageHandler.cs
├── Ingredient.cs
├── Recipe.cs
└── RecipeAdded.cs
├── ComPact.UnitTests
├── Builders
│ ├── CreateV2MatchingRulesTests.cs
│ ├── CreateV3MatchingRulesTests.cs
│ ├── PactJsonContentDslTests.cs
│ ├── PactJsonContentV3DslTests.cs
│ └── PactPublisherTests.cs
├── ComPact.UnitTests.csproj
├── JsonHelpers
│ └── JTokenExtensionsTests.cs
├── Matching
│ ├── MatcherTests.cs
│ ├── RequestTests
│ │ ├── QueryTests
│ │ │ ├── QueryMatchingTests.cs
│ │ │ └── Testcases
│ │ │ │ ├── different order.json
│ │ │ │ ├── different params.json
│ │ │ │ ├── matches with equals in the query value.json
│ │ │ │ ├── matches.json
│ │ │ │ ├── missing params.json
│ │ │ │ ├── same parameter different values.json
│ │ │ │ ├── same parameter multiple times in different order.json
│ │ │ │ ├── same parameter multiple times.json
│ │ │ │ └── unexpected param.json
│ │ └── Testcase.cs
│ ├── ResponseTests
│ │ ├── BodyTests
│ │ │ ├── BodyMatchingTests.cs
│ │ │ └── Testcases
│ │ │ │ ├── additional property with type matcher that does not match.json
│ │ │ │ ├── additional property with type matcher.json
│ │ │ │ ├── array at top level with matchers.json
│ │ │ │ ├── array at top level.json
│ │ │ │ ├── array in different order.json
│ │ │ │ ├── array with regex matcher.json
│ │ │ │ ├── array with type matcher mismatch.json
│ │ │ │ ├── array with type matcher.json
│ │ │ │ ├── compact - actual type does not match expected type.json
│ │ │ │ ├── compact - additional child element different type.json
│ │ │ │ ├── compact - additional child element different type2.json
│ │ │ │ ├── compact - array of objects with type matcher.json
│ │ │ │ ├── compact - datetime matching regex.json
│ │ │ │ ├── compact - datetime not matching regex.json
│ │ │ │ ├── compact - empty matchingrules object.json
│ │ │ │ ├── compact - type matcher on decimal allows integer.json
│ │ │ │ ├── compact - type matcher on integer allows decimal.json
│ │ │ │ ├── decimal matcher does not match.json
│ │ │ │ ├── decimal matcher.json
│ │ │ │ ├── deeply nested objects.json
│ │ │ │ ├── different value found at index.json
│ │ │ │ ├── different value found at key.json
│ │ │ │ ├── empty body no content type.json
│ │ │ │ ├── empty body.json
│ │ │ │ ├── equality matcher overrides type matcher.json
│ │ │ │ ├── include matcher does not match.json
│ │ │ │ ├── include matcher.json
│ │ │ │ ├── integer matcher does not match.json
│ │ │ │ ├── integer matcher.json
│ │ │ │ ├── keys out of order match.json
│ │ │ │ ├── matches with floats.json
│ │ │ │ ├── matches with integers.json
│ │ │ │ ├── matches with regex.json
│ │ │ │ ├── matches with type.json
│ │ │ │ ├── matches.json
│ │ │ │ ├── missing body found when empty expected.json
│ │ │ │ ├── missing body no content type.json
│ │ │ │ ├── missing body.json
│ │ │ │ ├── missing index.json
│ │ │ │ ├── missing key.json
│ │ │ │ ├── no body no content type.json
│ │ │ │ ├── not null found at key when null expected.json
│ │ │ │ ├── not null found in array when null expected.json
│ │ │ │ ├── null body no content type.json
│ │ │ │ ├── null body.json
│ │ │ │ ├── null found at key where not null expected.json
│ │ │ │ ├── null found in array when not null expected.json
│ │ │ │ ├── null matcher does not match when key not present.json
│ │ │ │ ├── null matcher does not match.json
│ │ │ │ ├── null matcher.json
│ │ │ │ ├── number found at key when string expected.json
│ │ │ │ ├── number found in array when string expected.json
│ │ │ │ ├── objects in array first matches.json
│ │ │ │ ├── objects in array no matches.json
│ │ │ │ ├── objects in array second matches.json
│ │ │ │ ├── objects in array type matching.json
│ │ │ │ ├── objects in array with type mismatching.json
│ │ │ │ ├── plain text empty body.json
│ │ │ │ ├── plain text missing body.json
│ │ │ │ ├── plain text regex matching missing body.json
│ │ │ │ ├── plain text regex matching that does not match.json
│ │ │ │ ├── plain text regex matching.json
│ │ │ │ ├── plain text that does not match.json
│ │ │ │ ├── plain text that matches.json
│ │ │ │ ├── property name is different case.json
│ │ │ │ ├── string found at key when number expected.json
│ │ │ │ ├── string found in array when number expected.json
│ │ │ │ ├── unexpected index with not null value.json
│ │ │ │ ├── unexpected index with null value.json
│ │ │ │ ├── unexpected key with not null value.json
│ │ │ │ └── unexpected key with null value.json
│ │ ├── HeaderTests
│ │ │ ├── HeaderMatchingTests.cs
│ │ │ └── Testcases
│ │ │ │ ├── content type parameters do not match.json
│ │ │ │ ├── empty headers.json
│ │ │ │ ├── header name is different case.json
│ │ │ │ ├── header value is different case.json
│ │ │ │ ├── matches content type with charset.json
│ │ │ │ ├── matches content type with parameters in different order.json
│ │ │ │ ├── matches with regex.json
│ │ │ │ ├── matches.json
│ │ │ │ ├── order of comma separated header values different.json
│ │ │ │ ├── unexpected header found.json
│ │ │ │ └── whitespace after comma different.json
│ │ └── Testcase.cs
│ └── TryGetApplicableMatcherListTests.cs
├── MockProvider
│ └── MatchableInteractionListTests.cs
├── Models
│ ├── HeadersFromHeaderDictionaryTests.cs
│ ├── HeadersMatchTests.cs
│ ├── MatcherTypeEnumTests.cs
│ ├── MatchingRulesUpgradeTests.cs
│ ├── V2
│ │ └── SetEmptyValuesToNullTests.cs
│ └── V3
│ │ ├── HttpRequestMessageFromRequestTests.cs
│ │ ├── QueryToAndFromQueryStringTests.cs
│ │ ├── RequestFromHttpRequestTests.cs
│ │ ├── RequestMatchTests.cs
│ │ ├── ResponseFromHttpResponseMessageTests.cs
│ │ └── SetEmptyValuesToNullTests.cs
└── Verifier
│ ├── GetPactFromPactBrokerTests.cs
│ ├── InvokeProviderStateHandlerTests.cs
│ ├── PublishVerificationResultsTests.cs
│ ├── TestToTestMessageStringTests.cs
│ ├── VerifyInteractionsTests.cs
│ └── VerifyMessagesTests.cs
├── ComPact.sln
├── ComPact
├── Builders
│ ├── PactBuilderBase.cs
│ ├── PactJsonContentDsl.cs
│ ├── PactPublisher.cs
│ ├── PactWriter.cs
│ ├── V2
│ │ ├── InteractionBuilder.cs
│ │ ├── Pact.cs
│ │ ├── PactBuilder.cs
│ │ ├── RequestBuilder.cs
│ │ └── ResponseBuilder.cs
│ └── V3
│ │ ├── InteractionBuilder.cs
│ │ ├── MessageBuilder.cs
│ │ ├── MessagePactBuilder.cs
│ │ ├── Pact.cs
│ │ ├── PactBuilder.cs
│ │ ├── PactJsonContentV3Dsl.cs
│ │ ├── RequestBuilder.cs
│ │ └── ResponseBuilder.cs
├── ComPact.csproj
├── Exceptions
│ ├── PactException.cs
│ └── PactVerificationException.cs
├── JsonHelpers
│ ├── JTokenExtensions.cs
│ ├── JTokenParser.cs
│ └── StringEnumWithDefaultConverter.cs
├── MockProvider
│ ├── MatchableInteraction.cs
│ ├── MatchableInteractionList.cs
│ ├── ProviderWebHost.cs
│ ├── RequestResponseMatcher.cs
│ └── RequestResponseMatchingErrorResponse.cs
├── Models
│ ├── BodyMatcher.cs
│ ├── ContractWithSomeVersion.cs
│ ├── Headers.cs
│ ├── IContract.cs
│ ├── Matcher.cs
│ ├── MatcherList.cs
│ ├── MatcherType.cs
│ ├── MatchingRuleCollection.cs
│ ├── Metadata.cs
│ ├── Method.cs
│ ├── Pacticipant.cs
│ ├── ProviderState.cs
│ ├── V2
│ │ ├── Contract.cs
│ │ ├── Interaction.cs
│ │ ├── Request.cs
│ │ └── Response.cs
│ └── V3
│ │ ├── Contract.cs
│ │ ├── Interaction.cs
│ │ ├── Message.cs
│ │ ├── MessageContract.cs
│ │ ├── Query.cs
│ │ ├── Request.cs
│ │ └── Response.cs
└── Verifier
│ ├── PactVerifier.cs
│ ├── PactVerifierConfig.cs
│ └── VerificationResults.cs
├── LICENSE
└── README.md
/ComPact.ConsumerTests/ComPact.ConsumerTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | ComPact.ConsumerTests
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ComPact.ConsumerTests/Handler/RecipeAddedHandler.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Tests.Shared;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 |
6 | namespace ComPact.ConsumerTests.Handler
7 | {
8 | public class RecipeAddedHandler
9 | {
10 | public List ReceivedRecipes { get; set; } = new List();
11 |
12 | public void Handle(RecipeAdded recipeAdded)
13 | {
14 | if (recipeAdded is null)
15 | {
16 | throw new ArgumentNullException(nameof(recipeAdded));
17 | }
18 |
19 | ReceivedRecipes.Add(recipeAdded.Recipe);
20 | }
21 |
22 | public Task HandleAsync(RecipeAdded recipeAdded)
23 | {
24 | if (recipeAdded is null)
25 | {
26 | throw new ArgumentNullException(nameof(recipeAdded));
27 | }
28 |
29 | ReceivedRecipes.Add(recipeAdded.Recipe);
30 | return Task.CompletedTask;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ComPact.ConsumerTests/TestSupport/Contract.cs:
--------------------------------------------------------------------------------
1 | namespace ComPact.ConsumerTests.TestSupport
2 | {
3 | public class Contract
4 | {
5 | public Pacticipant Consumer { get; set; }
6 | }
7 |
8 | public class Pacticipant
9 | {
10 | public string Name { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/ComPact.ProviderTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | InProcess
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:50519",
8 | "sslPort": 44374
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "api/values",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "ComPact.ProviderTests": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "api/values",
24 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/ComPact.ProviderTests/TestSupport/FakeRecipeRepository.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Tests.Shared;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace ComPact.ProviderTests.TestSupport
7 | {
8 | public class FakeRecipeRepository : IRecipeRepository
9 | {
10 | public List Recipes { get; set; } = new List();
11 |
12 | public void Add(Recipe recipe)
13 | {
14 | Recipes.Add(recipe);
15 | }
16 |
17 | public Recipe GetById(Guid id)
18 | {
19 | return Recipes.FirstOrDefault(r => r.Id == id);
20 | }
21 |
22 | public Recipe GetLatestAdded()
23 | {
24 | return Recipes.LastOrDefault();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/TestSupport/IRecipeRepository.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Tests.Shared;
2 | using System;
3 |
4 | namespace ComPact.ProviderTests
5 | {
6 | public interface IRecipeRepository
7 | {
8 | Recipe GetById(Guid id);
9 | Recipe GetLatestAdded();
10 | void Add(Recipe recipe);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/TestSupport/MessageProviderStateHandler.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using ComPact.Tests.Shared;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace ComPact.ProviderTests.TestSupport
7 | {
8 | public class MessageProviderStateHandler
9 | {
10 | private FakeRecipeRepository _recipeRepository;
11 |
12 | public MessageProviderStateHandler(FakeRecipeRepository recipeRepository)
13 | {
14 | _recipeRepository = recipeRepository;
15 | }
16 |
17 | public void Handle(IEnumerable providerStates)
18 | {
19 | foreach (var providerState in providerStates)
20 | {
21 | if (providerState.Name.StartsWith("A new recipe has been added"))
22 | {
23 | var id = providerState.Params["recipeId"];
24 |
25 | var recipe = new Recipe
26 | {
27 | Id = Guid.Parse(id),
28 | Name = "Pizza dough",
29 | Instructions = "Mix the yeast with a little water and the sugar. Let it sit for 10 minutes. " +
30 | "Add the flour, then add the salt and the oil and mix it all up. Then add the rest of the water. " +
31 | "Knead for a good 10 or 15 minutes until the dough can be stetched and isn't too sticky to handle any more. " +
32 | "Let it proof for about an hour covered with a tea towel or some plastic wrap.",
33 | Ingredients = new List
34 | {
35 | new Ingredient { Name = "Flour", Amount = 190, Unit = "gram" },
36 | new Ingredient { Name = "Yeast", Amount = 5, Unit = "gram" },
37 | new Ingredient { Name = "Sugar", Amount = 10, Unit = "gram" },
38 | new Ingredient { Name = "Water", Amount = 120, Unit = "ml" },
39 | new Ingredient { Name = "Olive oil", Amount = 10, Unit = "ml" },
40 | new Ingredient { Name = "Salt", Amount = 5, Unit = "gram" }
41 | }
42 | };
43 |
44 | _recipeRepository.Add(recipe);
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/TestSupport/MessageSender.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using ComPact.Tests.Shared;
3 | using System;
4 |
5 | namespace ComPact.ProviderTests.TestSupport
6 | {
7 | public class MessageSender
8 | {
9 | public FakeRecipeRepository RecipeRepository { get; set; }
10 |
11 | public MessageSender(FakeRecipeRepository recipeRepository)
12 | {
13 | RecipeRepository = recipeRepository;
14 | }
15 |
16 | public RecipeAdded Send(string description)
17 | {
18 | return new RecipeAdded
19 | {
20 | EventId = Guid.NewGuid(),
21 | Recipe = RecipeRepository.GetLatestAdded()
22 | };
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/TestSupport/ProviderStateHandler.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.Models;
3 | using ComPact.Tests.Shared;
4 | using System;
5 | using System.Collections.Generic;
6 |
7 | namespace ComPact.ProviderTests.TestSupport
8 | {
9 | public class ProviderStateHandler
10 | {
11 | public FakeRecipeRepository RecipeRepository { get; set; }
12 |
13 | public ProviderStateHandler(FakeRecipeRepository recipeRepository)
14 | {
15 | RecipeRepository = recipeRepository;
16 | }
17 |
18 | public void Handle(ProviderState providerState)
19 | {
20 | string id = null;
21 |
22 | if (providerState.Name.StartsWith("A new recipe has been added"))
23 | {
24 | id = providerState.Params["recipeId"];
25 | }
26 | else if (providerState.Name.StartsWith("There is a recipe with id"))
27 | {
28 | id = providerState.Name.Split('`')[1];
29 | }
30 | else
31 | {
32 | throw new PactVerificationException("Unknown provider state");
33 | }
34 |
35 | if (id != null)
36 | {
37 | var recipe = new Recipe
38 | {
39 | Id = Guid.Parse(id),
40 | Name = "Pizza dough",
41 | Instructions = "Mix the yeast with a little water and the sugar. Let it sit for 10 minutes. " +
42 | "Add the flour, then add the salt and the oil and mix it all up. Then add the rest of the water. " +
43 | "Knead for a good 10 or 15 minutes until the dough can be stetched and isn't too sticky to handle any more. " +
44 | "Let it proof for about an hour covered with a tea towel or some plastic wrap.",
45 | Ingredients = new List
46 | {
47 | new Ingredient { Name = "Flour", Amount = 190, Unit = "gram" },
48 | new Ingredient { Name = "Yeast", Amount = 5, Unit = "gram" },
49 | new Ingredient { Name = "Sugar", Amount = 10, Unit = "gram" },
50 | new Ingredient { Name = "Water", Amount = 120, Unit = "ml" },
51 | new Ingredient { Name = "Olive oil", Amount = 10, Unit = "ml" },
52 | new Ingredient { Name = "Salt", Amount = 5, Unit = "gram" }
53 | }
54 | };
55 |
56 | RecipeRepository.Add(recipe);
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/TestSupport/RecipesController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ComPact.Tests.Shared;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace ComPact.ProviderTests.TestSupport
6 | {
7 | [Route("api/[controller]")]
8 | [ApiController]
9 | public class RecipesController : ControllerBase
10 | {
11 | private readonly IRecipeRepository _recipeRepo;
12 |
13 | public RecipesController(IRecipeRepository recipeRepo)
14 | {
15 | _recipeRepo = recipeRepo;
16 | }
17 |
18 | // GET api/recipes/acb609ce-c5af-4391-a36f-700ac5ab5e88
19 | [HttpGet("{id}")]
20 | public ActionResult Get(string id)
21 | {
22 | if (!Guid.TryParse(id, out var guid))
23 | {
24 | return BadRequest($"Id {id} is not a valid Guid");
25 | }
26 | var recipe = _recipeRepo.GetById(guid);
27 | if (recipe == null)
28 | {
29 | return BadRequest($"No recipe found with id {id}");
30 | }
31 | return Ok(recipe);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/TestSupport/TestStartup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace ComPact.ProviderTests.TestSupport
5 | {
6 | public class TestStartup
7 | {
8 | // This method gets called by the runtime. Use this method to add services to the container.
9 | public void ConfigureServices(IServiceCollection services)
10 | {
11 | services.AddControllers();
12 | }
13 |
14 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
15 | public void Configure(IApplicationBuilder app)
16 | {
17 | app.UseHttpsRedirection();
18 | app.UseRouting();
19 | app.UseEndpoints(endpoints =>
20 | {
21 | endpoints.MapControllers();
22 | });
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*"
8 | }
9 |
--------------------------------------------------------------------------------
/ComPact.ProviderTests/pacts/messageConsumer-messageProvider.json:
--------------------------------------------------------------------------------
1 | {
2 | "consumer": {
3 | "name": "messageConsumer"
4 | },
5 | "provider": {
6 | "name": "messageProvider"
7 | },
8 | "messages": [
9 | {
10 | "providerStates": [
11 | {
12 | "name": "A new recipe has been added.",
13 | "params": { "recipeId": "7169de6d-df9b-4cf5-8cdc-2654062e5cdc" }
14 | }
15 | ],
16 | "description": "a RecipeAdded event.",
17 | "contents": {
18 | "eventId": "f84fe18f-d871-4dad-9723-65b6dc9b0578",
19 | "recipe": {
20 | "id": "7169de6d-df9b-4cf5-8cdc-2654062e5cdc",
21 | "name": "A Recipe",
22 | "instructions": "Mix it up",
23 | "ingredients": [
24 | {
25 | "name": "Salt",
26 | "amount": 5.5,
27 | "unit": "gram"
28 | }
29 | ]
30 | }
31 | },
32 | "matchingRules": {
33 | "body": {
34 | "$.eventId": {
35 | "combine": "AND",
36 | "matchers": [
37 | {
38 | "match": "regex",
39 | "regex": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
40 | }
41 | ]
42 | },
43 | "$.recipe.name": {
44 | "combine": "AND",
45 | "matchers": [
46 | {
47 | "match": "type"
48 | }
49 | ]
50 | },
51 | "$.recipe.instructions": {
52 | "combine": "AND",
53 | "matchers": [
54 | {
55 | "match": "type"
56 | }
57 | ]
58 | },
59 | "$.recipe.ingredients": {
60 | "combine": "AND",
61 | "matchers": [
62 | {
63 | "match": "type",
64 | "min": 1
65 | }
66 | ]
67 | },
68 | "$.recipe.ingredients[*].name": {
69 | "combine": "AND",
70 | "matchers": [
71 | {
72 | "match": "type"
73 | }
74 | ]
75 | },
76 | "$.recipe.ingredients[*].amount": {
77 | "combine": "AND",
78 | "matchers": [
79 | {
80 | "match": "type"
81 | }
82 | ]
83 | },
84 | "$.recipe.ingredients[*].unit": {
85 | "combine": "AND",
86 | "matchers": [
87 | {
88 | "match": "type"
89 | }
90 | ]
91 | }
92 | }
93 | },
94 | "metaData": {
95 | "ContentType": "application/json"
96 | }
97 | }
98 | ],
99 | "metadata": {
100 | "pactSpecification": {
101 | "version": "3.0.0"
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/ComPact.ProviderTests/pacts/recipe-consumer-recipe-service.json:
--------------------------------------------------------------------------------
1 | {
2 | "consumer": {
3 | "name": "recipe-consumer"
4 | },
5 | "provider": {
6 | "name": "recipe-service"
7 | },
8 | "interactions": [
9 | {
10 | "description": "a request",
11 | "providerState": "There is a recipe with id `2860dedb-a193-425f-b73e-ef02db0aa8cf`",
12 | "request": {
13 | "method": "GET",
14 | "path": "/api/recipes/2860dedb-a193-425f-b73e-ef02db0aa8cf",
15 | "headers": {
16 | "Accept": "application/json"
17 | },
18 | "query": ""
19 | },
20 | "response": {
21 | "status": 200,
22 | "headers": {
23 | "Content-Type": "application/json; charset=utf-8"
24 | },
25 | "body": {
26 | "name": "A Recipe",
27 | "instructions": "Mix it up",
28 | "ingredients": [
29 | {
30 | "name": "Salt",
31 | "amount": 5.5,
32 | "unit": "gram"
33 | }
34 | ]
35 | },
36 | "matchingRules": {
37 | "$.body.name": {
38 | "match": "type"
39 | },
40 | "$.body.instructions": {
41 | "match": "type"
42 | },
43 | "$.body.ingredients": {
44 | "match": "type",
45 | "min": "1"
46 | }
47 | }
48 | }
49 | }
50 | ],
51 | "metadata": {
52 | "pactSpecification": {
53 | "version": "2.0.0"
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/ComPact.Tests.Shared/ComPact.Tests.Shared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ComPact.Tests.Shared/Ingredient.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace ComPact.Tests.Shared
4 | {
5 | public class Ingredient
6 | {
7 | [JsonProperty("name")]
8 | public string Name { get; set; }
9 | [JsonProperty("amount")]
10 | public decimal Amount { get; set; }
11 | [JsonProperty("unit")]
12 | public string Unit { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ComPact.Tests.Shared/Recipe.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace ComPact.Tests.Shared
6 | {
7 | public class Recipe
8 | {
9 | [JsonProperty("id")]
10 | public Guid Id { get; set; }
11 | [JsonProperty("name")]
12 | public string Name { get; set; }
13 | [JsonProperty("ingredients")]
14 | public List Ingredients { get; set; }
15 | [JsonProperty("instructions")]
16 | public string Instructions { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ComPact.Tests.Shared/RecipeAdded.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 |
4 | namespace ComPact.Tests.Shared
5 | {
6 | public class RecipeAdded
7 | {
8 | [JsonProperty("eventId")]
9 | public Guid EventId { get; set; }
10 | [JsonProperty("recipe")]
11 | public Recipe Recipe { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Builders/PactJsonContentV3DslTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Builders.V3;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using Newtonsoft.Json;
4 |
5 | namespace ComPact.UnitTests.Builders
6 | {
7 | [TestClass]
8 | public class PactJsonContentV3DslTests
9 | {
10 | [TestMethod]
11 | public void Integer()
12 | {
13 | var pactJsonBody = Pact.JsonContent.With(Some.Integer.Like(1).Named("number")).ToJToken();
14 |
15 | var expectedObject = new { number = 1 };
16 |
17 | Assert.AreEqual(JsonConvert.SerializeObject(expectedObject), JsonConvert.SerializeObject(pactJsonBody));
18 | }
19 |
20 | [TestMethod]
21 | public void Decimal()
22 | {
23 | var pactJsonBody = Pact.JsonContent.With(Some.Decimal.Like(1).Named("number")).ToJToken();
24 |
25 | var expectedObject = new { number = 1.0 };
26 |
27 | Assert.AreEqual(JsonConvert.SerializeObject(expectedObject), JsonConvert.SerializeObject(pactJsonBody));
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/ComPact.UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | ComPact.UnitTests
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/JsonHelpers/JTokenExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using Newtonsoft.Json.Linq;
3 | using ComPact.JsonHelpers;
4 | using System.Linq;
5 |
6 | namespace ComPact.UnitTests.JsonHelpers
7 | {
8 | [TestClass]
9 | public class JTokenExtensionsTests
10 | {
11 | [TestMethod]
12 | public void ShouldReturnFlatEnumerableBaseOnJTokenTree()
13 | {
14 | var someObject = new
15 | {
16 | name = "test",
17 | number = 1,
18 | someNestedObject = new
19 | {
20 | decimalNumber = 1.1,
21 | array = new[]
22 | {
23 | 1,
24 | 2,
25 | 3
26 | }
27 | }
28 | };
29 |
30 | var jToken = JToken.FromObject(someObject);
31 | var flatEnumerable = jToken.ThisTokenAndAllItsDescendants();
32 |
33 | Assert.AreEqual(14, flatEnumerable.Count());
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/MatcherTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using Newtonsoft.Json.Linq;
4 | using System.Linq;
5 |
6 | namespace ComPact.UnitTests.Matching
7 | {
8 | [TestClass]
9 | public class MatcherTests
10 | {
11 | [TestMethod]
12 | public void TypeMatchForString()
13 | {
14 | var matcher = new Matcher { MatcherType = MatcherType.type };
15 |
16 | var differences = matcher.Match("expected", "actual");
17 |
18 | Assert.AreEqual(0, differences.Count);
19 | }
20 |
21 | [TestMethod]
22 | public void TypeMismatchForString()
23 | {
24 | var matcher = new Matcher { MatcherType = MatcherType.type };
25 |
26 | var differences = matcher.Match("expected", 1);
27 |
28 | Assert.AreEqual("Expected value of type String (like: 'expected') at , but was value of type Integer.", differences.First());
29 | }
30 |
31 | [TestMethod]
32 | public void RegexMatchForString()
33 | {
34 | var matcher = new Matcher { MatcherType = MatcherType.regex, Regex = "^ex.*$" };
35 |
36 | var differences = matcher.Match("expected", "example");
37 |
38 | Assert.AreEqual(0, differences.Count);
39 | }
40 |
41 | [TestMethod]
42 | public void TypeMismatchForRegex()
43 | {
44 | var matcher = new Matcher { MatcherType = MatcherType.regex, Regex = "^ex.*$" };
45 |
46 | var differences = matcher.Match("expected", "actual");
47 |
48 | Assert.AreEqual("Expected value matching '^ex.*$' (like: 'expected') at , but was 'actual'.", differences.First());
49 | }
50 |
51 | [TestMethod]
52 | public void ShouldReturnPathInMessage()
53 | {
54 | var matcher = new Matcher { MatcherType = MatcherType.type };
55 |
56 | var expectedObject = JToken.FromObject(new { body = new { name = "expected" } });
57 | var differences = matcher.Match(expectedObject.SelectToken("body.name"), 1);
58 |
59 | Assert.AreEqual("Expected value of type String (like: 'expected') at body.name, but was value of type Integer.", differences.First());
60 | }
61 |
62 | [TestMethod]
63 | public void ArrayWithTooFewItems()
64 | {
65 | var matcher = new Matcher { MatcherType = MatcherType.type, Min = 2 };
66 |
67 | var differences = matcher.Match(new [] { 1, 2 }, new[] { 1 } );
68 |
69 | Assert.AreEqual("Expected an array with at least 2 item(s) at , but was 1 items(s).", differences.First());
70 | }
71 |
72 | [TestMethod]
73 | public void ArrayWithTooManyItems()
74 | {
75 | var matcher = new Matcher { MatcherType = MatcherType.type, Min = 1, Max = 1 };
76 |
77 | var differences = matcher.Match(new[] { 1 }, new[] { 1, 2 });
78 |
79 | Assert.AreEqual("Expected an array with at most 1 item(s) at , but was 2 items(s).", differences.First());
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/QueryMatchingTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 |
8 | namespace ComPact.UnitTests.Matching.RequestTests.QueryTests
9 | {
10 | [TestClass]
11 | public class QueryMatchingTests
12 | {
13 | [TestMethod]
14 | public void ShouldSuccessfullyExecuteAllTestcase()
15 | {
16 | var testcasesDir = Path.GetFullPath($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}.." +
17 | $"{Path.DirectorySeparatorChar}Matching{Path.DirectorySeparatorChar}RequestTests{Path.DirectorySeparatorChar}QueryTests{Path.DirectorySeparatorChar}Testcases{Path.DirectorySeparatorChar}");
18 |
19 | var testcaseFiles = Directory.GetFiles(testcasesDir);
20 |
21 | var failedCases = new List();
22 |
23 | foreach (var file in testcaseFiles)
24 | {
25 | var testcase = JsonConvert.DeserializeObject(File.ReadAllText(file));
26 | if (file.Split(Path.DirectorySeparatorChar).Last().StartsWith("same parameter multiple times in different order"))
27 | {
28 | var isMatch = testcase.Expected.Match(testcase.Actual);
29 | if (isMatch != testcase.Match)
30 | {
31 | failedCases.Add(file.Split(Path.DirectorySeparatorChar).Last());
32 | }
33 | }
34 | }
35 |
36 | Assert.AreEqual(0, failedCases.Count, "Failed cases: " + Environment.NewLine + string.Join(Environment.NewLine, failedCases));
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/different order.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Queries are the same but in different key order",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "alligator": ["Mary"],
9 | "hippo": ["John"]
10 | },
11 | "headers": {}
12 | },
13 | "actual": {
14 | "method": "GET",
15 | "path": "/path",
16 | "query": {
17 | "hippo": ["John"],
18 | "alligator": ["Mary"]
19 | },
20 | "headers": {}
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/different params.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Queries are not the same - hippo is Fred instead of John",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "alligator": ["Mary"],
9 | "hippo": ["John"]
10 | },
11 | "headers": {}
12 | },
13 | "actual": {
14 | "method": "GET",
15 | "path": "/path",
16 | "query": {
17 | "alligator": ["Mary"],
18 | "hippo": ["Fred"]
19 | },
20 | "headers": {}
21 | }
22 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/matches with equals in the query value.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Queries are equivalent",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "options": ["delete.topic.enable=true"],
9 | "broker": ["1"]
10 | },
11 | "headers": {}
12 | },
13 | "actual": {
14 | "method": "GET",
15 | "path": "/path",
16 | "query": {
17 | "options": ["delete.topic.enable=true"],
18 | "broker": ["1"]
19 | },
20 | "headers": {}
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/matches.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Queries are the same",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "alligator": ["Mary"],
9 | "hippo": ["John"]
10 | },
11 | "headers": {}
12 | },
13 | "actual": {
14 | "method": "GET",
15 | "path": "/path",
16 | "query": {
17 | "alligator": ["Mary"],
18 | "hippo": ["John"]
19 | },
20 | "headers": {}
21 | }
22 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/missing params.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Queries are not the same - elephant is missing",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "alligator": ["Mary"],
9 | "hippo": ["Fred"],
10 | "elephant": ["missing"]
11 | },
12 | "headers": {}
13 | },
14 | "actual": {
15 | "method": "GET",
16 | "path": "/path",
17 | "query": {
18 | "alligator": ["Mary"],
19 | "hippo": ["Fred"]
20 | },
21 | "headers": {}
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/same parameter different values.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Queries are not the same - animals are alligator, hippo versus alligator, elephant",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "animal": ["alligator", "hippo"]
9 | },
10 | "headers": {}
11 | },
12 | "actual": {
13 | "method": "GET",
14 | "path": "/path",
15 | "query": {
16 | "animal": ["alligator", "elephant"]
17 | },
18 | "headers": {}
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/same parameter multiple times in different order.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Queries are not the same - values are in different order",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "animal": ["alligator", "hippo", "elephant"]
9 | },
10 | "headers": {}
11 | },
12 | "actual": {
13 | "method": "GET",
14 | "path": "/path",
15 | "query": {
16 | "animal": ["hippo", "alligator", "elephant"]
17 | },
18 | "headers": {}
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/same parameter multiple times.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Queries are the same - multiple values are in same order",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "animal": ["alligator", "hippo", "elephant"],
9 | "hippo": ["Fred"]
10 | },
11 | "headers": {}
12 | },
13 | "actual": {
14 | "method": "GET",
15 | "path": "/path",
16 | "query": {
17 | "hippo": ["Fred"],
18 | "animal": ["alligator", "hippo", "elephant"]
19 | },
20 | "headers": {}
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/QueryTests/Testcases/unexpected param.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Queries are not the same - elephant is not expected",
4 | "expected" : {
5 | "method": "GET",
6 | "path": "/path",
7 | "query": {
8 | "alligator": ["Mary"],
9 | "hippo": ["John"]
10 | },
11 | "headers": {}
12 | },
13 | "actual": {
14 | "method": "GET",
15 | "path": "/path",
16 | "query": {
17 | "alligator": ["Mary"],
18 | "hippo": ["John"],
19 | "elephant": ["unexpected"]
20 | },
21 | "headers": {}
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/RequestTests/Testcase.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models.V3;
2 |
3 | namespace ComPact.UnitTests.Matching.RequestTests
4 | {
5 | internal class Testcase
6 | {
7 | public bool Match { get; set; }
8 | public string Comment { get; set; }
9 | public Request Expected { get; set; }
10 | public Request Actual { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/BodyMatchingTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using Newtonsoft.Json;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 |
9 | namespace ComPact.UnitTests.Matching.ResponseTests.BodyTests
10 | {
11 | [TestClass]
12 | public class BodyMatchingTests
13 | {
14 | [TestMethod]
15 | public void ShouldSuccessfullyExecuteAllTestcases()
16 | {
17 | var testcasesDir = Path.GetFullPath($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}.." +
18 | $"{Path.DirectorySeparatorChar}Matching{Path.DirectorySeparatorChar}ResponseTests{Path.DirectorySeparatorChar}BodyTests{Path.DirectorySeparatorChar}Testcases{Path.DirectorySeparatorChar}");
19 |
20 | var testcaseFiles = Directory.GetFiles(testcasesDir);
21 |
22 | var failedCases = new List();
23 |
24 | foreach (var file in testcaseFiles)
25 | {
26 | var testcase = JsonConvert.DeserializeObject(File.ReadAllText(file));
27 | if (file.Split(Path.DirectorySeparatorChar).Last().StartsWith(""))
28 | {
29 | List differences = BodyMatcher.Match(testcase.Expected.Body, testcase.Actual.Body, testcase.Expected.MatchingRules);
30 | if (differences.Any() == testcase.Match)
31 | {
32 | failedCases.Add(file.Split(Path.DirectorySeparatorChar).Last());
33 | failedCases.AddRange(differences.Select(d => "- " + d));
34 | }
35 | else if (testcase.ExpectedMessage != null && !differences.Contains(testcase.ExpectedMessage))
36 | {
37 | failedCases.Add(file.Split(Path.DirectorySeparatorChar).Last());
38 | failedCases.Add($"- Expected message was not returned. Expected: {testcase.ExpectedMessage}, actual: {differences.First()}");
39 | }
40 | }
41 | }
42 |
43 | Assert.AreEqual(0, failedCases.Count, "Failed cases: " + Environment.NewLine + string.Join(Environment.NewLine, failedCases));
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/additional property with type matcher that does not match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "additional property with type matcher wildcards that don't match",
4 | "expected": {
5 | "headers": {},
6 | "body" : {
7 | "myPerson": {
8 | "name": "Any name"
9 | }
10 | },
11 | "matchingRules" : {
12 | "body": {
13 | "$.myPerson.*": {
14 | "matchers": [
15 | {
16 | "match": "type"
17 | }
18 | ]
19 | }
20 | }
21 | }
22 | },
23 | "actual": {
24 | "headers": {},
25 | "body": {
26 | "myPerson": {
27 | "name": 39,
28 | "age": 39,
29 | "nationality": "Australian"
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/additional property with type matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "additional property with type matcher wildcards",
4 | "expected": {
5 | "headers": {},
6 | "body" : {
7 | "myPerson": {
8 | "name": "Any name"
9 | }
10 | },
11 | "matchingRules" : {
12 | "body": {
13 | "$.myPerson.*": {
14 | "matchers": [
15 | {
16 | "match": "type"
17 | }
18 | ]
19 | }
20 | }
21 | }
22 | },
23 | "actual": {
24 | "headers": {},
25 | "body": {
26 | "myPerson": {
27 | "name": "Jon Peterson",
28 | "age": "39",
29 | "nationality": "Australian"
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/array at top level with matchers.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "top level array matches",
4 | "expected": {
5 | "headers": {"Content-Type": "application/json"},
6 | "body" : [ {
7 | "dob" : "06/11/2015",
8 | "name" : "Rogger the Dogger",
9 | "id" : 3380634027,
10 | "timestamp" : "2015-06-11T13:17:29"
11 | }, {
12 | "dob" : "06/11/2015",
13 | "name" : "Cat in the Hat",
14 | "id" : 1284270029,
15 | "timestamp" : "2015-06-11T13:17:29"
16 | } ],
17 | "matchingRules" : {
18 | "body": {
19 | "$[0].id": {
20 | "matchers": [
21 | {
22 | "match": "type"
23 | }
24 | ]
25 | },
26 | "$[1].id": {
27 | "matchers": [
28 | {
29 | "match": "type"
30 | }
31 | ]
32 | },
33 | "$[0].name": {
34 | "matchers": [
35 | {
36 | "match": "type"
37 | }
38 | ]
39 | },
40 | "$[1].name": {
41 | "matchers": [
42 | {
43 | "match": "type"
44 | }
45 | ]
46 | },
47 | "$[1].dob": {
48 | "matchers": [
49 | {
50 | "match": "regex",
51 | "regex": "\\d{2}/\\d{2}/\\d{4}"
52 | }
53 | ]
54 | },
55 | "$[1].timestamp": {
56 | "matchers": [
57 | {
58 | "match": "regex",
59 | "regex": "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"
60 | }
61 | ]
62 | },
63 | "$[0].timestamp": {
64 | "matchers": [
65 | {
66 | "match": "regex",
67 | "regex": "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"
68 | }
69 | ]
70 | },
71 | "$[0].dob": {
72 | "matchers": [
73 | {
74 | "match": "regex",
75 | "regex": "\\d{2}/\\d{2}/\\d{4}"
76 | }
77 | ]
78 | }
79 | }
80 | }
81 | },
82 | "actual": {
83 | "headers": {"Content-Type": "application/json"},
84 | "body": [
85 | {
86 | "dob": "11/06/2015",
87 | "name": "Bob The Builder",
88 | "id": 1234567890,
89 | "timestamp": "2000-06-10T20:41:37"
90 | },
91 | {
92 | "dob": "12/10/2000",
93 | "name": "Slinky Malinky",
94 | "id": 6677889900,
95 | "timestamp": "2015-06-10T22:98:78"
96 | }
97 | ]
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/array at top level.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "top level array matches",
4 | "expected": {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": [
7 | {
8 | "dob": "06/10/2015",
9 | "name": "Rogger the Dogger",
10 | "id": 1014753708,
11 | "timestamp": "2015-06-10T20:41:37"
12 | },
13 | {
14 | "dob": "06/10/2015",
15 | "name": "Cat in the Hat",
16 | "id": 8858030303,
17 | "timestamp": "2015-06-10T20:41:37"
18 | }
19 | ]
20 | },
21 | "actual": {
22 | "headers": {"Content-Type": "application/json"},
23 | "body": [
24 | {
25 | "dob": "06/10/2015",
26 | "name": "Rogger the Dogger",
27 | "id": 1014753708,
28 | "timestamp": "2015-06-10T20:41:37"
29 | },
30 | {
31 | "dob": "06/10/2015",
32 | "name": "Cat in the Hat",
33 | "id": 8858030303,
34 | "timestamp": "2015-06-10T20:41:37"
35 | }
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/array in different order.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Favourite colours in wrong order",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteColours": ["red","blue"]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "favouriteColours": ["blue", "red"]
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/array with regex matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "array with regex matcher",
4 | "expected": {
5 | "headers": {},
6 | "body" : {
7 | "myDates": [
8 | "29/10/2015"
9 | ]
10 | },
11 | "matchingRules" : {
12 | "body": {
13 | "$.myDates": {
14 | "matchers": [
15 | {
16 | "match": "type"
17 | }
18 | ]
19 | },
20 | "$.myDates[*]": {
21 | "matchers": [
22 | {
23 | "match": "regex",
24 | "regex": "\\d{2}/\\d{2}/\\d{4}"
25 | }
26 | ]
27 | }
28 | }
29 | }
30 | },
31 | "actual": {
32 | "headers": {},
33 | "body": {
34 | "myDates": [
35 | "01/11/2010",
36 | "15/12/2014",
37 | "30/06/2015"
38 | ]
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/array with type matcher mismatch.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "array with type matcher mismatch",
4 | "expected": {
5 | "headers": {},
6 | "body" : {
7 | "myDates": [
8 | 10
9 | ]
10 | },
11 | "matchingRules" : {
12 | "body": {
13 | "$.myDates[*]": {
14 | "matchers": [
15 | {
16 | "match": "type"
17 | }
18 | ]
19 | }
20 | }
21 | }
22 | },
23 | "actual": {
24 | "headers": {},
25 | "body": {
26 | "myDates": [
27 | 20,
28 | 5,
29 | "100299"
30 | ]
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/array with type matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "array with type matcher",
4 | "expected": {
5 | "headers": {},
6 | "body" : {
7 | "myDates": [
8 | 10
9 | ]
10 | },
11 | "matchingRules" : {
12 | "body": {
13 | "$.myDates": {
14 | "matchers": [
15 | {
16 | "match": "type"
17 | }
18 | ]
19 | },
20 | "$.myDates[*]": {
21 | "matchers": [
22 | {
23 | "match": "type"
24 | }
25 | ]
26 | }
27 | }
28 | }
29 | },
30 | "actual": {
31 | "headers": {},
32 | "body": {
33 | "myDates": [
34 | 20,
35 | 5,
36 | 1910
37 | ]
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - actual type does not match expected type.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "actual type does not match expected type",
4 | "expected": {
5 | "headers": {},
6 | "body": "test"
7 | },
8 | "actual": {
9 | "headers": {},
10 | "body": {
11 | "myPerson": {
12 | "name": " test"
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - additional child element different type.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "additional child element different type",
4 | "expected": {
5 | "headers": {},
6 | "body": {
7 | "myObjects": [
8 | { "letter": "a" }
9 | ]
10 | },
11 | "matchingRules": {
12 | "body": {
13 | "$.myObjects": {
14 | "matchers": [
15 | {
16 | "match": "min",
17 | "min": 1
18 | }
19 | ]
20 | },
21 | "$.myObjects[*].*": {
22 | "matchers": [
23 | {
24 | "match": "type"
25 | }
26 | ]
27 | },
28 | "$.myObjects[*].letter": {
29 | "matchers": [
30 | {
31 | "match": "type"
32 | }
33 | ]
34 | }
35 | }
36 | }
37 | },
38 | "actual": {
39 | "headers": {},
40 | "body": {
41 | "myObjects": [
42 | {
43 | "letter": "a"
44 | },
45 | {
46 | "letter": "a",
47 | "number": 2
48 | }
49 | ]
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - additional child element different type2.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "compact - additional child element different type 2",
4 | "expected": {
5 | "headers": {},
6 | "body": {
7 | "myObjects": [
8 | {
9 | "letter": "a",
10 | "number": 1
11 | }
12 | ]
13 | },
14 | "matchingRules": {
15 | "body": {
16 | "$.myObjects": {
17 | "matchers": [
18 | {
19 | "match": "min",
20 | "min": 1
21 | }
22 | ]
23 | },
24 | "$.myObjects[*].*": {
25 | "matchers": [
26 | {
27 | "match": "type"
28 | }
29 | ]
30 | },
31 | "$.myObjects[*].letter": {
32 | "matchers": [
33 | {
34 | "match": "type"
35 | }
36 | ]
37 | }
38 | }
39 | }
40 | },
41 | "actual": {
42 | "headers": {},
43 | "body": {
44 | "myObjects": [
45 | {
46 | "letter": "a",
47 | "number": 1
48 | },
49 | {
50 | "letter": "a",
51 | "number": 2,
52 | "otherNumber": 1
53 | }
54 | ]
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - array of objects with type matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "array of objects with type matcher",
4 | "expected": {
5 | "headers": {},
6 | "body": {
7 | "myObjects": [
8 | { "letter": "a" }
9 | ]
10 | },
11 | "matchingRules": {
12 | "body": {
13 | "$.myObjects": {
14 | "matchers": [
15 | {
16 | "match": "min",
17 | "min": 1
18 | }
19 | ]
20 | },
21 | "$.myObjects[*].letter": {
22 | "matchers": [
23 | {
24 | "match": "type"
25 | }
26 | ]
27 | }
28 | }
29 | }
30 | },
31 | "actual": {
32 | "headers": {},
33 | "body": {
34 | "myObjects": [
35 | { "letter": "a" },
36 | { "letter": 2 }
37 | ]
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - datetime matching regex.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "iso datetime should be matched as a string, not as a datetime",
4 | "expected": {
5 | "headers": {},
6 | "body": "2020-06-05T13:22:59",
7 | "matchingRules": {
8 | "body": {
9 | "$": {
10 | "matchers": [
11 | {
12 | "match": "regex",
13 | "regex": "^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$"
14 | }
15 | ]
16 | }
17 | }
18 | }
19 | },
20 | "actual": {
21 | "headers": {},
22 | "body": "1999-04-01T00:00:00"
23 | }
24 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - datetime not matching regex.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "iso datetime should be matched as a string, not as a datetime",
4 | "expected": {
5 | "headers": {},
6 | "body": "2020-06-05T13:22:59",
7 | "matchingRules": {
8 | "body": {
9 | "$": {
10 | "matchers": [
11 | {
12 | "match": "regex",
13 | "regex": "2020.*"
14 | }
15 | ]
16 | }
17 | }
18 | }
19 | },
20 | "actual": {
21 | "headers": {},
22 | "body": "1999-04-01T00:00:00"
23 | }
24 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - empty matchingrules object.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "empty matchingrules object",
4 | "expected": {
5 | "headers": {},
6 | "body": {
7 | "myPerson": {
8 | "name": "name"
9 | }
10 | },
11 | "matchingRules": {
12 | }
13 | },
14 | "actual": {
15 | "headers": {},
16 | "body": {
17 | "myPerson": {
18 | "name": "name"
19 | }
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - type matcher on decimal allows integer.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "type matcher on integer allows decimal",
4 | "expected": {
5 | "headers": { "Content-Type": "application/json" },
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.length": {
16 | "matchers": [
17 | {
18 | "match": "type"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator": {
26 | "name": "Mary",
27 | "length": 4.2,
28 | "favouriteColours": [ "red", "blue" ]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": { "Content-Type": "application/json" },
34 | "body": {
35 | "alligator": {
36 | "length": 5,
37 | "name": "Harry the very long alligator",
38 | "favouriteColours": [ "red", "blue" ]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/compact - type matcher on integer allows decimal.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "type matcher on decimal allows integer",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.length": {
16 | "matchers": [
17 | {
18 | "match": "type"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator":{
26 | "name": "Mary",
27 | "length": 4.2,
28 | "favouriteColours": ["red","blue"]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": {"Content-Type": "application/json"},
34 | "body": {
35 | "alligator": {
36 | "length": 5,
37 | "name": "Harry the very long alligator",
38 | "favouriteColours": [ "red", "blue" ]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/decimal matcher does not match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "response does not match because it does not contain a decimal",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.length": {
16 | "matchers": [
17 | {
18 | "match": "decimal"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator":{
26 | "name": "Mary",
27 | "length": 4.2,
28 | "favouriteColours": ["red","blue"]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": {"Content-Type": "application/json"},
34 | "body": {
35 | "alligator": {
36 | "length": 5,
37 | "name": "Harry the very long alligator",
38 | "favouriteColours": [ "red", "blue" ]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/decimal matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "response matches because it contains a decimal",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.length": {
16 | "matchers": [
17 | {
18 | "match": "decimal"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator":{
26 | "name": "Mary",
27 | "length": 4.2,
28 | "favouriteColours": ["red","blue"]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": {"Content-Type": "application/json"},
34 | "body": {
35 | "alligator": {
36 | "length": 5.0,
37 | "name": "Harry the very long alligator",
38 | "favouriteColours": [ "red", "blue" ]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/deeply nested objects.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Comparisons should work even on nested objects",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "object1": {
8 | "object2": {
9 | "object4": {
10 | "object5": {
11 | "name": "Mary",
12 | "friends": ["Fred", "John"]
13 | },
14 | "object6": {
15 | "phoneNumber": 1234567890
16 | }
17 | }
18 | }
19 | }
20 | }
21 | },
22 | "actual": {
23 | "headers": {"Content-Type": "application/json"},
24 | "body": {
25 | "object1":{
26 | "object2": {
27 | "object4":{
28 | "object5": {
29 | "name": "Mary",
30 | "friends": ["Fred", "John"],
31 | "gender": "F"
32 | },
33 | "object6": {
34 | "phoneNumber": 1234567890
35 | }
36 | }
37 | },
38 | "color": "red"
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/different value found at index.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Incorrect favourite colour",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteColours": ["red","blue"]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "favouriteColours": ["red","taupe"]
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/different value found at key.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Incorrect value at alligator name",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "name": "Mary"
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "name": "Fred"
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/empty body no content type.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Empty body, no content-type",
4 | "expected" : {
5 | "body": ""
6 | },
7 | "actual": {
8 | "headers": {"Content-Type": "application/json"},
9 | "body": ""
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/empty body.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Empty body",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": ""
7 | },
8 | "actual": {
9 | "headers": {"Content-Type": "application/json"},
10 | "body": ""
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/equality matcher overrides type matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "equality matcher overrides type matcher",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.feet": {
16 | "matchers": [
17 | {
18 | "match": "equality"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator":{
26 | "name": "Mary",
27 | "feet": 4,
28 | "favouriteColours": ["red","blue"]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": {"Content-Type": "application/json"},
34 | "body": {
35 | "alligator":{
36 | "feet": 5,
37 | "name": "Harry the very hungry alligator with an extra foot",
38 | "favouriteColours": ["red","blue"]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/include matcher does not match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "actual value does not include the value associated with the matcher",
4 | "expected": {
5 | "headers": { "Content-Type": "application/json" },
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.description": {
9 | "matchers": [
10 | {
11 | "match": "include",
12 | "value": "Mary"
13 | }
14 | ]
15 | }
16 | }
17 | },
18 | "body": {
19 | "alligator": {
20 | "description": "An alligator called Mary"
21 | }
22 | }
23 | },
24 | "actual": {
25 | "headers": { "Content-Type": "application/json" },
26 | "body": {
27 | "alligator": {
28 | "description": "Harry the very hungry alligator with an extra foot"
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/include matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "actual value includes the value associated with the matcher",
4 | "expected": {
5 | "headers": { "Content-Type": "application/json" },
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.description": {
9 | "matchers": [
10 | {
11 | "match": "include",
12 | "value": "Harry"
13 | }
14 | ]
15 | }
16 | }
17 | },
18 | "body": {
19 | "alligator": {
20 | "description": "An alligator called Harry"
21 | }
22 | }
23 | },
24 | "actual": {
25 | "headers": { "Content-Type": "application/json" },
26 | "body": {
27 | "alligator": {
28 | "description": "Harry the very hungry alligator with an extra foot"
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/integer matcher does not match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "response does not match because it does not contain an integer",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.feet": {
16 | "matchers": [
17 | {
18 | "match": "integer"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator":{
26 | "name": "Mary",
27 | "feet": 4,
28 | "favouriteColours": ["red","blue"]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": {"Content-Type": "application/json"},
34 | "body": {
35 | "alligator":{
36 | "feet": 4.5,
37 | "name": "Harry the very hungry alligator with half an extra foot",
38 | "favouriteColours": ["red","blue"]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/integer matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "response matches because it contains an integer",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.feet": {
16 | "matchers": [
17 | {
18 | "match": "integer"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator":{
26 | "name": "Mary",
27 | "feet": 4,
28 | "favouriteColours": ["red","blue"]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": {"Content-Type": "application/json"},
34 | "body": {
35 | "alligator":{
36 | "feet": 5,
37 | "name": "Harry the very hungry alligator with an extra foot",
38 | "favouriteColours": ["red","blue"]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/keys out of order match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Favourite number and favourite colours out of order",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "favouriteNumber": 7,
8 | "favouriteColours": ["red","blue"]
9 | }
10 | },
11 | "actual": {
12 | "headers": {"Content-Type": "application/json"},
13 | "body": {
14 | "favouriteColours": ["red","blue"],
15 | "favouriteNumber": 7
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/matches with floats.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Response match with floats",
4 | "expected": {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.product.price": {
9 | "matchers": [
10 | {
11 | "match": "regex",
12 | "regex": "\\d(\\.\\d{1,2})"
13 | }
14 | ]
15 | }
16 | }
17 | },
18 | "body": [
19 | {
20 | "product": {
21 | "id": 123,
22 | "description": "Television",
23 | "price": 500.55
24 | }
25 | }
26 | ]
27 | },
28 | "actual": {
29 | "headers": {"Content-Type": "application/json"},
30 | "body": [
31 | {
32 | "product": {
33 | "id": 123,
34 | "description": "Television",
35 | "price": 500.55
36 | }
37 | }
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/matches with integers.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Response match with integers",
4 | "expected" : {
5 | "method": "POST",
6 | "path": "/",
7 | "query": {},
8 | "headers": {"Content-Type": "application/json"},
9 | "matchingRules": {
10 | "body": {
11 | "$.alligator.feet": {
12 | "matchers": [
13 | {
14 | "match": "regex",
15 | "regex": "[0-9]"
16 | }
17 | ]
18 | }
19 | }
20 | },
21 | "body": {
22 | "alligator":{
23 | "name": "Mary",
24 | "feet": 4,
25 | "favouriteColours": ["red","blue"]
26 | }
27 | }
28 | },
29 | "actual": {
30 | "method": "POST",
31 | "path": "/",
32 | "query": {},
33 | "headers": {"Content-Type": "application/json"},
34 | "body": {
35 | "alligator":{
36 | "feet": 4,
37 | "name": "Mary",
38 | "favouriteColours": ["red","blue"]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/matches with regex.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Requests match with regex",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "regex",
12 | "regex": "\\w+"
13 | }
14 | ]
15 | }
16 | }
17 | },
18 | "body": {
19 | "alligator":{
20 | "name": "Mary",
21 | "feet": 4,
22 | "favouriteColours": ["red","blue"]
23 | }
24 | }
25 | },
26 | "actual": {
27 | "headers": {"Content-Type": "application/json"},
28 | "body": {
29 | "alligator":{
30 | "feet": 4,
31 | "name": "Harry",
32 | "favouriteColours": ["red","blue"]
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/matches with type.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Response match with same type",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.feet": {
16 | "matchers": [
17 | {
18 | "match": "type"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator":{
26 | "name": "Mary",
27 | "feet": 4,
28 | "favouriteColours": ["red","blue"]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": {"Content-Type": "application/json"},
34 | "body": {
35 | "alligator":{
36 | "feet": 5,
37 | "name": "Harry the very hungry alligator with an extra foot",
38 | "favouriteColours": ["red","blue"]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/matches.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Responses match",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "name": "Mary",
9 | "feet": 4,
10 | "favouriteColours": ["red","blue"]
11 | }
12 | }
13 | },
14 | "actual": {
15 | "headers": {"Content-Type": "application/json"},
16 | "body": {
17 | "alligator":{
18 | "feet": 4,
19 | "name": "Mary",
20 | "favouriteColours": ["red","blue"]
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/missing body found when empty expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Missing body found, when an empty body was expected",
4 | "expected" : {
5 | "body": null
6 | },
7 | "actual": {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/missing body no content type.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Missing body, no content-type",
4 | "expected" : {
5 | },
6 | "actual": {
7 | "headers": {"Content-Type": "application/json"},
8 | "body": {
9 | "alligator":{
10 | "feet": 4,
11 | "name": "Mary",
12 | "favouriteColours": ["red","blue"]
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/missing body.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Missing body",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"}
6 | },
7 | "actual": {
8 | "headers": {"Content-Type": "application/json"},
9 | "body": {
10 | "alligator":{
11 | "feet": 4,
12 | "name": "Mary",
13 | "favouriteColours": ["red","blue"]
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/missing index.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Missing favorite colour",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteColours": ["red","blue"]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator": {
16 | "favouriteColours": ["red"]
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/missing key.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Missing key alligator name",
4 | "expected": {
5 | "headers": { "Content-Type": "application/json" },
6 | "body": {
7 | "alligator": {
8 | "name": "Mary",
9 | "age": 3
10 | }
11 | }
12 | },
13 | "actual": {
14 | "headers": { "Content-Type": "application/json" },
15 | "body": {
16 | "alligator": {
17 | "age": 3
18 | }
19 | }
20 | },
21 | "expectedMessage": "Property 'alligator.name' was not present in the actual response."
22 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/no body no content type.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "No body, no content-type",
4 | "expected" : {
5 | },
6 | "actual": {
7 | "headers": {"Content-Type": "application/json"},
8 | "body": {
9 | "alligator":{
10 | "feet": 4,
11 | "name": "Mary",
12 | "favouriteColours": ["red","blue"]
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/not null found at key when null expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Name should be null",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "name": null
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "name": "Fred"
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/not null found in array when null expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Favourite numbers expected to contain null, but not null found",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteNumbers": ["1",null,"3"]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "favouriteNumbers": ["1","2","3"]
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/null body no content type.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "NULL body, no content-type",
4 | "expected" : {
5 | "body": null
6 | },
7 | "actual": {
8 | "headers": {"Content-Type": "application/json"},
9 | "body": null
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/null body.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "NULL body",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": null
7 | },
8 | "actual": {
9 | "headers": {"Content-Type": "application/json"},
10 | "body": null
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/null found at key where not null expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Name should not be null",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "name": "Mary"
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "name": null
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/null found in array when not null expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Favourite numbers expected to be strings found a null",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteNumbers": ["1","2","3"]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "favouriteNumbers": ["1",null,"3"]
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/null matcher does not match when key not present.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Response does not match because it does not contain null",
4 | "expected": {
5 | "headers": { "Content-Type": "application/json" },
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.wings": {
16 | "matchers": [
17 | {
18 | "match": "null"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator": {
26 | "name": "Mary",
27 | "wings": null,
28 | "favouriteColours": [ "red", "blue" ]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": { "Content-Type": "application/json" },
34 | "body": {
35 | "alligator": {
36 | "name": "Harry",
37 | "favouriteColours": [ "red", "blue" ]
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/null matcher does not match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Response does not match because it does not contain null",
4 | "expected": {
5 | "headers": { "Content-Type": "application/json" },
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.wings": {
16 | "matchers": [
17 | {
18 | "match": "null"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator": {
26 | "name": "Mary",
27 | "wings": null,
28 | "favouriteColours": [ "red", "blue" ]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": { "Content-Type": "application/json" },
34 | "body": {
35 | "alligator": {
36 | "wings": 2,
37 | "name": "Harry",
38 | "favouriteColours": [ "red", "blue" ]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/null matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Response matches because it contains null",
4 | "expected": {
5 | "headers": { "Content-Type": "application/json" },
6 | "matchingRules": {
7 | "body": {
8 | "$.alligator.name": {
9 | "matchers": [
10 | {
11 | "match": "type"
12 | }
13 | ]
14 | },
15 | "$.alligator.wings": {
16 | "matchers": [
17 | {
18 | "match": "null"
19 | }
20 | ]
21 | }
22 | }
23 | },
24 | "body": {
25 | "alligator": {
26 | "name": "Mary",
27 | "wings": null,
28 | "favouriteColours": [ "red", "blue" ]
29 | }
30 | }
31 | },
32 | "actual": {
33 | "headers": { "Content-Type": "application/json" },
34 | "body": {
35 | "alligator": {
36 | "wings": null,
37 | "name": "Harry",
38 | "favouriteColours": [ "red", "blue" ]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/number found at key when string expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Number of feet expected to be string but was number",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "feet": "4"
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "feet": 4
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/number found in array when string expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Favourite numbers expected to be strings found a number",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteNumbers": ["1","2","3"]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "favouriteNumbers": ["1",2,"3"]
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/objects in array first matches.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Properties match but unexpected element received",
4 | "expected": {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": [
7 | {
8 | "favouriteColor": "red"
9 | }
10 | ]
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": [
15 | {
16 | "favouriteColor": "red",
17 | "favouriteNumber": 2
18 | },
19 | {
20 | "favouriteColor": "blue",
21 | "favouriteNumber": 2
22 | }
23 | ]
24 | }
25 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/objects in array no matches.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Array of objects, properties match on incorrect objects",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": [
7 | {"favouriteColor": "red"},
8 | {"favouriteNumber": 2}
9 | ]
10 | },
11 | "actual": {
12 | "headers": {"Content-Type": "application/json"},
13 | "body": [
14 | {"favouriteColor": "blue",
15 | "favouriteNumber": 4},
16 | {"favouriteColor": "red",
17 | "favouriteNumber": 2}
18 | ]
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/objects in array second matches.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Property of second object matches, but unexpected element recieved",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": [
7 | {"favouriteColor": "red"}
8 | ]
9 | },
10 | "actual": {
11 | "headers": {"Content-Type": "application/json"},
12 | "body": [
13 | {"favouriteColor": "blue",
14 | "favouriteNumber": 4},
15 | {"favouriteColor": "red",
16 | "favouriteNumber": 2}
17 | ]
18 | }
19 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/objects in array type matching.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "objects in array type matching",
4 | "expected": {
5 | "headers": {},
6 | "body": [{
7 | "name": "John Smith",
8 | "age": 50
9 | }],
10 | "matchingRules": {
11 | "body": {
12 | "$": {
13 | "matchers": [
14 | {
15 | "match": "type"
16 | }
17 | ]
18 | },
19 | "$[*]": {
20 | "matchers": [
21 | {
22 | "match": "type"
23 | }
24 | ]
25 | }
26 | }
27 | }
28 | },
29 | "actual": {
30 | "headers": {},
31 | "body": [{
32 | "name": "Peter Peterson",
33 | "age": 22,
34 | "gender": "Male"
35 | }, {
36 | "name": "John Johnston",
37 | "age": 64
38 | }]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/objects in array with type mismatching.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "objects in array with type mismatching",
4 | "expected": {
5 | "headers": {},
6 | "body": [{
7 | "Name": "John Smith",
8 | "Age": 50
9 | }],
10 | "matchingRules": {
11 | "body": {
12 | "$[*]": {
13 | "matchers": [
14 | {
15 | "match": "type"
16 | }
17 | ]
18 | },
19 | "$[*].*": {
20 | "matchers": [
21 | {
22 | "match": "type"
23 | }
24 | ]
25 | }
26 | }
27 | }
28 | },
29 | "actual": {
30 | "headers": {},
31 | "body": [{
32 | "name": "Peter Peterson",
33 | "age": 22,
34 | "gender": "Male"
35 | }, {}]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/plain text empty body.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Plain text that matches",
4 | "expected" : {
5 | "headers": { "Content-Type": "text/plain" },
6 | "body": ""
7 | },
8 | "actual": {
9 | "headers": { "Content-Type": "text/plain" },
10 | "body": ""
11 |
12 | }
13 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/plain text missing body.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Plain text that matches",
4 | "expected" : {
5 | "headers": { "Content-Type": "text/plain" }
6 | },
7 | "actual": {
8 | "headers": { "Content-Type": "text/plain" }
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/plain text regex matching missing body.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Plain text that matches",
4 | "expected" : {
5 | "headers": { "Content-Type": "text/plain" },
6 | "body": "alligator named mary",
7 | "matchingRules": {
8 | "body": {
9 | "$": {
10 | "matchers": [
11 | {
12 | "match": "regex",
13 | "regex": "alligator named .{4}"
14 | }
15 | ]
16 | }
17 | }
18 | }
19 | },
20 | "actual": {
21 | "headers": { "Content-Type": "text/plain" }
22 | }
23 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/plain text regex matching that does not match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Plain text that matches",
4 | "expected" : {
5 | "headers": { "Content-Type": "text/plain" },
6 | "body": "alligator named mary",
7 | "matchingRules": {
8 | "body": {
9 | "$": {
10 | "matchers": [
11 | {
12 | "match": "regex",
13 | "regex": "alligator named .{4}"
14 | }
15 | ]
16 | }
17 | }
18 | }
19 | },
20 | "actual": {
21 | "headers": { "Content-Type": "text/plain" },
22 | "body": "alligator named brent"
23 | }
24 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/plain text regex matching.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Plain text that matches",
4 | "expected" : {
5 | "headers": { "Content-Type": "text/plain" },
6 | "body": "alligator named mary",
7 | "matchingRules": {
8 | "body": {
9 | "$": {
10 | "matchers": [
11 | {
12 | "match": "regex",
13 | "regex": "alligator.*"
14 | }
15 | ]
16 | }
17 | }
18 | }
19 | },
20 | "actual": {
21 | "headers": { "Content-Type": "text/plain" },
22 | "body": "alligator named brent"
23 | }
24 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/plain text that does not match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Plain text that does not match",
4 | "expected" : {
5 | "headers": { "Content-Type": "text/plain" },
6 | "body": "alligator named mary"
7 | },
8 | "actual": {
9 | "headers": { "Content-Type": "text/plain" },
10 | "body": "alligator named fred"
11 | }
12 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/plain text that matches.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Plain text that matches",
4 | "expected" : {
5 | "headers": { "Content-Type": "text/plain" },
6 | "body": "alligator named mary"
7 | },
8 | "actual": {
9 | "headers": { "Content-Type": "text/plain" },
10 | "body": "alligator named mary"
11 | }
12 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/property name is different case.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Property names on objects are case sensitive",
4 | "expected": {
5 | "headers": { "Content-Type": "application/json" },
6 | "body": {
7 | "alligator": {
8 | "FavouriteColour": "red"
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": { "Content-Type": "application/json" },
14 | "body": {
15 | "alligator": {
16 | "favouritecolour": "red"
17 | }
18 | }
19 | },
20 | "expectedMessage": "A property with a name like 'alligator.FavouriteColour' was present in the actual response, but the case did not match. Note that Pact is case sensitive."
21 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/string found at key when number expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Number of feet expected to be number but was string",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "feet": 4
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "feet": "4"
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/string found in array when number expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Favourite Numbers expected to be numbers, but 2 is a string",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteNumbers": [1,2,3]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "favouriteNumbers": [1,"2",3]
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/unexpected index with not null value.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Unexpected favourite colour",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteColours": ["red","blue"]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "favouriteColours": ["red","blue","taupe"]
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/unexpected index with null value.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Unexpected favourite colour with null value",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "favouriteColours": ["red","blue"]
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "favouriteColours": ["red","blue", null]
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/unexpected key with not null value.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Unexpected phone number",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "name": "Mary"
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "name": "Mary",
17 | "phoneNumber": "12345678"
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/BodyTests/Testcases/unexpected key with null value.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Unexpected phone number with null value",
4 | "expected" : {
5 | "headers": {"Content-Type": "application/json"},
6 | "body": {
7 | "alligator":{
8 | "name": "Mary"
9 | }
10 | }
11 | },
12 | "actual": {
13 | "headers": {"Content-Type": "application/json"},
14 | "body": {
15 | "alligator":{
16 | "name": "Mary",
17 | "phoneNumber": null
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/HeaderMatchingTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using Newtonsoft.Json;
6 | using System.Linq;
7 |
8 | namespace ComPact.UnitTests.Matching.ResponseTests.HeaderTests
9 | {
10 | [TestClass]
11 | public class HeaderMatchingTests
12 | {
13 | [TestMethod]
14 | public void ShouldSuccessfullyExecuteAllTestcase()
15 | {
16 | var testcasesDir = Path.GetFullPath($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}.." +
17 | $"{Path.DirectorySeparatorChar}Matching{Path.DirectorySeparatorChar}ResponseTests{Path.DirectorySeparatorChar}HeaderTests{Path.DirectorySeparatorChar}Testcases{Path.DirectorySeparatorChar}");
18 |
19 | var testcaseFiles = Directory.GetFiles(testcasesDir);
20 |
21 | var failedCases = new List();
22 |
23 | foreach(var file in testcaseFiles)
24 | {
25 | var testcase = JsonConvert.DeserializeObject(File.ReadAllText(file));
26 | var differences = testcase.Expected.Headers.Match(testcase.Actual.Headers, testcase.Expected.MatchingRules);
27 | if (differences.Any() == testcase.Match)
28 | {
29 | failedCases.Add(testcase.Comment);
30 | }
31 | }
32 |
33 | Assert.AreEqual(0, failedCases.Count, "Failed cases: " + Environment.NewLine + string.Join(Environment.NewLine, failedCases));
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/content type parameters do not match.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Headers don't match when the parameters are different",
4 | "expected" : {
5 | "headers": {
6 | "Content-Type": "application/json; charset=UTF-16"
7 | }
8 | },
9 | "actual": {
10 | "headers": {
11 | "Content-Type": "application/json; charset=UTF-8"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/empty headers.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Empty headers match",
4 | "expected" : {
5 | "headers": {}
6 | },
7 | "actual": {
8 | "headers": {}
9 | }
10 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/header name is different case.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Header name is case insensitive",
4 | "expected" : {
5 | "headers": {
6 | "Accept": "alligators"
7 | }
8 | },
9 | "actual": {
10 | "headers": {
11 | "ACCEPT": "alligators"
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/header value is different case.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Headers values are case sensitive",
4 | "expected" : {
5 | "headers": {
6 | "Accept": "alligators"
7 | }
8 | },
9 | "actual": {
10 | "headers": {
11 | "Accept": "Alligators"
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/matches content type with charset.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Headers match when the actual includes additional parameters",
4 | "expected" : {
5 | "headers": {
6 | "Content-Type": "application/json"
7 | }
8 | },
9 | "actual": {
10 | "headers": {
11 | "Content-Type": "application/json; charset=UTF-8"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/matches content type with parameters in different order.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Headers match when the content type parameters are in a different order",
4 | "expected" : {
5 | "headers": {
6 | "Content-Type": "Text/x-Okie; charset=iso-8859-1;\n declaration=\"<950118.AEB0@XIson.com>\""
7 | }
8 | },
9 | "actual": {
10 | "headers": {
11 | "Content-Type": "Text/x-Okie; declaration=\"<950118.AEB0@XIson.com>\";\n charset=iso-8859-1"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/matches with regex.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Headers match with regex",
4 | "expected" : {
5 | "headers": {
6 | "Accept": "alligators",
7 | "Content-Type": "hippos"
8 | },
9 | "matchingRules": {
10 | "header": {
11 | "Accept": {
12 | "matchers": [
13 | {
14 | "match": "regex",
15 | "regex": "\\w+"
16 | }
17 | ]
18 | }
19 | }
20 | }
21 | },
22 | "actual": {
23 | "headers": {
24 | "Content-Type": "hippos",
25 | "Accept": "godzilla"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/matches.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Headers match",
4 | "expected" : {
5 | "headers": {
6 | "Accept": "alligators",
7 | "Content-Type": "hippos"
8 | }
9 | },
10 | "actual": {
11 | "headers": {
12 | "Content-Type": "hippos",
13 | "Accept": "alligators"
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/order of comma separated header values different.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": false,
3 | "comment": "Comma separated headers out of order, order can matter http://tools.ietf.org/html/rfc2616",
4 | "expected" : {
5 | "headers": {
6 | "Accept": "alligators, hippos"
7 | }
8 | },
9 | "actual": {
10 | "headers": {
11 | "Accept": "hippos, alligators"
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/unexpected header found.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Extra headers allowed",
4 | "expected" : {
5 | "headers": {}
6 | },
7 | "actual": {
8 | "headers": {
9 | "Accept": "alligators"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/HeaderTests/Testcases/whitespace after comma different.json:
--------------------------------------------------------------------------------
1 | {
2 | "match": true,
3 | "comment": "Whitespace between comma separated headers does not matter",
4 | "expected" : {
5 | "headers": {
6 | "Accept": "alligators,hippos"
7 | }
8 | },
9 | "actual": {
10 | "headers": {
11 | "Accept": "alligators, hippos"
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/ComPact.UnitTests/Matching/ResponseTests/Testcase.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 |
3 | namespace ComPact.UnitTests.Matching.ResponseTests
4 | {
5 | internal class Testcase
6 | {
7 | public bool Match { get; set; }
8 | public string Comment { get; set; }
9 | public Expected Expected { get; set; }
10 | public Actual Actual { get; set; }
11 | public string ExpectedMessage { get; set; }
12 | }
13 |
14 | internal class Expected
15 | {
16 | public Headers Headers { get; set; }
17 | public dynamic Body { get; set; }
18 | public MatchingRuleCollection MatchingRules { get; set; }
19 | }
20 |
21 | internal class Actual
22 | {
23 | public Headers Headers { get; set; }
24 | public dynamic Body { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/MockProvider/MatchableInteractionListTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.MockProvider;
3 | using ComPact.Models;
4 | using ComPact.Models.V3;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 | using System.Collections.Generic;
7 |
8 | namespace ComPact.UnitTests.MockProvider
9 | {
10 | [TestClass]
11 | public class MatchableInteractionListTests
12 | {
13 | [TestMethod]
14 | public void ShoudlBeAbleToAddUniqueInteraction()
15 | {
16 | var interaction = new Interaction();
17 | var differentInteraction = new Interaction { Request = new Request { Path = "/test" } };
18 | var list = new MatchableInteractionList { new MatchableInteraction(interaction) };
19 |
20 | list.AddUnique(new MatchableInteraction(differentInteraction));
21 |
22 | Assert.AreEqual(2, list.Count);
23 | }
24 |
25 | [TestMethod]
26 | public void ShoudlBeAbleToAddInteractionWithSameRequestIfProviderStateIsDifferent()
27 | {
28 | var interaction = new Interaction();
29 | var differentInteraction = new Interaction { ProviderStates = new List { new ProviderState { Name = "some state" } } };
30 | var list = new MatchableInteractionList { new MatchableInteraction(interaction) };
31 |
32 | list.AddUnique(new MatchableInteraction(differentInteraction));
33 |
34 | Assert.AreEqual(2, list.Count);
35 | }
36 |
37 | [TestMethod]
38 | [ExpectedException(typeof(PactException))]
39 | public void ShoudlNotBeAbleToAddInteractionThatIsNotDistinguishableFromExisting()
40 | {
41 | var interaction = new Interaction { Request = new Request { Path = "/test" } };
42 | var differentInteraction = new Interaction { Request = new Request { Path = "/test" } };
43 | var list = new MatchableInteractionList { new MatchableInteraction(interaction) };
44 |
45 | try
46 | {
47 | list.AddUnique(new MatchableInteraction(differentInteraction));
48 | }
49 | catch (PactException e)
50 | {
51 | Assert.AreEqual("Cannot add multiple interactions with the same provider states and requests. " +
52 | "The provider will not be able to distinguish between them.", e.Message);
53 | throw;
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Models/HeadersFromHeaderDictionaryTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.Extensions.Primitives;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using System;
6 |
7 | namespace ComPact.UnitTests.Models
8 | {
9 | [TestClass]
10 | public class HeadersFromHeaderDictionaryTests
11 | {
12 | [TestMethod]
13 | public void ShouldSupportMultipleHeaders()
14 | {
15 | var actualHeaders = new HeaderDictionary
16 | {
17 | { "Accept", new StringValues("application/json") },
18 | { "Host", new StringValues("test") }
19 | };
20 |
21 | var pactHeaders = new Headers(actualHeaders);
22 |
23 | Assert.AreEqual(actualHeaders.Count, pactHeaders.Count);
24 | Assert.AreEqual("application/json", pactHeaders["Accept"]);
25 | Assert.AreEqual("test", pactHeaders["Host"]);
26 | }
27 |
28 | [TestMethod]
29 | public void ShouldJoinMultipleValuesAsCommaSeparatedList()
30 | {
31 | var actualHeaders = new HeaderDictionary
32 | {
33 | { "Key", new StringValues(new string[] { "value1", "value2", "value3" }) }
34 | };
35 |
36 | var pactHeaders = new Headers(actualHeaders);
37 |
38 | Assert.AreEqual("value1,value2,value3", pactHeaders["Key"]);
39 | }
40 |
41 | [TestMethod]
42 | public void ShouldAllowForEmptyDictionary()
43 | {
44 | var actualHeaders = new HeaderDictionary();
45 |
46 | var pactHeaders = new Headers(actualHeaders);
47 |
48 | Assert.AreEqual(0, pactHeaders.Count);
49 | }
50 |
51 | [TestMethod]
52 | [ExpectedException(typeof(ArgumentNullException))]
53 | public void ShouldThrowWhenNull()
54 | {
55 | new Headers(null as IHeaderDictionary);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Models/HeadersMatchTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System;
4 |
5 | namespace ComPact.UnitTests.Models
6 | {
7 | [TestClass]
8 | public class HeadersMatchTests
9 | {
10 | [TestMethod]
11 | public void ShouldMatchForExactMatchingHeaders()
12 | {
13 | var actual = new Headers { { "Accept", "application/json" } };
14 | var expected = new Headers { { "Accept", "application/json" } };
15 |
16 | Assert.IsTrue(expected.Match(actual));
17 | }
18 |
19 | [TestMethod]
20 | public void ShouldNotMatchWhenKeysDoNotMatch()
21 | {
22 | var actual = new Headers { { "Accept", "application/json" } };
23 | var expected = new Headers { { "Accep", "application/json" } };
24 |
25 | Assert.IsFalse(expected.Match(actual));
26 | }
27 |
28 | [TestMethod]
29 | public void ShouldNotMatchWhenValuesDoNotMatch()
30 | {
31 | var actual = new Headers { { "Accept", "application/json" } };
32 | var expected = new Headers { { "Accept", "application/hal+json" } };
33 |
34 | Assert.IsFalse(expected.Match(actual));
35 | }
36 |
37 | [TestMethod]
38 | public void ShouldAllowForAdditionalActualHeader()
39 | {
40 | var actual = new Headers { { "Accept", "application/json" }, { "Host", "test" } };
41 | var expected = new Headers { { "Accept", "application/json" } };
42 |
43 | Assert.IsTrue(expected.Match(actual));
44 | }
45 |
46 | [TestMethod]
47 | public void ShouldNotAllowForAdditionalExpectedHeader()
48 | {
49 | var actual = new Headers { { "Accept", "application/json" } };
50 | var expected = new Headers { { "Accept", "application/json" }, { "Host", "test" } };
51 |
52 | Assert.IsFalse(expected.Match(actual));
53 | }
54 |
55 | [TestMethod]
56 | public void ShouldMatchWhenNoHeadersAreExpected()
57 | {
58 | var actual = new Headers();
59 | var expected = new Headers();
60 |
61 | Assert.IsTrue(expected.Match(actual));
62 | }
63 |
64 | [TestMethod]
65 | [ExpectedException(typeof(ArgumentNullException))]
66 | public void ShouldThrowWhenNull()
67 | {
68 | new Headers().Match(null);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Models/MatcherTypeEnumTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using Newtonsoft.Json;
4 |
5 | namespace ComPact.UnitTests.Models
6 | {
7 | [TestClass]
8 | public class MatcherTypeEnumTests
9 | {
10 | [TestMethod]
11 | public void ShouldDeserializeTypeToType()
12 | {
13 | var jsonString = "{ \"match\":\"type\"}";
14 |
15 | var matcher = JsonConvert.DeserializeObject(jsonString);
16 |
17 | Assert.AreEqual(MatcherType.type, matcher.MatcherType);
18 | }
19 |
20 | [TestMethod]
21 | public void ShouldDeserializeRegexToRegex()
22 | {
23 | var jsonString = "{ \"match\":\"regex\"}";
24 |
25 | var matcher = JsonConvert.DeserializeObject(jsonString);
26 |
27 | Assert.AreEqual(MatcherType.regex, matcher.MatcherType);
28 | }
29 |
30 | [TestMethod]
31 | public void ShouldDeserializeUnknownValueToType()
32 | {
33 | var jsonString = "{ \"match\":\"number\"}";
34 |
35 | var matcher = JsonConvert.DeserializeObject(jsonString);
36 |
37 | Assert.AreEqual(MatcherType.type, matcher.MatcherType);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Models/MatchingRulesUpgradeTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace ComPact.UnitTests.Models
7 | {
8 | [TestClass]
9 | public class MatchingRulesUpgradeTests
10 | {
11 | [TestMethod]
12 | public void ShouldUpgradeOldMatchingRulesToNew()
13 | {
14 | var oldMatchingRules = new Dictionary()
15 | {
16 | { "$.body", new Matcher { MatcherType = MatcherType.type} },
17 | { "$.body.name", new Matcher { MatcherType = MatcherType.regex, Regex = "\\w+" } },
18 | { "$.headers.content-type", new Matcher { MatcherType = MatcherType.type} }
19 | };
20 |
21 | var newMatchingRules = new MatchingRuleCollection(oldMatchingRules);
22 |
23 | Assert.AreEqual(2, newMatchingRules.Body.Count);
24 | Assert.AreEqual(1, newMatchingRules.Body.First().Value.Matchers.Count);
25 | Assert.AreEqual(MatcherType.type, newMatchingRules.Body["$"].Matchers.First().MatcherType);
26 | Assert.AreEqual("\\w+", newMatchingRules.Body["$.name"].Matchers.First().Regex);
27 | Assert.AreEqual(MatcherType.regex, newMatchingRules.Body["$.name"].Matchers.First().MatcherType);
28 |
29 | Assert.AreEqual(1, newMatchingRules.Header.Count);
30 | Assert.AreEqual(MatcherType.type, newMatchingRules.Header["content-type"].Matchers.First().MatcherType);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Models/V2/SetEmptyValuesToNullTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using ComPact.Models.V2;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System.Collections.Generic;
5 |
6 | namespace ComPact.UnitTests.Models.V2
7 | {
8 | [TestClass]
9 | public class SetEmptyValuesToNullTests
10 | {
11 | [TestMethod]
12 | public void ShouldSetEmptyValuesToNull()
13 | {
14 | var interaction = new Interaction();
15 |
16 | interaction.SetEmptyValuesToNull();
17 |
18 | Assert.IsNull(interaction.ProviderState);
19 | Assert.IsNull(interaction.Request.Headers);
20 | Assert.IsNull(interaction.Request.Body);
21 | Assert.IsNull(interaction.Request.Query);
22 | Assert.IsNull(interaction.Response.Headers);
23 | Assert.IsNull(interaction.Response.Body);
24 | Assert.IsNull(interaction.Response.MatchingRules);
25 | }
26 |
27 | [TestMethod]
28 | public void ShouldNotSetNonEmptyValuesToNull()
29 | {
30 | var interaction = new Interaction
31 | {
32 | ProviderState = "provider state",
33 | Request = new Request
34 | {
35 | Headers = new Headers { { "Accept", "application/json" } },
36 | Body = "test",
37 | Query = "skip=100&take=10"
38 | },
39 | Response = new Response
40 | {
41 | Headers = new Headers { { "Content-Type", "application/json" } },
42 | Body = "test",
43 | MatchingRules = new Dictionary { { "$", new Matcher { MatcherType = MatcherType.type } } }
44 | }
45 | };
46 |
47 | interaction.SetEmptyValuesToNull();
48 |
49 | Assert.IsNotNull(interaction.ProviderState);
50 | Assert.IsNotNull(interaction.Description);
51 | Assert.IsNotNull(interaction.Request.Headers);
52 | Assert.IsNotNull(interaction.Request.Body);
53 | Assert.IsNotNull(interaction.Request.Query);
54 | Assert.IsNotNull(interaction.Request.Path);
55 | Assert.IsNotNull(interaction.Request.Method);
56 | Assert.IsNotNull(interaction.Response.Headers);
57 | Assert.IsNotNull(interaction.Response.Body);
58 | Assert.IsNotNull(interaction.Response.MatchingRules);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Models/V3/QueryToAndFromQueryStringTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models.V3;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.Linq;
4 |
5 | namespace ComPact.UnitTests.Models.V3
6 | {
7 | [TestClass]
8 | public class QueryToAndFromQueryStringTests
9 | {
10 | [TestMethod]
11 | public void TwoSimpleParameters()
12 | {
13 | var inputQueryString ="skip=100&take=10";
14 | var query = new Query(inputQueryString);
15 | var outputQueryString = query.ToQueryString();
16 |
17 | Assert.AreEqual(inputQueryString, outputQueryString);
18 | Assert.AreEqual("100", query["skip"].First());
19 | Assert.AreEqual(1, query["skip"].Count());
20 | Assert.AreEqual("10", query["take"].First());
21 | Assert.AreEqual(1, query["skip"].Count());
22 | }
23 |
24 | [TestMethod]
25 | public void EmptyQueryString()
26 | {
27 | var inputQueryString = string.Empty;
28 | var query = new Query(inputQueryString);
29 | var outputQueryString = query.ToQueryString();
30 |
31 | Assert.AreEqual(inputQueryString, outputQueryString);
32 | Assert.AreEqual(0, query.Count());
33 | }
34 |
35 | [TestMethod]
36 | public void NullQueryString()
37 | {
38 | string inputQueryString = null;
39 | var query = new Query(inputQueryString);
40 | var outputQueryString = query.ToQueryString();
41 |
42 | Assert.AreEqual(string.Empty, outputQueryString);
43 | Assert.AreEqual(0, query.Count());
44 | }
45 |
46 | [TestMethod]
47 | public void OneSimpleParameter()
48 | {
49 | var inputQueryString = "skip=100";
50 | var query = new Query(inputQueryString);
51 | var outputQueryString = query.ToQueryString();
52 |
53 | Assert.AreEqual(inputQueryString, outputQueryString);
54 | Assert.AreEqual("100", query["skip"].First());
55 | Assert.AreEqual(1, query["skip"].Count());
56 | }
57 |
58 | [TestMethod]
59 | public void OneParameterWithMultipleValues()
60 | {
61 | var inputQueryString = "colors=red,blue";
62 | var query = new Query(inputQueryString);
63 | var outputQueryString = query.ToQueryString();
64 |
65 | Assert.AreEqual(inputQueryString, outputQueryString);
66 | Assert.AreEqual(2, query["colors"].Count());
67 | Assert.AreEqual("red", query["colors"][0]);
68 | Assert.AreEqual("blue", query["colors"][1]);
69 | }
70 |
71 | [TestMethod]
72 | public void MultipleParametersWithSameKeyAreJoined()
73 | {
74 | var inputQueryString = "color=red&color=blue";
75 | var query = new Query(inputQueryString);
76 | var outputQueryString = query.ToQueryString();
77 |
78 | Assert.AreEqual("color=blue,red", outputQueryString);
79 | Assert.AreEqual(2, query["color"].Count());
80 | Assert.AreEqual("blue", query["color"][0]);
81 | Assert.AreEqual("red", query["color"][1]);
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Models/V3/ResponseFromHttpResponseMessageTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.Models.V3;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Text;
7 |
8 | namespace ComPact.UnitTests.Models.V3
9 | {
10 | [TestClass]
11 | public class ResponseFromHttpResponseMessageTests
12 | {
13 | [TestMethod]
14 | public void ShouldCreateResponseFromHttpResponseMessage()
15 | {
16 | var httpResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
17 | httpResponseMessage.Headers.Add("Some-Header", "some value");
18 | httpResponseMessage.Content = new StringContent("\"text\"", Encoding.UTF8, "application/json");
19 |
20 | var response = new Response(httpResponseMessage);
21 |
22 | Assert.AreEqual(200, response.Status);
23 | Assert.IsTrue(response.Headers.Any(h => h.Key == "Some-Header" && h.Value == "some value"));
24 | Assert.IsTrue(response.Headers.Any(h => h.Key == "Content-Type" && h.Value == "application/json; charset=utf-8"));
25 | Assert.AreEqual("text", response.Body);
26 | }
27 |
28 | [TestMethod]
29 | public void ShouldCreateResponseFromHttpResponseMessageWithoutContent()
30 | {
31 | var httpResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.NotFound);
32 | httpResponseMessage.Headers.Add("Some-Header", "some value");
33 |
34 | var response = new Response(httpResponseMessage);
35 |
36 | Assert.AreEqual(404, response.Status);
37 | Assert.AreEqual(1, response.Headers.Count);
38 | Assert.IsTrue(response.Headers.Any(h => h.Key == "Some-Header" && h.Value == "some value"));
39 | Assert.IsNull(response.Body);
40 | }
41 |
42 | [TestMethod]
43 | [ExpectedException(typeof(PactException))]
44 | public void ShouldThrowWhenContentCannotBeDeserialized()
45 | {
46 | var httpResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
47 | httpResponseMessage.Content = new StringContent("text", Encoding.UTF8, "text/plain");
48 |
49 | try
50 | {
51 | var response = new Response(httpResponseMessage);
52 | }
53 | catch (PactException e)
54 | {
55 | Assert.AreEqual("Response body could not be deserialized from JSON. Content-Type was text/plain; charset=utf-8", e.Message);
56 | throw;
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Verifier/InvokeProviderStateHandlerTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.Models;
3 | using ComPact.Verifier;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 |
9 | namespace ComPact.UnitTests.Verifier
10 | {
11 | [TestClass]
12 | public class InvokeProviderStateHandlerTests
13 | {
14 | [TestMethod]
15 | public void ShouldInvokeProviderStateHandlerForEveryProviderState()
16 | {
17 | var invocations = new List();
18 |
19 | PactVerifier.InvokeProviderStateHandler(
20 | new List { new ProviderState { Name = "ps1" }, new ProviderState { Name = "ps2" } },
21 | (p) => invocations.Add(p.Name));
22 |
23 | Assert.AreEqual(2, invocations.Count);
24 | }
25 |
26 | [TestMethod]
27 | [ExpectedException(typeof(PactException))]
28 | public void ShouldThrowPactExceptionWhenProviderStateHandlerIsNotConfigured()
29 | {
30 | try
31 | {
32 | PactVerifier.InvokeProviderStateHandler(new List { new ProviderState { Name = "ps1" } }, null);
33 | }
34 | catch (PactException e)
35 | {
36 | Assert.AreEqual("Cannot verify this Pact contract because a ProviderStateHandler was not configured.", e.Message);
37 | throw;
38 | }
39 | }
40 |
41 | [TestMethod]
42 | public void ShouldReturnVerificationMessagesWhenHandlerThrowsPactVerificationException()
43 | {
44 | var verificationMessages = PactVerifier.InvokeProviderStateHandler(
45 | new List { new ProviderState { Name = "ps1" } },
46 | (p) => throw new PactVerificationException("Unknown provider state."));
47 |
48 | Assert.AreEqual($"Provider could not handle provider state \"ps1\": Unknown provider state.", verificationMessages.First());
49 | }
50 |
51 | [TestMethod]
52 | [ExpectedException(typeof(PactException))]
53 | public void ShouldThrowPactExceptionWhenProviderStateHandlerThrowsAnyOtherException()
54 | {
55 | try
56 | {
57 | PactVerifier.InvokeProviderStateHandler(new List { new ProviderState { Name = "ps1" } }, (p) => throw new ArgumentNullException());
58 | }
59 | catch (PactException e)
60 | {
61 | Assert.AreEqual("Exception occured while invoking ProviderStateHandler.", e.Message);
62 | throw;
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Verifier/TestToTestMessageStringTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Verifier;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace ComPact.UnitTests.Verifier
7 | {
8 | [TestClass]
9 | public class TestToTestMessageStringTests
10 | {
11 | [TestMethod]
12 | public void ShouldReturnDescriptionAndStatusForPassedTest()
13 | {
14 | var test = new Test
15 | {
16 | Description = "An interaction"
17 | };
18 |
19 | Assert.AreEqual("An interaction (passed)", test.ToTestMessageString());
20 | }
21 |
22 | [TestMethod]
23 | public void ShouldReturnDescriptionStatusAndListOfIssuesForFailedTest()
24 | {
25 | var test = new Test
26 | {
27 | Description = "A failed interaction",
28 | Issues = new List { "issue 1", "issue 2" }
29 | };
30 |
31 | Assert.AreEqual("A failed interaction (failed):" + Environment.NewLine + "- issue 1" + Environment.NewLine + "- issue 2", test.ToTestMessageString());
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Verifier/VerifyInteractionsTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.Models.V3;
3 | using ComPact.Verifier;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net;
8 | using System.Net.Http;
9 | using System.Threading.Tasks;
10 |
11 | namespace ComPact.UnitTests.Verifier
12 | {
13 | [TestClass]
14 | public class VerifyInteractionsTests
15 | {
16 | private List _interactions;
17 |
18 | public VerifyInteractionsTests()
19 | {
20 | _interactions = new List
21 | {
22 | new Interaction
23 | {
24 | Description = "An interaction",
25 | ProviderStates = new List {new ComPact.Models.ProviderState { Name = "Some state"} },
26 | Request = new Request
27 | {
28 | Method = ComPact.Models.Method.POST
29 | },
30 | Response = new Response
31 | {
32 | Status = 200
33 | }
34 | },
35 | new Interaction
36 | {
37 | Description = "Another interaction",
38 | ProviderStates = new List {new ComPact.Models.ProviderState { Name = "Some state"} },
39 | Request = new Request
40 | {
41 | Method = ComPact.Models.Method.PUT
42 | },
43 | Response = new Response
44 | {
45 | Status = 200
46 | }
47 | }
48 | };
49 | }
50 |
51 | [TestMethod]
52 | public async Task ShouldReturnSuccessfulTest()
53 | {
54 | var tests = await PactVerifier.VerifyInteractions(_interactions, (req) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)), (p) => { });
55 |
56 | Assert.AreEqual(2, tests.Count);
57 | Assert.AreEqual("passed", tests.First().Status);
58 | Assert.AreEqual("An interaction", tests.First().Description);
59 | }
60 |
61 | [TestMethod]
62 | public async Task ShouldReturnFailedTestWhenResponsesDoNotMatch()
63 | {
64 | var tests = await PactVerifier.VerifyInteractions(_interactions, (req) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)), (p) => { });
65 |
66 | Assert.AreEqual(2, tests.Count);
67 | Assert.AreEqual("failed", tests.First().Status);
68 | Assert.AreEqual("An interaction", tests.First().Description);
69 | }
70 |
71 | [TestMethod]
72 | public async Task ShouldReturnFailedTestWhenHandlerThrowsPactVerificationException()
73 | {
74 | var tests = await PactVerifier.VerifyInteractions(_interactions, (req) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)),
75 | (p) => throw new PactVerificationException("Unknown provider state."));
76 |
77 | Assert.AreEqual(2, tests.Count);
78 | Assert.AreEqual("failed", tests.First().Status);
79 | Assert.AreEqual("An interaction", tests.First().Description);
80 | Assert.IsTrue(tests.First().Issues.First().Contains("Unknown provider state."));
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/ComPact.UnitTests/Verifier/VerifyMessagesTests.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.Models.V3;
3 | using ComPact.Verifier;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 |
8 | namespace ComPact.UnitTests.Verifier
9 | {
10 | [TestClass]
11 | public class VerifyMessagesTests
12 | {
13 | private List _messages;
14 |
15 | public VerifyMessagesTests()
16 | {
17 | _messages = new List
18 | {
19 | new Message
20 | {
21 | Description = "A message",
22 | ProviderStates = new List {new ComPact.Models.ProviderState { Name = "Some state"} },
23 | Contents = new { text = "test" }
24 | },
25 | new Message
26 | {
27 | Description = "Another message",
28 | ProviderStates = new List {new ComPact.Models.ProviderState { Name = "Other state"} },
29 | Contents = new { text = "test" }
30 | }
31 | };
32 | }
33 |
34 | [TestMethod]
35 | public void ShouldReturnSuccessfulTest()
36 | {
37 | var tests = PactVerifier.VerifyMessages(_messages, (p) => { }, (d) => new { text = "test" });
38 |
39 | Assert.AreEqual(2, tests.Count);
40 | Assert.AreEqual("passed", tests.First().Status);
41 | Assert.AreEqual("A message", tests.First().Description);
42 | }
43 |
44 | [TestMethod]
45 | public void ShouldReturnFailedTestWhenWrongMessageIsReturned()
46 | {
47 | var tests = PactVerifier.VerifyMessages(_messages, (p) => { }, (d) => new { text = "wrong" });
48 |
49 | Assert.AreEqual(2, tests.Count);
50 | Assert.AreEqual("failed", tests.First().Status);
51 | Assert.AreEqual("A message", tests.First().Description);
52 | }
53 |
54 | [TestMethod]
55 | public void ShouldReturnFailedTestWhenHandlerThrowsPactVerificationException()
56 | {
57 | var tests = PactVerifier.VerifyMessages(_messages, (p) => throw new PactVerificationException("Unknown provider state."), (d) => new { text = "test" });
58 |
59 | Assert.AreEqual(2, tests.Count);
60 | Assert.AreEqual("failed", tests.First().Status);
61 | Assert.AreEqual("A message", tests.First().Description);
62 | Assert.IsTrue(tests.First().Issues.First().Contains("Unknown provider state."));
63 | }
64 |
65 | [TestMethod]
66 | public void ShouldReturnFailedTestWhenMessageProducerThrowsPactVerificationException()
67 | {
68 | var tests = PactVerifier.VerifyMessages(_messages, (p) => { }, (d) => throw new PactVerificationException("Unknown description."));
69 |
70 | Assert.AreEqual(2, tests.Count);
71 | Assert.AreEqual("failed", tests.First().Status);
72 | Assert.AreEqual("A message", tests.First().Description);
73 | Assert.IsTrue(tests.First().Issues.First().Contains("Unknown description."));
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/ComPact.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28803.352
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComPact", "ComPact\ComPact.csproj", "{9A622242-5199-4EC0-9953-4E543759153F}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComPact.UnitTests", "ComPact.UnitTests\ComPact.UnitTests.csproj", "{0191CA45-2A47-4262-8065-BC18263EA67D}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComPact.ConsumerTests", "ComPact.ConsumerTests\ComPact.ConsumerTests.csproj", "{BDF3F200-B8DB-47EA-BD13-5BE7C609B467}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComPact.ProviderTests", "ComPact.ProviderTests\ComPact.ProviderTests.csproj", "{3A46C75D-A045-41B0-A4BE-523F9DC0CDAA}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComPact.Tests.Shared", "ComPact.Tests.Shared\ComPact.Tests.Shared.csproj", "{E97B3FF9-B6F6-451D-95CE-E650871C0173}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {9A622242-5199-4EC0-9953-4E543759153F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {9A622242-5199-4EC0-9953-4E543759153F}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {9A622242-5199-4EC0-9953-4E543759153F}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {9A622242-5199-4EC0-9953-4E543759153F}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {0191CA45-2A47-4262-8065-BC18263EA67D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {0191CA45-2A47-4262-8065-BC18263EA67D}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {0191CA45-2A47-4262-8065-BC18263EA67D}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {0191CA45-2A47-4262-8065-BC18263EA67D}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {BDF3F200-B8DB-47EA-BD13-5BE7C609B467}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {BDF3F200-B8DB-47EA-BD13-5BE7C609B467}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {BDF3F200-B8DB-47EA-BD13-5BE7C609B467}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {BDF3F200-B8DB-47EA-BD13-5BE7C609B467}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {3A46C75D-A045-41B0-A4BE-523F9DC0CDAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {3A46C75D-A045-41B0-A4BE-523F9DC0CDAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {3A46C75D-A045-41B0-A4BE-523F9DC0CDAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {3A46C75D-A045-41B0-A4BE-523F9DC0CDAA}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {E97B3FF9-B6F6-451D-95CE-E650871C0173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {E97B3FF9-B6F6-451D-95CE-E650871C0173}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {E97B3FF9-B6F6-451D-95CE-E650871C0173}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {E97B3FF9-B6F6-451D-95CE-E650871C0173}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {C53C8AD1-45FE-4F2F-B995-CE0F935F1234}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/ComPact/Builders/PactBuilderBase.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.MockProvider;
3 | using ComPact.Models;
4 | using System.Linq;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace ComPact.Builders
9 | {
10 | public abstract class PactBuilderBase
11 | {
12 | protected readonly string _consumer;
13 | protected readonly string _provider;
14 | protected readonly string _pactDir;
15 | protected readonly PactPublisher _pactPublisher;
16 | protected readonly CancellationTokenSource _cts;
17 | private readonly RequestResponseMatcher _matcher;
18 | internal MatchableInteractionList MatchableInteractions { get; private set; }
19 |
20 | internal PactBuilderBase(string consumer, string provider, string mockProviderServiceBaseUri, PactPublisher pactPublisher = null, string pactDir = null)
21 | {
22 | if (mockProviderServiceBaseUri is null)
23 | {
24 | throw new System.ArgumentNullException(nameof(mockProviderServiceBaseUri));
25 | }
26 |
27 | _pactDir = pactDir;
28 | _pactPublisher = pactPublisher;
29 |
30 | _cts = new CancellationTokenSource();
31 |
32 | _consumer = consumer ?? throw new System.ArgumentNullException(nameof(consumer));
33 | _provider = provider ?? throw new System.ArgumentNullException(nameof(provider));
34 | MatchableInteractions = new MatchableInteractionList();
35 |
36 | _matcher = new RequestResponseMatcher(MatchableInteractions);
37 |
38 | ProviderWebHost.Run(mockProviderServiceBaseUri, _matcher, _cts);
39 | }
40 |
41 | internal void SetUp(MatchableInteraction matchableInteraction)
42 | {
43 | MatchableInteractions.AddUnique(matchableInteraction);
44 | }
45 |
46 | internal void ClearMatchableInteractions()
47 | {
48 | MatchableInteractions.Clear();
49 | }
50 |
51 | internal async Task BuildAsync(IContract pact)
52 | {
53 | _cts.Cancel();
54 |
55 | if (!MatchableInteractions.Any())
56 | {
57 | throw new PactException("Cannot build pact. No interactions.");
58 | }
59 |
60 | if (!_matcher.AllHaveBeenMatched())
61 | {
62 | throw new PactException("Cannot build pact. Not all mocked interactions have been called.");
63 | }
64 |
65 | pact.Consumer = new Pacticipant { Name = _consumer };
66 | pact.Provider = new Pacticipant { Name = _provider };
67 |
68 | if (_pactDir != null)
69 | {
70 | PactWriter.Write(pact, _pactDir);
71 | }
72 | else
73 | {
74 | PactWriter.Write(pact);
75 | }
76 |
77 | if (_pactPublisher != null)
78 | {
79 | await _pactPublisher.PublishAsync(pact);
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/ComPact/Builders/PactPublisher.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.Models;
3 | using Newtonsoft.Json;
4 | using System;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ComPact.Builders
10 | {
11 | public class PactPublisher
12 | {
13 | private readonly HttpClient _pactBrokerClient;
14 | private readonly string _consumerVersion;
15 | private readonly string _consumerTag;
16 |
17 | ///
18 | /// Publishes generated contracts to your Pact Broker.
19 | ///
20 | /// Client that can be used to connect to your Pact Broker. Should be set up with the correct base URL and if needed any necessary headers.
21 | /// The version of your consumer application.
22 | /// An optional tag to tag your consumer version with.
23 | public PactPublisher(HttpClient pactBrokerClient, string consumerVersion, string consumerTag = null)
24 | {
25 | if (pactBrokerClient?.BaseAddress == null)
26 | {
27 | throw new PactException("A pactBrokerClient with at least a BaseAddress should be configured to be able to publish contracts.");
28 | }
29 |
30 | if (string.IsNullOrWhiteSpace(consumerVersion))
31 | {
32 | throw new PactException("ConsumerVersion should be configured to be able to publish contracts.");
33 | }
34 |
35 | _pactBrokerClient = pactBrokerClient ?? throw new ArgumentNullException(nameof(pactBrokerClient));
36 |
37 | _consumerVersion = consumerVersion ?? throw new ArgumentNullException(nameof(consumerVersion));
38 | _consumerTag = consumerTag;
39 | }
40 |
41 | internal async Task PublishAsync(IContract pact)
42 | {
43 | var settings = new JsonSerializerSettings
44 | {
45 | NullValueHandling = NullValueHandling.Ignore,
46 | Formatting = Formatting.None
47 | };
48 |
49 | var content = new StringContent(JsonConvert.SerializeObject(pact, settings), Encoding.UTF8, "application/json");
50 |
51 | HttpResponseMessage response;
52 |
53 | try
54 | {
55 | response = await _pactBrokerClient.PutAsync($"pacts/provider/{pact.Provider.Name}/consumer/{pact.Consumer.Name}/version/{_consumerVersion}", content);
56 | }
57 | catch (Exception e)
58 | {
59 | throw new PactException($"Pact cannot be published using the provided Pact Broker Client: {e.Message}");
60 | }
61 | if (!response.IsSuccessStatusCode)
62 | {
63 | throw new PactException("Publishing contract failed. Pact Broker returned " + response.StatusCode);
64 | }
65 |
66 | if (_consumerTag != null)
67 | {
68 | var tagResponse = await _pactBrokerClient.PutAsync($"pacticipants/{pact.Consumer.Name}/versions/{_consumerVersion}/tags/{_consumerTag}", new StringContent(string.Empty, Encoding.UTF8, "application/json"));
69 | if (!tagResponse.IsSuccessStatusCode)
70 | {
71 | throw new PactException("Tagging consumer version failed. Pact Broker returned " + response.StatusCode);
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/ComPact/Builders/PactWriter.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.IO;
5 |
6 | namespace ComPact.Builders
7 | {
8 | internal static class PactWriter
9 | {
10 | public static void Write(IContract pact)
11 | {
12 | #if USE_NET4X
13 | var buildDirectory = new Uri(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", ""))).LocalPath;
14 | var pactDir = Path.GetFullPath($"{buildDirectory}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}pacts{Path.DirectorySeparatorChar}");
15 | #else
16 | string buildDirectory = AppContext.BaseDirectory;
17 | string pactDir = Path.GetFullPath($"{buildDirectory}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}pacts{Path.DirectorySeparatorChar}");
18 | #endif
19 | Write(pact, pactDir);
20 | }
21 |
22 | public static void Write(IContract pact, string pactDir)
23 | {
24 | pact.SetEmptyValuesToNull();
25 |
26 | JsonSerializerSettings settings = new JsonSerializerSettings
27 | {
28 | NullValueHandling = NullValueHandling.Ignore,
29 | Formatting = Formatting.Indented
30 | };
31 | string serializedPact = JsonConvert.SerializeObject(pact, settings);
32 |
33 | Directory.CreateDirectory(pactDir);
34 | File.WriteAllText($"{pactDir}{Path.DirectorySeparatorChar}{pact.Consumer.Name}-{pact.Provider.Name}.json", serializedPact);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ComPact/Builders/V2/InteractionBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models.V2;
2 | using System;
3 |
4 | namespace ComPact.Builders.V2
5 | {
6 | public class InteractionBuilder
7 | {
8 | private readonly Interaction _interaction = new Interaction();
9 |
10 | public InteractionBuilder Given(string providerState)
11 | {
12 | _interaction.ProviderState = providerState ?? throw new ArgumentNullException(nameof(providerState));
13 | return this;
14 | }
15 |
16 | public InteractionBuilder UponReceiving(string description)
17 | {
18 | _interaction.Description = description ?? throw new ArgumentNullException(nameof(description));
19 | return this;
20 | }
21 |
22 | ///
23 | /// Type Pact.Request...
24 | ///
25 | ///
26 | ///
27 | public InteractionBuilder With(RequestBuilder request)
28 | {
29 | if (request == null)
30 | {
31 | throw new ArgumentNullException(nameof(request));
32 | }
33 |
34 | _interaction.Request = request.Build();
35 | return this;
36 | }
37 |
38 | ///
39 | /// Type Pact.Response...
40 | ///
41 | ///
42 | ///
43 | public InteractionBuilder WillRespondWith(ResponseBuilder response)
44 | {
45 | if (response == null)
46 | {
47 | throw new ArgumentNullException(nameof(response));
48 | }
49 |
50 | _interaction.Response = response.Build();
51 | return this;
52 | }
53 |
54 | internal Interaction Build()
55 | {
56 | return _interaction;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/ComPact/Builders/V2/Pact.cs:
--------------------------------------------------------------------------------
1 | namespace ComPact.Builders.V2
2 | {
3 | public static class Pact
4 | {
5 | public static InteractionBuilder Interaction => new InteractionBuilder();
6 | public static RequestBuilder Request => new RequestBuilder();
7 | public static ResponseBuilder Response => new ResponseBuilder();
8 | public static PactJsonContent JsonContent => new PactJsonContent();
9 | }
10 |
11 | public static class Some
12 | {
13 | public static UnknownSimpleValue Element => new UnknownSimpleValue();
14 | public static UnknownString String => new UnknownString();
15 | public static UnknownObject Object => new UnknownObject();
16 | public static UnknownArray Array => new UnknownArray();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ComPact/Builders/V2/PactBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.MockProvider;
2 | using ComPact.Models.V2;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 |
6 | namespace ComPact.Builders.V2
7 | {
8 | public class PactBuilder: PactBuilderBase
9 | {
10 | private List _interactions;
11 |
12 | ///
13 | /// Sets up a mock provider service, generates a V2 contract between a consumer and provider,
14 | /// writes the contract to disk and optionally publishes to a Pact Broker using the supplied client.
15 | ///
16 | /// Name of consuming party of the contract.
17 | /// Name of the providing party of the contract.
18 | /// URL where you will call the mock provider service to verify your consumer.
19 | /// If not supplied the contract will not be published.
20 | /// Directory where the generated pact file will be written to. Defaults to the current project directory.
21 | public PactBuilder(string consumer, string provider, string mockProviderServiceBaseUri, PactPublisher pactPublisher = null, string pactDir = null)
22 | : base(consumer, provider, mockProviderServiceBaseUri, pactPublisher, pactDir)
23 | {
24 | _interactions = new List();
25 | }
26 |
27 | ///
28 | /// Type Pact.Interaction...
29 | ///
30 | ///
31 | public void SetUp(InteractionBuilder interactionBuilder)
32 | {
33 | var interaction = interactionBuilder.Build();
34 | base.SetUp(new MatchableInteraction(new Models.V3.Interaction(interaction)));
35 | _interactions.Add(interaction);
36 | }
37 |
38 | public void ClearInteractions()
39 | {
40 | _interactions = new List();
41 | ClearMatchableInteractions();
42 | }
43 |
44 | public async Task BuildAsync()
45 | {
46 | await base.BuildAsync(new Contract { Interactions = _interactions });
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/ComPact/Builders/V2/RequestBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using ComPact.Models.V2;
3 |
4 | namespace ComPact.Builders.V2
5 | {
6 | public class RequestBuilder
7 | {
8 | private readonly Request _request;
9 |
10 | internal RequestBuilder()
11 | {
12 | _request = new Request();
13 | }
14 |
15 | public RequestBuilder WithPath(string path)
16 | {
17 | _request.Path = path ?? throw new System.ArgumentNullException(nameof(path));
18 | return this;
19 | }
20 |
21 | public RequestBuilder WithMethod(Method method)
22 | {
23 | _request.Method = method;
24 | return this;
25 | }
26 |
27 | public RequestBuilder WithHeader(string key, string value)
28 | {
29 | if (key == null)
30 | {
31 | throw new System.ArgumentNullException(nameof(key));
32 | }
33 |
34 | if (value == null)
35 | {
36 | throw new System.ArgumentNullException(nameof(value));
37 | }
38 |
39 | _request.Headers.Add(key, value);
40 | return this;
41 | }
42 |
43 | public RequestBuilder WithQuery(string query)
44 | {
45 | _request.Query = query ?? throw new System.ArgumentNullException(nameof(query));
46 | return this;
47 | }
48 |
49 | public RequestBuilder WithBody(object body)
50 | {
51 | _request.Body = body ?? throw new System.ArgumentNullException(nameof(body));
52 | return this;
53 | }
54 |
55 | internal Request Build()
56 | {
57 | return _request;
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/ComPact/Builders/V2/ResponseBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models.V2;
2 | using System;
3 |
4 | namespace ComPact.Builders.V2
5 | {
6 | public class ResponseBuilder
7 | {
8 | private readonly Response _response;
9 |
10 | internal ResponseBuilder()
11 | {
12 | _response = new Response();
13 | }
14 |
15 | public ResponseBuilder WithStatus(int status)
16 | {
17 | if (status < 100 || status > 599)
18 | {
19 | throw new ArgumentOutOfRangeException("Status should be between 100 and 599.");
20 | }
21 |
22 | _response.Status = status;
23 | return this;
24 | }
25 |
26 | public ResponseBuilder WithHeader(string key, string value)
27 | {
28 | if (key == null)
29 | {
30 | throw new ArgumentNullException(nameof(key));
31 | }
32 |
33 | if (value == null)
34 | {
35 | throw new ArgumentNullException(nameof(value));
36 | }
37 |
38 | _response.Headers.Add(key, value);
39 | return this;
40 | }
41 |
42 | ///
43 | /// Type Pact.ResponseBody...
44 | ///
45 | ///
46 | ///
47 | public ResponseBuilder WithBody(PactJsonContent responseBody)
48 | {
49 | _response.Body = responseBody.ToJToken();
50 | _response.MatchingRules = responseBody.CreateV2MatchingRules();
51 | return this;
52 | }
53 |
54 | internal Response Build()
55 | {
56 | return _response;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/ComPact/Builders/V3/InteractionBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using ComPact.Models.V3;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace ComPact.Builders.V3
7 | {
8 | public class InteractionBuilder
9 | {
10 | private readonly Interaction _interaction = new Interaction();
11 |
12 | public InteractionBuilder Given(ProviderState providerState)
13 | {
14 | if (providerState is null)
15 | {
16 | throw new ArgumentNullException(nameof(providerState));
17 | }
18 |
19 | if (_interaction.ProviderStates == null)
20 | {
21 | _interaction.ProviderStates = new List();
22 | }
23 | _interaction.ProviderStates.Add(providerState);
24 | return this;
25 | }
26 |
27 | public InteractionBuilder UponReceiving(string description)
28 | {
29 | _interaction.Description = description ?? throw new ArgumentNullException(nameof(description));
30 | return this;
31 | }
32 |
33 | ///
34 | /// Type Pact.Request...
35 | ///
36 | ///
37 | ///
38 | public InteractionBuilder With(RequestBuilder request)
39 | {
40 | if (request == null)
41 | {
42 | throw new ArgumentNullException(nameof(request));
43 | }
44 |
45 | _interaction.Request = request.Build();
46 | return this;
47 | }
48 |
49 | ///
50 | /// Type Pact.Response...
51 | ///
52 | ///
53 | ///
54 | public InteractionBuilder WillRespondWith(ResponseBuilder response)
55 | {
56 | if (response == null)
57 | {
58 | throw new ArgumentNullException(nameof(response));
59 | }
60 |
61 | _interaction.Response = response.Build();
62 | return this;
63 | }
64 |
65 | internal Interaction Build()
66 | {
67 | return _interaction;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ComPact/Builders/V3/MessagePactBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using ComPact.Models;
3 | using ComPact.Models.V3;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace ComPact.Builders.V3
9 | {
10 | public class MessagePactBuilder
11 | {
12 | private readonly string _consumer;
13 | private readonly string _provider;
14 | private readonly string _pactDir;
15 | private readonly PactPublisher _pactPublisher;
16 | private readonly List _messages;
17 |
18 | ///
19 | /// Generates a V3 message contract between a consumer and provider,
20 | /// writes the contract to disk and optionally publishes to a Pact Broker using the supplied client.
21 | ///
22 | /// Name of consuming party of the contract.
23 | /// Name of the providing party of the contract.
24 | /// If not supplied the contract will not be published.
25 | /// Directory where the generated pact file will be written to. Defaults to the current project directory.
26 | public MessagePactBuilder(string consumer, string provider, PactPublisher pactPublisher = null, string pactDir = null)
27 | {
28 | _consumer = consumer ?? throw new System.ArgumentNullException(nameof(consumer));
29 | _provider = provider ?? throw new System.ArgumentNullException(nameof(provider));
30 |
31 | _pactDir = pactDir;
32 | _pactPublisher = pactPublisher;
33 |
34 | _messages = new List();
35 | }
36 |
37 | ///
38 | /// Type Pact.Message...
39 | ///
40 | ///
41 | public MessagePactBuilder SetUp(MessageBuilder messageBuilder)
42 | {
43 | _messages.Add(messageBuilder.Build());
44 | return this;
45 | }
46 |
47 | public async Task BuildAsync()
48 | {
49 | if (!_messages.Any())
50 | {
51 | throw new PactException("Cannot build pact. No messages.");
52 | }
53 |
54 | var pact = new MessageContract
55 | {
56 | Consumer = new Pacticipant { Name = _consumer },
57 | Provider = new Pacticipant { Name = _provider },
58 | Messages = _messages
59 | };
60 |
61 | if (_pactDir != null)
62 | {
63 | PactWriter.Write(pact, _pactDir);
64 | }
65 | else
66 | {
67 | PactWriter.Write(pact);
68 | }
69 |
70 | if (_pactPublisher != null)
71 | {
72 | await _pactPublisher.PublishAsync(pact);
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/ComPact/Builders/V3/Pact.cs:
--------------------------------------------------------------------------------
1 | namespace ComPact.Builders.V3
2 | {
3 | public static class Pact
4 | {
5 | public static InteractionBuilder Interaction => new InteractionBuilder();
6 | public static RequestBuilder Request => new RequestBuilder();
7 | public static ResponseBuilder Response => new ResponseBuilder();
8 | public static MessageBuilder Message => new MessageBuilder();
9 | public static PactJsonContent JsonContent => new PactJsonContent();
10 | }
11 |
12 | public static class Some
13 | {
14 | public static UnknownSimpleValue Element => new UnknownSimpleValue();
15 | public static UnknownString String => new UnknownString();
16 | public static UnknownObject Object => new UnknownObject();
17 | public static UnknownArray Array => new UnknownArray();
18 | public static UnknownInteger Integer => new UnknownInteger();
19 | public static UnknownDecimal Decimal => new UnknownDecimal();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ComPact/Builders/V3/PactBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.MockProvider;
2 | using ComPact.Models.V3;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace ComPact.Builders.V3
7 | {
8 | public class PactBuilder : PactBuilderBase
9 | {
10 | ///
11 | /// Sets up a mock provider service, generates a V3 contract between a consumer and provider,
12 | /// writes the contract to disk and optionally publishes to a Pact Broker using the supplied client.
13 | ///
14 | /// Name of consuming party of the contract.
15 | /// Name of the providing party of the contract.
16 | /// URL where you will call the mock provider service to verify your consumer.
17 | /// If not supplied the contract will not be published.
18 | /// Directory where the generated pact file will be written to. Defaults to the current project directory.
19 | public PactBuilder(string consumer, string provider, string mockProviderServiceBaseUri, PactPublisher pactPublisher = null, string pactDir = null)
20 | : base(consumer, provider, mockProviderServiceBaseUri, pactPublisher, pactDir)
21 | {
22 | }
23 |
24 | ///
25 | /// Type Pact.Interaction...
26 | ///
27 | ///
28 | public void SetUp(InteractionBuilder interactionBuilder)
29 | {
30 | base.SetUp(new MatchableInteraction(interactionBuilder.Build()));
31 | }
32 |
33 | public void ClearInteractions()
34 | {
35 | base.ClearMatchableInteractions();
36 | }
37 |
38 | public async Task BuildAsync()
39 | {
40 | await base.BuildAsync(new Contract { Interactions = MatchableInteractions.Select(m => m.Interaction as Interaction).ToList() });
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ComPact/Builders/V3/PactJsonContentV3Dsl.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 |
3 | namespace ComPact.Builders.V3
4 | {
5 | public class UnknownInteger
6 | {
7 | public SimpleValueName Named(string name) => new SimpleValueName(name);
8 | public SimpleValue Like(int example) => new SimpleValue(example, MatcherType.integer);
9 | }
10 |
11 | public class UnknownDecimal
12 | {
13 | public SimpleValueName Named(string name) => new SimpleValueName(name);
14 | public SimpleValue Like(double example) => new SimpleValue(example, MatcherType.@decimal);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ComPact/Builders/V3/RequestBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using ComPact.Models.V3;
3 |
4 | namespace ComPact.Builders.V3
5 | {
6 | public class RequestBuilder
7 | {
8 | private readonly Request _request;
9 |
10 | internal RequestBuilder()
11 | {
12 | _request = new Request();
13 | }
14 |
15 | public RequestBuilder WithPath(string path)
16 | {
17 | _request.Path = path ?? throw new System.ArgumentNullException(nameof(path));
18 | return this;
19 | }
20 |
21 | public RequestBuilder WithMethod(Method method)
22 | {
23 | _request.Method = method;
24 | return this;
25 | }
26 |
27 | public RequestBuilder WithHeader(string key, string value)
28 | {
29 | if (key == null)
30 | {
31 | throw new System.ArgumentNullException(nameof(key));
32 | }
33 |
34 | if (value == null)
35 | {
36 | throw new System.ArgumentNullException(nameof(value));
37 | }
38 |
39 | _request.Headers.Add(key, value);
40 | return this;
41 | }
42 |
43 | public RequestBuilder WithQuery(string query)
44 | {
45 | if (query is null)
46 | {
47 | throw new System.ArgumentNullException(nameof(query));
48 | }
49 |
50 | _request.Query = new Query(query);
51 | return this;
52 | }
53 |
54 | public RequestBuilder WithBody(object body)
55 | {
56 | _request.Body = body ?? throw new System.ArgumentNullException(nameof(body));
57 | return this;
58 | }
59 |
60 | internal Request Build()
61 | {
62 | return _request;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/ComPact/Builders/V3/ResponseBuilder.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using ComPact.Models.V3;
3 | using System;
4 |
5 | namespace ComPact.Builders.V3
6 | {
7 | public class ResponseBuilder
8 | {
9 | private readonly Response _response;
10 |
11 | internal ResponseBuilder()
12 | {
13 | _response = new Response();
14 | }
15 |
16 | public ResponseBuilder WithStatus(int status)
17 | {
18 | if (status < 100 || status > 599)
19 | {
20 | throw new ArgumentOutOfRangeException("Status should be between 100 and 599.");
21 | }
22 |
23 | _response.Status = status;
24 | return this;
25 | }
26 |
27 | public ResponseBuilder WithHeader(string key, string value)
28 | {
29 | if (key == null)
30 | {
31 | throw new ArgumentNullException(nameof(key));
32 | }
33 |
34 | if (value == null)
35 | {
36 | throw new ArgumentNullException(nameof(value));
37 | }
38 |
39 | _response.Headers.Add(key, value);
40 | return this;
41 | }
42 |
43 | ///
44 | /// Type Pact.JsonContent...
45 | ///
46 | ///
47 | ///
48 | public ResponseBuilder WithBody(PactJsonContent responseBody)
49 | {
50 | _response.Body = responseBody.ToJToken();
51 | if (_response.MatchingRules == null)
52 | {
53 | _response.MatchingRules = new MatchingRuleCollection();
54 | }
55 | _response.MatchingRules.Body = responseBody.CreateV3MatchingRules();
56 | return this;
57 | }
58 |
59 | internal Response Build()
60 | {
61 | return _response;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/ComPact/ComPact.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | ComPact
6 | 0.4.4
7 | Bart Schotten
8 | A Pact implementation for .NET with support for Pact Specification v3.
9 |
10 | MIT
11 | https://github.com/bartschotten/com-pact
12 | https://github.com/bartschotten/com-pact
13 | git
14 | Pact, PactBroker, Message, Async, v3
15 | true
16 | true
17 |
18 |
19 |
20 | 1701;1702;1591
21 |
22 |
23 |
24 | 1701;1702;1591
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | <_Parameter1>ComPact.UnitTests
38 |
39 |
40 |
41 |
42 |
43 | ..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.dll
44 |
45 |
46 | ..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.server.kestrel.core\2.2.0\lib\netcoreapp2.1\Microsoft.AspNetCore.Server.Kestrel.Core.dll
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/ComPact/Exceptions/PactException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ComPact.Exceptions
4 | {
5 | public class PactException: Exception
6 | {
7 | public PactException(string message): base(message)
8 | {
9 | }
10 | public PactException(string message, Exception exception) : base(message,exception)
11 | {
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ComPact/Exceptions/PactVerificationException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ComPact.Exceptions
4 | {
5 | public class PactVerificationException: Exception
6 | {
7 | public PactVerificationException(string message): base(message)
8 | {
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ComPact/JsonHelpers/JTokenExtensions.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ComPact.JsonHelpers
6 | {
7 | internal static class JTokenExtensions
8 | {
9 | internal static IEnumerable ThisTokenAndAllItsDescendants(this JToken token)
10 | {
11 | var tokenAndItsChildren = new List { token };
12 | tokenAndItsChildren.AddRange(token.Children().Select(c => c.ThisTokenAndAllItsDescendants()).SelectMany(d => d));
13 | return tokenAndItsChildren;
14 | }
15 |
16 | internal static bool IsSameJsonTypeAs(this JToken token, JToken otherToken)
17 | {
18 | return token.Type == otherToken.Type ||
19 | (token.Type == JTokenType.Float && otherToken.Type == JTokenType.Integer) ||
20 | (token.Type == JTokenType.Integer && otherToken.Type == JTokenType.Float);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ComPact/JsonHelpers/JTokenParser.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System.IO;
4 |
5 | namespace ComPact.JsonHelpers
6 | {
7 | internal static class JTokenParser
8 | {
9 | internal static JToken Parse(dynamic body) => Parse(body);
10 | internal static JToken ParseToLower(dynamic body) => Parse(body);
11 |
12 | private static JToken Parse(dynamic body) where T : JsonTextReader
13 | {
14 | // This is necessary because DateParseHandling.None has no effect when using JToken.Parse or JToken.FromObject directly.
15 | var jsonTextReader = new JsonTextReader(new StringReader(JsonConvert.SerializeObject(body)))
16 | {
17 | // We don't want datetime-like strings to be converted to datetime, otherwise regex matching doesn't work.
18 | DateParseHandling = DateParseHandling.None
19 | };
20 | return JToken.Load(jsonTextReader);
21 | }
22 | }
23 |
24 | internal class LowerCaseJsonReader : JsonTextReader
25 | {
26 | public LowerCaseJsonReader(TextReader reader) : base(reader) { }
27 |
28 | public override object Value
29 | {
30 | get => TokenType == JsonToken.PropertyName ? ((string)base.Value).ToLowerInvariant() : base.Value;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ComPact/JsonHelpers/StringEnumWithDefaultConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Converters;
4 |
5 | namespace ComPact.JsonHelpers
6 | {
7 | public class StringEnumWithDefaultConverter: StringEnumConverter
8 | {
9 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
10 | {
11 | try
12 | {
13 | return base.ReadJson(reader, objectType, existingValue, serializer);
14 | }
15 | catch
16 | {
17 | return existingValue;
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ComPact/MockProvider/MatchableInteraction.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models.V3;
2 |
3 | namespace ComPact.MockProvider
4 | {
5 | internal class MatchableInteraction
6 | {
7 | public Interaction Interaction { get; set; }
8 | public bool HasBeenMatched { get; set; }
9 |
10 | public MatchableInteraction(Interaction interaction)
11 | {
12 | Interaction = interaction;
13 | }
14 |
15 | public Response Match(Models.V3.Request request)
16 | {
17 | var response = Interaction.Match(request);
18 | if (response != null)
19 | {
20 | HasBeenMatched = true;
21 | }
22 | return response;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ComPact/MockProvider/MatchableInteractionList.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ComPact.MockProvider
6 | {
7 | internal class MatchableInteractionList : List
8 | {
9 | internal void AddUnique(MatchableInteraction matchableInteraction)
10 | {
11 | if (this.All(m => m.Interaction.ProviderStatesAndRequestCanBeDistinguishedFrom(matchableInteraction.Interaction)))
12 | {
13 | Add(matchableInteraction);
14 | }
15 | else
16 | {
17 | throw new PactException("Cannot add multiple interactions with the same provider states and requests. The provider will not be able to distinguish between them.");
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ComPact/MockProvider/ProviderWebHost.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Hosting;
4 | using System.Threading;
5 |
6 | namespace ComPact.MockProvider
7 | {
8 | internal static class ProviderWebHost
9 | {
10 | internal static void Run(string mockProviderServiceBaseUri, RequestResponseMatcher matcher, CancellationTokenSource cancellationTokenSource)
11 | {
12 | var host = WebHost.CreateDefaultBuilder()
13 | .UseUrls(mockProviderServiceBaseUri)
14 | .ConfigureKestrel(options => options.AllowSynchronousIO = true)
15 | .Configure(app =>
16 | {
17 | app.Run(async context =>
18 | {
19 | await matcher.MatchRequestAndReturnResponseAsync(context.Request, context.Response);
20 | });
21 | })
22 | .Build();
23 |
24 | host.RunAsync(cancellationTokenSource.Token);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ComPact/MockProvider/RequestResponseMatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System;
3 | using Microsoft.AspNetCore.Http;
4 | using System.Threading.Tasks;
5 | using Microsoft.Extensions.Primitives;
6 | using System.Text;
7 | using Newtonsoft.Json;
8 | using ComPact.Models.V3;
9 |
10 | namespace ComPact.MockProvider
11 | {
12 | internal class RequestResponseMatcher
13 | {
14 | private readonly MatchableInteractionList _matchableInteractions;
15 |
16 | public RequestResponseMatcher(MatchableInteractionList interactions)
17 | {
18 | _matchableInteractions = interactions;
19 | }
20 |
21 | public async Task MatchRequestAndReturnResponseAsync(HttpRequest httpRequest, HttpResponse httpResponseToReturn)
22 | {
23 | if (httpRequest == null)
24 | {
25 | throw new ArgumentNullException(nameof(httpRequest));
26 | }
27 |
28 | var request = new Request(httpRequest);
29 |
30 | var response = _matchableInteractions.Select(m => m.Match(request)).Where(r => r != null).LastOrDefault();
31 |
32 | string stringToReturn;
33 | if (response != null)
34 | {
35 | httpResponseToReturn.StatusCode = response.Status;
36 | foreach (var header in response.Headers)
37 | {
38 | httpResponseToReturn.Headers.Add(header.Key, new StringValues((string)header.Value));
39 | }
40 | stringToReturn = JsonConvert.SerializeObject(response.Body);
41 | }
42 | else
43 | {
44 | var errorResponse = new RequestResponseMatchingErrorResponse()
45 | {
46 | ActualRequest = request,
47 | ExpectedRequests = _matchableInteractions.Select(m => m.Interaction.Request).ToList(),
48 | Message = "No matching response set up for this request."
49 | };
50 | httpResponseToReturn.StatusCode = 400;
51 | stringToReturn = JsonConvert.SerializeObject(errorResponse);
52 | }
53 | await httpResponseToReturn.Body.WriteAsync(Encoding.UTF8.GetBytes(stringToReturn), 0, Encoding.UTF8.GetByteCount(stringToReturn));
54 | }
55 |
56 | public bool AllHaveBeenMatched()
57 | {
58 | return _matchableInteractions.All(m => m.HasBeenMatched);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ComPact/MockProvider/RequestResponseMatchingErrorResponse.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models.V3;
2 | using Newtonsoft.Json;
3 | using System.Collections.Generic;
4 |
5 | namespace ComPact.MockProvider
6 | {
7 | internal class RequestResponseMatchingErrorResponse
8 | {
9 | [JsonProperty("message")]
10 | internal string Message { get; set; }
11 | [JsonProperty("actualRequests")]
12 | internal Request ActualRequest { get; set; }
13 | [JsonProperty("expectedRequests")]
14 | internal List ExpectedRequests { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ComPact/Models/ContractWithSomeVersion.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace ComPact.Models
4 | {
5 | internal class ContractWithSomeVersion
6 | {
7 | [JsonProperty("metadata")]
8 | internal Metadata Metadata { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ComPact/Models/Headers.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Http;
5 |
6 | namespace ComPact.Models
7 | {
8 | public class Headers : Dictionary
9 | {
10 | public Headers() { }
11 |
12 | internal Headers(IHeaderDictionary headers)
13 | {
14 | if (headers == null)
15 | {
16 | throw new System.ArgumentNullException(nameof(headers));
17 | }
18 |
19 | foreach (var header in headers)
20 | {
21 | Add(header.Key, string.Join(",", header.Value));
22 | }
23 | }
24 |
25 | internal Headers(HttpResponseMessage response)
26 | {
27 | if (response == null)
28 | {
29 | throw new System.ArgumentNullException(nameof(response));
30 | }
31 |
32 | response.Headers.ToList().ForEach(h => Add(h.Key, string.Join(",", h.Value)));
33 | response.Content?.Headers.ToList().ForEach(h => Add(h.Key, string.Join(",", h.Value)));
34 | }
35 |
36 | internal bool Match(Headers actualHeaders)
37 | {
38 | if (actualHeaders == null)
39 | {
40 | throw new System.ArgumentNullException(nameof(actualHeaders));
41 | }
42 |
43 | return this.All(h => actualHeaders.Any(a => h.Key == a.Key && h.Value == a.Value));
44 | }
45 |
46 | internal List Match(Headers actualHeaders, MatchingRuleCollection matchingRules)
47 | {
48 | if (actualHeaders == null)
49 | {
50 | throw new System.ArgumentNullException(nameof(actualHeaders));
51 | }
52 |
53 | var differences = new List();
54 | foreach(var expectedHeader in KeysToLowerCase(this))
55 | {
56 | if (KeysToLowerCase(actualHeaders).TryGetValue(expectedHeader.Key, out var actualHeaderValue))
57 | {
58 | var expectedParts = SplitValueIntoParts(expectedHeader.Value);
59 | var actualParts = SplitValueIntoParts(actualHeaderValue);
60 |
61 | if (matchingRules?.Header != null && KeysToLowerCase(matchingRules.Header).TryGetValue(expectedHeader.Key, out var matchers))
62 | {
63 | differences.AddRange(matchers.Match(expectedHeader.Value, actualHeaderValue));
64 | }
65 | else if (!expectedParts.All(e => actualParts.Any(a => RemoveWhiteSpaceAfterCommas(a) == RemoveWhiteSpaceAfterCommas(e))))
66 | {
67 | differences.Add($"Expected {expectedHeader.Value} for {expectedHeader.Key}, but was {actualHeaderValue}");
68 | }
69 | }
70 | else
71 | {
72 | differences.Add($"Expected a header named {expectedHeader.Key}, but was not found.");
73 | }
74 | }
75 |
76 | return differences;
77 | }
78 |
79 | private Dictionary KeysToLowerCase(Dictionary headers)
80 | {
81 | return headers.ToDictionary(h => h.Key.ToLowerInvariant(), h => h.Value);
82 | }
83 |
84 | private List SplitValueIntoParts(string value)
85 | {
86 | return value.Split(';').Select(p => p.Trim()).ToList();
87 | }
88 |
89 | private string RemoveWhiteSpaceAfterCommas(string value)
90 | {
91 | return string.Join(",", value.Split(',').Select(x => x.Trim()));
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/ComPact/Models/IContract.cs:
--------------------------------------------------------------------------------
1 | namespace ComPact.Models
2 | {
3 | internal interface IContract
4 | {
5 | Pacticipant Consumer { get; set; }
6 | Pacticipant Provider { get; set; }
7 | void SetEmptyValuesToNull();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ComPact/Models/Matcher.cs:
--------------------------------------------------------------------------------
1 | using ComPact.JsonHelpers;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | namespace ComPact.Models
8 | {
9 | internal class Matcher
10 | {
11 | [JsonProperty("match")]
12 | [JsonConverter(typeof(StringEnumWithDefaultConverter))]
13 | internal MatcherType MatcherType { get; set; }
14 | [JsonProperty("min")]
15 | internal int? Min { get; set; }
16 | [JsonProperty("max")]
17 | internal int? Max { get; set; }
18 | [JsonProperty("regex")]
19 | internal string Regex { get; set; }
20 | [JsonProperty("value")]
21 | internal string Value { get; set; }
22 |
23 | internal List Match(JToken expectedToken, JToken actualToken)
24 | {
25 | var differences = new List();
26 |
27 | if (MatcherType == MatcherType.type && !expectedToken.IsSameJsonTypeAs(actualToken))
28 | {
29 | differences.Add($"Expected value of type {expectedToken.Type} (like: \'{expectedToken}\') at {expectedToken.Path}, but was value of type {actualToken.Type}.");
30 | }
31 | if (Regex != null && System.Text.RegularExpressions.Regex.Match(actualToken.Value(), Regex).Value != actualToken.Value())
32 | {
33 | differences.Add($"Expected value matching \'{Regex}\' (like: \'{expectedToken.Value()}\') at {expectedToken.Path}, but was \'{actualToken.Value()}\'.");
34 | }
35 | if (MatcherType == MatcherType.include && !actualToken.Value().Contains(Value))
36 | {
37 | differences.Add($"Expected value at {expectedToken.Path} to include '{Value}', but was {actualToken.Value()}.");
38 | }
39 | if (MatcherType == MatcherType.integer && actualToken.Type != JTokenType.Integer)
40 | {
41 | differences.Add($"Expected integer (like: \'{expectedToken.Value()}\') at {expectedToken.Path}, but was {actualToken.ToString()}.");
42 | }
43 | if (MatcherType == MatcherType.@decimal && actualToken.Type != JTokenType.Float)
44 | {
45 | differences.Add($"Expected decimal (like: \'{expectedToken.Value()}\') at {expectedToken.Path}, but was {actualToken.ToString()}.");
46 | }
47 | if (MatcherType == MatcherType.@null && actualToken.Type != JTokenType.Null)
48 | {
49 | differences.Add($"Expected null at {expectedToken.Path}, but was {actualToken}.");
50 | }
51 |
52 | if (expectedToken.Type == JTokenType.Array)
53 | {
54 | if (Min != null && actualToken.Children().Count() < Min)
55 | {
56 | differences.Add($"Expected an array with at least {Min} item(s) at {expectedToken.Path}, but was {actualToken.Children().Count()} items(s).");
57 | }
58 | if (Max != null && actualToken.Children().Count() > Max)
59 | {
60 | differences.Add($"Expected an array with at most {Max} item(s) at {expectedToken.Path}, but was {actualToken.Children().Count()} items(s).");
61 | }
62 | }
63 |
64 | return differences;
65 | }
66 |
67 | internal List Match(object expectedValue, object actualValue)
68 | {
69 | return Match(JToken.FromObject(expectedValue), JToken.FromObject(actualValue));
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/ComPact/Models/MatcherList.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace ComPact.Models
7 | {
8 | public class MatcherList
9 | {
10 | [JsonProperty("combine")]
11 | internal string Combine { get; set; }
12 | [JsonProperty("matchers")]
13 | internal List Matchers { get; set; }
14 |
15 | internal List Match(JToken expectedToken, JToken actualToken)
16 | {
17 | var differences = new List();
18 |
19 | var anySuccessfulMatchers = false;
20 | foreach (var matcher in Matchers)
21 | {
22 | var matcherDifferences = matcher.Match(expectedToken, actualToken);
23 | if (matcherDifferences.Any())
24 | {
25 | differences.AddRange(matcherDifferences);
26 | }
27 | else
28 | {
29 | anySuccessfulMatchers = true;
30 | }
31 | }
32 |
33 | if ((Combine != "OR" && differences.Any()) || !anySuccessfulMatchers)
34 | {
35 | return differences;
36 | }
37 | return new List();
38 | }
39 |
40 | internal List Match(object expectedValue, object actualValue)
41 | {
42 | return Match(JToken.FromObject(expectedValue), JToken.FromObject(actualValue));
43 | }
44 |
45 | internal bool ImpliesEquality()
46 | {
47 | if (Combine != "OR" && Matchers.Any(m => m.MatcherType == MatcherType.equality))
48 | {
49 | return true;
50 | }
51 | if (Matchers.All(m => m.MatcherType == MatcherType.equality))
52 | {
53 | return true;
54 | }
55 | return false;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ComPact/Models/MatcherType.cs:
--------------------------------------------------------------------------------
1 | namespace ComPact.Models
2 | {
3 | public enum MatcherType
4 | {
5 | type,
6 | equality,
7 | regex,
8 | integer,
9 | @decimal,
10 | include,
11 | @null
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ComPact/Models/MatchingRuleCollection.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace ComPact.Models
7 | {
8 | internal class MatchingRuleCollection
9 | {
10 | [JsonProperty("header")]
11 | internal Dictionary Header { get; set; } = new Dictionary();
12 | [JsonProperty("body")]
13 | internal Dictionary Body { get; set; } = new Dictionary();
14 |
15 | internal MatchingRuleCollection() { }
16 |
17 | internal MatchingRuleCollection(Dictionary matchingRules)
18 | {
19 | if (matchingRules == null)
20 | {
21 | throw new System.ArgumentNullException(nameof(matchingRules));
22 | }
23 |
24 | foreach (var rule in matchingRules.Where(m => m.Key.StartsWith("$.body")))
25 | {
26 | Body.Add(rule.Key.Replace(".body", ""), new MatcherList { Matchers = new List { rule.Value } });
27 | }
28 |
29 | foreach (var rule in matchingRules.Where(m => m.Key.StartsWith("$.headers")))
30 | {
31 | Header.Add(rule.Key.Replace("$.headers.", ""), new MatcherList { Matchers = new List { rule.Value } });
32 | }
33 | }
34 |
35 | internal bool TryGetApplicableMatcherListForToken(JToken token, out MatcherList matcherList)
36 | {
37 | var matchingPaths = new List();
38 | foreach (var path in Body.Select(b => b.Key))
39 | {
40 | var matchingTokens = token.Root.SelectTokens(path).ToList();
41 | if (matchingTokens.Select(t => t.Path).Intersect(token.AncestorsAndSelf().Select(t => t.Path)).Any())
42 | {
43 | matchingPaths.Add(path);
44 | }
45 | }
46 |
47 | if (!matchingPaths.Any())
48 | {
49 | matcherList = null;
50 | return false;
51 | }
52 |
53 | var orderedPaths = matchingPaths.OrderBy(m => m, new MatchingRulePathComparer()).ToList();
54 | matcherList = Body[orderedPaths.Last()];
55 | if (matcherList.ImpliesEquality())
56 | {
57 | return false;
58 | }
59 | return true;
60 | }
61 |
62 | internal void SetEmptyValuesToNull()
63 | {
64 | Body = Body?.FirstOrDefault() != null ? Body : null;
65 | Header = Header?.FirstOrDefault() != null ? Header : null;
66 | }
67 | }
68 |
69 | internal class MatchingRulePathComparer : IComparer
70 | {
71 | public int Compare(string x, string y)
72 | {
73 | var lengthComparison = x.Length.CompareTo(y.Length);
74 | if (lengthComparison == 0)
75 | {
76 | return y.Count(c => c == '*').CompareTo(x.Count(c => c == '*'));
77 | }
78 | else
79 | {
80 | return lengthComparison;
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/ComPact/Models/Metadata.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace ComPact.Models
4 | {
5 | internal class Metadata
6 | {
7 | [JsonProperty("pactSpecification")]
8 | public PactSpecification PactSpecification { get; set; }
9 | [JsonProperty("pact-specification")]
10 | public PactSpecification PactSpecificationWithDash { get; set; }
11 | [JsonProperty("pactSpecificationVersion")]
12 | public string PactSpecificationVersion { get; set; }
13 |
14 | public SpecificationVersion GetVersion()
15 | {
16 | var stringVersion = PactSpecification?.Version ?? PactSpecificationWithDash?.Version ?? PactSpecificationVersion;
17 | if (stringVersion.StartsWith("2"))
18 | {
19 | return SpecificationVersion.Two;
20 | }
21 | if (stringVersion.StartsWith("3"))
22 | {
23 | return SpecificationVersion.Three;
24 | }
25 | return SpecificationVersion.Unsupported;
26 | }
27 | }
28 |
29 | internal class PactSpecification
30 | {
31 | [JsonProperty("version")]
32 | public string Version { get; set; }
33 | }
34 |
35 | internal enum SpecificationVersion
36 | {
37 | Two,
38 | Three,
39 | Unsupported
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/ComPact/Models/Method.cs:
--------------------------------------------------------------------------------
1 | namespace ComPact.Models
2 | {
3 | public enum Method
4 | {
5 | CONNECT,
6 | DELETE,
7 | GET,
8 | HEAD,
9 | OPTIONS,
10 | POST,
11 | PUT,
12 | TRACE
13 | }
14 | }
--------------------------------------------------------------------------------
/ComPact/Models/Pacticipant.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace ComPact.Models
4 | {
5 | internal class Pacticipant
6 | {
7 | [JsonProperty("name")]
8 | public string Name { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ComPact/Models/ProviderState.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 |
4 | namespace ComPact.Models
5 | {
6 | public class ProviderState
7 | {
8 | [JsonProperty("name")]
9 | public string Name { get; set; }
10 | [JsonProperty("params")]
11 | public Dictionary Params { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ComPact/Models/V2/Contract.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 |
4 | namespace ComPact.Models.V2
5 | {
6 | internal class Contract: IContract
7 | {
8 | [JsonProperty("consumer")]
9 | public Pacticipant Consumer { get; set; }
10 | [JsonProperty("provider")]
11 | public Pacticipant Provider { get; set; }
12 | [JsonProperty("interactions")]
13 | internal List Interactions { get; set; }
14 | [JsonProperty("metadata")]
15 | internal Metadata Metadata { get; set; } = new Metadata { PactSpecification = new PactSpecification { Version = "2.0.0" } };
16 | public void SetEmptyValuesToNull()
17 | {
18 | Interactions.ForEach(i => i.SetEmptyValuesToNull());
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ComPact/Models/V2/Interaction.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace ComPact.Models.V2
4 | {
5 | internal class Interaction
6 | {
7 | [JsonProperty("description")]
8 | public string Description { get; set; } = string.Empty;
9 | [JsonProperty("providerState")]
10 | public string ProviderState { get; set; }
11 | [JsonProperty("request")]
12 | public Request Request { get; set; } = new Request();
13 | [JsonProperty("response")]
14 | public Response Response { get; set; } = new Response();
15 |
16 | public Response Match(Models.V3.Request actualRequest)
17 | {
18 | if (new V3.Request(Request).Match(actualRequest))
19 | {
20 | return Response;
21 | }
22 | return null;
23 | }
24 |
25 | internal void SetEmptyValuesToNull()
26 | {
27 | ProviderState = string.IsNullOrWhiteSpace(ProviderState) ? null : ProviderState;
28 | Request.SetEmptyValuesToNull();
29 | Response.SetEmptyValuesToNull();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ComPact/Models/V2/Request.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Converters;
3 | using System.Linq;
4 |
5 | namespace ComPact.Models.V2
6 | {
7 | public class Request
8 | {
9 | [JsonProperty("method")]
10 | [JsonConverter(typeof(StringEnumConverter))]
11 | public Method Method { get; set; }
12 | [JsonProperty("path")]
13 | public string Path { get; set; } = "/";
14 | [JsonProperty("headers")]
15 | public Headers Headers { get; set; } = new Headers();
16 | [JsonProperty("query")]
17 | public string Query { get; set; }
18 | [JsonProperty("body")]
19 | public dynamic Body { get; set; }
20 |
21 | public Request() { }
22 |
23 | internal void SetEmptyValuesToNull()
24 | {
25 | Headers = Headers.Any() ? Headers : null;
26 | Query = string.IsNullOrWhiteSpace(Query) ? null : Query;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/ComPact/Models/V2/Response.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ComPact.Models.V2
6 | {
7 | public class Response
8 | {
9 | [JsonProperty("status")]
10 | public int Status { get; set; } = 200;
11 | [JsonProperty("headers")]
12 | public Headers Headers { get; set; } = new Headers();
13 | [JsonProperty("body")]
14 | public dynamic Body { get; set; }
15 | [JsonProperty("matchingRules")]
16 | internal Dictionary MatchingRules { get; set; } = new Dictionary();
17 |
18 | internal Response()
19 | {
20 | }
21 |
22 | internal void SetEmptyValuesToNull()
23 | {
24 | Headers = Headers.Any() ? Headers : null;
25 | MatchingRules = MatchingRules.Any() ? MatchingRules : null;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ComPact/Models/V3/Contract.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ComPact.Models.V3
6 | {
7 | internal class Contract: IContract
8 | {
9 | [JsonProperty("consumer")]
10 | public Pacticipant Consumer { get; set; }
11 | [JsonProperty("provider")]
12 | public Pacticipant Provider { get; set; }
13 | [JsonProperty("interactions")]
14 | internal List Interactions { get; set; }
15 | [JsonProperty("messages")]
16 | internal List Messages { get; set; }
17 | [JsonProperty("metadata")]
18 | internal Metadata Metadata { get; set; } = new Metadata { PactSpecification = new PactSpecification { Version = "3.0.0" } };
19 |
20 | internal Contract() { }
21 | internal Contract(V2.Contract contract)
22 | {
23 | Consumer = contract?.Consumer ?? throw new System.ArgumentNullException(nameof(contract));
24 | Provider = contract.Provider;
25 | Interactions = contract.Interactions.Select(i => new Interaction(i)).ToList();
26 | }
27 |
28 | public void SetEmptyValuesToNull()
29 | {
30 | Interactions.ForEach(i => i.SetEmptyValuesToNull());
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ComPact/Models/V3/Interaction.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace ComPact.Models.V3
7 | {
8 | internal class Interaction
9 | {
10 | [JsonProperty("description")]
11 | public string Description { get; set; } = string.Empty;
12 | [JsonProperty("providerStates")]
13 | public List ProviderStates { get; set; }
14 | [JsonProperty("request")]
15 | public Request Request { get; set; } = new Request();
16 | [JsonProperty("response")]
17 | public Response Response { get; set; } = new Response();
18 |
19 | public Interaction() { }
20 | public Interaction(V2.Interaction interaction)
21 | {
22 | Description = interaction?.Description ?? throw new ArgumentNullException(nameof(interaction));
23 | ProviderStates = new List { new ProviderState { Name = interaction.ProviderState } };
24 | Request = new Request(interaction.Request);
25 | Response = new Response(interaction.Response);
26 | }
27 |
28 | public Response Match(Request actualRequest)
29 | {
30 | if (Request.Match(actualRequest))
31 | {
32 | return Response;
33 | }
34 | return null;
35 | }
36 |
37 | internal bool ProviderStatesAndRequestCanBeDistinguishedFrom(Interaction otherInteraction)
38 | {
39 | var requestsCanBeDistinguished = !(Request.Match(otherInteraction.Request) && otherInteraction.Request.Match(Request));
40 | var providerStatesCanBeDistinguished = JsonConvert.SerializeObject(ProviderStates) != JsonConvert.SerializeObject(otherInteraction.ProviderStates);
41 |
42 | return requestsCanBeDistinguished || providerStatesCanBeDistinguished;
43 | }
44 |
45 | internal void SetEmptyValuesToNull()
46 | {
47 | ProviderStates = ProviderStates?.FirstOrDefault() != null ? ProviderStates : null;
48 | Request.SetEmptyValuesToNull();
49 | Response.SetEmptyValuesToNull();
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ComPact/Models/V3/Message.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ComPact.Models.V3
6 | {
7 | internal class Message
8 | {
9 | [JsonProperty("providerStates")]
10 | internal List ProviderStates { get; set; }
11 | [JsonProperty("description")]
12 | internal string Description { get; set; } = string.Empty;
13 | [JsonProperty("contents")]
14 | internal object Contents { get; set; }
15 | [JsonProperty("matchingRules")]
16 | internal MatchingRuleCollection MatchingRules { get; set; }
17 | [JsonProperty("metaData")]
18 | internal object Metadata { get; set; } = new { ContentType = "application/json" };
19 |
20 | internal List Match(object actualMessage)
21 | {
22 | return BodyMatcher.Match(Contents, actualMessage, MatchingRules);
23 | }
24 |
25 | internal void SetEmptyValuesToNull()
26 | {
27 | ProviderStates = ProviderStates?.FirstOrDefault() != null ? ProviderStates : null;
28 | if (MatchingRules != null)
29 | {
30 | MatchingRules.SetEmptyValuesToNull();
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ComPact/Models/V3/MessageContract.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 |
4 | namespace ComPact.Models.V3
5 | {
6 | internal class MessageContract : IContract
7 | {
8 | [JsonProperty("consumer")]
9 | public Pacticipant Consumer { get; set; }
10 | [JsonProperty("provider")]
11 | public Pacticipant Provider { get; set; }
12 | [JsonProperty("messages")]
13 | internal List Messages { get; set; }
14 | [JsonProperty("metadata")]
15 | internal Metadata Metadata { get; set; } = new Metadata { PactSpecification = new PactSpecification { Version = "3.0.0" } };
16 | public void SetEmptyValuesToNull()
17 | {
18 | Messages.ForEach(i => i.SetEmptyValuesToNull());
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ComPact/Models/V3/Query.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ComPact.Models.V3
6 | {
7 | internal class Query : Dictionary>
8 | {
9 | internal Query() { }
10 |
11 | internal Query(string queryString)
12 | {
13 | if (!string.IsNullOrWhiteSpace(queryString))
14 | {
15 | var parameters = queryString.Split('&');
16 | foreach (var param in parameters)
17 | {
18 | var splitParam = param.Split('=');
19 | var valuesToAdd = splitParam[1].Split(',').ToList();
20 | if (TryGetValue(splitParam[0], out var existingValues))
21 | {
22 | Remove(splitParam[0]);
23 | valuesToAdd.AddRange(existingValues);
24 | }
25 | Add(splitParam[0], valuesToAdd);
26 | }
27 | }
28 | }
29 |
30 | internal Query(IQueryCollection queryCollection)
31 | {
32 | queryCollection.ToList().ForEach(q => Add(q.Key, q.Value.ToList()));
33 | }
34 |
35 | internal bool Match(Query actualQuery)
36 | {
37 | if (Count != actualQuery.Count)
38 | {
39 | return false;
40 | }
41 | foreach (var expectedParam in this)
42 | {
43 | var existsInActual = actualQuery.TryGetValue(expectedParam.Key, out var actualValues);
44 | if (!existsInActual)
45 | {
46 | return false;
47 | }
48 | if (expectedParam.Value.Count != actualValues.Count)
49 | {
50 | return false;
51 | }
52 | if (string.Join(",", expectedParam.Value) != string.Join(",", actualValues))
53 | {
54 | return false;
55 | }
56 | }
57 |
58 | return true;
59 | }
60 |
61 | internal string ToQueryString()
62 | {
63 | var queryString = string.Join("&", this.Select(q => q.Key + "=" + string.Join(",", q.Value)));
64 | return queryString;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ComPact/Models/V3/Response.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Exceptions;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Net.Http;
7 |
8 | namespace ComPact.Models.V3
9 | {
10 | internal class Response
11 | {
12 | [JsonProperty("status")]
13 | public int Status { get; set; } = 200;
14 | [JsonProperty("headers")]
15 | public Headers Headers { get; set; } = new Headers();
16 | [JsonProperty("body")]
17 | public dynamic Body { get; set; }
18 | [JsonProperty("matchingRules")]
19 | internal MatchingRuleCollection MatchingRules { get; set; }
20 |
21 | internal Response() { }
22 |
23 | internal Response(V2.Response responseV2)
24 | {
25 | if (responseV2 == null)
26 | {
27 | throw new System.ArgumentNullException(nameof(responseV2));
28 | }
29 |
30 | Status = responseV2.Status;
31 | Headers = responseV2.Headers;
32 | Body = responseV2.Body;
33 | if (responseV2.MatchingRules != null)
34 | {
35 | MatchingRules = new MatchingRuleCollection(responseV2.MatchingRules);
36 | }
37 | }
38 |
39 | internal Response(HttpResponseMessage response)
40 | {
41 | if (response == null)
42 | {
43 | throw new ArgumentNullException(nameof(response));
44 | }
45 |
46 | Status = (int)response?.StatusCode;
47 | Headers = new Headers(response);
48 | try
49 | {
50 | Body = JsonConvert.DeserializeObject(response.Content?.ReadAsStringAsync().Result ?? string.Empty);
51 | }
52 | catch (JsonReaderException)
53 | {
54 | throw new PactException($"Response body could not be deserialized from JSON. Content-Type was {response.Content.Headers.ContentType}");
55 | }
56 | }
57 |
58 | internal List Match(Response actualResponse)
59 | {
60 | var differences = new List();
61 |
62 | if (Status != actualResponse.Status)
63 | {
64 | differences.Add($"Expected status {Status}, but was {actualResponse.Status}");
65 | }
66 |
67 | differences.AddRange(Headers.Match(actualResponse.Headers, MatchingRules));
68 |
69 | differences.AddRange(Models.BodyMatcher.Match(Body, actualResponse.Body, MatchingRules));
70 |
71 | return differences;
72 | }
73 |
74 | internal void SetEmptyValuesToNull()
75 | {
76 | Headers = Headers.Any() ? Headers : null;
77 | if (MatchingRules != null)
78 | {
79 | MatchingRules.SetEmptyValuesToNull();
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/ComPact/Verifier/PactVerifierConfig.cs:
--------------------------------------------------------------------------------
1 | using ComPact.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Net.Http;
5 |
6 | namespace ComPact.Verifier
7 | {
8 | public class PactVerifierConfig
9 | {
10 | ///
11 | /// The base url where to call your actual provider service. To set up provider states, {base-url}/provider-states will be called.
12 | ///
13 | public string ProviderBaseUrl { get; set; }
14 |
15 | ///
16 | /// Optional HttpClient for the provider in case you want to supply your own preconfigured instance.
17 | ///
18 | public HttpClient ProviderHttpClient { get; set; }
19 |
20 | ///
21 | /// An action that will be invoked for every provider state in a (message) interaction. Use this to set up any necessary test data.
22 | /// If you cannot handle a specific provider state, throw a PactVerificationException to make the issue show up in the test results
23 | /// that are published to the Pact Broker.
24 | ///
25 | public Action ProviderStateHandler { get; set; }
26 | ///
27 | /// For message interactions, this function will be called with the description defined in the contract as a parameter.
28 | /// It should return the message that your code produces, so the mock consumer can verify it.
29 | /// If a string is returned, the mock consumer will assume that it is a serialized json string and try to deserialize it.
30 | /// If you cannot handle a specific description, throw a PactVerificationException to make the issue show up in the test results
31 | /// that are published to the Pact Broker.
32 | ///
33 | public Func MessageProducer { get; set; }
34 | ///
35 | /// Client that can be used to connect to your Pact Broker to retrieve pacts and (optionally) publish verification results.
36 | /// Should be set up with the correct base URL and if needed any necessary headers.
37 | ///
38 | public HttpClient PactBrokerClient { get; set; }
39 | ///
40 | /// Whether to actually publish the verification results.
41 | ///
42 | public bool PublishVerificationResults { get; set; }
43 | ///
44 | /// The provider version to use when publishing verification results.
45 | ///
46 | public string ProviderVersion { get; set; }
47 | ///
48 | /// Tags to add to this version of the provider.
49 | ///
50 | public IEnumerable ProviderTags { get; set; }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ComPact/Verifier/VerificationResults.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace ComPact.Verifier
8 | {
9 | public class VerificationResults
10 | {
11 | [JsonProperty("providerName")]
12 | public string ProviderName { get; set; }
13 | [JsonProperty("providerApplicationVersion")]
14 | public string ProviderApplicationVersion { get; set; }
15 | [JsonProperty("success")]
16 | public bool Success { get; set; }
17 | [JsonProperty("verificationDate")]
18 | public string VerificationDate { get; set; }
19 | [JsonProperty("testResults")]
20 | public TestResults TestResults { get; set; }
21 | }
22 |
23 | public class TestResults
24 | {
25 | [JsonProperty("summary")]
26 | public Summary Summary { get; set; }
27 | [JsonProperty("tests")]
28 | public List Tests { get; set; }
29 | }
30 |
31 | public class Summary
32 | {
33 | [JsonProperty("testCount")]
34 | public int TestCount { get; set; }
35 | [JsonProperty("failureCount")]
36 | public int FailureCount { get; set; }
37 | }
38 |
39 | public class Test
40 | {
41 | [JsonProperty("testDescription")]
42 | public string Description { get; set; }
43 | [JsonProperty("success")]
44 | public string Status => Issues.Any() ? "failed" : "passed";
45 | [JsonProperty("issues")]
46 | public List Issues { get; set; } = new List();
47 |
48 | public string ToTestMessageString()
49 | {
50 | var stringBuilder = new StringBuilder(Description);
51 | stringBuilder.Append($" ({Status})");
52 | if (Status == "failed")
53 | {
54 | stringBuilder.Append(":");
55 | foreach (var issues in Issues)
56 | {
57 | stringBuilder.Append(Environment.NewLine);
58 | stringBuilder.Append("- ");
59 | stringBuilder.Append(issues);
60 | }
61 | }
62 | return stringBuilder.ToString();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Bart Schotten
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------