From 12444cc11e0c806a2436e2a823227dc757781b29 Mon Sep 17 00:00:00 2001 From: JackCreativeCrew <49384070+JackCreativeCrew@users.noreply.github.com> Date: Thu, 11 Apr 2019 16:46:14 +1000 Subject: [PATCH] [265] Add file upload to allow mocking of file operations (#266) * [265] Add file upload to allow mocking of file operations * [265] Fix failing test * Update code + add tests * LocalFileSystemHandlerTests * 1.0.13 * Fixed the file post to create the mapping folder if none exists to begin with, otherwise the file upload fails with 404 (can't find the folder to upload to). * fix tests * add more tests for LocalFileSystemHandler * Added the head method for files to check if a file exists without returning it as a body. * Add a test and fix the response message (head requires no body). * Fix newline * Fix newline. * Fix the number of mapping tests * Update tests and update client-interface-api * Cleanup "MappingConverter.cs" --- Directory.Build.props | 2 +- GitHubReleaseNotes.txt | 2 +- examples/WireMock.Net.Client/Program.cs | 6 + .../CustomFileSystemFileHandler.cs | 34 +++ .../Client/IFluentMockServerAdmin.cs | 37 +++ .../Handlers/IFileSystemHandler.cs | 44 +++- .../Handlers/LocalFileSystemHandler.cs | 58 ++++- src/WireMock.Net/ResponseMessageBuilder.cs | 8 + .../Serialization/MappingConverter.cs | 2 +- .../Server/FluentMockServer.Admin.cs | 14 +- .../Server/FluentMockServer.AdminFiles.cs | 114 +++++++++ .../FLuentMockServerTests.AdminFiles.cs | 215 ++++++++++++++++ .../FluentMockServerAdminRestClientTests.cs | 238 +++++++++++++++++- .../FluentMockServerTests.Admin.cs | 8 +- .../FluentMockServerTests.Settings.cs | 6 +- .../Handlers/LocalFileSystemHandlerTests.cs | 48 +++- .../ResponseBuilders/ResponseWithBodyTests.cs | 51 +++- 17 files changed, 850 insertions(+), 37 deletions(-) create mode 100644 src/WireMock.Net/Server/FluentMockServer.AdminFiles.cs create mode 100644 test/WireMock.Net.Tests/FLuentMockServerTests.AdminFiles.cs diff --git a/Directory.Build.props b/Directory.Build.props index 8031a4fb..2e22d82f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.0.12 + 1.0.13 diff --git a/GitHubReleaseNotes.txt b/GitHubReleaseNotes.txt index fa63df65..ecf3d4f2 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.13.0 \ 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/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/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/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index 4919d031..3ba9ebff 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. /// 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/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/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."); + } } }