using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; using WireMock.Http; using WireMock.Matchers; using WireMock.Matchers.Request; using WireMock.RequestBuilders; using WireMock.Settings; using WireMock.Validation; using WireMock.Owin; namespace WireMock.Server { /// /// The fluent mock server. /// public partial class FluentMockServer : IDisposable { private const int ServerStartDelay = 100; private readonly IOwinSelfHost _httpServer; private readonly WireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); /// /// Gets a value indicating whether this server is started. /// [PublicAPI] public bool IsStarted { get; } /// /// Gets the ports. /// [PublicAPI] public List Ports { get; } /// /// Gets the urls. /// [PublicAPI] public string[] Urls { get; } /// /// Gets the mappings. /// [PublicAPI] public IEnumerable Mappings => new ReadOnlyCollection(_options.Mappings); /// /// Gets the scenarios. /// [PublicAPI] public IDictionary Scenarios => new ConcurrentDictionary(_options.Scenarios); #region Start/Stop /// /// Starts the specified settings. /// /// The FluentMockServerSettings. /// The . [PublicAPI] public static FluentMockServer Start(IFluentMockServerSettings settings) { Check.NotNull(settings, nameof(settings)); return new FluentMockServer(settings); } /// /// Start this FluentMockServer. /// /// The port. /// The SSL support. /// The . [PublicAPI] public static FluentMockServer Start([CanBeNull] int? port = 0, bool ssl = false) { return new FluentMockServer(new FluentMockServerSettings { Port = port, UseSSL = ssl }); } /// /// Start this FluentMockServer. /// /// The urls to listen on. /// The . [PublicAPI] public static FluentMockServer Start(params string[] urls) { Check.NotEmpty(urls, nameof(urls)); return new FluentMockServer(new FluentMockServerSettings { Urls = urls }); } /// /// Start this FluentMockServer with the admin interface. /// /// The port. /// The SSL support. /// The . [PublicAPI] public static FluentMockServer StartWithAdminInterface(int? port = 0, bool ssl = false) { return new FluentMockServer(new FluentMockServerSettings { Port = port, UseSSL = ssl, StartAdminInterface = true }); } /// /// Start this FluentMockServer with the admin interface. /// /// The urls. /// The . [PublicAPI] public static FluentMockServer StartWithAdminInterface(params string[] urls) { Check.NotEmpty(urls, nameof(urls)); return new FluentMockServer(new FluentMockServerSettings { Urls = urls, StartAdminInterface = true }); } /// /// Start this FluentMockServer with the admin interface and read static mappings. /// /// The urls. /// The . [PublicAPI] public static FluentMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls) { Check.NotEmpty(urls, nameof(urls)); return new FluentMockServer(new FluentMockServerSettings { Urls = urls, StartAdminInterface = true, ReadStaticMappings = true }); } private FluentMockServer(IFluentMockServerSettings settings) { if (settings.Urls != null) { Urls = settings.Urls; } else { int port = settings.Port > 0 ? settings.Port.Value : PortUtil.FindFreeTcpPort(); Urls = new[] { (settings.UseSSL == true ? "https" : "http") + "://localhost:" + port + "/" }; } _options.PreWireMockMiddlewareInit = settings.PreWireMockMiddlewareInit; _options.PostWireMockMiddlewareInit = settings.PostWireMockMiddlewareInit; #if NETSTANDARD _httpServer = new AspNetCoreSelfHost(_options, Urls); #else _httpServer = new OwinSelfHost(_options, Urls); #endif IsStarted = _httpServer.IsStarted; Ports = _httpServer.Ports; _httpServer.StartAsync(); // Fix for 'Bug: Server not listening after Start() returns (on macOS)' Task.Delay(ServerStartDelay).Wait(); if (settings.AllowPartialMapping == true) { AllowPartialMapping(); } if (settings.StartAdminInterface == true) { if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) { SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword); } InitAdmin(); } if (settings.ReadStaticMappings == true) { ReadStaticMappings(); } if (settings.ProxyAndRecordSettings != null) { InitProxyAndRecord(settings.ProxyAndRecordSettings); } if (settings.MaxRequestLogCount != null) { SetMaxRequestLogCount(settings.MaxRequestLogCount); } } /// /// Stop this server. /// [PublicAPI] public void Stop() { _httpServer?.StopAsync(); } #endregion /// /// Adds the catch all mapping. /// [PublicAPI] public void AddCatchAllMapping() { Given(Request.Create().WithPath("/*").UsingAnyVerb()) .WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05")) .AtPriority(1000) .RespondWith(new DynamicResponseProvider(request => new ResponseMessage { StatusCode = 404, Body = "No matching mapping found" })); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { if (_httpServer != null && _httpServer.IsStarted) { _httpServer.StopAsync(); } } /// /// Resets LogEntries and Mappings. /// [PublicAPI] public void Reset() { ResetLogEntries(); ResetMappings(); } /// /// Resets the Mappings. /// [PublicAPI] public void ResetMappings() { _options.Mappings = _options.Mappings.Where(m => m.IsAdminInterface).ToList(); } /// /// Deletes the mapping. /// /// The unique identifier. [PublicAPI] public bool DeleteMapping(Guid guid) { // Check a mapping exists with the same GUID, if so, remove it. var existingMapping = _options.Mappings.FirstOrDefault(m => m.Guid == guid); if (existingMapping != null) { _options.Mappings.Remove(existingMapping); return true; } return false; } /// /// The add request processing delay. /// /// The delay. [PublicAPI] public void AddGlobalProcessingDelay(TimeSpan delay) { _options.RequestProcessingDelay = delay; } /// /// Allows the partial mapping. /// [PublicAPI] public void AllowPartialMapping() { _options.AllowPartialMapping = true; } /// /// Sets the basic authentication. /// /// The username. /// The password. [PublicAPI] public void SetBasicAuthentication([NotNull] string username, [NotNull] string password) { Check.NotNull(username, nameof(username)); Check.NotNull(password, nameof(password)); string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)); _options.AuthorizationMatcher = new RegexMatcher("^(?i)BASIC " + authorization + "$"); } /// /// Removes the basic authentication. /// [PublicAPI] public void RemoveBasicAuthentication() { _options.AuthorizationMatcher = null; } /// /// Sets the maximum RequestLog count. /// /// The maximum RequestLog count. [PublicAPI] public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount) { _options.MaxRequestLogCount = maxRequestLogCount; } /// /// Sets RequestLog expiration in hours. /// /// The RequestLog expiration in hours. [PublicAPI] public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration) { _options.RequestLogExpirationDuration = requestLogExpirationDuration; } /// /// Resets the Scenarios. /// [PublicAPI] public void ResetScenarios() { _options.Scenarios.Clear(); } /// /// The given. /// /// The request matcher. /// The . [PublicAPI] public IRespondWithAProvider Given(IRequestMatcher requestMatcher) { return new RespondWithAProvider(RegisterMapping, requestMatcher); } private void RegisterMapping(Mapping mapping) { // Check a mapping exists with the same Guid, if so, replace it. var existingMapping = _options.Mappings.FirstOrDefault(m => m.Guid == mapping.Guid); if (existingMapping != null) { _options.Mappings[_options.Mappings.IndexOf(existingMapping)] = mapping; } else { _options.Mappings.Add(mapping); } } } }