diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da785c5..12fc69db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# 1.0.15.0 (04 May 2019) +- [#271](https://github.com/WireMock-Net/WireMock.Net/pull/271) - Support Dynamic response files using Handlebars templating [bug, feature] contributed by [StefH](https://github.com/StefH) +- [#272](https://github.com/WireMock-Net/WireMock.Net/pull/272) - Add unit test for JsonPath and BodyAsFile mapping contributed by [denstorti](https://github.com/denstorti) +- [#273](https://github.com/WireMock-Net/WireMock.Net/pull/273) - Dynamic response handlebars templating (2) [bug, feature] contributed by [StefH](https://github.com/StefH) +- [#270](https://github.com/WireMock-Net/WireMock.Net/issues/270) - Dynamic response files using Handlebars templating [bug, question] + +# 1.0.14.0 (20 April 2019) +- [#269](https://github.com/WireMock-Net/WireMock.Net/pull/269) - Add JmesPath matcher [feature] contributed by [StefH](https://github.com/StefH) +- [#268](https://github.com/WireMock-Net/WireMock.Net/issues/268) - Swagger UI for admin api [question] + +# 1.0.13.0 (11 April 2019) +- [#266](https://github.com/WireMock-Net/WireMock.Net/pull/266) - [265] Add file upload to allow mocking of file operations contributed by [JackCreativeCrew](https://github.com/JackCreativeCrew) +- [#265](https://github.com/WireMock-Net/WireMock.Net/issues/265) - File Upload [feature] + # 1.0.12.0 (05 April 2019) - [#264](https://github.com/WireMock-Net/WireMock.Net/pull/264) - Proxy : also save multipart as string in mapping file contributed by [StefH](https://github.com/StefH) - [#263](https://github.com/WireMock-Net/WireMock.Net/issues/263) - Content-Type multipart/form-data is not serialized in proxy and recording mode [bug] diff --git a/Directory.Build.props b/Directory.Build.props index 8031a4fb..085afa88 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.0.12 + 1.0.15 diff --git a/GitHubReleaseNotes.txt b/GitHubReleaseNotes.txt index fa63df65..75e562c1 100644 --- a/GitHubReleaseNotes.txt +++ b/GitHubReleaseNotes.txt @@ -1,3 +1,3 @@ https://github.com/StefH/GitHubReleaseNotes -GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.11.0 \ No newline at end of file +GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.15.0 \ No newline at end of file diff --git a/README.md b/README.md index 82988722..bfa2eae5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w * Record/playback of stubs (proxying) * Per-request conditional proxying * Stateful behaviour simulation -* Response transformation +* Response templating / transformation using Handlebars and extensions ## Info | | | diff --git a/WireMock.Net Solution.sln.DotSettings b/WireMock.Net Solution.sln.DotSettings index 4556d123..8f9aa5d2 100644 --- a/WireMock.Net Solution.sln.DotSettings +++ b/WireMock.Net Solution.sln.DotSettings @@ -10,6 +10,7 @@ XUA True True + True True True \ No newline at end of file diff --git a/examples/WireMock.Net.Client/Program.cs b/examples/WireMock.Net.Client/Program.cs index 560195f0..4a8dabfa 100644 --- a/examples/WireMock.Net.Client/Program.cs +++ b/examples/WireMock.Net.Client/Program.cs @@ -52,6 +52,12 @@ namespace WireMock.Net.Client var scenarioStates = api.GetScenariosAsync().Result; Console.WriteLine($"GetScenariosAsync = {JsonConvert.SerializeObject(scenarioStates)}"); + var postFileResult = api.PostFileAsync("1.cs", "C# Hello").GetAwaiter().GetResult(); + Console.WriteLine($"postFileResult = {JsonConvert.SerializeObject(postFileResult)}"); + + var getFileResult = api.GetFileAsync("1.cs").GetAwaiter().GetResult(); + Console.WriteLine($"getFileResult = {getFileResult}"); + Console.WriteLine("Press any key to quit"); Console.ReadKey(); } diff --git a/examples/WireMock.Net.Console.Net452.Classic/CustomFileSystemFileHandler.cs b/examples/WireMock.Net.Console.Net452.Classic/CustomFileSystemFileHandler.cs index 0c558479..fcad7619 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/CustomFileSystemFileHandler.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/CustomFileSystemFileHandler.cs @@ -49,5 +49,39 @@ namespace WireMock.Net.ConsoleApplication { return File.ReadAllBytes(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path); } + + /// + public bool FileExists(string path) + { + return File.Exists(AdjustPath(path)); + } + + /// + public void WriteFile(string path, byte[] bytes) + { + File.WriteAllBytes(AdjustPath(path), bytes); + } + + /// + public void DeleteFile(string path) + { + File.Delete(AdjustPath(path)); + } + + /// + public byte[] ReadFile(string path) + { + return File.ReadAllBytes(AdjustPath(path)); + } + + /// + /// Adjusts the path to the MappingFolder. + /// + /// The path. + /// Adjusted path + private string AdjustPath(string path) + { + return Path.Combine(GetMappingFolder(), path); + } } } \ No newline at end of file diff --git a/examples/WireMock.Net.StandAlone.Net461/App.config b/examples/WireMock.Net.StandAlone.Net461/App.config index 4be9c6ef..0b11f8d0 100644 --- a/examples/WireMock.Net.StandAlone.Net461/App.config +++ b/examples/WireMock.Net.StandAlone.Net461/App.config @@ -44,7 +44,7 @@ - + diff --git a/examples/WireMock.Net.StandAlone.Net461/WireMock.Net.StandAlone.Net461.csproj b/examples/WireMock.Net.StandAlone.Net461/WireMock.Net.StandAlone.Net461.csproj index cc1988ce..4a9309c9 100644 --- a/examples/WireMock.Net.StandAlone.Net461/WireMock.Net.StandAlone.Net461.csproj +++ b/examples/WireMock.Net.StandAlone.Net461/WireMock.Net.StandAlone.Net461.csproj @@ -225,8 +225,8 @@ ..\..\packages\System.IO.Pipelines.4.5.2\lib\netstandard2.0\System.IO.Pipelines.dll - - ..\..\packages\System.Linq.Dynamic.Core.1.0.11\lib\net46\System.Linq.Dynamic.Core.dll + + ..\..\packages\System.Linq.Dynamic.Core.1.0.12\lib\net46\System.Linq.Dynamic.Core.dll ..\..\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll diff --git a/examples/WireMock.Net.StandAlone.Net461/packages.config b/examples/WireMock.Net.StandAlone.Net461/packages.config index 9ff4575a..679c143c 100644 --- a/examples/WireMock.Net.StandAlone.Net461/packages.config +++ b/examples/WireMock.Net.StandAlone.Net461/packages.config @@ -65,7 +65,7 @@ - + diff --git a/src/WireMock.Net/Client/IFluentMockServerAdmin.cs b/src/WireMock.Net/Client/IFluentMockServerAdmin.cs index 430dfa4f..bddaf37f 100644 --- a/src/WireMock.Net/Client/IFluentMockServerAdmin.cs +++ b/src/WireMock.Net/Client/IFluentMockServerAdmin.cs @@ -167,5 +167,42 @@ namespace WireMock.Client /// [Post("__admin/scenarios")] Task ResetScenariosAsync(); + + /// + /// Create a new File + /// + /// The filename + /// The body + [Post("__admin/files/{filename}")] + Task PostFileAsync([Path] string filename, [Body] string body); + + /// + /// Update an existing File + /// + /// The filename + /// The body + [Put("__admin/files/{filename}")] + Task PutFileAsync([Path] string filename, [Body] string body); + + /// + /// Get the content of an existing File + /// + /// The filename + [Get("__admin/files/{filename}")] + Task GetFileAsync([Path] string filename); + + /// + /// Delete an existing File + /// + /// The filename + [Delete("__admin/files/{filename}")] + Task DeleteFileAsync([Path] string filename); + + /// + /// Check if a file exists + /// + /// The filename + [Head("__admin/files/{filename}")] + Task FileExistsAsync([Path] string filename); } } \ No newline at end of file diff --git a/src/WireMock.Net/Handlers/IFileSystemHandler.cs b/src/WireMock.Net/Handlers/IFileSystemHandler.cs index 15268735..5ef5ef5c 100644 --- a/src/WireMock.Net/Handlers/IFileSystemHandler.cs +++ b/src/WireMock.Net/Handlers/IFileSystemHandler.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using JetBrains.Annotations; +using System.Collections.Generic; namespace WireMock.Handlers { @@ -18,40 +19,67 @@ namespace WireMock.Handlers /// /// The path. /// true if path refers to an existing directory; false if the directory does not exist or an error occurs when trying to determine if the specified directory exists. - bool FolderExists(string path); + bool FolderExists([NotNull] string path); /// /// Creates all directories and subdirectories in the specified path unless they already exist. /// /// The path. - void CreateFolder(string path); + void CreateFolder([NotNull] string path); /// /// Returns an enumerable collection of file names in a specified path. /// /// The path. /// An enumerable collection of the full names (including paths) for the files in the directory specified by path. - IEnumerable EnumerateFiles(string path); + IEnumerable EnumerateFiles([NotNull] string path); /// /// Read a static mapping file as text. /// /// The path (folder + filename with .json extension). /// The file content as text. - string ReadMappingFile(string path); + string ReadMappingFile([NotNull] string path); /// - /// Write the static mapping. + /// Write the static mapping file. /// /// The path (folder + filename with .json extension). /// The text. - void WriteMappingFile(string path, string text); + void WriteMappingFile([NotNull] string path, [NotNull] string text); /// /// Read a response body file as text. /// /// The path or filename from the file to read. /// The file content as bytes. - byte[] ReadResponseBodyAsFile(string path); + byte[] ReadResponseBodyAsFile([NotNull] string path); + + /// + /// Delete a file. + /// + /// The filename. + void DeleteFile([NotNull] string filename); + + /// + /// Determines whether the given path refers to an existing file on disk. + /// + /// The filename. + /// true if path refers to an existing file; false if the file does not exist. + bool FileExists([NotNull] string filename); + + /// + /// Write a file. + /// + /// The filename. + /// The bytes. + void WriteFile([NotNull] string filename, [NotNull] byte[] bytes); + + /// + /// Read a file as bytes. + /// + /// The filename. + /// The file content as bytes. + byte[] ReadFile([NotNull] string filename); } } \ No newline at end of file diff --git a/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs b/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs index bfc7007a..d70bdc53 100644 --- a/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs +++ b/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs @@ -1,5 +1,4 @@ -using JetBrains.Annotations; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using WireMock.Validation; @@ -13,7 +12,7 @@ namespace WireMock.Handlers private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings"); /// - public bool FolderExists([NotNull] string path) + public bool FolderExists(string path) { Check.NotNullOrEmpty(path, nameof(path)); @@ -21,7 +20,7 @@ namespace WireMock.Handlers } /// - public void CreateFolder([NotNull] string path) + public void CreateFolder(string path) { Check.NotNullOrEmpty(path, nameof(path)); @@ -29,7 +28,7 @@ namespace WireMock.Handlers } /// - public IEnumerable EnumerateFiles([NotNull] string path) + public IEnumerable EnumerateFiles(string path) { Check.NotNullOrEmpty(path, nameof(path)); @@ -43,15 +42,15 @@ namespace WireMock.Handlers } /// - public string ReadMappingFile([NotNull] string path) + public string ReadMappingFile(string path) { Check.NotNullOrEmpty(path, nameof(path)); return File.ReadAllText(path); } - /// - public void WriteMappingFile([NotNull] string path, [NotNull] string text) + /// + public void WriteMappingFile(string path, string text) { Check.NotNullOrEmpty(path, nameof(path)); Check.NotNull(text, nameof(text)); @@ -68,5 +67,48 @@ namespace WireMock.Handlers // Else the path will just be as-is. return File.ReadAllBytes(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path); } + + /// + public bool FileExists(string filename) + { + Check.NotNullOrEmpty(filename, nameof(filename)); + + return File.Exists(AdjustPath(filename)); + } + + /// + public void WriteFile(string filename, byte[] bytes) + { + Check.NotNullOrEmpty(filename, nameof(filename)); + Check.NotNull(bytes, nameof(bytes)); + + File.WriteAllBytes(AdjustPath(filename), bytes); + } + + /// + public void DeleteFile(string filename) + { + Check.NotNullOrEmpty(filename, nameof(filename)); + + File.Delete(AdjustPath(filename)); + } + + /// + public byte[] ReadFile(string filename) + { + Check.NotNullOrEmpty(filename, nameof(filename)); + + return File.ReadAllBytes(AdjustPath(filename)); + } + + /// + /// Adjusts the path to the MappingFolder. + /// + /// The path. + /// Adjusted path + private string AdjustPath(string filename) + { + return Path.Combine(GetMappingFolder(), filename); + } } } diff --git a/src/WireMock.Net/Matchers/JmesPathMatcher.cs b/src/WireMock.Net/Matchers/JmesPathMatcher.cs new file mode 100644 index 00000000..a01308f0 --- /dev/null +++ b/src/WireMock.Net/Matchers/JmesPathMatcher.cs @@ -0,0 +1,83 @@ +using DevLab.JmesPath; +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Linq; +using WireMock.Validation; + +namespace WireMock.Matchers +{ + /// + /// http://jmespath.org/ + /// + public class JmesPathMatcher : IStringMatcher, IObjectMatcher + { + private readonly string[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public JmesPathMatcher([NotNull] params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The patterns. + public JmesPathMatcher(MatchBehaviour matchBehaviour, [NotNull] params string[] patterns) + { + Check.NotNull(patterns, nameof(patterns)); + + MatchBehaviour = matchBehaviour; + _patterns = patterns; + } + + /// + public double IsMatch(string input) + { + double match = MatchScores.Mismatch; + if (input != null) + { + try + { + match = MatchScores.ToScore(_patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern)))); + } + catch (JsonException) + { + // just ignore JsonException + } + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + + /// + public double IsMatch(object input) + { + double match = MatchScores.Mismatch; + + // When input is null or byte[], return Mismatch. + if (input != null && !(input is byte[])) + { + string inputAsString = JsonConvert.SerializeObject(input); + return IsMatch(inputAsString); + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + + /// + public string[] GetPatterns() + { + return _patterns; + } + + /// + public string Name => "JmesPathMatcher"; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs index 3df50690..82ba0852 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.cs @@ -224,7 +224,7 @@ namespace WireMock.ResponseBuilders BodyAsFileIsCached = cache }; - if (cache) + if (cache && !UseTransformer) { ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes; ResponseMessage.BodyData.BodyAsBytes = _fileSystemHandler.ReadResponseBodyAsFile(filename); diff --git a/src/WireMock.Net/ResponseMessageBuilder.cs b/src/WireMock.Net/ResponseMessageBuilder.cs index 7a14ffd9..a6d85bb5 100644 --- a/src/WireMock.Net/ResponseMessageBuilder.cs +++ b/src/WireMock.Net/ResponseMessageBuilder.cs @@ -33,5 +33,13 @@ namespace WireMock return response; } + + internal static ResponseMessage Create(int statusCode) + { + return new ResponseMessage + { + StatusCode = statusCode + }; + } } } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 4cc45e75..5f848b48 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -77,7 +77,7 @@ namespace WireMock.Serialization }, Response = new ResponseModel { - Delay = (int?) response.Delay?.TotalMilliseconds + Delay = (int?)response.Delay?.TotalMilliseconds } }; diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index 57b128e5..14154c7f 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -45,6 +45,9 @@ namespace WireMock.Serialization case "JsonPathMatcher": return new JsonPathMatcher(matchBehaviour, stringPatterns); + case "JmesPathMatcher": + return new JmesPathMatcher(matchBehaviour, stringPatterns); + case "XPathMatcher": return new XPathMatcher(matchBehaviour, (string)matcher.Pattern); diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index 4919d031..1da20c3c 100644 --- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs +++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs @@ -34,13 +34,14 @@ namespace WireMock.Server private const int AdminPriority = int.MinValue; private const int ProxyPriority = 1000; private const string ContentTypeJson = "application/json"; + private const string AdminFiles = "/__admin/files"; private const string AdminMappings = "/__admin/mappings"; private const string AdminRequests = "/__admin/requests"; private const string AdminSettings = "/__admin/settings"; private const string AdminScenarios = "/__admin/scenarios"; - private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/mappings\/(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$"); - private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/requests\/(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$"); + private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); + private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); private readonly JsonSerializerSettings _settings = new JsonSerializerSettings { @@ -97,10 +98,17 @@ namespace WireMock.Server // __admin/scenarios/reset Given(Request.Create().WithPath(AdminScenarios + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); + + // __admin/files/{filename} + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FilePost)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPut()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FilePut)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileGet)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileHead)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete)); } #endregion - #region StaticMappings + #region StaticMappings /// /// Saves the static mappings. /// @@ -714,6 +722,11 @@ namespace WireMock.Server responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value); } + if (responseModel.UseTransformer) + { + responseBuilder = responseBuilder.WithTransformer(); + } + if (!string.IsNullOrEmpty(responseModel.ProxyUrl)) { if (string.IsNullOrEmpty(responseModel.X509Certificate2ThumbprintOrSubjectName)) @@ -770,11 +783,6 @@ namespace WireMock.Server responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile); } - if (responseModel.UseTransformer) - { - responseBuilder = responseBuilder.WithTransformer(); - } - return responseBuilder; } diff --git a/src/WireMock.Net/Server/FluentMockServer.AdminFiles.cs b/src/WireMock.Net/Server/FluentMockServer.AdminFiles.cs new file mode 100644 index 00000000..5c72015a --- /dev/null +++ b/src/WireMock.Net/Server/FluentMockServer.AdminFiles.cs @@ -0,0 +1,114 @@ +using System.IO; +using System.Linq; +using System.Text; +using WireMock.Matchers; +using WireMock.Util; + +namespace WireMock.Server +{ + public partial class FluentMockServer + { + private readonly RegexMatcher _adminFilesFilenamePathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/files\/.*$"); + private static readonly Encoding[] FileBodyIsString = { Encoding.UTF8, Encoding.ASCII }; + + #region Files/{filename} + private ResponseMessage FilePost(RequestMessage requestMessage) + { + string filename = GetFileNameFromRequestMessage(requestMessage); + + string mappingFolder = _fileSystemHandler.GetMappingFolder(); + if (!_fileSystemHandler.FolderExists(mappingFolder)) + { + _fileSystemHandler.CreateFolder(mappingFolder); + } + + _fileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes); + + return ResponseMessageBuilder.Create("File created"); + } + + private ResponseMessage FilePut(RequestMessage requestMessage) + { + string filename = GetFileNameFromRequestMessage(requestMessage); + + if (!_fileSystemHandler.FileExists(filename)) + { + _logger.Info("The file '{0}' does not exist, updating file will be skipped.", filename); + return ResponseMessageBuilder.Create("File is not found", 404); + } + + _fileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes); + + return ResponseMessageBuilder.Create("File updated"); + } + + private ResponseMessage FileGet(RequestMessage requestMessage) + { + string filename = GetFileNameFromRequestMessage(requestMessage); + + if (!_fileSystemHandler.FileExists(filename)) + { + _logger.Info("The file '{0}' does not exist.", filename); + return ResponseMessageBuilder.Create("File is not found", 404); + } + + byte[] bytes = _fileSystemHandler.ReadFile(filename); + var response = new ResponseMessage + { + StatusCode = 200, + BodyData = new BodyData + { + BodyAsBytes = bytes, + DetectedBodyType = BodyType.Bytes, + DetectedBodyTypeFromContentType = BodyType.None + } + }; + + if (BytesEncodingUtils.TryGetEncoding(bytes, out Encoding encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any()) + { + response.BodyData.DetectedBodyType = BodyType.String; + response.BodyData.BodyAsString = encoding.GetString(bytes); + } + + return response; + } + + /// + /// Checks if file exists. + /// Note: Response is returned with no body as a head request doesn't accept a body, only the status code. + /// + /// The request message. + private ResponseMessage FileHead(RequestMessage requestMessage) + { + string filename = GetFileNameFromRequestMessage(requestMessage); + + if (!_fileSystemHandler.FileExists(filename)) + { + _logger.Info("The file '{0}' does not exist.", filename); + return ResponseMessageBuilder.Create(404); + } + + return ResponseMessageBuilder.Create(204); + } + + private ResponseMessage FileDelete(RequestMessage requestMessage) + { + string filename = GetFileNameFromRequestMessage(requestMessage); + + if (!_fileSystemHandler.FileExists(filename)) + { + _logger.Info("The file '{0}' does not exist.", filename); + return ResponseMessageBuilder.Create("File is not deleted", 404); + } + + _fileSystemHandler.DeleteFile(filename); + return ResponseMessageBuilder.Create("File deleted."); + } + + private string GetFileNameFromRequestMessage(RequestMessage requestMessage) + { + return Path.GetFileName(requestMessage.Path.Substring(AdminFiles.Length + 1)); + } + #endregion + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs b/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs index cdd8ae82..e2c742dc 100644 --- a/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs +++ b/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using HandlebarsDotNet; +using HandlebarsDotNet; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using WireMock.Util; namespace WireMock.Transformers @@ -24,23 +24,24 @@ namespace WireMock.Transformers public static ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original) { - bool bodyIsJson = original.BodyData.DetectedBodyType == BodyType.Json; var responseMessage = new ResponseMessage { StatusCode = original.StatusCode }; - if (!bodyIsJson) - { - responseMessage.BodyOriginal = original.BodyData.BodyAsString; - } - var template = new { request = requestMessage }; - if (!bodyIsJson) + switch (original.BodyData.DetectedBodyType) { - TransformBodyAsString(template, original, responseMessage); - } - else - { - TransformBodyAsJson(template, original, responseMessage); + case BodyType.Json: + TransformBodyAsJson(template, original, responseMessage); + break; + + case BodyType.File: + TransformBodyAsFile(template, original, responseMessage); + break; + + case BodyType.String: + responseMessage.BodyOriginal = original.BodyData.BodyAsString; + TransformBodyAsString(template, original, responseMessage); + break; } // Headers @@ -150,13 +151,25 @@ namespace WireMock.Transformers private static void TransformBodyAsString(object template, ResponseMessage original, ResponseMessage responseMessage) { - var templateBody = HandlebarsContext.Compile(original.BodyData.BodyAsString); + var templateBodyAsString = HandlebarsContext.Compile(original.BodyData.BodyAsString); responseMessage.BodyData = new BodyData { DetectedBodyType = original.BodyData.DetectedBodyType, DetectedBodyTypeFromContentType = original.BodyData.DetectedBodyTypeFromContentType, - BodyAsString = templateBody(template) + BodyAsString = templateBodyAsString(template) + }; + } + + private static void TransformBodyAsFile(object template, ResponseMessage original, ResponseMessage responseMessage) + { + var templateBodyAsFile = HandlebarsContext.Compile(original.BodyData.BodyAsFile); + + responseMessage.BodyData = new BodyData + { + DetectedBodyType = original.BodyData.DetectedBodyType, + DetectedBodyTypeFromContentType = original.BodyData.DetectedBodyTypeFromContentType, + BodyAsFile = templateBodyAsFile(template) }; } } diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index 6b405481..f8e012e4 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -57,8 +57,9 @@ all runtime; build; native; contentfiles; analyzers - + + diff --git a/test/WireMock.Net.Tests/FLuentMockServerTests.AdminFiles.cs b/test/WireMock.Net.Tests/FLuentMockServerTests.AdminFiles.cs new file mode 100644 index 00000000..4d362d4a --- /dev/null +++ b/test/WireMock.Net.Tests/FLuentMockServerTests.AdminFiles.cs @@ -0,0 +1,215 @@ +using Moq; +using NFluent; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using WireMock.Handlers; +using WireMock.Server; +using WireMock.Settings; +using Xunit; + +namespace WireMock.Net.Tests +{ + public class FluentMockServerAdminFilesTests + { + private readonly HttpClient _client = new HttpClient(); + + [Fact] + public async Task FluentMockServer_Admin_Files_Post_Ascii() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.GetMappingFolder()).Returns("__admin/mappings"); + filesystemHandlerMock.Setup(fs => fs.FolderExists(It.IsAny())).Returns(true); + filesystemHandlerMock.Setup(fs => fs.WriteFile(It.IsAny(), It.IsAny())); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var multipartFormDataContent = new MultipartFormDataContent(); + multipartFormDataContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); + multipartFormDataContent.Add(new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes("Here's a string.")))); + + // Act + var httpResponseMessage = await _client.PostAsync("http://localhost:" + server.Ports[0] + "/__admin/files/filename.txt", multipartFormDataContent); + + // Assert + Check.That(httpResponseMessage.StatusCode).Equals(HttpStatusCode.OK); + Check.That(server.LogEntries.Count().Equals(1)); + + // Verify + filesystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Once); + filesystemHandlerMock.Verify(fs => fs.FolderExists(It.IsAny()), Times.Once); + filesystemHandlerMock.Verify(fs => fs.WriteFile(It.Is(p => p == "filename.txt"), It.IsAny()), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task FluentMockServer_Admin_Files_Post_MappingFolderDoesNotExistsButWillBeCreated() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.GetMappingFolder()).Returns("x"); + filesystemHandlerMock.Setup(fs => fs.CreateFolder(It.IsAny())); + filesystemHandlerMock.Setup(fs => fs.FolderExists(It.IsAny())).Returns(false); + filesystemHandlerMock.Setup(fs => fs.WriteFile(It.IsAny(), It.IsAny())); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var multipartFormDataContent = new MultipartFormDataContent(); + multipartFormDataContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); + multipartFormDataContent.Add(new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes("Here's a string.")))); + + // Act + var httpResponseMessage = await _client.PostAsync("http://localhost:" + server.Ports[0] + "/__admin/files/filename.txt", multipartFormDataContent); + + // Assert + Check.That(httpResponseMessage.StatusCode).Equals(HttpStatusCode.OK); + + // Verify + filesystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Once); + filesystemHandlerMock.Verify(fs => fs.FolderExists(It.IsAny()), Times.Once); + filesystemHandlerMock.Verify(fs => fs.CreateFolder(It.Is(p => p == "x")), Times.Once); + filesystemHandlerMock.Verify(fs => fs.WriteFile(It.Is(p => p == "filename.txt"), It.IsAny()), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task FluentMockServer_Admin_Files_GetAscii() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(true); + filesystemHandlerMock.Setup(fs => fs.ReadFile(It.IsAny())).Returns(Encoding.ASCII.GetBytes("Here's a string.")); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var multipartFormDataContent = new MultipartFormDataContent(); + multipartFormDataContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); + multipartFormDataContent.Add(new StreamContent(new MemoryStream())); + + // Act + var httpResponseMessageGet = await _client.GetAsync("http://localhost:" + server.Ports[0] + "/__admin/files/filename.txt"); + + // Assert + Check.That(httpResponseMessageGet.StatusCode).Equals(HttpStatusCode.OK); + + Check.That(httpResponseMessageGet.Content.ReadAsStringAsync().Result).Equals("Here's a string."); + + Check.That(server.LogEntries.Count().Equals(2)); + + // Verify + filesystemHandlerMock.Verify(fs => fs.ReadFile(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task FluentMockServer_Admin_Files_GetUTF16() + { + // Arrange + byte[] symbol = Encoding.UTF32.GetBytes(char.ConvertFromUtf32(0x1D161)); + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(true); + filesystemHandlerMock.Setup(fs => fs.ReadFile(It.IsAny())).Returns(symbol); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var multipartFormDataContent = new MultipartFormDataContent(); + multipartFormDataContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); + multipartFormDataContent.Add(new StreamContent(new MemoryStream())); + + // Act + var httpResponseMessageGet = await _client.GetAsync("http://localhost:" + server.Ports[0] + "/__admin/files/filename.bin"); + + // Assert + Check.That(httpResponseMessageGet.StatusCode).Equals(HttpStatusCode.OK); + Check.That(httpResponseMessageGet.Content.ReadAsByteArrayAsync().Result).Equals(symbol); + Check.That(server.LogEntries.Count().Equals(2)); + + // Verify + filesystemHandlerMock.Verify(fs => fs.ReadFile(It.Is(p => p == "filename.bin")), Times.Once); + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.bin")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task FluentMockServer_Admin_Files_Head() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(true); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + // Act + var requestUri = "http://localhost:" + server.Ports[0] + "/__admin/files/filename.txt"; + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, requestUri); + var httpResponseMessage = await _client.SendAsync(httpRequestMessage); + + // Assert + Check.That(httpResponseMessage.StatusCode).Equals(HttpStatusCode.NoContent); + Check.That(server.LogEntries.Count().Equals(1)); + + // Verify + filesystemHandlerMock.Verify(fs => fs.FileExists(It.IsAny()), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task FluentMockServer_Admin_Files_Head_FileDoesNotExistsReturns404() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(false); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + // Act + var requestUri = "http://localhost:" + server.Ports[0] + "/__admin/files/filename.txt"; + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, requestUri); + var httpResponseMessage = await _client.SendAsync(httpRequestMessage); + + // Assert + Check.That(httpResponseMessage.StatusCode).Equals(HttpStatusCode.NotFound); + Check.That(server.LogEntries.Count().Equals(1)); + + // Verify + filesystemHandlerMock.Verify(fs => fs.FileExists(It.IsAny()), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/FluentMockServerAdminRestClientTests.cs b/test/WireMock.Net.Tests/FluentMockServerAdminRestClientTests.cs index cfaac4d9..cbfcf271 100644 --- a/test/WireMock.Net.Tests/FluentMockServerAdminRestClientTests.cs +++ b/test/WireMock.Net.Tests/FluentMockServerAdminRestClientTests.cs @@ -1,12 +1,15 @@ -using NFluent; +using Moq; +using NFluent; using RestEase; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Text; using System.Threading.Tasks; using WireMock.Admin.Mappings; using WireMock.Admin.Settings; using WireMock.Client; +using WireMock.Handlers; using WireMock.Logging; using WireMock.Server; using WireMock.Settings; @@ -232,6 +235,237 @@ namespace WireMock.Net.Tests Check.That(requestLogged.Request.Body).IsNotNull(); Check.That(requestLogged.Request.Body).Contains("T000001"); } - } + [Fact] + public async Task IFluentMockServerAdmin_PostFileAsync_Ascii() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.GetMappingFolder()).Returns("__admin/mappings"); + filesystemHandlerMock.Setup(fs => fs.FolderExists(It.IsAny())).Returns(true); + filesystemHandlerMock.Setup(fs => fs.WriteFile(It.IsAny(), It.IsAny())); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var api = RestClient.For(server.Urls[0]); + + // Act + var request = await api.PostFileAsync("filename.txt", "abc"); + + // Assert + Check.That(request.Guid).IsNull(); + Check.That(request.Status).Contains("File"); + + // Verify + filesystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Once); + filesystemHandlerMock.Verify(fs => fs.FolderExists(It.IsAny()), Times.Once); + filesystemHandlerMock.Verify(fs => fs.WriteFile(It.Is(p => p == "filename.txt"), It.IsAny()), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + + server.Stop(); + } + + [Fact] + public async Task IFluentMockServerAdmin_PutFileAsync_Ascii() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(true); + filesystemHandlerMock.Setup(fs => fs.WriteFile(It.IsAny(), It.IsAny())); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var api = RestClient.For(server.Urls[0]); + + // Act + var request = await api.PutFileAsync("filename.txt", "abc-abc"); + + // Assert + Check.That(request.Guid).IsNull(); + Check.That(request.Status).Contains("File"); + + // Verify + filesystemHandlerMock.Verify(fs => fs.WriteFile(It.Is(p => p == "filename.txt"), It.IsAny()), Times.Once); + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + + server.Stop(); + } + + [Fact] + public void IFluentMockServerAdmin_PutFileAsync_NotFound() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(false); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var api = RestClient.For(server.Urls[0]); + + // Act and Assert + Check.ThatAsyncCode(() => api.PutFileAsync("filename.txt", "xxx")).Throws(); + + // Verify + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + + server.Stop(); + } + + [Fact] + public void IFluentMockServerAdmin_GetFileAsync_NotFound() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(false); + filesystemHandlerMock.Setup(fs => fs.ReadFile(It.IsAny())).Returns(Encoding.ASCII.GetBytes("Here's a string.")); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var api = RestClient.For(server.Urls[0]); + + // Act and Assert + Check.ThatAsyncCode(() => api.GetFileAsync("filename.txt")).Throws(); + + // Verify + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + + server.Stop(); + } + + [Fact] + public async Task IFluentMockServerAdmin_GetFileAsync_Found() + { + // Arrange + string data = "Here's a string."; + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(true); + filesystemHandlerMock.Setup(fs => fs.ReadFile(It.IsAny())).Returns(Encoding.ASCII.GetBytes(data)); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var api = RestClient.For(server.Urls[0]); + + // Act + string file = await api.GetFileAsync("filename.txt"); + + // Assert + Check.That(file).Equals(data); + + // Verify + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.Verify(fs => fs.ReadFile(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + + server.Stop(); + } + + [Fact] + public async Task IFluentMockServerAdmin_DeleteFileAsync_Ok() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(true); + filesystemHandlerMock.Setup(fs => fs.DeleteFile(It.IsAny())); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var api = RestClient.For(server.Urls[0]); + + // Act + await api.DeleteFileAsync("filename.txt"); + + // Verify + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.Verify(fs => fs.DeleteFile(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + + server.Stop(); + } + + [Fact] + public void IFluentMockServerAdmin_DeleteFileAsync_NotFound() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(false); + filesystemHandlerMock.Setup(fs => fs.DeleteFile(It.IsAny())); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var api = RestClient.For(server.Urls[0]); + + // Act and Assert + Check.ThatAsyncCode(() => api.DeleteFileAsync("filename.txt")).Throws(); + + // Verify + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + + server.Stop(); + } + + [Fact] + public void IFluentMockServerAdmin_FileExistsAsync_NotFound() + { + // Arrange + var filesystemHandlerMock = new Mock(MockBehavior.Strict); + filesystemHandlerMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(false); + + var server = FluentMockServer.Start(new FluentMockServerSettings + { + UseSSL = false, + StartAdminInterface = true, + FileSystemHandler = filesystemHandlerMock.Object + }); + + var api = RestClient.For(server.Urls[0]); + + // Act and Assert + Check.ThatAsyncCode(() => api.FileExistsAsync("filename.txt")).Throws(); + + // Verify + filesystemHandlerMock.Verify(fs => fs.FileExists(It.Is(p => p == "filename.txt")), Times.Once); + filesystemHandlerMock.VerifyNoOtherCalls(); + + server.Stop(); + } + } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.Admin.cs b/test/WireMock.Net.Tests/FluentMockServerTests.Admin.cs index 17baa808..471d1b85 100644 --- a/test/WireMock.Net.Tests/FluentMockServerTests.Admin.cs +++ b/test/WireMock.Net.Tests/FluentMockServerTests.Admin.cs @@ -1,11 +1,11 @@ -using System; +using Moq; +using Newtonsoft.Json; +using NFluent; +using System; using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Moq; -using Newtonsoft.Json; -using NFluent; using WireMock.Handlers; using WireMock.Logging; using WireMock.RequestBuilders; diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.Settings.cs b/test/WireMock.Net.Tests/FluentMockServerTests.Settings.cs index 920a5424..b819a397 100644 --- a/test/WireMock.Net.Tests/FluentMockServerTests.Settings.cs +++ b/test/WireMock.Net.Tests/FluentMockServerTests.Settings.cs @@ -62,7 +62,7 @@ namespace WireMock.Net.Tests // Assert var mappings = server.Mappings; - Check.That(mappings.Count()).IsEqualTo(19); + Check.That(mappings.Count()).IsEqualTo(24); Check.That(mappings.All(m => m.Priority == int.MinValue)).IsTrue(); } @@ -81,8 +81,8 @@ namespace WireMock.Net.Tests // Assert var mappings = server.Mappings; - Check.That(mappings.Count()).IsEqualTo(20); - Check.That(mappings.Count(m => m.Priority == int.MinValue)).IsEqualTo(19); + Check.That(mappings.Count()).IsEqualTo(25); + Check.That(mappings.Count(m => m.Priority == int.MinValue)).IsEqualTo(24); Check.That(mappings.Count(m => m.Priority == 1000)).IsEqualTo(1); } diff --git a/test/WireMock.Net.Tests/Handlers/LocalFileSystemHandlerTests.cs b/test/WireMock.Net.Tests/Handlers/LocalFileSystemHandlerTests.cs index 24c42956..b8d06c95 100644 --- a/test/WireMock.Net.Tests/Handlers/LocalFileSystemHandlerTests.cs +++ b/test/WireMock.Net.Tests/Handlers/LocalFileSystemHandlerTests.cs @@ -1,6 +1,6 @@ -using System; +using NFluent; +using System; using System.IO; -using NFluent; using WireMock.Handlers; using Xunit; @@ -21,24 +21,62 @@ namespace WireMock.Net.Tests.Handlers } [Fact] - public void LocalFileSystemHandler_CreateFolder_Throws() + public void LocalFileSystemHandler_CreateFolder_ThrowsArgumentNullException() { // Act Check.ThatCode(() => _sut.CreateFolder(null)).Throws(); } [Fact] - public void LocalFileSystemHandler_WriteMappingFile_Throws() + public void LocalFileSystemHandler_WriteMappingFile_ThrowsArgumentNullException() { // Act Check.ThatCode(() => _sut.WriteMappingFile(null, null)).Throws(); } [Fact] - public void LocalFileSystemHandler_ReadResponseBodyAsFile_Throws() + public void LocalFileSystemHandler_ReadResponseBodyAsFile_ThrowsArgumentNullException() { // Act Check.ThatCode(() => _sut.ReadResponseBodyAsFile(null)).Throws(); } + + [Fact] + public void LocalFileSystemHandler_FileExists_ReturnsFalse() + { + // Act + var result = _sut.FileExists("x.x"); + + // Assert + Check.That(result).IsFalse(); + } + + [Fact] + public void LocalFileSystemHandler_FileExists_ThrowsArgumentNullException() + { + // Act + Check.ThatCode(() => _sut.FileExists(null)).Throws(); + } + + [Fact] + public void LocalFileSystemHandler_ReadFile_ThrowsArgumentNullException() + { + // Act + Check.ThatCode(() => _sut.ReadFile(null)).Throws(); + } + + [Fact] + public void LocalFileSystemHandler_WriteFile_ThrowsArgumentNullException() + { + // Act + Check.ThatCode(() => _sut.WriteFile(null, null)).Throws(); + } + + [Fact] + public void LocalFileSystemHandler_DeleteFile_ThrowsArgumentNullException() + { + // Act + Check.ThatCode(() => _sut.DeleteFile(null)).Throws(); + } } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs new file mode 100644 index 00000000..bef4724f --- /dev/null +++ b/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs @@ -0,0 +1,166 @@ +using Newtonsoft.Json.Linq; +using NFluent; +using WireMock.Matchers; +using Xunit; + +namespace WireMock.Net.Tests.Matchers +{ + public class JmesPathMatcherTests + { + [Fact] + public void JmesPathMatcher_GetName() + { + // Assign + var matcher = new JmesPathMatcher("X"); + + // Act + string name = matcher.Name; + + // Assert + Check.That(name).Equals("JmesPathMatcher"); + } + + [Fact] + public void JmesPathMatcher_GetPatterns() + { + // Assign + var matcher = new JmesPathMatcher("X"); + + // Act + string[] patterns = matcher.GetPatterns(); + + // Assert + Check.That(patterns).ContainsExactly("X"); + } + + [Fact] + public void JmesPathMatcher_IsMatch_ByteArray() + { + // Assign + var bytes = new byte[0]; + var matcher = new JmesPathMatcher(""); + + // Act + double match = matcher.IsMatch(bytes); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JmesPathMatcher_IsMatch_NullString() + { + // Assign + string s = null; + var matcher = new JmesPathMatcher(""); + + // Act + double match = matcher.IsMatch(s); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JmesPathMatcher_IsMatch_NullObject() + { + // Assign + object o = null; + var matcher = new JmesPathMatcher(""); + + // Act + double match = matcher.IsMatch(o); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JmesPathMatcher_IsMatch_String_Exception_Mismatch() + { + // Assign + var matcher = new JmesPathMatcher("xxx"); + + // Act + double match = matcher.IsMatch(""); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JmesPathMatcher_IsMatch_Object_Exception_Mismatch() + { + // Assign + var matcher = new JmesPathMatcher(""); + + // Act + double match = matcher.IsMatch("x"); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JmesPathMatcher_IsMatch_AnonymousObject() + { + // Assign + var matcher = new JmesPathMatcher("things.name == 'RequiredThing'"); + + // Act + double match = matcher.IsMatch(new { things = new { name = "RequiredThing" } }); + + // Assert + Check.That(match).IsEqualTo(1); + } + + [Fact] + public void JmesPathMatcher_IsMatch_JObject() + { + // Assign + string[] patterns = { "things.x == 'RequiredThing'" }; + var matcher = new JmesPathMatcher(patterns); + + // Act + var sub = new JObject + { + { "x", new JValue("RequiredThing") } + }; + var jobject = new JObject + { + { "Id", new JValue(1) }, + { "things", sub } + }; + double match = matcher.IsMatch(jobject); + + // Assert + Check.That(match).IsEqualTo(1); + } + + [Fact] + public void JmesPathMatcher_IsMatch_JObject_Parsed() + { + // Assign + var matcher = new JmesPathMatcher("things.x == 'RequiredThing'"); + + // Act + double match = matcher.IsMatch(JObject.Parse("{ \"things\": { \"x\": \"RequiredThing\" } }")); + + // Assert + Check.That(match).IsEqualTo(1); + } + + [Fact] + public void JmesPathMatcher_IsMatch_RejectOnMatch() + { + // Assign + var matcher = new JmesPathMatcher(MatchBehaviour.RejectOnMatch, "things.x == 'RequiredThing'"); + + // Act + double match = matcher.IsMatch(JObject.Parse("{ \"things\": { \"x\": \"RequiredThing\" } }")); + + // Assert + Check.That(match).IsEqualTo(0.0); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyTests.cs index 54287d2c..4328fd4c 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyTests.cs @@ -1,4 +1,5 @@ -using System.Text; +using System; +using System.Text; using System.Threading.Tasks; using NFluent; using WireMock.Models; @@ -231,5 +232,53 @@ namespace WireMock.Net.Tests.ResponseBuilders Check.That(response2Message.BodyData.BodyAsString).IsNull(); Check.That(response2Message.StatusCode).IsEqualTo(200); } + + [Fact] + public async Task Response_ProvideResponse_WithBodyAsFile() + { + var fileContents = "testFileContents" + Guid.NewGuid(); + var bodyDataAsFile = new BodyData {BodyAsFile = fileContents}; + + var request1 = new RequestMessage(new UrlDetails("http://localhost/__admin/files/filename.txt"), "PUT", ClientIp, bodyDataAsFile); + + var response = Response.Create().WithStatusCode(200).WithBody(fileContents); + + var provideResponseAsync = await response.ProvideResponseAsync(request1); + + Check.That(provideResponseAsync.StatusCode).IsEqualTo(200); + Check.That(provideResponseAsync.BodyData.BodyAsString).Contains(fileContents); + } + + [Fact] + public async Task Response_ProvideResponse_WithResponseAsFile() + { + var fileContents = "testFileContents" + Guid.NewGuid(); + var bodyDataAsFile = new BodyData { BodyAsFile = fileContents }; + + var request1 = new RequestMessage(new UrlDetails("http://localhost/__admin/files/filename.txt"), "GET", ClientIp, bodyDataAsFile); + + var response = Response.Create().WithStatusCode(200).WithBody(fileContents); + + var provideResponseAsync = await response.ProvideResponseAsync(request1); + + Check.That(provideResponseAsync.StatusCode).IsEqualTo(200); + Check.That(provideResponseAsync.BodyData.BodyAsString).Contains(fileContents); + } + + [Fact] + public async Task Response_ProvideResponse_WithResponseDeleted() + { + var fileContents = "testFileContents" + Guid.NewGuid(); + var bodyDataAsFile = new BodyData { BodyAsFile = fileContents }; + + var request1 = new RequestMessage(new UrlDetails("http://localhost/__admin/files/filename.txt"), "DELETE", ClientIp, bodyDataAsFile); + + var response = Response.Create().WithStatusCode(200).WithBody("File deleted."); + + var provideResponseAsync = await response.ProvideResponseAsync(request1); + + Check.That(provideResponseAsync.StatusCode).IsEqualTo(200); + Check.That(provideResponseAsync.BodyData.BodyAsString).Contains("File deleted."); + } } } diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsTests.cs index d609be14..fad6d28d 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsTests.cs @@ -214,5 +214,49 @@ namespace WireMock.Net.Tests.ResponseBuilders // Assert Check.That(JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson)).Equals("[\"first\",\"/foo_array\",\"test 1\",\"test 2\",\"last\"]"); } + + [Fact] + public async Task Response_ProvideResponse_Handlebars_WithBodyAsFile() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost/foo?MyUniqueNumber=1"), "GET", ClientIp); + + var response = Response.Create() + .WithTransformer() + .WithBodyFromFile(@"c:\\{{request.query.MyUniqueNumber}}\test.xml"); // why use a \\ here ? + + // Act + var responseMessage = await response.ProvideResponseAsync(request); + + // Assert + Check.That(responseMessage.BodyData.BodyAsFile).Equals(@"c:\1\test.xml"); + } + + [Fact] + public async Task Response_ProvideResponse_Handlebars_WithBodyAsFile_JsonPath() + { + // Assign + string jsonString = "{ \"MyUniqueNumber\": \"1\" }"; + var bodyData = new BodyData + { + BodyAsString = jsonString, + BodyAsJson = JsonConvert.DeserializeObject(jsonString), + DetectedBodyType = BodyType.Json, + DetectedBodyTypeFromContentType = BodyType.Json, + Encoding = Encoding.UTF8 + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, bodyData); + + string jsonPath = "\"$.MyUniqueNumber\""; + var response = Response.Create() + .WithTransformer() + .WithBodyFromFile(@"c:\\{{JsonPath.SelectToken request.body " + jsonPath + "}}\\test.json"); // why use a \\ here ? + + // Act + var responseMessage = await response.ProvideResponseAsync(request); + + // Assert + Check.That(responseMessage.BodyData.BodyAsFile).Equals(@"c:\1\test.json"); + } } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index d8e5e9ea..16306f97 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -36,7 +36,7 @@ - + all