├── .nuget ├── NuGet.exe ├── NuGet.Config └── packages.config ├── PactNet ├── Mappers │ ├── IMapper.cs │ └── IHttpMethodMapper.cs ├── Comparers │ ├── IComparer.cs │ ├── ComparisonFailure.cs │ ├── ErrorMessageComparisonFailure.cs │ ├── DiffComparisonFailure.cs │ ├── UnexpectedRequestComparisonFailure.cs │ ├── MissingInteractionComparisonFailure.cs │ └── ComparisonResult.cs ├── Mocks │ ├── MockHttpService │ │ ├── IHttpHost.cs │ │ ├── Nancy │ │ │ ├── IMockProviderRequestHandler.cs │ │ │ ├── IMockProviderAdminRequestHandler.cs │ │ │ ├── IMockProviderNancyRequestHandler.cs │ │ │ ├── MockProviderNancyBootstrapper.cs │ │ │ ├── MockProviderRequestHandler.cs │ │ │ ├── MockProviderNancyRequestDispatcher.cs │ │ │ └── NancyHttpHost.cs │ │ ├── Comparers │ │ │ ├── IHttpPathComparer.cs │ │ │ ├── IHttpStatusCodeComparer.cs │ │ │ ├── IHttpQueryStringComparer.cs │ │ │ ├── IHttpMethodComparer.cs │ │ │ ├── IHttpHeaderComparer.cs │ │ │ ├── IProviderServiceResponseComparer.cs │ │ │ ├── IProviderServiceRequestComparer.cs │ │ │ ├── IHttpBodyComparer.cs │ │ │ ├── HttpStatusCodeComparer.cs │ │ │ ├── HttpMethodComparer.cs │ │ │ ├── HttpPathComparer.cs │ │ │ ├── ProviderServiceResponseComparer.cs │ │ │ ├── HttpBodyComparer.cs │ │ │ ├── HttpQueryStringComparer.cs │ │ │ ├── ProviderServiceRequestComparer.cs │ │ │ └── HttpHeaderComparer.cs │ │ ├── Mappers │ │ │ ├── IHttpVerbMapper.cs │ │ │ ├── IHttpMethodMapper.cs │ │ │ ├── IHttpContentMapper.cs │ │ │ ├── INancyResponseMapper.cs │ │ │ ├── IProviderServiceRequestMapper.cs │ │ │ ├── IHttpRequestMessageMapper.cs │ │ │ ├── IProviderServiceResponseMapper.cs │ │ │ ├── IHttpBodyContentMapper.cs │ │ │ ├── HttpContentMapper.cs │ │ │ ├── HttpVerbMapper.cs │ │ │ ├── HttpMethodMapper.cs │ │ │ ├── NancyResponseMapper.cs │ │ │ ├── HttpBodyContentMapper.cs │ │ │ ├── ProviderServiceRequestMapper.cs │ │ │ ├── ProviderServiceResponseMapper.cs │ │ │ └── HttpRequestMessageMapper.cs │ │ ├── IHttpRequestSender.cs │ │ ├── Models │ │ │ ├── HttpVerb.cs │ │ │ ├── IHttpMessage.cs │ │ │ ├── ProviderServicePactFile.cs │ │ │ ├── ProviderServiceInteraction.cs │ │ │ ├── HandledRequest.cs │ │ │ ├── ProviderServiceResponse.cs │ │ │ └── ProviderServiceRequest.cs │ │ ├── Validators │ │ │ └── IProviderServiceValidator.cs │ │ ├── IMockProviderService.cs │ │ ├── CustomRequestSender.cs │ │ ├── IMockProviderRepository.cs │ │ ├── HttpClientRequestSender.cs │ │ └── Matchers │ │ │ └── DefaultHttpBodyMatcher.cs │ └── IMockProvider.cs ├── Reporters │ ├── Outputters │ │ ├── IReportOutputter.cs │ │ ├── ConsoleReportOutputter.cs │ │ └── FileReportOutputter.cs │ └── IReporter.cs ├── Matchers │ ├── IMatcher.cs │ ├── MatcherCheckFailureType.cs │ ├── SuccessfulMatcherCheck.cs │ ├── FailedMatcherCheck.cs │ ├── MatcherCheck.cs │ └── MatcherResult.cs ├── readme.txt ├── Models │ ├── Pacticipant.cs │ ├── PactFile.cs │ ├── ProviderState.cs │ ├── PactDetails.cs │ ├── Interaction.cs │ └── ProviderStates.cs ├── PactFailureException.cs ├── Validators │ └── IPactValidator.cs ├── Infrastructure │ └── Logging │ │ ├── ILocalLogMessageHandler.cs │ │ ├── LocalLogMessage.cs │ │ ├── LocalLogger.cs │ │ ├── LocalRollingLogFileMessageHandler.cs │ │ └── LocalLogProvider.cs ├── Extensions │ ├── TaskExtensions.cs │ └── StringExtensions.cs ├── Configuration │ └── Json │ │ ├── Converters │ │ ├── CamelCaseStringEnumConverter.cs │ │ └── PreserveCasingDictionaryConverter.cs │ │ └── JsonConfig.cs ├── PactConfig.cs ├── IPactBuilder.cs ├── Constants.cs ├── PactVerifierConfig.cs ├── packages.config ├── app.config ├── PactUriOptions.cs ├── IPactVerifier.cs ├── PactNet.nuspec ├── Properties │ └── AssemblyInfo.cs └── PactBuilder.cs ├── Samples └── EventApi │ ├── Consumer │ ├── Models │ │ ├── StatusResponseBody.cs │ │ ├── UptimeResponseBody.cs │ │ ├── HypermediaLink.cs │ │ ├── Event.cs │ │ └── RestResponseBody.cs │ ├── DateTimeFactory.cs │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ └── Consumer.csproj │ ├── Consumer.Tests │ ├── packages.config │ ├── ConsumerEventApiPact.cs │ └── Properties │ │ └── AssemblyInfo.cs │ ├── Provider.Api.Web │ ├── Models │ │ ├── HypermediaLink.cs │ │ └── Event.cs │ ├── Controllers │ │ ├── StatsController.cs │ │ ├── BlobsController.cs │ │ └── EventsController.cs │ ├── packages.config │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Startup.cs │ └── Web.config │ └── Provider.Api.Web.Tests │ ├── packages.config │ ├── AuthorizationTokenReplacementMiddleware.cs │ ├── TokenGenerator.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── app.config │ └── EventAPITests.cs ├── .gitmodules ├── PactNet.Tests ├── IntegrationTests │ ├── Specification │ │ ├── Models │ │ │ ├── IVerifiable.cs │ │ │ ├── RequestTestCase.cs │ │ │ └── ResponseTestCase.cs │ │ └── MockHttpServiceSpecificationTests.cs │ ├── IntegrationTestingMockProviderNancyBootstrapper.cs │ ├── PactBuilderIntegrationTests.cs │ ├── IntegrationTestsMyApiPact.cs │ └── FailureIntegrationTestsMyApiPact.cs ├── Models │ ├── PactFileTests.cs │ ├── InteractionTests.cs │ ├── ProviderStateTests.cs │ ├── PactDetailsTests.cs │ └── ProviderStatesTests.cs ├── ProviderServicePactFileTests.cs ├── PactConfigTests.cs ├── Reporters │ └── Outputters │ │ └── FileReportOutputterTests.cs ├── app.config ├── packages.config ├── Mocks │ └── MockHttpService │ │ ├── CustomRequestSenderTests.cs │ │ ├── Models │ │ ├── ProviderServiceInteractionTests.cs │ │ ├── ProviderServiceResponseTests.cs │ │ └── ProviderServiceRequestTests.cs │ │ ├── Mappers │ │ ├── HttpContentMapperTests.cs │ │ ├── HttpVerbMapperTests.cs │ │ └── HttpMethodMapperTests.cs │ │ └── Comparers │ │ └── HttpQueryStringComparerTests.cs ├── Properties │ └── AssemblyInfo.cs ├── Fakes │ └── FakeHttpMessageHandler.cs ├── PactUriOptionsTests.cs └── Comparers │ └── ComparisonResultTests.cs ├── packages └── repositories.config ├── Build ├── Setup-Build-Environment.ps1 ├── Publish-Release.ps1 └── Tests-Coverage.ps1 ├── .gitignore ├── appveyor.yml └── LICENSE.txt /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dibble-james/pact-net/master/.nuget/NuGet.exe -------------------------------------------------------------------------------- /PactNet/Mappers/IMapper.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Mappers 2 | { 3 | internal interface IMapper 4 | { 5 | TTo Convert(TFrom from); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PactNet/Comparers/IComparer.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Comparers 2 | { 3 | internal interface IComparer 4 | { 5 | ComparisonResult Compare(T expected, T actual); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/IHttpHost.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Mocks.MockHttpService 2 | { 3 | internal interface IHttpHost 4 | { 5 | void Start(); 6 | void Stop(); 7 | } 8 | } -------------------------------------------------------------------------------- /PactNet/Reporters/Outputters/IReportOutputter.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Reporters.Outputters 2 | { 3 | public interface IReportOutputter 4 | { 5 | void Write(string report); 6 | } 7 | } -------------------------------------------------------------------------------- /PactNet/Comparers/ComparisonFailure.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Comparers 2 | { 3 | internal abstract class ComparisonFailure 4 | { 5 | public string Result { get; protected set; } 6 | } 7 | } -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/Models/StatusResponseBody.cs: -------------------------------------------------------------------------------- 1 | namespace Consumer.Models 2 | { 3 | public class StatusResponseBody : RestResponseBody 4 | { 5 | public bool Alive { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "PactNet.Tests/IntegrationTests/Specification/pact-specification"] 2 | path = PactNet.Tests/IntegrationTests/Specification/pact-specification 3 | url = https://github.com/bethesque/pact-specification.git 4 | -------------------------------------------------------------------------------- /PactNet.Tests/IntegrationTests/Specification/Models/IVerifiable.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Tests.IntegrationTests.Specification.Models 2 | { 3 | public interface IVerifiable 4 | { 5 | void Verify(); 6 | } 7 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Nancy/IMockProviderRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Mocks.MockHttpService.Nancy 2 | { 3 | internal interface IMockProviderRequestHandler : IMockProviderNancyRequestHandler 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /PactNet/Matchers/IMatcher.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace PactNet.Matchers 4 | { 5 | internal interface IMatcher 6 | { 7 | MatcherResult Match(string path, JToken expected, JToken actual); 8 | } 9 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/IHttpPathComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | 3 | namespace PactNet.Mocks.MockHttpService.Comparers 4 | { 5 | internal interface IHttpPathComparer : IComparer 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Nancy/IMockProviderAdminRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Mocks.MockHttpService.Nancy 2 | { 3 | internal interface IMockProviderAdminRequestHandler : IMockProviderNancyRequestHandler 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/DateTimeFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Consumer 4 | { 5 | public class DateTimeFactory 6 | { 7 | public static Func Now = () => DateTime.UtcNow; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/Models/UptimeResponseBody.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Consumer.Models 4 | { 5 | public class UptimeResponseBody : RestResponseBody 6 | { 7 | public DateTime UpSince { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/IHttpStatusCodeComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | 3 | namespace PactNet.Mocks.MockHttpService.Comparers 4 | { 5 | internal interface IHttpStatusCodeComparer : IComparer 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /PactNet/readme.txt: -------------------------------------------------------------------------------- 1 | A .NET implementation of the Ruby consumer driven contract library, Pact (https://github.com/realestate-com-au/pact). 2 | 3 | Everything you need to get started is on github (https://github.com/SEEK-Jobs/pact-net/blob/master/README.md) -------------------------------------------------------------------------------- /PactNet/Mappers/IHttpMethodMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using PactNet.Models; 3 | 4 | namespace PactNet.Mappers 5 | { 6 | public interface IHttpMethodMapper 7 | { 8 | HttpMethod Convert(HttpVerb from); 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Matchers/MatcherCheckFailureType.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Matchers 2 | { 3 | internal enum MatcherCheckFailureType 4 | { 5 | AdditionalItemInArray, 6 | AdditionalPropertyInObject, 7 | ValueDoesNotMatch 8 | } 9 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/IHttpQueryStringComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | 3 | namespace PactNet.Mocks.MockHttpService.Comparers 4 | { 5 | internal interface IHttpQueryStringComparer : IComparer 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /PactNet/Models/Pacticipant.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PactNet.Models 4 | { 5 | public class Pacticipant 6 | { 7 | [JsonProperty(PropertyName = "name")] 8 | public string Name { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /Samples/EventApi/Consumer.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /PactNet/Matchers/SuccessfulMatcherCheck.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Matchers 2 | { 3 | internal class SuccessfulMatcherCheck : MatcherCheck 4 | { 5 | public SuccessfulMatcherCheck(string path) 6 | { 7 | Path = path; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Nancy/IMockProviderNancyRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using Nancy; 2 | 3 | namespace PactNet.Mocks.MockHttpService.Nancy 4 | { 5 | internal interface IMockProviderNancyRequestHandler 6 | { 7 | Response Handle(NancyContext context); 8 | } 9 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/IHttpVerbMapper.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Mappers; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Mappers 5 | { 6 | internal interface IHttpVerbMapper : IMapper 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /PactNet/PactFailureException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PactNet 4 | { 5 | public class PactFailureException : Exception 6 | { 7 | public PactFailureException(string message) 8 | : base(message) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/IHttpMethodComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Comparers 5 | { 6 | internal interface IHttpMethodComparer : IComparer 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/IHttpRequestSender.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Mocks.MockHttpService.Models; 2 | 3 | namespace PactNet.Mocks.MockHttpService 4 | { 5 | internal interface IHttpRequestSender 6 | { 7 | ProviderServiceResponse Send(ProviderServiceRequest request); 8 | } 9 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Models/HttpVerb.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Mocks.MockHttpService.Models 2 | { 3 | public enum HttpVerb 4 | { 5 | NotSet = 0, 6 | Get, 7 | Post, 8 | Put, 9 | Delete, 10 | Head, 11 | Patch 12 | } 13 | } -------------------------------------------------------------------------------- /PactNet/Validators/IPactValidator.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Models; 2 | 3 | namespace PactNet.Validators 4 | { 5 | internal interface IPactValidator where TPactFile : PactFile 6 | { 7 | void Validate(TPactFile pactFile, ProviderStates providerStates); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/Models/HypermediaLink.cs: -------------------------------------------------------------------------------- 1 | namespace Consumer.Models 2 | { 3 | public class HypermediaLink 4 | { 5 | public string Href { get; set; } 6 | 7 | public HypermediaLink(string href) 8 | { 9 | Href = href; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /PactNet/Infrastructure/Logging/ILocalLogMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PactNet.Infrastructure.Logging 4 | { 5 | internal interface ILocalLogMessageHandler : IDisposable 6 | { 7 | void Handle(LocalLogMessage logMessage); 8 | string LogPath { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/Models/Event.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Consumer.Models 4 | { 5 | public class Event 6 | { 7 | public Guid EventId { get; set; } 8 | public DateTime Timestamp { get; set; } 9 | public string EventType { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/IHttpHeaderComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PactNet.Mocks.MockHttpService.Comparers 4 | { 5 | using PactNet.Comparers; 6 | 7 | internal interface IHttpHeaderComparer : IComparer> 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Models/IHttpMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PactNet.Mocks.MockHttpService.Models 4 | { 5 | internal interface IHttpMessage 6 | { 7 | IDictionary Headers { get; set; } 8 | dynamic Body { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Comparers/ErrorMessageComparisonFailure.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Comparers 2 | { 3 | internal class ErrorMessageComparisonFailure : ComparisonFailure 4 | { 5 | public ErrorMessageComparisonFailure(string errorMessage) 6 | { 7 | Result = errorMessage; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Validators/IProviderServiceValidator.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Mocks.MockHttpService.Models; 2 | using PactNet.Validators; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Validators 5 | { 6 | internal interface IProviderServiceValidator : IPactValidator 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Models/HypermediaLink.cs: -------------------------------------------------------------------------------- 1 | namespace Provider.Api.Web.Models 2 | { 3 | public class HypermediaLink 4 | { 5 | public string Href { get; set; } 6 | 7 | public HypermediaLink(string href) 8 | { 9 | Href = href; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/IProviderServiceResponseComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Comparers 5 | { 6 | internal interface IProviderServiceResponseComparer : IComparer 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/IHttpMethodMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using PactNet.Mappers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Mappers 6 | { 7 | internal interface IHttpMethodMapper : IMapper 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Models/Event.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Provider.Api.Web.Models 4 | { 5 | public class Event 6 | { 7 | public Guid EventId { get; set; } 8 | public DateTime Timestamp { get; set; } 9 | public string EventType { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/IHttpContentMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using PactNet.Mappers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Mappers 6 | { 7 | internal interface IHttpContentMapper : IMapper 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/INancyResponseMapper.cs: -------------------------------------------------------------------------------- 1 | using Nancy; 2 | using PactNet.Mappers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Mappers 6 | { 7 | internal interface INancyResponseMapper : IMapper 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Reporters/Outputters/ConsoleReportOutputter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PactNet.Reporters.Outputters 4 | { 5 | internal class ConsoleReportOutputter : IReportOutputter 6 | { 7 | public void Write(string report) 8 | { 9 | Console.WriteLine(report); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/IProviderServiceRequestComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Comparers 5 | { 6 | internal interface IProviderServiceRequestComparer : IComparer 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /PactNet/Mocks/IMockProvider.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Mocks 2 | { 3 | public interface IMockProvider where TMockProviderInterface : IMockProvider 4 | { 5 | TMockProviderInterface Given(string providerState); 6 | TMockProviderInterface UponReceiving(string description); 7 | } 8 | } -------------------------------------------------------------------------------- /PactNet/Extensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace PactNet.Extensions 4 | { 5 | public static class TaskExtensions 6 | { 7 | public static T RunSync(this Task task) 8 | { 9 | return Task.Factory.StartNew(() => task.Result).Result; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/IProviderServiceRequestMapper.cs: -------------------------------------------------------------------------------- 1 | using Nancy; 2 | using PactNet.Mappers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Mappers 6 | { 7 | internal interface IProviderServiceRequestMapper : IMapper 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/IHttpRequestMessageMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using PactNet.Mappers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Mappers 6 | { 7 | internal interface IHttpRequestMessageMapper : IMapper 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/IProviderServiceResponseMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using PactNet.Mappers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Mappers 6 | { 7 | internal interface IProviderServiceResponseMapper : IMapper 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /PactNet/Configuration/Json/Converters/CamelCaseStringEnumConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Converters; 2 | 3 | namespace PactNet.Configuration.Json.Converters 4 | { 5 | public class CamelCaseStringEnumConverter : StringEnumConverter 6 | { 7 | public CamelCaseStringEnumConverter() 8 | { 9 | CamelCaseText = true; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PactNet/Comparers/DiffComparisonFailure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PactNet.Comparers 4 | { 5 | internal class DiffComparisonFailure : ComparisonFailure 6 | { 7 | public DiffComparisonFailure(object expected, object actual) 8 | { 9 | Result = String.Format("Expected: {0}, Actual: {1}", expected ?? "null", actual ?? "null"); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/IHttpBodyComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PactNet.Comparers; 3 | using PactNet.Matchers; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Comparers 6 | { 7 | internal interface IHttpBodyComparer 8 | { 9 | ComparisonResult Compare(dynamic expected, dynamic actual, IDictionary matchingRules); 10 | } 11 | } -------------------------------------------------------------------------------- /PactNet/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PactNet.Extensions 4 | { 5 | public static class StringExtensions 6 | { 7 | public static string ToLowerSnakeCase(this string input) 8 | { 9 | return !String.IsNullOrEmpty(input) ? 10 | input.Replace(' ', '_').ToLower() : 11 | String.Empty; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /PactNet/PactConfig.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet 2 | { 3 | public class PactConfig 4 | { 5 | public string PactDir { get; set; } 6 | public string LogDir { get; set; } 7 | 8 | internal string LoggerName; 9 | 10 | public PactConfig() 11 | { 12 | PactDir = Constants.DefaultPactDir; 13 | LogDir = Constants.DefaultLogDir; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /PactNet/Matchers/FailedMatcherCheck.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Matchers 2 | { 3 | internal class FailedMatcherCheck : MatcherCheck 4 | { 5 | public MatcherCheckFailureType FailureType { get; private set; } 6 | 7 | public FailedMatcherCheck(string path, MatcherCheckFailureType failureType) 8 | { 9 | Path = path; 10 | FailureType = failureType; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /PactNet/Matchers/MatcherCheck.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Matchers 2 | { 3 | internal abstract class MatcherCheck 4 | { 5 | private const string PathPrefix = "$."; 6 | private string _path; 7 | 8 | public string Path 9 | { 10 | get { return _path; } 11 | protected set { _path = value.StartsWith(PathPrefix) ? value : PathPrefix + value; } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Models/ProviderServicePactFile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | using PactNet.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Models 6 | { 7 | public class ProviderServicePactFile : PactFile 8 | { 9 | [JsonProperty(PropertyName = "interactions")] 10 | public IEnumerable Interactions { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/IHttpBodyContentMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Mappers 5 | { 6 | internal interface IHttpBodyContentMapper 7 | { 8 | HttpBodyContent Convert(dynamic body, IDictionary headers); 9 | HttpBodyContent Convert(byte[] content, IDictionary headers); 10 | } 11 | } -------------------------------------------------------------------------------- /PactNet/Reporters/IReporter.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | 3 | namespace PactNet.Reporters 4 | { 5 | internal interface IReporter 6 | { 7 | void ReportInfo(string infoMessage); 8 | void ReportSummary(ComparisonResult comparisonResult); 9 | void ReportFailureReasons(ComparisonResult comparisonResult); 10 | 11 | void Indent(); 12 | void ResetIndentation(); 13 | 14 | void Flush(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PactNet.Tests/Models/PactFileTests.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Models; 2 | using Xunit; 3 | 4 | namespace PactNet.Tests.Models 5 | { 6 | public class PactFileTests 7 | { 8 | [Fact] 9 | public void Ctor_WhenInstantiated_SetsPactSpecificationVersionMetaDataTo1() 10 | { 11 | var pactFile = new PactFile(); 12 | 13 | Assert.Equal("1.1.0", pactFile.Metadata.pactSpecificationVersion); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PactNet/Models/PactFile.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PactNet.Models 4 | { 5 | public class PactFile : PactDetails 6 | { 7 | [JsonProperty(PropertyName = "metadata")] 8 | public dynamic Metadata { get; private set; } 9 | 10 | public PactFile() 11 | { 12 | Metadata = new 13 | { 14 | pactSpecificationVersion = "1.1.0" 15 | }; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /PactNet.Tests/ProviderServicePactFileTests.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Mocks.MockHttpService.Models; 2 | using Xunit; 3 | 4 | namespace PactNet.Tests 5 | { 6 | public class ProviderServicePactFileTests 7 | { 8 | [Fact] 9 | public void Interactions_WithNoInteractions_ReturnsNull() 10 | { 11 | var pactFile = new ProviderServicePactFile(); 12 | 13 | Assert.Null(pactFile.Interactions); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/Models/RestResponseBody.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Consumer.Models 5 | { 6 | public class RestResponseBody 7 | { 8 | [JsonProperty(PropertyName = "_links")] 9 | public Dictionary Links { get; set; } 10 | 11 | public RestResponseBody() 12 | { 13 | Links = new Dictionary(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Models/ProviderServiceInteraction.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using PactNet.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Models 5 | { 6 | public class ProviderServiceInteraction : Interaction 7 | { 8 | [JsonProperty(PropertyName = "request")] 9 | public ProviderServiceRequest Request { get; set; } 10 | 11 | [JsonProperty(PropertyName = "response")] 12 | public ProviderServiceResponse Response { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PactNet/Matchers/MatcherResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PactNet.Matchers 4 | { 5 | internal class MatcherResult 6 | { 7 | public IEnumerable MatcherChecks { get; private set; } 8 | 9 | public MatcherResult(MatcherCheck matcherCheck) 10 | { 11 | MatcherChecks = new List { matcherCheck }; 12 | } 13 | 14 | public MatcherResult(IEnumerable matcherChecks) 15 | { 16 | MatcherChecks = matcherChecks; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Models/HandledRequest.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet.Mocks.MockHttpService.Models 2 | { 3 | internal class HandledRequest 4 | { 5 | public ProviderServiceRequest ActualRequest { get; private set; } 6 | public ProviderServiceInteraction MatchedInteraction { get; private set; } 7 | 8 | public HandledRequest(ProviderServiceRequest actualRequest, ProviderServiceInteraction matchedInteraction) 9 | { 10 | ActualRequest = actualRequest; 11 | MatchedInteraction = matchedInteraction; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /PactNet/Models/ProviderState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PactNet.Models 4 | { 5 | public class ProviderState 6 | { 7 | public Action SetUp { get; private set; } 8 | public Action TearDown { get; private set; } 9 | 10 | public string ProviderStateDescription { get; private set; } 11 | 12 | public ProviderState(string providerState, Action setUp = null, Action tearDown = null) 13 | { 14 | ProviderStateDescription = providerState; 15 | SetUp = setUp; 16 | TearDown = tearDown; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /PactNet/IPactBuilder.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using PactNet.Mocks.MockHttpService; 3 | 4 | namespace PactNet 5 | { 6 | public interface IPactBuilder 7 | { 8 | IPactBuilder ServiceConsumer(string consumerName); 9 | IPactBuilder HasPactWith(string providerName); 10 | IMockProviderService MockService(int port, bool enableSsl = false, bool bindOnAllAdapters = false); 11 | IMockProviderService MockService(int port, JsonSerializerSettings jsonSerializerSettings, bool enableSsl = false, bool bindOnAllAdapters = false); 12 | void Build(); 13 | } 14 | } -------------------------------------------------------------------------------- /PactNet/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace PactNet 2 | { 3 | internal static class Constants 4 | { 5 | public const string AdministrativeRequestHeaderKey = "X-Pact-Mock-Service"; 6 | public const string AdministrativeRequestTestContextHeaderKey = "X-Test-Context"; 7 | public const string InteractionsPath = "/interactions"; 8 | public const string InteractionsVerificationPath = "/interactions/verification"; 9 | public const string PactPath = "/pact"; 10 | public const string DefaultPactDir = @"..\..\pacts\"; 11 | public const string DefaultLogDir = @"..\..\logs\"; 12 | } 13 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/HttpStatusCodeComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | 3 | namespace PactNet.Mocks.MockHttpService.Comparers 4 | { 5 | internal class HttpStatusCodeComparer : IHttpStatusCodeComparer 6 | { 7 | public ComparisonResult Compare(int expected, int actual) 8 | { 9 | var result = new ComparisonResult("has status code {0}", expected); 10 | if (!expected.Equals(actual)) 11 | { 12 | result.RecordFailure(new DiffComparisonFailure(expected, actual)); 13 | } 14 | 15 | return result; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /PactNet.Tests/PactConfigTests.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Mocks.MockHttpService; 2 | using Xunit; 3 | 4 | namespace PactNet.Tests 5 | { 6 | public class PactConfigTests 7 | { 8 | [Fact] 9 | public void Ctor_WithDefaults_UsesDefaultPactDir() 10 | { 11 | var options = new PactConfig(); 12 | 13 | Assert.Equal(Constants.DefaultPactDir, options.PactDir); 14 | } 15 | 16 | [Fact] 17 | public void Ctor_WithDefaults_UsesDefaultLogDir() 18 | { 19 | var options = new PactConfig(); 20 | 21 | Assert.Equal(Constants.DefaultLogDir, options.LogDir); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/IMockProviderService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService 5 | { 6 | public interface IMockProviderService : IMockProvider 7 | { 8 | IMockProviderService With(ProviderServiceRequest request); 9 | void WillRespondWith(ProviderServiceResponse response); 10 | void Start(); 11 | void Stop(); 12 | void ClearInteractions(); 13 | void VerifyInteractions(); 14 | void SendAdminHttpRequest(HttpVerb method, string path, T requestContent, IDictionary headers = null) where T : class; 15 | } 16 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/HttpMethodComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Comparers 5 | { 6 | internal class HttpMethodComparer : IHttpMethodComparer 7 | { 8 | public ComparisonResult Compare(HttpVerb expected, HttpVerb actual) 9 | { 10 | var result = new ComparisonResult("has method {0}", expected); 11 | 12 | if (!expected.Equals(actual)) 13 | { 14 | result.RecordFailure(new DiffComparisonFailure(expected, actual)); 15 | return result; 16 | } 17 | 18 | return result; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /PactNet/PactVerifierConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PactNet.Reporters.Outputters; 3 | 4 | namespace PactNet 5 | { 6 | public class PactVerifierConfig 7 | { 8 | public string LogDir { get; set; } 9 | 10 | public IList ReportOutputters { get; private set; } 11 | 12 | internal string LoggerName; 13 | 14 | public PactVerifierConfig() 15 | { 16 | LogDir = Constants.DefaultLogDir; 17 | ReportOutputters = new List 18 | { 19 | new ConsoleReportOutputter(), 20 | new FileReportOutputter(() => LoggerName) 21 | }; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /PactNet/Reporters/Outputters/FileReportOutputter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Logging; 3 | 4 | namespace PactNet.Reporters.Outputters 5 | { 6 | internal class FileReportOutputter : IReportOutputter 7 | { 8 | private readonly Func _logFactory; 9 | 10 | internal FileReportOutputter(Func logFactory) 11 | { 12 | _logFactory = logFactory; 13 | } 14 | 15 | public FileReportOutputter(Func loggerNameGenerator) 16 | : this(() => LogProvider.GetLogger(loggerNameGenerator())) 17 | { 18 | } 19 | 20 | public void Write(string report) 21 | { 22 | _logFactory().Debug(report); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/CustomRequestSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService 5 | { 6 | internal class CustomRequestSender : IHttpRequestSender 7 | { 8 | private readonly Func _httpRequestSenderFunc; 9 | 10 | public CustomRequestSender(Func httpRequestSenderFunc) 11 | { 12 | _httpRequestSenderFunc = httpRequestSenderFunc; 13 | } 14 | 15 | public ProviderServiceResponse Send(ProviderServiceRequest request) 16 | { 17 | return _httpRequestSenderFunc(request); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PactNet/Models/PactDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PactNet.Extensions; 4 | 5 | namespace PactNet.Models 6 | { 7 | public class PactDetails 8 | { 9 | [JsonProperty(Order = -3, PropertyName = "provider")] 10 | public Pacticipant Provider { get; set; } 11 | 12 | [JsonProperty(Order = -2, PropertyName = "consumer")] 13 | public Pacticipant Consumer { get; set; } 14 | 15 | public string GeneratePactFileName() 16 | { 17 | return String.Format("{0}-{1}.json", 18 | Consumer != null ? Consumer.Name : String.Empty, 19 | Provider != null ? Provider.Name : String.Empty) 20 | .ToLowerSnakeCase(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/HttpContentMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService.Mappers 5 | { 6 | internal class HttpContentMapper : IHttpContentMapper 7 | { 8 | public HttpContent Convert(HttpBodyContent from) 9 | { 10 | if (from == null) 11 | { 12 | return null; 13 | } 14 | 15 | var stringContent = new StringContent(from.Content, from.Encoding); 16 | 17 | stringContent.Headers.ContentType = from.ContentType; 18 | stringContent.Headers.ContentType.CharSet = from.Encoding.WebName; 19 | 20 | return stringContent; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/HttpPathComparer.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Comparers; 2 | 3 | namespace PactNet.Mocks.MockHttpService.Comparers 4 | { 5 | internal class HttpPathComparer : IHttpPathComparer 6 | { 7 | public ComparisonResult Compare(string expected, string actual) 8 | { 9 | var result = new ComparisonResult("has path {0}", expected); 10 | 11 | if (expected == null) 12 | { 13 | return result; 14 | } 15 | 16 | if (!expected.Equals(actual)) 17 | { 18 | result.RecordFailure(new DiffComparisonFailure(expected, actual)); 19 | return result; 20 | } 21 | 22 | return result; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Build/Setup-Build-Environment.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | ) 3 | 4 | $BuildNumber = $env:APPVEYOR_BUILD_NUMBER 5 | $IsTagBuild = $env:APPVEYOR_REPO_TAG 6 | 7 | $PactNetVersion 8 | $PactNetAssemblyVersion 9 | 10 | if($IsTagBuild -eq 'True') 11 | { 12 | $TagName = $env:APPVEYOR_REPO_TAG_NAME #was APPVEYOR_REPO_BRANCH 13 | $PactNetVersion = "$TagName" 14 | $PactNetAssemblyVersion = ($TagName -replace "[^0-9,.]", '') + ".$BuildNumber" 15 | } 16 | else 17 | { 18 | $PactNetVersion = "0.0.0.$BuildNumber-beta" 19 | $PactNetAssemblyVersion = "0.0.0.$BuildNumber" 20 | } 21 | 22 | $env:PACTNET_VERSION = $PactNetVersion 23 | $env:PACTNET_ASSEMBLY_VERSION = $PactNetAssemblyVersion 24 | 25 | Write-Host "Set env:PACTNET_VERSION = $PactNetVersion" 26 | Write-Host "Set env:PACTNET_ASSEMBLY_VERSION = $PactNetAssemblyVersion" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #ignore thumbnails created by windows 2 | Thumbs.db 3 | #Ignore files build by Visual Studio 4 | *.obj 5 | *.exe 6 | !.nuget/Nuget.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vsp 12 | *.vspscc 13 | *.vssscc 14 | *_i.c 15 | *_p.c 16 | *.ncb 17 | *.suo 18 | *.tlb 19 | *.tlh 20 | *.bak 21 | *.cache 22 | *.ilk 23 | *.log 24 | [Bb]in 25 | [Dd]ebug*/ 26 | *.lib 27 | *.sbr 28 | [Rr]elease*/ 29 | _ReSharper*/ 30 | [Tt]est[Rr]esult* 31 | #[Ww]eb.[Cc]onfig 32 | *.csproj.user 33 | *.orig 34 | *.psess 35 | [Bb]in/ 36 | [Oo]bj/ 37 | [Oo]utput/ 38 | [Ee]nvironment/ 39 | TestResults/* 40 | _TeamCity*/ 41 | Build/PactNet*.nupkg 42 | *.ncrunchproject 43 | *.ncrunchsolution 44 | /packages/* 45 | !packages/repositories.config 46 | /Build/coverage 47 | [Ll]ogs 48 | *.DotSettings 49 | -------------------------------------------------------------------------------- /PactNet.Tests/Models/InteractionTests.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Models; 2 | using Xunit; 3 | 4 | namespace PactNet.Tests.Models 5 | { 6 | public class InteractionTests 7 | { 8 | [Fact] 9 | public void AsJsonString_WhenCalled_ReturnsJsonRepresentation() 10 | { 11 | const string expectedInteractionJson = "{\"description\":\"My description\",\"provider_state\":\"My provider state\"}"; 12 | 13 | var interaction = new Interaction 14 | { 15 | Description = "My description", 16 | ProviderState = "My provider state" 17 | }; 18 | 19 | var actualInteractionJson = interaction.AsJsonString(); 20 | 21 | Assert.Equal(expectedInteractionJson, actualInteractionJson); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PactNet/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /PactNet.Tests/IntegrationTests/IntegrationTestingMockProviderNancyBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | using NSubstitute; 3 | using PactNet.Mocks.MockHttpService.Nancy; 4 | 5 | namespace PactNet.Tests.IntegrationTests 6 | { 7 | internal class IntegrationTestingMockProviderNancyBootstrapper : MockProviderNancyBootstrapper 8 | { 9 | public IntegrationTestingMockProviderNancyBootstrapper(PactConfig config) 10 | : base(config) 11 | { 12 | } 13 | 14 | protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) 15 | { 16 | base.ApplicationStartup(container, pipelines); 17 | 18 | container.Register(typeof(IFileSystem), Substitute.For()); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/IMockProviderRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Mocks.MockHttpService 5 | { 6 | internal interface IMockProviderRepository 7 | { 8 | string TestContext { get; set; } 9 | ICollection TestScopedInteractions { get; } 10 | ICollection Interactions { get; } 11 | ICollection HandledRequests { get; } 12 | 13 | void AddInteraction(ProviderServiceInteraction interaction); 14 | void AddHandledRequest(HandledRequest handledRequest); 15 | ProviderServiceInteraction GetMatchingTestScopedInteraction(ProviderServiceRequest providerServiceRequest); 16 | void ClearTestScopedState(); 17 | } 18 | } -------------------------------------------------------------------------------- /PactNet/Comparers/UnexpectedRequestComparisonFailure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Comparers 5 | { 6 | internal class UnexpectedRequestComparisonFailure : ComparisonFailure 7 | { 8 | public string RequestDescription { get; private set; } 9 | 10 | public UnexpectedRequestComparisonFailure(ProviderServiceRequest request) 11 | { 12 | var requestMethod = request != null ? request.Method.ToString().ToUpperInvariant() : "No Method"; 13 | var requestPath = request != null ? request.Path : "No Path"; 14 | 15 | RequestDescription = String.Format("{0} {1}", requestMethod, requestPath); 16 | Result = String.Format( 17 | "An unexpected request {0} was seen by the mock provider service.", 18 | RequestDescription); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /PactNet.Tests/IntegrationTests/PactBuilderIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Mocks.MockHttpService; 2 | using Xunit; 3 | 4 | namespace PactNet.Tests.IntegrationTests 5 | { 6 | public class PactBuilderIntegrationTests : IUseFixture 7 | { 8 | private IMockProviderService _mockProviderService; 9 | private string _mockProviderServiceBaseUri; 10 | 11 | public void SetFixture(IntegrationTestsMyApiPact data) 12 | { 13 | _mockProviderService = data.MockProviderService; 14 | _mockProviderServiceBaseUri = data.MockProviderServiceBaseUri; 15 | _mockProviderService.ClearInteractions(); 16 | } 17 | 18 | [Fact] 19 | public void WhenNotRegisteringAnyInteractions_VerificationSucceeds() 20 | { 21 | _mockProviderService.VerifyInteractions(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PactNet.Tests/Reporters/Outputters/FileReportOutputterTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NSubstitute; 3 | using PactNet.Logging; 4 | using PactNet.Reporters; 5 | using PactNet.Reporters.Outputters; 6 | using Xunit; 7 | 8 | namespace PactNet.Tests.Reporters.Outputters 9 | { 10 | public class FileReportOutputterTests 11 | { 12 | private ILog _log; 13 | 14 | private IReportOutputter GetSubject() 15 | { 16 | _log = Substitute.For(); 17 | 18 | return new FileReportOutputter(() => _log); 19 | } 20 | 21 | [Fact] 22 | public void Write_WithReport_CallsDebugOnTheLoggerWithReport() 23 | { 24 | const string report = "Hello!"; 25 | var outputter = GetSubject(); 26 | 27 | outputter.Write(report); 28 | 29 | _log.Received(1).Debug(report); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PactNet/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /PactNet.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /PactNet.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Samples/EventApi/Consumer.Tests/ConsumerEventApiPact.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet; 3 | using PactNet.Mocks.MockHttpService; 4 | 5 | namespace Consumer.Tests 6 | { 7 | public class ConsumerEventApiPact : IDisposable 8 | { 9 | public IPactBuilder PactBuilder { get; private set; } 10 | public IMockProviderService MockProviderService { get; private set; } 11 | 12 | public int MockServerPort { get { return 1234; } } 13 | public string MockProviderServiceBaseUri { get { return String.Format("http://localhost:{0}", MockServerPort); } } 14 | 15 | public ConsumerEventApiPact() 16 | { 17 | PactBuilder = new PactBuilder() 18 | .ServiceConsumer("Consumer") 19 | .HasPactWith("Event API"); 20 | 21 | MockProviderService = PactBuilder.MockService(MockServerPort); 22 | } 23 | 24 | public void Dispose() 25 | { 26 | PactBuilder.Build(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/HttpVerbMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Mappers 6 | { 7 | internal class HttpVerbMapper : IHttpVerbMapper 8 | { 9 | private static readonly IDictionary Map = new Dictionary() 10 | { 11 | { "GET", HttpVerb.Get }, 12 | { "POST", HttpVerb.Post }, 13 | { "PUT", HttpVerb.Put }, 14 | { "DELETE", HttpVerb.Delete }, 15 | { "HEAD", HttpVerb.Head }, 16 | { "PATCH", HttpVerb.Patch } 17 | }; 18 | 19 | public HttpVerb Convert(string from) 20 | { 21 | if (!Map.ContainsKey(from)) 22 | { 23 | throw new ArgumentException(String.Format("Cannot map {0} to a HttpVerb, no matching item has been registered.", from)); 24 | } 25 | 26 | return Map[from]; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /PactNet/Infrastructure/Logging/LocalLogMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Logging; 3 | 4 | namespace PactNet.Infrastructure.Logging 5 | { 6 | internal class LocalLogMessage 7 | { 8 | public DateTime DateTime { get; private set; } 9 | public String DateTimeFormatted { get { return DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff zzz"); } } 10 | public LogLevel Level { get; private set; } 11 | public Func MessagePredicate { get; private set; } 12 | public Exception Exception { get; private set; } 13 | public object[] FormatParameters { get; private set; } 14 | 15 | public LocalLogMessage( 16 | LogLevel level, 17 | Func messagePredicate, 18 | Exception exception, 19 | object[] formatParameters) 20 | { 21 | DateTime = DateTime.Now; 22 | Level = level; 23 | MessagePredicate = messagePredicate; 24 | Exception = exception; 25 | FormatParameters = formatParameters; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /PactNet/Comparers/MissingInteractionComparisonFailure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Mocks.MockHttpService.Models; 3 | 4 | namespace PactNet.Comparers 5 | { 6 | internal class MissingInteractionComparisonFailure : ComparisonFailure 7 | { 8 | public string RequestDescription { get; private set; } 9 | 10 | public MissingInteractionComparisonFailure(ProviderServiceInteraction interaction) 11 | { 12 | var requestMethod = interaction.Request != null ? interaction.Request.Method.ToString().ToUpperInvariant() : "No Method"; 13 | var requestPath = interaction.Request != null ? interaction.Request.Path : "No Path"; 14 | 15 | RequestDescription = String.Format("{0} {1}", requestMethod, requestPath); 16 | Result = String.Format( 17 | "The interaction with description '{0}' and provider state '{1}', was not used by the test. Missing request {2}.", 18 | interaction.Description, 19 | interaction.ProviderState, 20 | RequestDescription); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Build/Publish-Release.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory=$true)] 3 | [ValidatePattern('\d\.\d\.*')] 4 | [string] 5 | $ReleaseVersionNumber, 6 | 7 | [switch]$Push, 8 | 9 | [string]$ApiKey 10 | ) 11 | 12 | $PSScriptFilePath = (Get-Item $MyInvocation.MyCommand.Path).FullName 13 | 14 | $BuildRoot = Split-Path -Path $PSScriptFilePath -Parent 15 | $SolutionRoot = Split-Path -Path $BuildRoot -Parent 16 | $NuGetExe = Join-Path $BuildRoot -ChildPath '..\.nuget\nuget.exe' 17 | 18 | # Build the NuGet package 19 | $ProjectPath = Join-Path -Path $SolutionRoot -ChildPath 'PactNet\PactNet.nuspec' 20 | & $NuGetExe pack $ProjectPath -Prop Configuration=Release -OutputDirectory $BuildRoot -Version $ReleaseVersionNumber 21 | if (-not $?) 22 | { 23 | throw 'The NuGet process returned an error code.' 24 | } 25 | 26 | # Upload the NuGet package 27 | if ($Push) 28 | { 29 | if($ApiKey) 30 | { 31 | & $NuGetExe setApiKey $ApiKey 32 | } 33 | 34 | $NuPkgPath = Join-Path -Path $BuildRoot -ChildPath "PactNet.$ReleaseVersionNumber.nupkg" 35 | & $NuGetExe push $NuPkgPath 36 | if (-not $?) 37 | { 38 | throw 'The NuGet process returned an error code.' 39 | } 40 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | os: Visual Studio 2015 4 | 5 | install: 6 | - git submodule update --init --recursive 7 | - set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% 8 | - ps: .\Build\Setup-Build-Environment.ps1 9 | 10 | assembly_info: 11 | patch: true 12 | file: AssemblyInfo.* 13 | assembly_version: "$(pactnet_assembly_version)" 14 | assembly_file_version: "$(pactnet_assembly_version)" 15 | assembly_informational_version: "$(pactnet_version)" 16 | 17 | deploy: 18 | - provider: NuGet 19 | skip_symbols: true 20 | api_key: 21 | secure: Vlw7m7mXEZ9XqlZBsQ4fzqDKjO8yTdtXhO9c04RnrD4zwhf6dx+lxBmC9TJ7EF6G 22 | on: 23 | appveyor_repo_tag: true 24 | 25 | artifacts: 26 | - path: '**\Build\PactNet*.nupkg' 27 | - path: '**\Build\coverage\*' 28 | 29 | configuration: Release 30 | 31 | build: 32 | project: PactNet.sln 33 | verbosity: normal 34 | 35 | before_build: 36 | - nuget restore 37 | 38 | after_build: 39 | - ps: .\Build\Publish-Release.ps1 -ReleaseVersionNumber "$env:PACTNET_VERSION" 40 | 41 | after_test: 42 | - ps: .\Build\Tests-Coverage.ps1 -GenerateSummaryReport -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 seek.com.au 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/HttpMethodMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using PactNet.Mocks.MockHttpService.Models; 5 | 6 | namespace PactNet.Mocks.MockHttpService.Mappers 7 | { 8 | internal class HttpMethodMapper : IHttpMethodMapper 9 | { 10 | private static readonly IDictionary Map = new Dictionary 11 | { 12 | { HttpVerb.Get, HttpMethod.Get }, 13 | { HttpVerb.Post, HttpMethod.Post }, 14 | { HttpVerb.Put, HttpMethod.Put }, 15 | { HttpVerb.Delete, HttpMethod.Delete }, 16 | { HttpVerb.Head, HttpMethod.Head }, 17 | { HttpVerb.Patch, new HttpMethod("PATCH") } 18 | }; 19 | 20 | public HttpMethod Convert(HttpVerb from) 21 | { 22 | if (!Map.ContainsKey(from)) 23 | { 24 | throw new ArgumentException(String.Format("Cannot map HttpVerb.{0} to a HttpMethod, no matching item has been registered.", from)); 25 | } 26 | 27 | return Map[from]; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Controllers/StatsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Web.Http; 4 | using Provider.Api.Web.Models; 5 | 6 | namespace Provider.Api.Web.Controllers 7 | { 8 | public class StatsController : ApiController 9 | { 10 | [Route("stats/status")] 11 | public dynamic GetAlive() 12 | { 13 | return new 14 | { 15 | alive = true, 16 | _links = new Dictionary 17 | { 18 | { "self", new HypermediaLink("/stats/status") }, 19 | { "uptime", new HypermediaLink("/stats/uptime") } 20 | } 21 | }; 22 | } 23 | 24 | [Route("stats/uptime")] 25 | public dynamic GetUptime() 26 | { 27 | return new 28 | { 29 | upSince = new DateTime(2014, 6, 27, 23, 51, 12, DateTimeKind.Utc), 30 | _links = new Dictionary 31 | { 32 | { "self", new HypermediaLink("/stats/uptime") } 33 | } 34 | }; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /PactNet.Tests/IntegrationTests/Specification/Models/RequestTestCase.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using PactNet.Mocks.MockHttpService.Comparers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | using Xunit; 5 | 6 | namespace PactNet.Tests.IntegrationTests.Specification.Models 7 | { 8 | public class RequestTestCase : IVerifiable 9 | { 10 | private readonly IProviderServiceRequestComparer _requestComparer; 11 | 12 | public bool Match { get; set; } 13 | public string Comment { get; set; } 14 | public ProviderServiceRequest Expected { get; set; } 15 | public ProviderServiceRequest Actual { get; set; } 16 | 17 | public RequestTestCase() 18 | { 19 | _requestComparer = new ProviderServiceRequestComparer(); 20 | } 21 | 22 | public void Verify() 23 | { 24 | var result = _requestComparer.Compare(Expected, Actual); 25 | 26 | if (Match) 27 | { 28 | Assert.False(result.HasFailure, "There should not be any errors"); 29 | } 30 | else 31 | { 32 | Assert.Equal(1, result.Failures.Count()); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /PactNet.Tests/IntegrationTests/Specification/Models/ResponseTestCase.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using PactNet.Mocks.MockHttpService.Comparers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | using Xunit; 5 | 6 | namespace PactNet.Tests.IntegrationTests.Specification.Models 7 | { 8 | public class ResponseTestCase : IVerifiable 9 | { 10 | private readonly IProviderServiceResponseComparer _responseComparer; 11 | 12 | public bool Match { get; set; } 13 | public string Comment { get; set; } 14 | public ProviderServiceResponse Expected { get; set; } 15 | public ProviderServiceResponse Actual { get; set; } 16 | 17 | public ResponseTestCase() 18 | { 19 | _responseComparer = new ProviderServiceResponseComparer(); 20 | } 21 | 22 | public void Verify() 23 | { 24 | var result = _responseComparer.Compare(Expected, Actual); 25 | 26 | if (Match) 27 | { 28 | Assert.False(result.HasFailure, "There should not be any errors"); 29 | } 30 | else 31 | { 32 | Assert.Equal(1, result.Failures.Count()); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /PactNet/Models/Interaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace PactNet.Models 5 | { 6 | public class Interaction 7 | { 8 | private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings 9 | { 10 | NullValueHandling = NullValueHandling.Ignore, 11 | Formatting = Formatting.None 12 | }; 13 | 14 | [JsonProperty(Order = -3, PropertyName = "description")] 15 | public string Description { get; set; } 16 | 17 | [JsonProperty(Order = -2, PropertyName = "provider_state")] //provider_state will become providerState 18 | public string ProviderState { get; set; } 19 | 20 | //[Obsolete("For backwards compatibility.")] 21 | //public string provider_state { set { ProviderState = value; } } //Uncomment when provider_state becomes providerState 22 | [Obsolete("For forwards compatibility.")] 23 | public string providerState { set { ProviderState = value; } } //Remove when provider_state becomes providerState 24 | 25 | public string AsJsonString() 26 | { 27 | return JsonConvert.SerializeObject(this, _jsonSerializerSettings); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /PactNet.Tests/Models/ProviderStateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Models; 3 | using Xunit; 4 | 5 | namespace PactNet.Tests.Models 6 | { 7 | public class ProviderStateTests 8 | { 9 | [Fact] 10 | public void Ctor_WithProviderState_SetsProviderStateDescription() 11 | { 12 | const string provideStateDescription = "my provider state"; 13 | var providerState = new ProviderState(provideStateDescription); 14 | 15 | Assert.Equal(provideStateDescription, providerState.ProviderStateDescription); 16 | } 17 | 18 | [Fact] 19 | public void Ctor_WithSetUpAction_SetsSetUpAction() 20 | { 21 | Action setUp = () => { }; 22 | var providerState = new ProviderState("my provider state", setUp: setUp); 23 | 24 | Assert.Equal(setUp, providerState.SetUp); 25 | } 26 | 27 | [Fact] 28 | public void Ctor_WithTearDownAction_SetsTearDownAction() 29 | { 30 | Action tearDown = () => { }; 31 | var providerState = new ProviderState("my provider state", tearDown: tearDown); 32 | 33 | Assert.Equal(tearDown, providerState.TearDown); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web.Tests/AuthorizationTokenReplacementMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Threading.Tasks; 5 | using Microsoft.Owin.Security.DataProtection; 6 | 7 | namespace Provider.Api.Web.Tests 8 | { 9 | public class AuthorizationTokenReplacementMiddleware 10 | { 11 | private readonly Func, Task> _next; 12 | private readonly TokenGenerator _tokenGenerator; 13 | 14 | public AuthorizationTokenReplacementMiddleware(Func, Task> next, IDataProtector dataProtector) 15 | { 16 | _next = next; 17 | _tokenGenerator = new TokenGenerator(dataProtector); 18 | } 19 | 20 | public async Task Invoke(IDictionary environment) 21 | { 22 | var headers = environment["owin.RequestHeaders"] as IDictionary; 23 | 24 | Debug.Assert(headers != null, "headers != null"); 25 | if (headers.ContainsKey("Authorization") && headers["Authorization"][0] == "Bearer SomeValidAuthToken") 26 | { 27 | headers["Authorization"][0] = $"Bearer {_tokenGenerator.Generate()}"; 28 | } 29 | 30 | await _next.Invoke(environment); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /PactNet/Infrastructure/Logging/LocalLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using PactNet.Logging; 4 | 5 | namespace PactNet.Infrastructure.Logging 6 | { 7 | internal class LocalLogger : IDisposable 8 | { 9 | internal string LogPath { get { return _logHandler.LogPath; } } 10 | 11 | private readonly ILocalLogMessageHandler _logHandler; 12 | 13 | public LocalLogger(string logFilePath) 14 | { 15 | _logHandler = new LocalRollingLogFileMessageHandler(logFilePath); 16 | } 17 | 18 | public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) 19 | { 20 | //Handles the log level enabled checks 21 | if (messageFunc == null && 22 | exception == null && 23 | (formatParameters == null || !formatParameters.Any())) 24 | { 25 | return true; 26 | } 27 | 28 | _logHandler.Handle(new LocalLogMessage(logLevel, messageFunc, exception, formatParameters)); 29 | 30 | return true; 31 | } 32 | 33 | public void Dispose() 34 | { 35 | if (_logHandler != null) 36 | { 37 | _logHandler.Dispose(); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /PactNet.Tests/Mocks/MockHttpService/CustomRequestSenderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Mocks.MockHttpService; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | using Xunit; 5 | 6 | namespace PactNet.Tests.Mocks.MockHttpService 7 | { 8 | public class CustomRequestSenderTests 9 | { 10 | private Tuple _httpRequestSenderFuncCallInfo; 11 | 12 | private IHttpRequestSender GetSubject() 13 | { 14 | _httpRequestSenderFuncCallInfo = null; 15 | Func httpRequestSenderFunc = request => 16 | { 17 | _httpRequestSenderFuncCallInfo = new Tuple(true, request); 18 | return null; 19 | }; 20 | return new CustomRequestSender(httpRequestSenderFunc); 21 | } 22 | 23 | [Fact] 24 | public void Send_WhenCalled_InvokesTheFuncWithRequest() 25 | { 26 | var request = new ProviderServiceRequest(); 27 | var requestSender = GetSubject(); 28 | 29 | requestSender.Send(request); 30 | 31 | Assert.True(_httpRequestSenderFuncCallInfo.Item1, "httpRequestSenderFunc was called"); 32 | Assert.Equal(request, _httpRequestSenderFuncCallInfo.Item2); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PactNet/Configuration/Json/JsonConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PactNet.Configuration.Json 4 | { 5 | internal static class JsonConfig 6 | { 7 | private static JsonSerializerSettings _serializerSettings; 8 | internal static JsonSerializerSettings PactFileSerializerSettings 9 | { 10 | get 11 | { 12 | _serializerSettings = _serializerSettings ?? new JsonSerializerSettings 13 | { 14 | NullValueHandling = NullValueHandling.Ignore, 15 | Formatting = Formatting.Indented 16 | }; 17 | return _serializerSettings; 18 | } 19 | } 20 | 21 | private static JsonSerializerSettings _apiRequestSerializerSettings; 22 | internal static JsonSerializerSettings ApiSerializerSettings 23 | { 24 | get 25 | { 26 | _apiRequestSerializerSettings = _apiRequestSerializerSettings ?? new JsonSerializerSettings 27 | { 28 | NullValueHandling = NullValueHandling.Ignore, 29 | Formatting = Formatting.None 30 | }; 31 | return _apiRequestSerializerSettings; 32 | } 33 | set { _apiRequestSerializerSettings = value; } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web.Tests/TokenGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Security; 3 | using Microsoft.Owin.Security.DataHandler; 4 | using System.Security.Claims; 5 | using Microsoft.Owin.Security.DataProtection; 6 | 7 | namespace Provider.Api.Web.Tests 8 | { 9 | public class TokenGenerator 10 | { 11 | private readonly IDataProtector _dataProtector; 12 | 13 | public TokenGenerator(IDataProtector dataProtector) 14 | { 15 | _dataProtector = dataProtector; 16 | } 17 | 18 | public string Generate() 19 | { 20 | // Generate an OAuth bearer token for ASP.NET/Owin Web Api service that uses the default OAuthBearer token middleware. 21 | var claims = new[] 22 | { 23 | new Claim(ClaimTypes.Name, "WebApiUser"), 24 | new Claim(ClaimTypes.Role, "User"), 25 | new Claim(ClaimTypes.Role, "PowerUser"), 26 | }; 27 | var identity = new ClaimsIdentity(claims, "Test"); 28 | 29 | // Use the same token generation logic as the OAuthBearer Owin middleware. 30 | var tdf = new TicketDataFormat(_dataProtector); 31 | var ticket = new AuthenticationTicket(identity, new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddHours(1) }); 32 | var accessToken = tdf.Protect(ticket); 33 | 34 | return accessToken; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Build/Tests-Coverage.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [switch]$GenerateSummaryReport 3 | ) 4 | 5 | $PSScriptFilePath = (Get-Item $MyInvocation.MyCommand.Path).FullName 6 | $BuildRoot = Split-Path -Path $PSScriptFilePath -Parent 7 | $SolutionRoot = Split-Path -Path $BuildRoot -Parent 8 | 9 | cd $SolutionRoot 10 | & git submodule update --init 11 | cd $BuildRoot 12 | 13 | $NuGetExe = Join-Path $BuildRoot -ChildPath '..\.nuget\nuget.exe' 14 | $OpenCoverExe = Join-Path $BuildRoot -ChildPath '..\packages\OpenCover.4.5.3207\OpenCover.Console.exe' 15 | $XUnitExe = Join-Path $BuildRoot -ChildPath '..\packages\xunit.runners.1.9.2\tools\xunit.console.clr4.x86.exe' 16 | $ReportGenExe = Join-Path $BuildRoot -ChildPath '..\packages\ReportGenerator.1.9.1.0\ReportGenerator.exe' 17 | 18 | & $NuGetExe install "$SolutionRoot\.nuget\packages.config" -outputdirectory "$SolutionRoot\packages" 19 | 20 | New-Item -ItemType directory -Path "$BuildRoot\coverage" -ErrorAction:ignore 21 | 22 | & $OpenCoverExe ` 23 | -register:user ` 24 | "-target:$XUnitExe" ` 25 | '-targetargs:..\PactNet.Tests\bin\Release\PactNet.Tests.dll /noshadow' ` 26 | '-filter:+[PactNet]* -[*Tests]*' ` 27 | '-output:.\coverage\results.xml' 28 | 29 | if($GenerateSummaryReport) 30 | { 31 | & $ReportGenExe '-reports:.\coverage\results.xml' '-reporttypes:HtmlSummary' "-targetdir:$BuildRoot\coverage" 32 | } 33 | else 34 | { 35 | & $ReportGenExe '-reports:.\coverage\results.xml' "-targetdir:$BuildRoot\coverage" 36 | } 37 | 38 | cd $SolutionRoot 39 | -------------------------------------------------------------------------------- /PactNet/PactUriOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace PactNet 5 | { 6 | public class PactUriOptions 7 | { 8 | private const string AuthScheme = "Basic"; 9 | private readonly string _username; 10 | private readonly string _password; 11 | 12 | internal string AuthorizationScheme 13 | { 14 | get { return AuthScheme; } 15 | } 16 | 17 | internal string AuthorizationValue 18 | { 19 | get 20 | { 21 | return Convert.ToBase64String( 22 | Encoding.UTF8.GetBytes( 23 | String.Format("{0}:{1}", _username, _password))); 24 | } 25 | } 26 | 27 | public PactUriOptions(string username, string password) 28 | { 29 | if (String.IsNullOrEmpty(username)) 30 | { 31 | throw new ArgumentException("username is null or empty."); 32 | } 33 | 34 | if (username.Contains(":")) 35 | { 36 | throw new ArgumentException("username contains a ':' character, which is not allowed."); 37 | } 38 | 39 | if (String.IsNullOrEmpty(password)) 40 | { 41 | throw new ArgumentException("password is null or empty."); 42 | } 43 | 44 | _username = username; 45 | _password = password; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /PactNet/Configuration/Json/Converters/PreserveCasingDictionaryConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace PactNet.Configuration.Json.Converters 6 | { 7 | public class PreserveCasingDictionaryConverter : JsonConverter 8 | { 9 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 10 | { 11 | if (value == null) 12 | { 13 | writer.WriteNull(); 14 | } 15 | else 16 | { 17 | var dictionary = (IDictionary) value; 18 | 19 | writer.WriteStartObject(); 20 | 21 | foreach (var item in dictionary) 22 | { 23 | writer.WritePropertyName(item.Key); 24 | serializer.Serialize(writer, item.Value); 25 | } 26 | 27 | writer.WriteEndObject(); 28 | } 29 | } 30 | 31 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 32 | { 33 | throw new InvalidOperationException(); 34 | } 35 | 36 | public override bool CanRead 37 | { 38 | get { return false; } 39 | } 40 | 41 | public override bool CanConvert(Type objectType) 42 | { 43 | return typeof(IDictionary).IsAssignableFrom(objectType); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Provider.Api.Web")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Provider.Api.Web")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a0347a9a-4672-4793-9660-6b15e74f33df")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Controllers/BlobsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Net.Http.Headers; 5 | using System.Text; 6 | using System.Web.Http; 7 | 8 | namespace Provider.Api.Web.Controllers 9 | { 10 | public class BlobsController : ApiController 11 | { 12 | private const string Data = "This is a test"; 13 | 14 | [HttpGet] 15 | [Route("blobs/{id}")] 16 | public HttpResponseMessage GetById(Guid id) 17 | { 18 | var responseContent = new ByteArrayContent(Encoding.UTF8.GetBytes(Data)); 19 | responseContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "text.txt" }; 20 | responseContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); 21 | 22 | return new HttpResponseMessage(HttpStatusCode.Created) 23 | { 24 | Content = responseContent 25 | }; 26 | } 27 | 28 | [HttpPost] 29 | [Route("blobs/{id}")] 30 | public HttpResponseMessage Post(Guid id) 31 | { 32 | var bytes = Request.Content.ReadAsByteArrayAsync().Result; 33 | var requestBody = Encoding.UTF8.GetString(bytes); 34 | 35 | if (requestBody != Data) 36 | { 37 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 38 | } 39 | 40 | return new HttpResponseMessage(HttpStatusCode.Created); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /PactNet.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PactNet.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PactNet.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c2798d20-5ba4-49ea-9545-5d48bec3d39c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Consumer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Consumer")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("78faa00e-3a1a-4d56-9557-101f09d4adde")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Samples/EventApi/Consumer.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Consumer.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Consumer.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f3fc872d-a7f2-437a-b0d4-ad2ed8574681")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PactNet.Tests/Models/PactDetailsTests.cs: -------------------------------------------------------------------------------- 1 | using PactNet.Models; 2 | using Xunit; 3 | 4 | namespace PactNet.Tests.Models 5 | { 6 | public class PactDetailsTests 7 | { 8 | [Fact] 9 | public void GeneratePactFileName_WhenConsumerNameAndProviderNameHaveBeenSet_ReturnsFileName() 10 | { 11 | var details = new PactDetails 12 | { 13 | Consumer = new Pacticipant { Name = "My Consumer" }, 14 | Provider = new Pacticipant { Name = "My Provider" } 15 | }; 16 | 17 | var fileName = details.GeneratePactFileName(); 18 | 19 | Assert.Equal("my_consumer-my_provider.json", fileName); 20 | } 21 | 22 | [Fact] 23 | public void GeneratePactFileName_WhenConsumerNameAndProviderNameHaveNotBeenSet_ReturnsFileNameWithNoConsumerAndProviderNameAndDoesNotThrow() 24 | { 25 | var details = new PactDetails 26 | { 27 | Consumer = new Pacticipant(), 28 | Provider = new Pacticipant() 29 | }; 30 | 31 | var fileName = details.GeneratePactFileName(); 32 | 33 | Assert.Equal("-.json", fileName); 34 | } 35 | 36 | [Fact] 37 | public void GeneratePactFileName_WhenConsumerAndProviderHaveNotBeenSet_ReturnsFileNameWithNoConsumerAndProviderNameAndDoesNotThrow() 38 | { 39 | var details = new PactDetails(); 40 | 41 | var fileName = details.GeneratePactFileName(); 42 | 43 | Assert.Equal("-.json", fileName); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PactNet/IPactVerifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet 6 | { 7 | public interface IPactVerifier 8 | { 9 | /// 10 | /// Define a set up and/or tear down action for a specific state specified by the consumer. 11 | /// This is where you should set up test data, so that you can fulfil the contract outlined by a consumer. 12 | /// 13 | /// The name of the provider state as defined by the consumer interaction, which lives in the Pact file. 14 | /// A set up action that will be run before the interaction verify, if the provider has specified it in the interaction. If no action is required please use an empty lambda () => {}. 15 | /// A tear down action that will be run after the interaction verify, if the provider has specified it in the interaction. If no action is required please use an empty lambda () => {}. 16 | IPactVerifier ProviderState(string providerState, Action setUp = null, Action tearDown = null); 17 | IPactVerifier ServiceProvider(string providerName, HttpClient httpClient); 18 | IPactVerifier ServiceProvider(string providerName, Func httpRequestSender); 19 | IPactVerifier HonoursPactWith(string consumerName); 20 | IPactVerifier PactUri(string uri, PactUriOptions options = null); 21 | void Verify(string description = null, string providerState = null); 22 | } 23 | } -------------------------------------------------------------------------------- /PactNet.Tests/Fakes/FakeHttpMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace PactNet.Tests.Fakes 8 | { 9 | public class FakeHttpMessageHandler : DelegatingHandler 10 | { 11 | private readonly IList _requestsReceived; 12 | public IEnumerable RequestsReceived { get { return _requestsReceived; } } 13 | 14 | private readonly IList _requestContentReceived; 15 | public IEnumerable RequestContentReceived { get { return _requestContentReceived; } } 16 | 17 | public HttpResponseMessage Response { get; set; } 18 | 19 | public FakeHttpMessageHandler(HttpResponseMessage response = null) 20 | { 21 | Response = response ?? new HttpResponseMessage(HttpStatusCode.OK); 22 | _requestsReceived = new List(); 23 | _requestContentReceived = new List(); 24 | } 25 | 26 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 27 | { 28 | _requestsReceived.Add(request); 29 | if (request.Content != null) 30 | { 31 | _requestContentReceived.Add(request.Content.ReadAsStringAsync().Result); 32 | } 33 | 34 | var tcs = new TaskCompletionSource(); 35 | tcs.SetResult(Response); 36 | 37 | return tcs.Task; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Provider.Api.Web.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Provider.Api.Web.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("57de71eb-e4e2-4404-84a6-bb5376026b19")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PactNet.Tests/IntegrationTests/IntegrationTestsMyApiPact.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using PactNet.Mocks.MockHttpService; 4 | using PactNet.Mocks.MockHttpService.Mappers; 5 | using PactNet.Mocks.MockHttpService.Nancy; 6 | 7 | namespace PactNet.Tests.IntegrationTests 8 | { 9 | public class IntegrationTestsMyApiPact : IDisposable 10 | { 11 | public IPactBuilder PactBuilder { get; private set; } 12 | public IMockProviderService MockProviderService { get; private set; } 13 | 14 | public int MockServerPort { get { return 4321; } } 15 | public string MockProviderServiceBaseUri { get { return String.Format("http://localhost:{0}", MockServerPort); } } 16 | 17 | public IntegrationTestsMyApiPact() 18 | { 19 | var pactConfig = new PactConfig(); 20 | 21 | PactBuilder = new PactBuilder((port, enableSsl, providerName, bindOnAllAdapters) => 22 | new MockProviderService( 23 | baseUri => new NancyHttpHost(baseUri, "MyApi", pactConfig, new IntegrationTestingMockProviderNancyBootstrapper(pactConfig)), 24 | port, enableSsl, 25 | baseUri => new HttpClient { BaseAddress = new Uri(baseUri) }, 26 | new HttpMethodMapper())) 27 | .ServiceConsumer("IntegrationTests") 28 | .HasPactWith("MyApi"); 29 | 30 | MockProviderService = PactBuilder.MockService(MockServerPort); 31 | } 32 | 33 | public void Dispose() 34 | { 35 | PactBuilder.Build(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /PactNet.Tests/IntegrationTests/FailureIntegrationTestsMyApiPact.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using PactNet.Mocks.MockHttpService; 4 | using PactNet.Mocks.MockHttpService.Mappers; 5 | using PactNet.Mocks.MockHttpService.Nancy; 6 | 7 | namespace PactNet.Tests.IntegrationTests 8 | { 9 | public class FailureIntegrationTestsMyApiPact : IDisposable 10 | { 11 | public IPactBuilder PactBuilder { get; private set; } 12 | public IMockProviderService MockProviderService { get; private set; } 13 | 14 | public int MockServerPort { get { return 4321; } } 15 | public string MockProviderServiceBaseUri { get { return String.Format("http://localhost:{0}", MockServerPort); } } 16 | 17 | public FailureIntegrationTestsMyApiPact() 18 | { 19 | var pactConfig = new PactConfig(); 20 | 21 | PactBuilder = new PactBuilder((port, enableSsl, providerName, bindOnAllAdapters) => 22 | new MockProviderService( 23 | baseUri => new NancyHttpHost(baseUri, "MyApi", pactConfig, new IntegrationTestingMockProviderNancyBootstrapper(pactConfig)), 24 | port, enableSsl, 25 | baseUri => new HttpClient { BaseAddress = new Uri(baseUri) }, 26 | new HttpMethodMapper())) 27 | .ServiceConsumer("FailureIntegrationTests") 28 | .HasPactWith("MyApi"); 29 | 30 | MockProviderService = PactBuilder.MockService(MockServerPort); 31 | } 32 | 33 | public void Dispose() 34 | { 35 | PactBuilder.Build(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/NancyResponseMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Nancy; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Mappers 6 | { 7 | internal class NancyResponseMapper : INancyResponseMapper 8 | { 9 | private readonly IHttpBodyContentMapper _httpBodyContentMapper; 10 | 11 | internal NancyResponseMapper(IHttpBodyContentMapper httpBodyContentMapper) 12 | { 13 | _httpBodyContentMapper = httpBodyContentMapper; 14 | } 15 | 16 | public NancyResponseMapper() : this(new HttpBodyContentMapper()) 17 | { 18 | } 19 | 20 | public Response Convert(ProviderServiceResponse from) 21 | { 22 | if (from == null) 23 | { 24 | return null; 25 | } 26 | 27 | var to = new Response 28 | { 29 | StatusCode = (HttpStatusCode)from.Status, 30 | Headers = from.Headers ?? new Dictionary() 31 | }; 32 | 33 | if (from.Body != null) 34 | { 35 | HttpBodyContent bodyContent = _httpBodyContentMapper.Convert(body: from.Body, headers: from.Headers); 36 | to.ContentType = bodyContent.ContentType.MediaType; 37 | to.Contents = s => 38 | { 39 | byte[] bytes = bodyContent.ContentBytes; 40 | s.Write(bytes, 0, bytes.Length); 41 | s.Flush(); 42 | }; 43 | } 44 | 45 | return to; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /PactNet/Models/ProviderStates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace PactNet.Models 6 | { 7 | public class ProviderStates 8 | { 9 | public Action SetUp { get; private set; } 10 | public Action TearDown { get; private set; } 11 | 12 | private List _providerStates; 13 | 14 | public ProviderStates(Action setUp = null, Action tearDown = null) 15 | { 16 | SetUp = setUp; 17 | TearDown = tearDown; 18 | } 19 | 20 | public void Add(ProviderState providerState) 21 | { 22 | _providerStates = _providerStates ?? new List(); 23 | 24 | if (_providerStates.Any(x => x.ProviderStateDescription == providerState.ProviderStateDescription)) 25 | { 26 | throw new ArgumentException(String.Format("providerState '{0}' has already been added", providerState.ProviderStateDescription)); 27 | } 28 | 29 | _providerStates.Add(providerState); 30 | } 31 | 32 | public ProviderState Find(string providerState) 33 | { 34 | if (providerState == null) 35 | { 36 | throw new ArgumentNullException("Please supply a non null providerState"); 37 | } 38 | 39 | if (_providerStates != null && _providerStates.Any(x => x.ProviderStateDescription == providerState)) 40 | { 41 | return _providerStates.FirstOrDefault(x => x.ProviderStateDescription == providerState); 42 | } 43 | 44 | return null; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/HttpBodyContentMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http.Headers; 5 | using System.Text; 6 | using PactNet.Mocks.MockHttpService.Models; 7 | 8 | namespace PactNet.Mocks.MockHttpService.Mappers 9 | { 10 | internal class HttpBodyContentMapper : IHttpBodyContentMapper 11 | { 12 | public HttpBodyContent Convert(dynamic body, IDictionary headers) 13 | { 14 | return body == null 15 | ? null 16 | : new HttpBodyContent(body, this.ParseContentTypeHeader(headers)); 17 | } 18 | 19 | public HttpBodyContent Convert(byte[] content, IDictionary headers) 20 | { 21 | return content == null 22 | ? null 23 | : new HttpBodyContent(content, this.ParseContentTypeHeader(headers)); 24 | } 25 | 26 | private MediaTypeHeaderValue ParseContentTypeHeader(IDictionary headers) 27 | { 28 | string contentType = headers? 29 | .Where(hdr => hdr.Key.Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase)) 30 | .Select(hdr => hdr.Value) 31 | .FirstOrDefault(); 32 | 33 | MediaTypeHeaderValue contentTypeHeader = (contentType == null) 34 | ? new MediaTypeHeaderValue("text/plain") 35 | : MediaTypeHeaderValue.Parse(contentType); 36 | 37 | contentTypeHeader.CharSet = contentTypeHeader.CharSet ?? Encoding.UTF8.WebName; 38 | 39 | return contentTypeHeader; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /PactNet/PactNet.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PactNet 5 | $version$ 6 | seek.com.au 7 | seek.com.au 8 | https://github.com/SEEK-Jobs/pact-net/blob/master/LICENSE.txt 9 | https://github.com/SEEK-Jobs/pact-net 10 | false 11 | A .NET version of Pact, which enables consumer driven contract testing. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Http; 2 | using Autofac; 3 | using Autofac.Integration.WebApi; 4 | using Microsoft.Owin; 5 | using Microsoft.Owin.Security.OAuth; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Serialization; 8 | using Owin; 9 | using Provider.Api.Web; 10 | using Provider.Api.Web.Controllers; 11 | 12 | [assembly: OwinStartup("ApiConfiguration", typeof(Startup))] 13 | namespace Provider.Api.Web 14 | { 15 | public class Startup 16 | { 17 | public void Configuration(IAppBuilder app) 18 | { 19 | var config = new HttpConfiguration(); 20 | 21 | // Owin Middleware; we use token middleware for requests that require authorization. 22 | var oAuthBearerOptions = new OAuthBearerAuthenticationOptions(); 23 | app.UseOAuthBearerAuthentication(oAuthBearerOptions); 24 | 25 | config.MapHttpAttributeRoutes(); 26 | 27 | app.UseWebApi(config); 28 | 29 | var json = config.Formatters.JsonFormatter; 30 | json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 31 | json.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; 32 | json.SerializerSettings.Formatting = Formatting.None; 33 | config.Formatters.Remove(config.Formatters.XmlFormatter); 34 | 35 | var builder = new ContainerBuilder(); 36 | 37 | builder.RegisterApiControllers(typeof(EventsController).Assembly); 38 | var container = builder.Build(); 39 | 40 | app.UseAutofacMiddleware(container); 41 | app.UseAutofacWebApi(config); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /PactNet/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PactNet")] 9 | [assembly: AssemblyDescription("A .NET version of Pact, which enables consumer driven contract testing.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("seek.com.au")] 12 | [assembly: AssemblyProduct("PactNet")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | [assembly: InternalsVisibleTo("PactNet.Tests")] 17 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [assembly: Guid("d86e1984-4e8e-42ee-8671-c394371d177e")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | [assembly: AssemblyVersion("0.0.0.1")] 38 | [assembly: AssemblyFileVersion("0.0.0.1")] -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 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 | -------------------------------------------------------------------------------- /PactNet.Tests/PactUriOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace PactNet.Tests 5 | { 6 | public class PactUriOptionsTests 7 | { 8 | [Fact] 9 | public void Ctor_WithEmptyUsername_ThrowsArgumentException() 10 | { 11 | const string username = ""; 12 | const string password = "somepassword"; 13 | 14 | Assert.Throws(() => new PactUriOptions(username, password)); 15 | } 16 | 17 | [Fact] 18 | public void Ctor_WithInvalidUsername_ThrowsArgumentException() 19 | { 20 | const string username = "some:user"; 21 | const string password = "somepassword"; 22 | 23 | Assert.Throws(() => new PactUriOptions(username, password)); 24 | } 25 | 26 | [Fact] 27 | public void Ctor_WithEmptyPassword_ThrowsArgumentException() 28 | { 29 | const string username = "someuser"; 30 | const string password = ""; 31 | 32 | Assert.Throws(() => new PactUriOptions(username, password)); 33 | } 34 | 35 | [Fact] 36 | public void Ctor_WithValidUsernameAndPassword_ReturnsCorrectAuthorizationSchemeAndValue() 37 | { 38 | const string username = "Aladdin"; 39 | const string password = "open sesame"; 40 | var expectedAuthScheme = "Basic"; 41 | var expectedAuthValue = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; 42 | 43 | var options = new PactUriOptions(username, password); 44 | 45 | Assert.Equal(expectedAuthScheme, options.AuthorizationScheme); 46 | Assert.Equal(expectedAuthValue, options.AuthorizationValue); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Models/ProviderServiceResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | using PactNet.Configuration.Json.Converters; 4 | using PactNet.Matchers; 5 | using PactNet.Mocks.MockHttpService.Matchers; 6 | 7 | namespace PactNet.Mocks.MockHttpService.Models 8 | { 9 | public class ProviderServiceResponse : IHttpMessage 10 | { 11 | private bool _bodyWasSet; 12 | private dynamic _body; 13 | 14 | [JsonProperty(PropertyName = "status")] 15 | public int Status { get; set; } 16 | 17 | [JsonProperty(PropertyName = "headers")] 18 | [JsonConverter(typeof(PreserveCasingDictionaryConverter))] 19 | public IDictionary Headers { get; set; } 20 | 21 | [JsonIgnore] 22 | [JsonProperty(PropertyName = "matchingRules")] 23 | internal IDictionary MatchingRules { get; private set; } 24 | 25 | [JsonProperty(PropertyName = "body", NullValueHandling = NullValueHandling.Include)] 26 | public dynamic Body 27 | { 28 | get { return _body; } 29 | set 30 | { 31 | _bodyWasSet = true; 32 | _body = ParseBodyMatchingRules(value); 33 | } 34 | } 35 | 36 | // A not so well known feature in JSON.Net to do conditional serialization at runtime 37 | public bool ShouldSerializeBody() 38 | { 39 | return _bodyWasSet; 40 | } 41 | 42 | private dynamic ParseBodyMatchingRules(dynamic body) 43 | { 44 | MatchingRules = new Dictionary 45 | { 46 | { DefaultHttpBodyMatcher.Path, new DefaultHttpBodyMatcher(true) } 47 | }; 48 | 49 | return body; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /PactNet.Tests/Mocks/MockHttpService/Models/ProviderServiceInteractionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Nancy; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | using Xunit; 5 | 6 | namespace PactNet.Tests.Mocks.MockHttpService.Models 7 | { 8 | public class ProviderServiceInteractionTests 9 | { 10 | [Fact] 11 | public void ToString_WhenCalled_ReturnsJsonRepresentation() 12 | { 13 | const string expectedInteractionJson = "{\"description\":\"My description\",\"provider_state\":\"My provider state\",\"request\":{\"method\":\"delete\",\"path\":\"/tester\",\"query\":\"test=2\",\"headers\":{\"Accept\":\"application/json\"},\"body\":{\"test\":\"hello\"}},\"response\":{\"status\":407,\"headers\":{\"Content-Type\":\"application/json\"},\"body\":{\"yep\":\"it worked\"}}}"; 14 | var interaction = new ProviderServiceInteraction 15 | { 16 | Request = new ProviderServiceRequest 17 | { 18 | Method = HttpVerb.Delete, 19 | Body = new 20 | { 21 | test = "hello" 22 | }, 23 | Headers = new Dictionary 24 | { 25 | { "Accept", "application/json" } 26 | }, 27 | Path = "/tester", 28 | Query = "test=2" 29 | }, 30 | Response = new ProviderServiceResponse 31 | { 32 | Status = (int)HttpStatusCode.ProxyAuthenticationRequired, 33 | Body = new 34 | { 35 | yep = "it worked" 36 | }, 37 | Headers = new Dictionary 38 | { 39 | { "Content-Type", "application/json" } 40 | } 41 | }, 42 | Description = "My description", 43 | ProviderState = "My provider state", 44 | }; 45 | 46 | var actualInteractionJson = interaction.AsJsonString(); 47 | 48 | Assert.Equal(expectedInteractionJson, actualInteractionJson); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/HttpClientRequestSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using PactNet.Extensions; 5 | using PactNet.Mocks.MockHttpService.Mappers; 6 | using PactNet.Mocks.MockHttpService.Models; 7 | 8 | namespace PactNet.Mocks.MockHttpService 9 | { 10 | internal class HttpClientRequestSender : IHttpRequestSender 11 | { 12 | private readonly HttpClient _httpClient; 13 | private readonly IHttpRequestMessageMapper _httpRequestMessageMapper; 14 | private readonly IProviderServiceResponseMapper _providerServiceResponseMapper; 15 | 16 | internal HttpClientRequestSender( 17 | HttpClient httpClient, 18 | IHttpRequestMessageMapper httpRequestMessageMapper, 19 | IProviderServiceResponseMapper providerServiceResponseMapper) 20 | { 21 | _httpClient = httpClient; 22 | _httpRequestMessageMapper = httpRequestMessageMapper; 23 | _providerServiceResponseMapper = providerServiceResponseMapper; 24 | } 25 | 26 | public HttpClientRequestSender(HttpClient httpClient) 27 | : this(httpClient, new HttpRequestMessageMapper(), new ProviderServiceResponseMapper()) 28 | { 29 | } 30 | 31 | public ProviderServiceResponse Send(ProviderServiceRequest request) 32 | { 33 | //Added because of this http://stackoverflow.com/questions/23438416/why-is-httpclient-baseaddress-not-working 34 | if (_httpClient.BaseAddress != null && _httpClient.BaseAddress.OriginalString.EndsWith("/")) 35 | { 36 | request.Path = request.Path.TrimStart('/'); 37 | } 38 | 39 | var httpRequest = _httpRequestMessageMapper.Convert(request); 40 | 41 | var httpResponse = _httpClient.SendAsync(httpRequest, CancellationToken.None).RunSync(); 42 | var response = _providerServiceResponseMapper.Convert(httpResponse); 43 | 44 | Dispose(httpRequest); 45 | Dispose(httpResponse); 46 | 47 | return response; 48 | } 49 | 50 | private static void Dispose(IDisposable disposable) 51 | { 52 | if (disposable != null) 53 | { 54 | disposable.Dispose(); 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /PactNet.Tests/Mocks/MockHttpService/Mappers/HttpContentMapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Net.Http.Headers; 4 | using System.Text; 5 | using PactNet.Mocks.MockHttpService.Mappers; 6 | using PactNet.Mocks.MockHttpService.Models; 7 | using Xunit; 8 | 9 | namespace PactNet.Tests.Mocks.MockHttpService.Mappers 10 | { 11 | public class HttpContentMapperTests 12 | { 13 | private IHttpContentMapper GetSubject() 14 | { 15 | return new HttpContentMapper(); 16 | } 17 | 18 | [Fact] 19 | public void Convert_WithNullHttpBodyContent_ReturnsNull() 20 | { 21 | var mapper = GetSubject(); 22 | 23 | var result = mapper.Convert(null); 24 | 25 | Assert.Null(result); 26 | } 27 | 28 | [Fact] 29 | public void Convert_WithEmptyContent_ReturnsNull() 30 | { 31 | var httpBodyContent = new HttpBodyContent(content: Encoding.UTF8.GetBytes(String.Empty), contentType: new MediaTypeHeaderValue("text/plain") { CharSet = "utf-8" }); 32 | var mapper = GetSubject(); 33 | 34 | var result = mapper.Convert(httpBodyContent); 35 | 36 | Assert.Empty(result.ReadAsStringAsync().Result); 37 | } 38 | 39 | [Fact] 40 | public void Convert_WithContentTypeContainingParameter_ReturnsContentWithContentTypeHeader() 41 | { 42 | const string contentType = "text/plain"; 43 | const string content = "test"; 44 | NameValueHeaderValue versionParameter = new NameValueHeaderValue("version", "1"); 45 | 46 | var httpBodyContent = new HttpBodyContent( 47 | content: Encoding.UTF8.GetBytes(content), 48 | contentType: new MediaTypeHeaderValue(contentType) { CharSet = "utf-8", Parameters = { versionParameter } }); 49 | IHttpContentMapper mapper = GetSubject(); 50 | 51 | HttpContent result = mapper.Convert(httpBodyContent); 52 | 53 | Assert.Equal(contentType, result.Headers.ContentType.MediaType); 54 | Assert.Contains(versionParameter, result.Headers.ContentType.Parameters); 55 | Assert.Equal("utf-8", result.Headers.ContentType.CharSet); 56 | Assert.Equal(content, result.ReadAsStringAsync().Result); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/ProviderServiceResponseComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using PactNet.Comparers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Comparers 6 | { 7 | internal class ProviderServiceResponseComparer : IProviderServiceResponseComparer 8 | { 9 | private readonly IHttpStatusCodeComparer _httpStatusCodeComparer; 10 | private readonly IHttpHeaderComparer _httpHeaderComparer; 11 | private readonly IHttpBodyComparer _httpBodyComparer; 12 | 13 | public ProviderServiceResponseComparer() 14 | { 15 | _httpStatusCodeComparer = new HttpStatusCodeComparer(); 16 | _httpHeaderComparer = new HttpHeaderComparer(); 17 | _httpBodyComparer = new HttpBodyComparer(); 18 | } 19 | 20 | public ComparisonResult Compare(ProviderServiceResponse expected, ProviderServiceResponse actual) 21 | { 22 | var result = new ComparisonResult("returns a response which"); 23 | 24 | if (expected == null) 25 | { 26 | result.RecordFailure(new ErrorMessageComparisonFailure("Expected response cannot be null")); 27 | return result; 28 | } 29 | 30 | var statusResult = _httpStatusCodeComparer.Compare(expected.Status, actual.Status); 31 | result.AddChildResult(statusResult); 32 | 33 | if (expected.Headers != null && expected.Headers.Any()) 34 | { 35 | var headerResult = _httpHeaderComparer.Compare(expected.Headers, actual.Headers); 36 | result.AddChildResult(headerResult); 37 | } 38 | 39 | if (expected.Body != null) 40 | { 41 | var bodyResult = _httpBodyComparer.Compare(expected.Body, actual.Body, expected.MatchingRules); 42 | result.AddChildResult(bodyResult); 43 | } 44 | else if (expected.Body == null && 45 | expected.ShouldSerializeBody() && //Body was explicitly set 46 | actual.Body != null) 47 | { 48 | result.RecordFailure(new ErrorMessageComparisonFailure("Expected response body was explicitly set to null however the actual response body was not null")); 49 | } 50 | 51 | return result; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/HttpBodyComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Newtonsoft.Json.Linq; 4 | using PactNet.Comparers; 5 | using PactNet.Matchers; 6 | 7 | namespace PactNet.Mocks.MockHttpService.Comparers 8 | { 9 | internal class HttpBodyComparer : IHttpBodyComparer 10 | { 11 | public ComparisonResult Compare(dynamic expected, dynamic actual, IDictionary matchingRules) 12 | { 13 | var result = new ComparisonResult("has a matching body"); 14 | 15 | if (expected == null) 16 | { 17 | return result; 18 | } 19 | 20 | if (expected != null && actual == null) 21 | { 22 | result.RecordFailure(new ErrorMessageComparisonFailure("Actual Body is null")); 23 | return result; 24 | } 25 | 26 | var expectedToken = JToken.FromObject(expected); 27 | var actualToken = JToken.FromObject(actual); 28 | 29 | foreach (var rule in matchingRules) 30 | { 31 | MatcherResult matchResult = rule.Value.Match(rule.Key, expectedToken, actualToken); 32 | 33 | //TODO: Maybe we should call this a list of differences 34 | var comparisonFailures = new List(); 35 | 36 | foreach (var failedCheck in matchResult.MatcherChecks.Where(x => x is FailedMatcherCheck).Cast()) 37 | { 38 | //TODO: We should be able to generate a better output, as we know exactly the path that failed 39 | var comparisonFailure = new DiffComparisonFailure(expectedToken, actualToken); 40 | if (comparisonFailures.All(x => x.Result != comparisonFailure.Result)) 41 | { 42 | comparisonFailures.Add(comparisonFailure); 43 | } 44 | } 45 | 46 | foreach (var failure in comparisonFailures) 47 | { 48 | result.RecordFailure(failure); 49 | } 50 | 51 | //TODO: When more than 1 rule deal with the situation when a success overrides a failure (either more specific rule or order it's applied?) 52 | } 53 | 54 | return result; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Controllers/EventsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Web.Http; 7 | using Provider.Api.Web.Models; 8 | 9 | namespace Provider.Api.Web.Controllers 10 | { 11 | public class EventsController : ApiController 12 | { 13 | [Authorize] 14 | [Route("events")] 15 | public IEnumerable Get() 16 | { 17 | return GetAllEventsFromRepo(); 18 | } 19 | 20 | [Route("events/{id}")] 21 | public Event GetById(Guid id) 22 | { 23 | return GetAllEventsFromRepo().First(x => x.EventId == id); 24 | } 25 | 26 | [Route("events")] 27 | public IEnumerable GetByType(string type) 28 | { 29 | return GetAllEventsFromRepo().Where(x => x.EventType.Equals(type, StringComparison.InvariantCultureIgnoreCase)); 30 | } 31 | 32 | [Route("events")] 33 | public HttpResponseMessage Post(Event @event) 34 | { 35 | if (@event == null) 36 | { 37 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 38 | } 39 | 40 | return new HttpResponseMessage(HttpStatusCode.Created); 41 | } 42 | 43 | private IEnumerable GetAllEventsFromRepo() 44 | { 45 | return new List 46 | { 47 | new Event 48 | { 49 | EventId = Guid.Parse("45D80D13-D5A2-48D7-8353-CBB4C0EAABF5"), 50 | Timestamp = DateTime.Parse("2014-06-30T01:37:41.0660548"), 51 | EventType = "SearchView" 52 | }, 53 | new Event 54 | { 55 | EventId = Guid.Parse("83F9262F-28F1-4703-AB1A-8CFD9E8249C9"), 56 | Timestamp = DateTime.Parse("2014-06-30T01:37:52.2618864"), 57 | EventType = "DetailsView" 58 | }, 59 | new Event 60 | { 61 | EventId = Guid.Parse("3E83A96B-2A0C-49B1-9959-26DF23F83AEB"), 62 | Timestamp = DateTime.Parse("2014-06-30T01:38:00.8518952"), 63 | EventType = "SearchView" 64 | } 65 | }; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /PactNet.Tests/Mocks/MockHttpService/Mappers/HttpVerbMapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Mocks.MockHttpService.Mappers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | using Xunit; 5 | 6 | namespace PactNet.Tests.Mocks.MockHttpService.Mappers 7 | { 8 | public class HttpVerbMapperTests 9 | { 10 | private IHttpVerbMapper GetSubject() 11 | { 12 | return new HttpVerbMapper(); 13 | } 14 | 15 | [Fact] 16 | public void Convert_WithValueThatDoesNotHaveAMap_ThrowsArgumentException() 17 | { 18 | var mapper = GetSubject(); 19 | 20 | Assert.Throws(() => mapper.Convert("blah")); 21 | } 22 | 23 | [Fact] 24 | public void Convert_WithGetString_ReturnsHttpVerbGet() 25 | { 26 | var mapper = GetSubject(); 27 | 28 | var result = mapper.Convert("GET"); 29 | 30 | Assert.Equal(HttpVerb.Get, result); 31 | } 32 | 33 | [Fact] 34 | public void Convert_WithPostString_ReturnsHttpVerbPost() 35 | { 36 | var mapper = GetSubject(); 37 | 38 | var result = mapper.Convert("POST"); 39 | 40 | Assert.Equal(HttpVerb.Post, result); 41 | } 42 | 43 | [Fact] 44 | public void Convert_WithPutString_ReturnsHttpVerbPut() 45 | { 46 | var mapper = GetSubject(); 47 | 48 | var result = mapper.Convert("PUT"); 49 | 50 | Assert.Equal(HttpVerb.Put, result); 51 | } 52 | 53 | [Fact] 54 | public void Convert_WithDeleteString_ReturnsHttpVerbDelete() 55 | { 56 | var mapper = GetSubject(); 57 | 58 | var result = mapper.Convert("DELETE"); 59 | 60 | Assert.Equal(HttpVerb.Delete, result); 61 | } 62 | 63 | [Fact] 64 | public void Convert_WithHeadString_ReturnsHttpVerbHead() 65 | { 66 | var mapper = GetSubject(); 67 | 68 | var result = mapper.Convert("HEAD"); 69 | 70 | Assert.Equal(HttpVerb.Head, result); 71 | } 72 | 73 | [Fact] 74 | public void Convert_WithPatchString_ReturnsHttpVerbPatch() 75 | { 76 | var mapper = GetSubject(); 77 | 78 | var result = mapper.Convert("PATCH"); 79 | 80 | Assert.Equal(HttpVerb.Patch, result); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/ProviderServiceRequestMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Nancy; 5 | using PactNet.Mocks.MockHttpService.Models; 6 | 7 | namespace PactNet.Mocks.MockHttpService.Mappers 8 | { 9 | internal class ProviderServiceRequestMapper : IProviderServiceRequestMapper 10 | { 11 | private readonly IHttpVerbMapper _httpVerbMapper; 12 | private readonly IHttpBodyContentMapper _httpBodyContentMapper; 13 | 14 | internal ProviderServiceRequestMapper( 15 | IHttpVerbMapper httpVerbMapper, 16 | IHttpBodyContentMapper httpBodyContentMapper) 17 | { 18 | _httpVerbMapper = httpVerbMapper; 19 | _httpBodyContentMapper = httpBodyContentMapper; 20 | } 21 | 22 | public ProviderServiceRequestMapper() : this( 23 | new HttpVerbMapper(), 24 | new HttpBodyContentMapper()) 25 | { 26 | } 27 | 28 | public ProviderServiceRequest Convert(Request from) 29 | { 30 | if (from == null) 31 | { 32 | return null; 33 | } 34 | 35 | var httpVerb = _httpVerbMapper.Convert(from.Method.ToUpper()); 36 | 37 | var to = new ProviderServiceRequest 38 | { 39 | Method = httpVerb, 40 | Path = from.Path, 41 | Query = !String.IsNullOrEmpty(from.Url.Query) ? from.Url.Query.TrimStart('?') : null 42 | }; 43 | 44 | if (from.Headers != null && from.Headers.Any()) 45 | { 46 | var fromHeaders = from.Headers.ToDictionary(x => x.Key, x => String.Join(", ", x.Value)); 47 | to.Headers = fromHeaders; 48 | } 49 | 50 | if (from.Body != null && from.Body.Length > 0) 51 | { 52 | var httpBodyContent = _httpBodyContentMapper.Convert(content: ConvertStreamToBytes(from.Body), headers: to.Headers); 53 | 54 | to.Body = httpBodyContent.Body; 55 | } 56 | 57 | return to; 58 | } 59 | 60 | private static byte[] ConvertStreamToBytes(Stream content) 61 | { 62 | using (var memoryStream = new MemoryStream()) 63 | { 64 | content.CopyTo(memoryStream); 65 | return memoryStream.ToArray(); 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /PactNet/Comparers/ComparisonResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace PactNet.Comparers 6 | { 7 | internal class ComparisonResult 8 | { 9 | public string Message { get; private set; } 10 | 11 | private readonly IList _failures = new List(); 12 | public IEnumerable Failures 13 | { 14 | get 15 | { 16 | var failuresDeep = new List(); 17 | GetChildComparisonResultFailures(this, failuresDeep); 18 | return failuresDeep; 19 | } 20 | } 21 | 22 | public bool HasFailure 23 | { 24 | get 25 | { 26 | return Failures.Any(); 27 | } 28 | } 29 | 30 | public int ShallowFailureCount 31 | { 32 | get 33 | { 34 | return _failures.Count(); 35 | } 36 | } 37 | 38 | private readonly IList _childResults = new List(); 39 | public IEnumerable ChildResults 40 | { 41 | get { return _childResults; } 42 | } 43 | 44 | public ComparisonResult(string message = null) 45 | { 46 | Message = message; 47 | } 48 | 49 | public ComparisonResult(string messageFormat, params object[] args) 50 | { 51 | Message = String.Format(messageFormat, args); 52 | } 53 | 54 | public void RecordFailure(ComparisonFailure comparisonFailure) 55 | { 56 | _failures.Add(comparisonFailure); 57 | } 58 | 59 | public void AddChildResult(ComparisonResult comparisonResult) 60 | { 61 | if (comparisonResult == null) 62 | { 63 | return; 64 | } 65 | 66 | _childResults.Add(comparisonResult); 67 | } 68 | 69 | private static void GetChildComparisonResultFailures(ComparisonResult comparisonResult, List fails) 70 | { 71 | fails.AddRange(comparisonResult._failures); 72 | foreach (var childComparisonResult in comparisonResult.ChildResults) 73 | { 74 | GetChildComparisonResultFailures(childComparisonResult, fails); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Models/ProviderServiceRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using PactNet.Configuration.Json.Converters; 5 | using PactNet.Matchers; 6 | using PactNet.Mocks.MockHttpService.Matchers; 7 | 8 | namespace PactNet.Mocks.MockHttpService.Models 9 | { 10 | public class ProviderServiceRequest : IHttpMessage 11 | { 12 | private bool _bodyWasSet; 13 | private dynamic _body; 14 | 15 | [JsonProperty(PropertyName = "method")] 16 | [JsonConverter(typeof(CamelCaseStringEnumConverter))] 17 | public HttpVerb Method { get; set; } 18 | 19 | [JsonProperty(PropertyName = "path")] 20 | public string Path { get; set; } 21 | 22 | [JsonProperty(PropertyName = "query")] 23 | public string Query { get; set; } 24 | 25 | [JsonProperty(PropertyName = "headers")] 26 | [JsonConverter(typeof(PreserveCasingDictionaryConverter))] 27 | public IDictionary Headers { get; set; } 28 | 29 | [JsonIgnore] 30 | [JsonProperty(PropertyName = "matchingRules")] 31 | internal IDictionary MatchingRules { get; private set; } 32 | 33 | [JsonProperty(PropertyName = "body", NullValueHandling = NullValueHandling.Include)] 34 | public dynamic Body 35 | { 36 | get { return _body; } 37 | set 38 | { 39 | _bodyWasSet = true; 40 | _body = ParseBodyMatchingRules(value); 41 | } 42 | } 43 | 44 | // A not so well known feature in JSON.Net to do conditional serialization at runtime 45 | public bool ShouldSerializeBody() 46 | { 47 | return _bodyWasSet; 48 | } 49 | 50 | private dynamic ParseBodyMatchingRules(dynamic body) 51 | { 52 | MatchingRules = new Dictionary 53 | { 54 | { DefaultHttpBodyMatcher.Path, new DefaultHttpBodyMatcher(false) } 55 | }; 56 | 57 | return body; 58 | } 59 | 60 | public string PathWithQuery() 61 | { 62 | if (String.IsNullOrEmpty(Path) && !String.IsNullOrEmpty(Query)) 63 | { 64 | throw new InvalidOperationException("Query has been supplied, however Path has not. Please specify as Path."); 65 | } 66 | 67 | return !String.IsNullOrEmpty(Query) ? 68 | String.Format("{0}?{1}", Path, Query) : 69 | Path; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Nancy/MockProviderNancyBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions; 3 | using Nancy; 4 | using Nancy.Bootstrapper; 5 | using Nancy.Diagnostics; 6 | using Nancy.TinyIoc; 7 | using PactNet.Logging; 8 | using PactNet.Mocks.MockHttpService.Comparers; 9 | using PactNet.Mocks.MockHttpService.Mappers; 10 | 11 | namespace PactNet.Mocks.MockHttpService.Nancy 12 | { 13 | public class MockProviderNancyBootstrapper : DefaultNancyBootstrapper 14 | { 15 | private readonly PactConfig _config; 16 | 17 | public MockProviderNancyBootstrapper(PactConfig config) 18 | { 19 | _config = config; 20 | } 21 | 22 | protected override IEnumerable Modules 23 | { 24 | get { return new List(); } 25 | } 26 | 27 | protected override NancyInternalConfiguration InternalConfiguration 28 | { 29 | get 30 | { 31 | return NancyInternalConfiguration.WithOverrides(c => 32 | { 33 | c.RequestDispatcher = typeof(MockProviderNancyRequestDispatcher); 34 | c.StatusCodeHandlers.Clear(); 35 | }); 36 | } 37 | } 38 | 39 | protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) 40 | { 41 | RegisterDependenciesWithNancyContainer(container); 42 | 43 | DiagnosticsHook.Disable(pipelines); 44 | } 45 | 46 | protected override void ConfigureApplicationContainer(TinyIoCContainer container) 47 | { 48 | } 49 | 50 | private void RegisterDependenciesWithNancyContainer(TinyIoCContainer container) 51 | { 52 | container.Register(typeof(PactConfig), (c, o) => _config); 53 | container.Register().AsMultiInstance(); 54 | container.Register().AsMultiInstance(); 55 | container.Register().AsMultiInstance(); 56 | container.Register().AsMultiInstance(); 57 | container.Register().AsMultiInstance(); 58 | container.Register().AsSingleton(); 59 | container.Register().AsMultiInstance(); 60 | container.Register(typeof(ILog), (c, o) => LogProvider.GetLogger(_config.LoggerName)); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/HttpQueryStringComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | using Nancy.Helpers; 5 | using PactNet.Comparers; 6 | 7 | namespace PactNet.Mocks.MockHttpService.Comparers 8 | { 9 | internal class HttpQueryStringComparer : IHttpQueryStringComparer 10 | { 11 | public ComparisonResult Compare(string expected, string actual) 12 | { 13 | if (String.IsNullOrEmpty(expected) && String.IsNullOrEmpty(actual)) 14 | { 15 | return new ComparisonResult("has no query strings"); 16 | } 17 | 18 | var normalisedExpectedQuery = NormaliseUrlEncodingAndTrimTrailingAmpersand(expected); 19 | var normalisedActualQuery = NormaliseUrlEncodingAndTrimTrailingAmpersand(actual); 20 | var result = new ComparisonResult("has query {0}", normalisedExpectedQuery ?? "null"); 21 | 22 | if (expected == null || actual == null) 23 | { 24 | result.RecordFailure(new DiffComparisonFailure(expected, actual)); 25 | return result; 26 | } 27 | 28 | var expectedQueryItems = HttpUtility.ParseQueryString(normalisedExpectedQuery); 29 | var actualQueryItems = HttpUtility.ParseQueryString(normalisedActualQuery); 30 | 31 | if (expectedQueryItems.Count != actualQueryItems.Count) 32 | { 33 | result.RecordFailure(new DiffComparisonFailure(normalisedExpectedQuery, normalisedActualQuery)); 34 | return result; 35 | } 36 | 37 | foreach (string expectedKey in expectedQueryItems) 38 | { 39 | if (!actualQueryItems.AllKeys.Contains(expectedKey)) 40 | { 41 | result.RecordFailure(new DiffComparisonFailure(normalisedExpectedQuery, normalisedActualQuery)); 42 | return result; 43 | } 44 | 45 | var expectedValue = expectedQueryItems[expectedKey]; 46 | var actualValue = actualQueryItems[expectedKey]; 47 | 48 | if (expectedValue != actualValue) 49 | { 50 | result.RecordFailure(new DiffComparisonFailure(normalisedExpectedQuery, normalisedActualQuery)); 51 | return result; 52 | } 53 | } 54 | 55 | return result; 56 | } 57 | 58 | private string NormaliseUrlEncodingAndTrimTrailingAmpersand(string query) 59 | { 60 | return query != null ? 61 | Regex.Replace(query, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper()).TrimEnd('&') : 62 | query; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /PactNet.Tests/Mocks/MockHttpService/Models/ProviderServiceResponseTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Serialization; 4 | using PactNet.Configuration.Json; 5 | using PactNet.Mocks.MockHttpService.Models; 6 | using Xunit; 7 | 8 | namespace PactNet.Tests.Mocks.MockHttpService.Models 9 | { 10 | public class ProviderServiceResponseTests 11 | { 12 | [Fact] 13 | public void SerializeObject_WithDefaultApiSerializerSettings_ReturnsCorrectJson() 14 | { 15 | var request = new ProviderServiceResponse 16 | { 17 | Status = 200, 18 | Headers = new Dictionary { { "Content-Type", "application/json" } }, 19 | Body = new 20 | { 21 | Test1 = "hi", 22 | test2 = 2 23 | } 24 | }; 25 | 26 | var responseJson = JsonConvert.SerializeObject(request, JsonConfig.ApiSerializerSettings); 27 | var expectedJson = "{\"status\":200,\"headers\":{\"Content-Type\":\"application/json\"},\"body\":{\"Test1\":\"hi\",\"test2\":2}}"; 28 | Assert.Equal(expectedJson, responseJson); 29 | } 30 | 31 | [Fact] 32 | public void SerializeObject_WithDefaultApiSerializerSettingsAndNoHeadersOrBody_ReturnsCorrectJson() 33 | { 34 | var response = new ProviderServiceResponse 35 | { 36 | Status = 500 37 | }; 38 | 39 | var responseJson = JsonConvert.SerializeObject(response, JsonConfig.ApiSerializerSettings); 40 | var expectedJson = "{\"status\":500}"; 41 | Assert.Equal(expectedJson, responseJson); 42 | } 43 | 44 | [Fact] 45 | public void SerializeObject_WithCamelCaseApiSerializerSettings_ReturnsCorrectJson() 46 | { 47 | var response = new ProviderServiceResponse 48 | { 49 | Status = 200, 50 | Headers = new Dictionary { { "Content-Type", "application/json" } }, 51 | Body = new 52 | { 53 | Test1 = "hi", 54 | test2 = 2 55 | } 56 | }; 57 | 58 | var responseJson = JsonConvert.SerializeObject(response, new JsonSerializerSettings 59 | { 60 | NullValueHandling = NullValueHandling.Ignore, 61 | Formatting = Formatting.None, 62 | ContractResolver = new CamelCasePropertyNamesContractResolver() 63 | }); 64 | var expectedJson = "{\"status\":200,\"headers\":{\"Content-Type\":\"application/json\"},\"body\":{\"test1\":\"hi\",\"test2\":2}}"; 65 | Assert.Equal(expectedJson, responseJson); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Matchers/DefaultHttpBodyMatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Newtonsoft.Json.Linq; 4 | using PactNet.Matchers; 5 | 6 | namespace PactNet.Mocks.MockHttpService.Matchers 7 | { 8 | internal class DefaultHttpBodyMatcher : IMatcher 9 | { 10 | public const string Path = "$..*"; 11 | 12 | public bool AllowExtraKeys { get; private set; } 13 | 14 | public DefaultHttpBodyMatcher(bool allowExtraKeysInObjects) 15 | { 16 | AllowExtraKeys = allowExtraKeysInObjects; 17 | } 18 | 19 | public MatcherResult Match(string path, JToken expected, JToken actual) 20 | { 21 | if (expected is JValue) 22 | { 23 | return actual != null && expected.Equals(actual) ? 24 | new MatcherResult(new SuccessfulMatcherCheck(path)) : 25 | new MatcherResult(new FailedMatcherCheck(path, MatcherCheckFailureType.ValueDoesNotMatch)); 26 | } 27 | 28 | var checks = new List(); 29 | 30 | var expectedTokens = expected.SelectTokens(path); 31 | 32 | foreach (var expectedToken in expectedTokens) 33 | { 34 | if (expectedToken is JArray || (!AllowExtraKeys && expectedToken is JObject)) 35 | { 36 | var actualToken = actual.SelectToken(expectedToken.Path); 37 | 38 | if (actualToken != null && expectedToken.Count().Equals(actualToken.Count())) 39 | { 40 | checks.Add(new SuccessfulMatcherCheck(expectedToken.Path)); 41 | } 42 | else 43 | { 44 | var failureType = expectedToken is JArray ? 45 | MatcherCheckFailureType.AdditionalItemInArray : 46 | MatcherCheckFailureType.AdditionalPropertyInObject; 47 | 48 | checks.Add(new FailedMatcherCheck(expectedToken.Path, failureType)); 49 | } 50 | } 51 | else if (expectedToken is JValue) 52 | { 53 | var actualToken = actual.SelectToken(expectedToken.Path); 54 | 55 | if (actualToken != null && expectedToken.Equals(actualToken)) 56 | { 57 | checks.Add(new SuccessfulMatcherCheck(expectedToken.Path)); 58 | } 59 | else 60 | { 61 | checks.Add(new FailedMatcherCheck(expectedToken.Path, MatcherCheckFailureType.ValueDoesNotMatch)); 62 | } 63 | } 64 | } 65 | 66 | return new MatcherResult(checks); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /PactNet.Tests/Mocks/MockHttpService/Mappers/HttpMethodMapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using PactNet.Mocks.MockHttpService.Mappers; 4 | using PactNet.Mocks.MockHttpService.Models; 5 | using Xunit; 6 | 7 | namespace PactNet.Tests.Mocks.MockHttpService.Mappers 8 | { 9 | public class HttpMethodMapperTests 10 | { 11 | private IHttpMethodMapper GetSubject() 12 | { 13 | return new HttpMethodMapper(); 14 | } 15 | 16 | [Fact] 17 | public void Convert_WithHttpVerbThatDoesNotHaveAMap_ThrowsArgumentException() 18 | { 19 | var mapper = GetSubject(); 20 | 21 | Assert.Throws(() => mapper.Convert((HttpVerb)10000)); 22 | } 23 | 24 | [Fact] 25 | public void Convert_WithGetHttpVerb_ReturnsGetHttpMethod() 26 | { 27 | var mapper = GetSubject(); 28 | 29 | var httpMethod = mapper.Convert(HttpVerb.Get); 30 | 31 | Assert.Equal(HttpMethod.Get, httpMethod); 32 | } 33 | 34 | [Fact] 35 | public void Convert_WithPostHttpVerb_ReturnsPostHttpMethod() 36 | { 37 | var mapper = GetSubject(); 38 | 39 | var httpMethod = mapper.Convert(HttpVerb.Post); 40 | 41 | Assert.Equal(HttpMethod.Post, httpMethod); 42 | } 43 | 44 | 45 | [Fact] 46 | public void Convert_WithPutHttpVerb_ReturnsPutHttpMethod() 47 | { 48 | var mapper = GetSubject(); 49 | 50 | var httpMethod = mapper.Convert(HttpVerb.Put); 51 | 52 | Assert.Equal(HttpMethod.Put, httpMethod); 53 | } 54 | 55 | [Fact] 56 | public void Convert_WithDeleteHttpVerb_ReturnsDeleteHttpMethod() 57 | { 58 | var mapper = GetSubject(); 59 | 60 | var httpMethod = mapper.Convert(HttpVerb.Delete); 61 | 62 | Assert.Equal(HttpMethod.Delete, httpMethod); 63 | } 64 | 65 | [Fact] 66 | public void Convert_WithHeadHttpVerb_ReturnsHeadHttpMethod() 67 | { 68 | var mapper = GetSubject(); 69 | 70 | var httpMethod = mapper.Convert(HttpVerb.Head); 71 | 72 | Assert.Equal(HttpMethod.Head, httpMethod); 73 | } 74 | 75 | 76 | [Fact] 77 | public void Convert_WithPatchHttpVerb_ReturnsPatchHttpMethod() 78 | { 79 | var mapper = GetSubject(); 80 | 81 | var httpMethod = mapper.Convert(HttpVerb.Patch); 82 | 83 | Assert.Equal("PATCH", httpMethod.ToString()); 84 | } 85 | 86 | [Fact] 87 | public void Convert_WithNotSetHttpVerb_ThrowsArgumentException() 88 | { 89 | var mapper = GetSubject(); 90 | 91 | Assert.Throws(() => mapper.Convert(HttpVerb.NotSet)); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Nancy/MockProviderRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Nancy; 3 | using Newtonsoft.Json; 4 | using PactNet.Configuration.Json; 5 | using PactNet.Logging; 6 | using PactNet.Mocks.MockHttpService.Mappers; 7 | using PactNet.Mocks.MockHttpService.Models; 8 | 9 | namespace PactNet.Mocks.MockHttpService.Nancy 10 | { 11 | internal class MockProviderRequestHandler : IMockProviderRequestHandler 12 | { 13 | private readonly INancyResponseMapper _responseMapper; 14 | private readonly IProviderServiceRequestMapper _requestMapper; 15 | private readonly IMockProviderRepository _mockProviderRepository; 16 | private readonly ILog _log; 17 | 18 | public MockProviderRequestHandler( 19 | IProviderServiceRequestMapper requestMapper, 20 | INancyResponseMapper responseMapper, 21 | IMockProviderRepository mockProviderRepository, 22 | ILog log) 23 | { 24 | _requestMapper = requestMapper; 25 | _responseMapper = responseMapper; 26 | _mockProviderRepository = mockProviderRepository; 27 | _log = log; 28 | } 29 | 30 | public Response Handle(NancyContext context) 31 | { 32 | return HandlePactRequest(context); 33 | } 34 | 35 | private Response HandlePactRequest(NancyContext context) 36 | { 37 | var actualRequest = _requestMapper.Convert(context.Request); 38 | var actualRequestMethod = actualRequest.Method.ToString().ToUpperInvariant(); 39 | var actualRequestPath = actualRequest.Path; 40 | 41 | _log.InfoFormat("Received request {0} {1}", actualRequestMethod, actualRequestPath); 42 | _log.Debug(JsonConvert.SerializeObject(actualRequest, JsonConfig.PactFileSerializerSettings)); 43 | 44 | ProviderServiceInteraction matchingInteraction; 45 | 46 | try 47 | { 48 | matchingInteraction = _mockProviderRepository.GetMatchingTestScopedInteraction(actualRequest); 49 | _mockProviderRepository.AddHandledRequest(new HandledRequest(actualRequest, matchingInteraction)); 50 | 51 | _log.InfoFormat("Found matching response for {0} {1}", actualRequestMethod, actualRequestPath); 52 | _log.Debug(JsonConvert.SerializeObject(matchingInteraction.Response, JsonConfig.PactFileSerializerSettings)); 53 | } 54 | catch (Exception) 55 | { 56 | _log.ErrorFormat("No matching interaction found for {0} {1}", actualRequestMethod, actualRequestPath); 57 | _mockProviderRepository.AddHandledRequest(new HandledRequest(actualRequest, null)); 58 | throw; 59 | } 60 | 61 | return _responseMapper.Convert(matchingInteraction.Response); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /PactNet/Infrastructure/Logging/LocalRollingLogFileMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Abstractions; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace PactNet.Infrastructure.Logging 8 | { 9 | internal class LocalRollingLogFileMessageHandler : ILocalLogMessageHandler 10 | { 11 | public string LogPath { get; set; } 12 | 13 | private readonly object _sync = new object(); 14 | private readonly StreamWriter _writer; 15 | 16 | internal LocalRollingLogFileMessageHandler(IFileSystem fileSystem, string logFilePath) 17 | { 18 | LogPath = logFilePath; 19 | TryCreateDirectory(logFilePath); 20 | var file = fileSystem.File.Open(logFilePath, FileMode.Append, FileAccess.Write, FileShare.Read); 21 | _writer = new StreamWriter(file, Encoding.UTF8); 22 | } 23 | 24 | internal LocalRollingLogFileMessageHandler(string logFilePath) 25 | : this(new FileSystem(), logFilePath) 26 | { 27 | } 28 | 29 | public void Handle(LocalLogMessage logMessage) 30 | { 31 | var messageFormat = logMessage.MessagePredicate != null ? 32 | logMessage.MessagePredicate() : 33 | String.Empty; 34 | 35 | string message; 36 | if (logMessage.Exception != null) 37 | { 38 | message = String.Format("{0}. Exception: {1} - {2}", messageFormat, logMessage.Exception, logMessage.Exception.StackTrace); 39 | } 40 | else if (logMessage.FormatParameters != null && logMessage.FormatParameters.Any()) 41 | { 42 | message = String.Format(messageFormat, logMessage.FormatParameters); 43 | } 44 | else 45 | { 46 | message = messageFormat; 47 | } 48 | 49 | lock (_sync) 50 | { 51 | _writer.WriteLine("{0} [{1}] {2}", logMessage.DateTimeFormatted, logMessage.Level, message); 52 | _writer.Flush(); 53 | } 54 | } 55 | 56 | public void Dispose() 57 | { 58 | if (_writer != null) 59 | { 60 | _writer.Dispose(); 61 | } 62 | } 63 | 64 | private static void TryCreateDirectory(string filePath) 65 | { 66 | try 67 | { 68 | var directory = Path.GetDirectoryName(filePath); 69 | if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) 70 | { 71 | Directory.CreateDirectory(directory); 72 | } 73 | } 74 | catch (Exception ex) 75 | { 76 | throw new Exception("Could not create log directory.", ex); 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/ProviderServiceRequestComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using PactNet.Comparers; 3 | using PactNet.Mocks.MockHttpService.Models; 4 | 5 | namespace PactNet.Mocks.MockHttpService.Comparers 6 | { 7 | internal class ProviderServiceRequestComparer : IProviderServiceRequestComparer 8 | { 9 | private readonly IHttpMethodComparer _httpMethodComparer; 10 | private readonly IHttpPathComparer _httpPathComparer; 11 | private readonly IHttpQueryStringComparer _httpQueryStringComparer; 12 | private readonly IHttpHeaderComparer _httpHeaderComparer; 13 | private readonly IHttpBodyComparer _httpBodyComparer; 14 | 15 | public ProviderServiceRequestComparer() 16 | { 17 | _httpMethodComparer = new HttpMethodComparer(); 18 | _httpPathComparer = new HttpPathComparer(); 19 | _httpQueryStringComparer = new HttpQueryStringComparer(); 20 | _httpHeaderComparer = new HttpHeaderComparer(); 21 | _httpBodyComparer = new HttpBodyComparer(); 22 | } 23 | 24 | public ComparisonResult Compare(ProviderServiceRequest expected, ProviderServiceRequest actual) 25 | { 26 | var result = new ComparisonResult("sends a request which"); 27 | 28 | if (expected == null) 29 | { 30 | result.RecordFailure(new ErrorMessageComparisonFailure("Expected request cannot be null")); 31 | return result; 32 | } 33 | 34 | var methodResult = _httpMethodComparer.Compare(expected.Method, actual.Method); 35 | result.AddChildResult(methodResult); 36 | 37 | var pathResult = _httpPathComparer.Compare(expected.Path, actual.Path); 38 | result.AddChildResult(pathResult); 39 | 40 | var queryResult = _httpQueryStringComparer.Compare(expected.Query, actual.Query); 41 | result.AddChildResult(queryResult); 42 | 43 | if (expected.Headers != null && expected.Headers.Any()) 44 | { 45 | var headerResult = _httpHeaderComparer.Compare(expected.Headers, actual.Headers); 46 | result.AddChildResult(headerResult); 47 | } 48 | 49 | if (expected.Body != null) 50 | { 51 | var bodyResult = _httpBodyComparer.Compare(expected.Body, actual.Body, expected.MatchingRules); 52 | result.AddChildResult(bodyResult); 53 | } 54 | else if (expected.Body == null && 55 | expected.ShouldSerializeBody() && //Body was explicitly set 56 | actual.Body != null) 57 | { 58 | result.RecordFailure(new ErrorMessageComparisonFailure("Expected request body was explicitly set to null however the actual request body was not null")); 59 | } 60 | 61 | return result; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Comparers/HttpHeaderComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using PactNet.Comparers; 5 | 6 | namespace PactNet.Mocks.MockHttpService.Comparers 7 | { 8 | internal class HttpHeaderComparer : IHttpHeaderComparer 9 | { 10 | public ComparisonResult Compare(IDictionary expected, IDictionary actual) 11 | { 12 | var result = new ComparisonResult("includes headers"); 13 | 14 | if (actual == null) 15 | { 16 | result.RecordFailure(new ErrorMessageComparisonFailure("Actual Headers are null")); 17 | return result; 18 | } 19 | 20 | actual = MakeDictionaryCaseInsensitive(actual); 21 | 22 | foreach (var header in expected) 23 | { 24 | var headerResult = new ComparisonResult("'{0}' with value {1}", header.Key, header.Value); 25 | 26 | string actualValue; 27 | 28 | if (actual.TryGetValue(header.Key, out actualValue)) 29 | { 30 | var expectedValue = header.Value; 31 | 32 | var actualValueSplit = actualValue.Split(new[] { ',', ';' }); 33 | if (actualValueSplit.Length == 1) 34 | { 35 | if (!header.Value.Equals(actualValue)) 36 | { 37 | headerResult.RecordFailure(new DiffComparisonFailure(expectedValue, actualValue)); 38 | } 39 | } 40 | else 41 | { 42 | var expectedValueSplit = expectedValue.Split(new[] {',', ';'}); 43 | var expectedValueSplitJoined = String.Join(",", expectedValueSplit.Select(x => x.Trim())); 44 | var actualValueSplitJoined = String.Join(",", actualValueSplit.Select(x => x.Trim())); 45 | 46 | if (!expectedValueSplitJoined.Equals(actualValueSplitJoined)) 47 | { 48 | headerResult.RecordFailure(new DiffComparisonFailure(expectedValue, actualValue)); 49 | } 50 | } 51 | } 52 | else 53 | { 54 | headerResult.RecordFailure(new ErrorMessageComparisonFailure(String.Format("Header with key '{0}', does not exist in actual", header.Key))); 55 | } 56 | 57 | result.AddChildResult(headerResult); 58 | } 59 | 60 | return result; 61 | } 62 | 63 | private IDictionary MakeDictionaryCaseInsensitive(IDictionary from) 64 | { 65 | return new Dictionary(from, StringComparer.InvariantCultureIgnoreCase); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/ProviderServiceResponseMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using PactNet.Extensions; 7 | using PactNet.Mocks.MockHttpService.Models; 8 | 9 | namespace PactNet.Mocks.MockHttpService.Mappers 10 | { 11 | internal class ProviderServiceResponseMapper : IProviderServiceResponseMapper 12 | { 13 | private readonly IHttpBodyContentMapper _httpBodyContentMapper; 14 | 15 | internal ProviderServiceResponseMapper(IHttpBodyContentMapper httpBodyContentMapper) 16 | { 17 | _httpBodyContentMapper = httpBodyContentMapper; 18 | } 19 | 20 | public ProviderServiceResponseMapper() : this( 21 | new HttpBodyContentMapper()) 22 | { 23 | 24 | } 25 | 26 | public ProviderServiceResponse Convert(HttpResponseMessage from) 27 | { 28 | if (from == null) 29 | { 30 | return null; 31 | } 32 | 33 | var to = new ProviderServiceResponse 34 | { 35 | Status = (int) from.StatusCode, 36 | Headers = ConvertHeaders(from.Headers, from.Content) 37 | }; 38 | 39 | if(from.Content != null) 40 | { 41 | var responseContent = from.Content.ReadAsByteArrayAsync().RunSync(); 42 | if (responseContent != null) 43 | { 44 | var httpBodyContent = _httpBodyContentMapper.Convert(content: responseContent, headers: to.Headers); 45 | 46 | to.Body = httpBodyContent.Body; 47 | } 48 | } 49 | 50 | return to; 51 | } 52 | 53 | private Dictionary ConvertHeaders(HttpResponseHeaders responseHeaders, HttpContent httpContent) 54 | { 55 | if ((responseHeaders == null || !responseHeaders.Any()) && 56 | (httpContent == null || (httpContent.Headers == null || !httpContent.Headers.Any()))) 57 | { 58 | return null; 59 | } 60 | 61 | var headers = new Dictionary(); 62 | 63 | if (responseHeaders != null && responseHeaders.Any()) 64 | { 65 | foreach (var responseHeader in responseHeaders) 66 | { 67 | headers.Add(responseHeader.Key, String.Join(", ", responseHeader.Value.Select(x => x))); 68 | } 69 | } 70 | 71 | if (httpContent != null && httpContent.Headers != null && httpContent.Headers.Any()) 72 | { 73 | foreach (var contentHeader in httpContent.Headers) 74 | { 75 | headers.Add(contentHeader.Key, String.Join(", ", contentHeader.Value.Select(x => x))); 76 | } 77 | } 78 | 79 | return headers; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /PactNet.Tests/Comparers/ComparisonResultTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using PactNet.Comparers; 3 | using Xunit; 4 | 5 | namespace PactNet.Tests.Comparers 6 | { 7 | public class ComparisonResultTests 8 | { 9 | private ComparisonResult GetSubject() 10 | { 11 | return new ComparisonResult(); 12 | } 13 | 14 | [Fact] 15 | public void Failures_WithNestedChildResultsWithFailures_ReturnsAllFailuresFromNestedResultsInCorrectOrder() 16 | { 17 | var failure1 = "Failure 1"; 18 | var failure2 = "Failure 2"; 19 | var failure3 = "Failure 3"; 20 | 21 | var nestedChildResult2 = new ComparisonResult(); 22 | nestedChildResult2.RecordFailure(new ErrorMessageComparisonFailure(failure3)); 23 | 24 | var nestedChildResult = new ComparisonResult(); 25 | nestedChildResult.RecordFailure(new ErrorMessageComparisonFailure(failure2)); 26 | nestedChildResult.AddChildResult(nestedChildResult2); 27 | 28 | var comparisonResult = GetSubject(); 29 | 30 | comparisonResult.RecordFailure(new ErrorMessageComparisonFailure(failure1)); 31 | comparisonResult.AddChildResult(nestedChildResult); 32 | 33 | var failures = comparisonResult.Failures; 34 | 35 | Assert.Equal(3, failures.Count()); 36 | Assert.Equal(failure1, failures.First().Result); 37 | Assert.Equal(failure2, failures.Skip(1).First().Result); 38 | Assert.Equal(failure3, failures.Last().Result); 39 | } 40 | 41 | [Fact] 42 | public void HasFailure_WithOnlyNestedChildResultsWithFailures_ReturnsTrue() 43 | { 44 | var nestedChildResult = new ComparisonResult(); 45 | nestedChildResult.RecordFailure(new ErrorMessageComparisonFailure("failure 1")); 46 | 47 | var comparisonResult = GetSubject(); 48 | 49 | comparisonResult.AddChildResult(nestedChildResult); 50 | 51 | var hasFailure = comparisonResult.HasFailure; 52 | 53 | Assert.True(hasFailure); 54 | } 55 | 56 | [Fact] 57 | public void ShallowFailureCount_WithOnlyNestedChildResultsWithFailures_ReturnsZero() 58 | { 59 | var nestedChildResult = new ComparisonResult(); 60 | nestedChildResult.RecordFailure(new ErrorMessageComparisonFailure("failure 1")); 61 | 62 | var comparisonResult = GetSubject(); 63 | 64 | comparisonResult.AddChildResult(nestedChildResult); 65 | 66 | var shallowFailureCount = comparisonResult.ShallowFailureCount; 67 | 68 | Assert.Equal(0, shallowFailureCount); 69 | } 70 | 71 | [Fact] 72 | public void ShallowFailureCount_WithDirectFailure_ReturnsOne() 73 | { 74 | var comparisonResult = GetSubject(); 75 | 76 | comparisonResult.RecordFailure(new ErrorMessageComparisonFailure("failure 1")); 77 | 78 | var shallowFailureCount = comparisonResult.ShallowFailureCount; 79 | 80 | Assert.Equal(1, shallowFailureCount); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PactNet/Infrastructure/Logging/LocalLogProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using PactNet.Logging; 5 | using PactNet.Logging.LogProviders; 6 | 7 | namespace PactNet.Infrastructure.Logging 8 | { 9 | internal class LocalLogProvider : LogProviderBase 10 | { 11 | private readonly object _sync = new object(); 12 | private int _addLoggerRetryCount; 13 | 14 | private readonly IDictionary _loggers = new Dictionary(); 15 | 16 | public override string AddLogger(string logDir, string loggerNameSeed, string logFileNameTemplate = "{0}.log") 17 | { 18 | var loggerName = GenerateUniqueLoggerName(loggerNameSeed); 19 | 20 | var logFileName = String.Format(logFileNameTemplate, loggerName); 21 | var logFilePath = Path.Combine(logDir, logFileName); 22 | 23 | lock (_sync) 24 | { 25 | try 26 | { 27 | _loggers.Add(loggerName, new LocalLogger(Path.GetFullPath(logFilePath))); 28 | } 29 | catch (IOException) 30 | { 31 | if (_addLoggerRetryCount > 2) 32 | { 33 | throw; 34 | } 35 | 36 | _addLoggerRetryCount++; 37 | return AddLogger(logDir, loggerNameSeed + "_" + Guid.NewGuid().ToString("N"), logFileNameTemplate); 38 | } 39 | } 40 | 41 | return loggerName; 42 | } 43 | 44 | public override Logger GetLogger(string name) 45 | { 46 | lock (_sync) 47 | { 48 | if (!String.IsNullOrEmpty(name) && 49 | _loggers.ContainsKey(name)) 50 | { 51 | return _loggers[name].Log; 52 | } 53 | } 54 | return LogProvider.NoOpLogger.Instance.Log; 55 | } 56 | 57 | public override void RemoveLogger(string name) 58 | { 59 | lock (_sync) 60 | { 61 | if (_loggers.ContainsKey(name)) 62 | { 63 | _loggers[name].Dispose(); 64 | _loggers.Remove(name); 65 | } 66 | } 67 | } 68 | 69 | private string GenerateUniqueLoggerName(string nameSeed) 70 | { 71 | var count = 0; 72 | var loggerName = nameSeed; 73 | lock (_sync) 74 | { 75 | while (_loggers.ContainsKey(loggerName)) 76 | { 77 | loggerName = String.Format("{0}.{1}", nameSeed, ++count); 78 | } 79 | } 80 | 81 | return loggerName; 82 | } 83 | 84 | public override string ResolveLogPath(string name) 85 | { 86 | lock (_sync) 87 | { 88 | if (_loggers.ContainsKey(name)) 89 | { 90 | return _loggers[name].LogPath; 91 | } 92 | } 93 | 94 | return String.Empty; 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /PactNet.Tests/IntegrationTests/Specification/MockHttpServiceSpecificationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using NSubstitute.Exceptions; 6 | using Newtonsoft.Json; 7 | using PactNet.Tests.IntegrationTests.Specification.Models; 8 | using Xunit; 9 | 10 | namespace PactNet.Tests.IntegrationTests.Specification 11 | { 12 | public class MockHttpServiceSpecificationTests 13 | { 14 | [Fact] 15 | public void ValidateRequestSpecification() 16 | { 17 | var failedTestCases = RunPactSpecificationTests("..\\..\\IntegrationTests\\Specification\\pact-specification\\testcases\\request"); 18 | 19 | if (failedTestCases.Any()) 20 | { 21 | Console.WriteLine("### FAILED ###"); 22 | foreach (var failedTestCase in failedTestCases) 23 | { 24 | Console.WriteLine(failedTestCase); 25 | } 26 | } 27 | 28 | Assert.Empty(failedTestCases); 29 | } 30 | 31 | [Fact] 32 | public void ValidateResponseSpecification() 33 | { 34 | var failedTestCases = RunPactSpecificationTests("..\\..\\IntegrationTests\\Specification\\pact-specification\\testcases\\response"); 35 | 36 | if (failedTestCases.Any()) 37 | { 38 | Console.WriteLine("### FAILED ###"); 39 | foreach (var failedTestCase in failedTestCases) 40 | { 41 | Console.WriteLine(failedTestCase); 42 | } 43 | } 44 | 45 | Assert.Empty(failedTestCases); 46 | } 47 | 48 | private IEnumerable RunPactSpecificationTests(string pathToTestCases) 49 | where T : class, IVerifiable 50 | { 51 | var failedTestCases = new List(); 52 | 53 | if (!Directory.Exists(pathToTestCases)) 54 | { 55 | throw new InvalidOperationException(String.Format("Specification tests not found in path '{0}'. Please ensure pact-specification git submodule has been pulled (git submodule update --init).", pathToTestCases)); 56 | } 57 | 58 | foreach (var testCaseSubDirectory in Directory.EnumerateDirectories(pathToTestCases)) 59 | { 60 | var testCaseFileNames = Directory.GetFiles(testCaseSubDirectory); 61 | foreach (var testCaseFileName in testCaseFileNames) 62 | { 63 | var testCaseJson = File.ReadAllText(testCaseFileName); 64 | var testCase = (T)JsonConvert.DeserializeObject(testCaseJson, typeof(T)); 65 | 66 | try 67 | { 68 | Console.WriteLine("Running test: " + testCaseFileName); 69 | testCase.Verify(); 70 | } 71 | catch (SubstituteException) 72 | { 73 | failedTestCases.Add(String.Format("[Failed] {0}", testCaseFileName)); 74 | } 75 | } 76 | } 77 | 78 | return failedTestCases; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web.Tests/EventAPITests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Security.DataProtection; 3 | using Microsoft.Owin.Security.OAuth; 4 | using Microsoft.Owin.Testing; 5 | using PactNet; 6 | using PactNet.Reporters.Outputters; 7 | using Xunit; 8 | 9 | namespace Provider.Api.Web.Tests 10 | { 11 | public class EventApiTests : IDisposable 12 | { 13 | private TestServer _server; 14 | 15 | [Fact] 16 | public void EnsureEventApiHonoursPactWithConsumer() 17 | { 18 | //Arrange 19 | var outputter = new CustomOutputter(); 20 | var config = new PactVerifierConfig(); 21 | config.ReportOutputters.Add(outputter); 22 | IPactVerifier pactVerifier = new PactVerifier(() => {}, () => {}, config); 23 | 24 | pactVerifier 25 | .ProviderState( 26 | "there are events with ids '45D80D13-D5A2-48D7-8353-CBB4C0EAABF5', '83F9262F-28F1-4703-AB1A-8CFD9E8249C9' and '3E83A96B-2A0C-49B1-9959-26DF23F83AEB'", 27 | setUp: InsertEventsIntoDatabase) 28 | .ProviderState("there is an event with id '83f9262f-28f1-4703-ab1a-8cfd9e8249c9'", 29 | setUp: InsertEventIntoDatabase) 30 | .ProviderState("there is one event with type 'DetailsView'", 31 | setUp: EnsureOneDetailsViewEventExists); 32 | 33 | _server = TestServer.Create(app => 34 | { 35 | app.Use(typeof(AuthorizationTokenReplacementMiddleware), app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1")); 36 | var apiStartup = new Startup(); 37 | apiStartup.Configuration(app); 38 | }); 39 | 40 | //Act / Assert 41 | pactVerifier 42 | .ServiceProvider("Event API", _server.HttpClient) 43 | .HonoursPactWith("Consumer") 44 | .PactUri("../../../Consumer.Tests/pacts/consumer-event_api.json") 45 | .Verify(); 46 | 47 | // Verify that verifaction log is also sent to additional reporters defined in the config 48 | Assert.Contains("Verifying a Pact between Consumer and Event API", outputter.Output); 49 | } 50 | 51 | private void EnsureOneDetailsViewEventExists() 52 | { 53 | //Logic to check and insert a details view event 54 | } 55 | 56 | private void InsertEventsIntoDatabase() 57 | { 58 | //Logic to do database inserts or events api calls to create data 59 | } 60 | 61 | private void InsertEventIntoDatabase() 62 | { 63 | //Logic to do database inserts for event with id 83F9262F-28F1-4703-AB1A-8CFD9E8249C9 64 | } 65 | 66 | 67 | public virtual void Dispose() 68 | { 69 | if (_server != null) 70 | { 71 | _server.Dispose(); 72 | } 73 | } 74 | 75 | private class CustomOutputter : IReportOutputter 76 | { 77 | public string Output { get; private set; } 78 | 79 | public void Write(string report) 80 | { 81 | Output += report; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Samples/EventApi/Provider.Api.Web/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Nancy/MockProviderNancyRequestDispatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Nancy; 7 | using Nancy.Routing; 8 | using Newtonsoft.Json; 9 | using PactNet.Logging; 10 | 11 | namespace PactNet.Mocks.MockHttpService.Nancy 12 | { 13 | internal class MockProviderNancyRequestDispatcher : IRequestDispatcher 14 | { 15 | private readonly IMockProviderRequestHandler _requestHandler; 16 | private readonly IMockProviderAdminRequestHandler _adminRequestHandler; 17 | private readonly ILog _log; 18 | private readonly PactConfig _pactConfig; 19 | 20 | public MockProviderNancyRequestDispatcher( 21 | IMockProviderRequestHandler requestHandler, 22 | IMockProviderAdminRequestHandler adminRequestHandler, 23 | ILog log, 24 | PactConfig pactConfig) 25 | { 26 | _requestHandler = requestHandler; 27 | _adminRequestHandler = adminRequestHandler; 28 | _log = log; 29 | _pactConfig = pactConfig; 30 | } 31 | 32 | public Task Dispatch(NancyContext context, CancellationToken cancellationToken) 33 | { 34 | var tcs = new TaskCompletionSource(); 35 | 36 | if (cancellationToken.IsCancellationRequested) 37 | { 38 | tcs.SetException(new OperationCanceledException()); 39 | return tcs.Task; 40 | } 41 | 42 | if (context == null) 43 | { 44 | tcs.SetException(new ArgumentException("context is null")); 45 | return tcs.Task; 46 | } 47 | 48 | Response response; 49 | 50 | try 51 | { 52 | response = IsAdminRequest(context.Request) ? 53 | _adminRequestHandler.Handle(context) : 54 | _requestHandler.Handle(context); 55 | } 56 | catch (Exception ex) 57 | { 58 | if (ex.GetType() != typeof(PactFailureException)) 59 | { 60 | _log.ErrorException("Failed to handle the request", ex); 61 | } 62 | 63 | var exceptionMessage = String.Format("{0} See {1} for details.", 64 | JsonConvert.ToString(ex.Message).Trim('"'), 65 | !String.IsNullOrEmpty(_pactConfig.LoggerName) ? LogProvider.CurrentLogProvider.ResolveLogPath(_pactConfig.LoggerName) : "logs"); 66 | 67 | response = new Response 68 | { 69 | StatusCode = HttpStatusCode.InternalServerError, 70 | ReasonPhrase = exceptionMessage, 71 | Contents = s => 72 | { 73 | var bytes = Encoding.UTF8.GetBytes(exceptionMessage); 74 | s.Write(bytes, 0, bytes.Length); 75 | s.Flush(); 76 | } 77 | }; 78 | } 79 | 80 | context.Response = response; 81 | tcs.SetResult(context.Response); 82 | 83 | return tcs.Task; 84 | } 85 | 86 | private static bool IsAdminRequest(Request request) 87 | { 88 | return request.Headers != null && 89 | request.Headers.Any(x => x.Key == Constants.AdministrativeRequestHeaderKey); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /PactNet.Tests/Mocks/MockHttpService/Comparers/HttpQueryStringComparerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using PactNet.Mocks.MockHttpService.Comparers; 4 | using Xunit; 5 | 6 | namespace PactNet.Tests.Mocks.MockHttpService.Comparers 7 | { 8 | public class HttpQueryStringComparerTests 9 | { 10 | private HttpQueryStringComparer GetSubject() 11 | { 12 | return new HttpQueryStringComparer(); 13 | } 14 | 15 | [Fact] 16 | public void Compare_WithNullExpectedQuery_NoErrorsAreAddedToTheComparisonResult() 17 | { 18 | var comparer = GetSubject(); 19 | 20 | var result = comparer.Compare(null, String.Empty); 21 | 22 | Assert.False(result.HasFailure, "There should not be any errors"); 23 | } 24 | 25 | [Fact] 26 | public void Compare_WithNullExpectedQueryAndNonNullActualQuery_OneErrorIsAddedToTheComparisonResult() 27 | { 28 | const string actualQuery = "test=1234"; 29 | var comparer = GetSubject(); 30 | 31 | var result = comparer.Compare(null, actualQuery); 32 | 33 | Assert.Equal(1, result.Failures.Count()); 34 | } 35 | 36 | [Fact] 37 | public void Compare_WithNonNullExpectedQueryAndNullActualQuery_OneErrorIsAddedToTheComparisonResult() 38 | { 39 | const string expectedQuery = "test=1234"; 40 | var comparer = GetSubject(); 41 | 42 | var result = comparer.Compare(expectedQuery, null); 43 | 44 | Assert.Equal(1, result.Failures.Count()); 45 | } 46 | 47 | [Fact] 48 | public void Compare_WithNonEncodedQueryThatMatch_NoErrorsAreAddedToTheComparisonResult() 49 | { 50 | const string expected = "test=1234&hello=test"; 51 | const string actual = "test=1234&hello=test"; 52 | var comparer = GetSubject(); 53 | 54 | var result = comparer.Compare(expected, actual); 55 | 56 | Assert.False(result.HasFailure, "There should not be any errors"); 57 | } 58 | 59 | [Fact] 60 | public void Compare_WithNonEncodedQueryThatDontMatch_OneErrorIsAddedToTheComparisonResult() 61 | { 62 | const string expected = "test=1234&hello=test"; 63 | const string actual = "test=1234&hello=Test"; 64 | var comparer = GetSubject(); 65 | 66 | var result = comparer.Compare(expected, actual); 67 | 68 | Assert.Equal(1, result.Failures.Count()); 69 | } 70 | 71 | [Fact] 72 | public void Compare_WithUrlEncodingCaseInsensitiveMatching_NoErrorsAreAddedToTheComparisonResult() 73 | { 74 | const string expected = "2014-08-31T00%3A00%3A00%2B10%3A00"; 75 | const string actual = "2014-08-31T00%3a00%3a00%2b10%3a00"; 76 | var comparer = GetSubject(); 77 | 78 | var result = comparer.Compare(expected, actual); 79 | 80 | Assert.False(result.HasFailure, "There should not be any errors"); 81 | } 82 | 83 | [Fact] 84 | public void Compare_WithUrlEncodingCaseInsensitiveMatching_OneErrorIsAddedToTheComparisonResult() 85 | { 86 | const string expected = "dv=chipbeth%3A00%3A00%2B10%3A00"; 87 | const string actual = "dv=ChipBeth%3a00%3a00%2b10%3a00"; 88 | var comparer = GetSubject(); 89 | 90 | var result = comparer.Compare(expected, actual); 91 | 92 | Assert.Equal(1, result.Failures.Count()); 93 | } 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Mappers/HttpRequestMessageMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using PactNet.Mocks.MockHttpService.Models; 6 | 7 | namespace PactNet.Mocks.MockHttpService.Mappers 8 | { 9 | internal class HttpRequestMessageMapper : IHttpRequestMessageMapper 10 | { 11 | private readonly IHttpMethodMapper _httpMethodMapper; 12 | private readonly IHttpContentMapper _httpContentMapper; 13 | private readonly IHttpBodyContentMapper _httpBodyContentMapper; 14 | 15 | internal HttpRequestMessageMapper( 16 | IHttpMethodMapper httpMethodMapper, 17 | IHttpContentMapper httpContentMapper, 18 | IHttpBodyContentMapper httpBodyContentMapper) 19 | { 20 | _httpMethodMapper = httpMethodMapper; 21 | _httpContentMapper = httpContentMapper; 22 | _httpBodyContentMapper = httpBodyContentMapper; 23 | } 24 | 25 | public HttpRequestMessageMapper() : this( 26 | new HttpMethodMapper(), 27 | new HttpContentMapper(), 28 | new HttpBodyContentMapper()) 29 | { 30 | } 31 | 32 | public HttpRequestMessage Convert(ProviderServiceRequest from) 33 | { 34 | if (from == null) 35 | { 36 | return null; 37 | } 38 | 39 | var requestHttpMethod = _httpMethodMapper.Convert(from.Method); 40 | var requestPath = from.PathWithQuery(); 41 | 42 | var to = new HttpRequestMessage(requestHttpMethod, requestPath); 43 | 44 | var contentRelatedHeaders = new Dictionary(); 45 | if (from.Headers != null && from.Headers.Any()) 46 | { 47 | foreach (var requestHeader in from.Headers) 48 | { 49 | //Strip any Content- headers as they need to be attached to Request content when using a HttpRequestMessage 50 | if (requestHeader.Key.IndexOf("Content-", StringComparison.InvariantCultureIgnoreCase) == 0) 51 | { 52 | contentRelatedHeaders.Add(requestHeader.Key, requestHeader.Value); 53 | continue; 54 | } 55 | 56 | to.Headers.Add(requestHeader.Key, requestHeader.Value); 57 | } 58 | } 59 | 60 | if (from.Body != null) 61 | { 62 | HttpBodyContent bodyContent = _httpBodyContentMapper.Convert(body: from.Body, headers: from.Headers); 63 | var httpContent = _httpContentMapper.Convert(bodyContent); 64 | 65 | //Set the content related headers 66 | if (httpContent != null && contentRelatedHeaders.Any()) 67 | { 68 | foreach (var contentHeader in contentRelatedHeaders) 69 | { 70 | if (contentHeader.Key.Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase) && 71 | httpContent.Headers.Any(x => x.Key.Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase))) 72 | { 73 | continue; 74 | } 75 | 76 | httpContent.Headers.Add(contentHeader.Key, contentHeader.Value); 77 | } 78 | } 79 | 80 | to.Content = httpContent; 81 | } 82 | 83 | return to; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /PactNet/Mocks/MockHttpService/Nancy/NancyHttpHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Nancy.Bootstrapper; 3 | using Nancy.Hosting.Self; 4 | using PactNet.Extensions; 5 | using PactNet.Logging; 6 | 7 | namespace PactNet.Mocks.MockHttpService.Nancy 8 | { 9 | internal class NancyHttpHost : IHttpHost 10 | { 11 | private readonly Uri _baseUri; 12 | private readonly INancyBootstrapper _bootstrapper; 13 | private readonly ILog _log; 14 | private readonly PactConfig _config; 15 | private readonly HostConfiguration _nancyConfiguration; 16 | private NancyHost _host; 17 | 18 | internal NancyHttpHost(Uri baseUri, string providerName, PactConfig config, INancyBootstrapper bootstrapper) : 19 | this(baseUri, providerName, config, false) 20 | { 21 | _bootstrapper = bootstrapper; 22 | } 23 | 24 | internal NancyHttpHost(Uri baseUri, string providerName, PactConfig config, bool bindOnAllAdapters) 25 | { 26 | var loggerName = LogProvider.CurrentLogProvider.AddLogger(config.LogDir, providerName.ToLowerSnakeCase(), "{0}_mock_service.log"); 27 | config.LoggerName = loggerName; 28 | 29 | _baseUri = baseUri; 30 | _bootstrapper = new MockProviderNancyBootstrapper(config); 31 | _log = LogProvider.GetLogger(config.LoggerName); 32 | _config = config; 33 | 34 | _nancyConfiguration = new HostConfiguration 35 | { 36 | AllowChunkedEncoding = false 37 | }; 38 | 39 | if (bindOnAllAdapters) 40 | { 41 | _nancyConfiguration.UrlReservations = new UrlReservations 42 | { 43 | CreateAutomatically = true 44 | }; 45 | _nancyConfiguration.RewriteLocalhost = true; 46 | } 47 | else 48 | { 49 | _nancyConfiguration.RewriteLocalhost = false; 50 | } 51 | } 52 | 53 | public void Start() 54 | { 55 | Stop(); 56 | try 57 | { 58 | _host = new NancyHost(_bootstrapper, _nancyConfiguration, _baseUri); 59 | _host.Start(); 60 | } 61 | catch (AutomaticUrlReservationCreationFailureException) 62 | { 63 | //An existing binding is present, flip to binding on all adapters 64 | //This code exists to sociably handle changing the default binding mode to not require admin privileges 65 | _nancyConfiguration.UrlReservations = new UrlReservations 66 | { 67 | CreateAutomatically = true 68 | }; 69 | _nancyConfiguration.RewriteLocalhost = true; 70 | _host = new NancyHost(_bootstrapper, _nancyConfiguration, _baseUri); 71 | _host.Start(); 72 | } 73 | _log.InfoFormat("Started {0}", _baseUri.OriginalString); 74 | } 75 | 76 | public void Stop() 77 | { 78 | if (_host != null) 79 | { 80 | _host.Stop(); 81 | Dispose(_host); 82 | _host = null; 83 | _log.InfoFormat("Stopped {0}", _baseUri.OriginalString); 84 | 85 | LogProvider.CurrentLogProvider.RemoveLogger(_config.LoggerName); 86 | } 87 | } 88 | 89 | private void Dispose(IDisposable disposable) 90 | { 91 | if (disposable != null) 92 | { 93 | disposable.Dispose(); 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /PactNet.Tests/Models/ProviderStatesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PactNet.Models; 3 | using Xunit; 4 | 5 | namespace PactNet.Tests.Models 6 | { 7 | public class ProviderStatesTests 8 | { 9 | [Fact] 10 | public void Ctor_WithSetUpAction_SetsSetUpAction() 11 | { 12 | Action setUp = () => { }; 13 | var providerStates = new ProviderStates(setUp: setUp); 14 | 15 | Assert.Equal(setUp, providerStates.SetUp); 16 | } 17 | 18 | [Fact] 19 | public void Ctor_WithTearDownAction_SetsTearDownAction() 20 | { 21 | Action tearDown = () => { }; 22 | var providerStates = new ProviderStates(tearDown: tearDown); 23 | 24 | Assert.Equal(tearDown, providerStates.TearDown); 25 | } 26 | 27 | [Fact] 28 | public void Add_WithProviderState_AddsProviderState() 29 | { 30 | const string providerStateDescription = "my provider state"; 31 | var providerState = new ProviderState(providerStateDescription); 32 | var providerStates = new ProviderStates(); 33 | 34 | providerStates.Add(providerState); 35 | 36 | Assert.Equal(providerState, providerStates.Find(providerStateDescription)); 37 | } 38 | 39 | [Fact] 40 | public void Add_WithAndAlreadyAddedProviderState_ThrowsArgumentException() 41 | { 42 | const string providerStateDescription = "my provider state"; 43 | var providerState1 = new ProviderState(providerStateDescription); 44 | var providerState2 = new ProviderState(providerStateDescription); 45 | var providerStates = new ProviderStates(); 46 | providerStates.Add(providerState1); 47 | 48 | Assert.Throws(() => providerStates.Add(providerState2)); 49 | } 50 | 51 | [Fact] 52 | public void Find_WithNullProviderState_ThrowsArgumentNullException() 53 | { 54 | var providerStates = new ProviderStates(); 55 | 56 | Assert.Throws(() => providerStates.Find(null)); 57 | } 58 | 59 | [Fact] 60 | public void Find_WithProviderStateThatHasBeenAdded_ReturnsProviderState() 61 | { 62 | const string providerStateDescription = "my provider state 2"; 63 | var providerState1 = new ProviderState("my provider state"); 64 | var providerState2 = new ProviderState(providerStateDescription); 65 | var providerStates = new ProviderStates(); 66 | providerStates.Add(providerState1); 67 | providerStates.Add(providerState2); 68 | 69 | var actualProviderState = providerStates.Find(providerStateDescription); 70 | 71 | Assert.Equal(providerState2, actualProviderState); 72 | } 73 | 74 | [Fact] 75 | public void Find_WithNoAddedProviderStates_ReturnsNull() 76 | { 77 | var providerStates = new ProviderStates(); 78 | 79 | var actualProviderState = providerStates.Find("my provider state"); 80 | 81 | Assert.Null(actualProviderState); 82 | } 83 | 84 | [Fact] 85 | public void Find_WithProviderStateThatDoesNotMatchProviderStates_ReturnsNull() 86 | { 87 | const string providerStateDescription = "my provider state 2"; 88 | var providerState1 = new ProviderState("my provider state"); 89 | var providerState2 = new ProviderState(providerStateDescription); 90 | var providerStates = new ProviderStates(); 91 | providerStates.Add(providerState1); 92 | providerStates.Add(providerState2); 93 | 94 | var actualProviderState = providerStates.Find("something else"); 95 | 96 | Assert.Null(actualProviderState); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /PactNet/PactBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PactNet.Configuration.Json; 4 | using PactNet.Mocks.MockHttpService; 5 | using PactNet.Mocks.MockHttpService.Models; 6 | using PactNet.Models; 7 | 8 | namespace PactNet 9 | { 10 | public class PactBuilder : IPactBuilder 11 | { 12 | public string ConsumerName { get; private set; } 13 | public string ProviderName { get; private set; } 14 | private readonly Func _mockProviderServiceFactory; 15 | private IMockProviderService _mockProviderService; 16 | 17 | internal PactBuilder(Func mockProviderServiceFactory) 18 | { 19 | _mockProviderServiceFactory = mockProviderServiceFactory; 20 | } 21 | 22 | public PactBuilder() 23 | : this(new PactConfig()) 24 | { 25 | } 26 | 27 | public PactBuilder(PactConfig config) 28 | : this((port, enableSsl, providerName, bindOnAllAdapters) => new MockProviderService(port, enableSsl, providerName, config, bindOnAllAdapters)) 29 | { 30 | } 31 | 32 | public IPactBuilder ServiceConsumer(string consumerName) 33 | { 34 | if (String.IsNullOrEmpty(consumerName)) 35 | { 36 | throw new ArgumentException("Please supply a non null or empty consumerName"); 37 | } 38 | 39 | ConsumerName = consumerName; 40 | 41 | return this; 42 | } 43 | 44 | public IPactBuilder HasPactWith(string providerName) 45 | { 46 | if (String.IsNullOrEmpty(providerName)) 47 | { 48 | throw new ArgumentException("Please supply a non null or empty providerName"); 49 | } 50 | 51 | ProviderName = providerName; 52 | 53 | return this; 54 | } 55 | 56 | public IMockProviderService MockService(int port, bool enableSsl = false, bool bindOnAllAdapters = false) 57 | { 58 | return MockService(port, jsonSerializerSettings: null, enableSsl: enableSsl, bindOnAllAdapters: bindOnAllAdapters); 59 | } 60 | 61 | 62 | public IMockProviderService MockService(int port, JsonSerializerSettings jsonSerializerSettings, bool enableSsl = false, bool bindOnAllAdapters = false) 63 | { 64 | if (_mockProviderService != null) 65 | { 66 | _mockProviderService.Stop(); 67 | } 68 | 69 | if (jsonSerializerSettings != null) 70 | { 71 | JsonConfig.ApiSerializerSettings = jsonSerializerSettings; 72 | } 73 | 74 | _mockProviderService = _mockProviderServiceFactory(port, enableSsl, ProviderName, bindOnAllAdapters); 75 | 76 | _mockProviderService.Start(); 77 | 78 | return _mockProviderService; 79 | } 80 | 81 | public void Build() 82 | { 83 | if (_mockProviderService == null) 84 | { 85 | throw new InvalidOperationException("The Pact file could not be saved because the mock provider service is not initialised. Please initialise by calling the MockService() method."); 86 | } 87 | 88 | PersistPactFile(); 89 | _mockProviderService.Stop(); 90 | } 91 | 92 | private void PersistPactFile() 93 | { 94 | if (String.IsNullOrEmpty(ConsumerName)) 95 | { 96 | throw new InvalidOperationException("ConsumerName has not been set, please supply a consumer name using the ServiceConsumer method."); 97 | } 98 | 99 | if (String.IsNullOrEmpty(ProviderName)) 100 | { 101 | throw new InvalidOperationException("ProviderName has not been set, please supply a provider name using the HasPactWith method."); 102 | } 103 | 104 | var pactDetails = new PactDetails 105 | { 106 | Provider = new Pacticipant { Name = ProviderName }, 107 | Consumer = new Pacticipant { Name = ConsumerName } 108 | }; 109 | 110 | _mockProviderService.SendAdminHttpRequest(HttpVerb.Post, Constants.PactPath, pactDetails); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /PactNet.Tests/Mocks/MockHttpService/Models/ProviderServiceRequestTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Serialization; 5 | using PactNet.Configuration.Json; 6 | using PactNet.Mocks.MockHttpService.Models; 7 | using Xunit; 8 | 9 | namespace PactNet.Tests.Mocks.MockHttpService.Models 10 | { 11 | public class ProviderServiceRequestTests 12 | { 13 | [Fact] 14 | public void PathWithQuery_WithNullPathAndQuery_ReturnsNull() 15 | { 16 | var request = new ProviderServiceRequest(); 17 | 18 | var uri = request.PathWithQuery(); 19 | 20 | Assert.Null(uri); 21 | } 22 | 23 | [Fact] 24 | public void PathWithQuery_WithJustPath_ReturnsPath() 25 | { 26 | var request = new ProviderServiceRequest 27 | { 28 | Path = "/events" 29 | }; 30 | 31 | var uri = request.PathWithQuery(); 32 | 33 | Assert.Equal(request.Path, uri); 34 | } 35 | 36 | [Fact] 37 | public void PathWithQuery_WithJustQuery_ThrowsInvalidOperationException() 38 | { 39 | var request = new ProviderServiceRequest 40 | { 41 | Query = "test1=1&test2=2" 42 | }; 43 | 44 | Assert.Throws(() => request.PathWithQuery()); 45 | } 46 | 47 | [Fact] 48 | public void PathWithQuery_WithPathAndQuery_ReturnsPathWithQuery() 49 | { 50 | var request = new ProviderServiceRequest 51 | { 52 | Path = "/events", 53 | Query = "test1=1&test2=2" 54 | }; 55 | 56 | var uri = request.PathWithQuery(); 57 | 58 | Assert.Equal(request.Path + "?" + request.Query, uri); 59 | } 60 | 61 | [Fact] 62 | public void SerializeObject_WithDefaultApiSerializerSettings_ReturnsCorrectJson() 63 | { 64 | var request = new ProviderServiceRequest 65 | { 66 | Method = HttpVerb.Get, 67 | Headers = new Dictionary { { "Content-Type", "application/json" } }, 68 | Body = new 69 | { 70 | Test1 = "hi", 71 | test2 = 2 72 | } 73 | }; 74 | 75 | var requestJson = JsonConvert.SerializeObject(request, JsonConfig.ApiSerializerSettings); 76 | var expectedJson = "{\"method\":\"get\",\"headers\":{\"Content-Type\":\"application/json\"},\"body\":{\"Test1\":\"hi\",\"test2\":2}}"; 77 | Assert.Equal(expectedJson, requestJson); 78 | } 79 | 80 | [Fact] 81 | public void SerializeObject_WithDefaultApiSerializerSettingsAndNoHeadersOrBody_ReturnsCorrectJson() 82 | { 83 | var request = new ProviderServiceRequest 84 | { 85 | Method = HttpVerb.Get 86 | }; 87 | 88 | var requestJson = JsonConvert.SerializeObject(request, JsonConfig.ApiSerializerSettings); 89 | var expectedJson = "{\"method\":\"get\"}"; 90 | Assert.Equal(expectedJson, requestJson); 91 | } 92 | 93 | [Fact] 94 | public void SerializeObject_WithCamelCaseApiSerializerSettings_ReturnsCorrectJson() 95 | { 96 | var request = new ProviderServiceRequest 97 | { 98 | Method = HttpVerb.Get, 99 | Headers = new Dictionary { { "Content-Type", "application/json" } }, 100 | Body = new 101 | { 102 | Test1 = "hi", 103 | test2 = 2 104 | } 105 | }; 106 | 107 | var requestJson = JsonConvert.SerializeObject(request, new JsonSerializerSettings 108 | { 109 | NullValueHandling = NullValueHandling.Ignore, 110 | Formatting = Formatting.None, 111 | ContractResolver = new CamelCasePropertyNamesContractResolver() 112 | }); 113 | var expectedJson = "{\"method\":\"get\",\"headers\":{\"Content-Type\":\"application/json\"},\"body\":{\"test1\":\"hi\",\"test2\":2}}"; 114 | Assert.Equal(expectedJson, requestJson); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Samples/EventApi/Consumer/Consumer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {4DF20682-F8DD-4271-86CA-17FA2B3D8D29} 9 | Library 10 | Properties 11 | Consumer 12 | Consumer 13 | v4.5 14 | 512 15 | ..\..\..\ 16 | true 17 | 32cfa714 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\..\..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 67 | 68 | 69 | 70 | 71 | 78 | --------------------------------------------------------------------------------