diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs index 0e91132c8..fa09a5b05 100644 --- a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs @@ -69,6 +69,11 @@ public class ProxyAndRecordSettingsModel /// public bool AppendGuidToSavedMappingFile { get; set; } + /// + /// Set prefix for saved mapping file. + /// + public string PrefixForSavedMappingFile { get; set; } + /// /// Defines the Replace Settings. /// diff --git a/src/WireMock.Net/Serialization/MappingFileNameSanitizer.cs b/src/WireMock.Net/Serialization/MappingFileNameSanitizer.cs new file mode 100644 index 000000000..8e5e73f09 --- /dev/null +++ b/src/WireMock.Net/Serialization/MappingFileNameSanitizer.cs @@ -0,0 +1,48 @@ +using System.IO; +using System.Linq; +using Stef.Validation; +using WireMock.Settings; + +namespace WireMock.Serialization; + +/// +/// Creates sanitized file names for mappings +/// +public class MappingFileNameSanitizer +{ + private const char ReplaceChar = '_'; + + private readonly WireMockServerSettings _settings; + + public MappingFileNameSanitizer(WireMockServerSettings settings) + { + _settings = Guard.NotNull(settings); + } + + /// + /// Creates sanitized file names for mappings + /// + public string BuildSanitizedFileName(IMapping mapping) + { + string name; + if (!string.IsNullOrEmpty(mapping.Title)) + { + // remove 'Proxy Mapping for ' and an extra space character after the HTTP request method + name = mapping.Title.Replace(ProxyAndRecordSettings.DefaultPrefixForSavedMappingFile, "").Replace(' '.ToString(), string.Empty); + if (_settings.ProxyAndRecordSettings?.AppendGuidToSavedMappingFile == true) + { + name += $"{ReplaceChar}{mapping.Guid}"; + } + } + else + { + name = mapping.Guid.ToString(); + } + + if (!string.IsNullOrEmpty(_settings.ProxyAndRecordSettings?.PrefixForSavedMappingFile)) + { + name = $"{_settings.ProxyAndRecordSettings.PrefixForSavedMappingFile}{ReplaceChar}{name}"; + } + return $"{Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, ReplaceChar))}.json"; + } +} diff --git a/src/WireMock.Net/Serialization/MappingToFileSaver.cs b/src/WireMock.Net/Serialization/MappingToFileSaver.cs index dcaa29127..7b548299b 100644 --- a/src/WireMock.Net/Serialization/MappingToFileSaver.cs +++ b/src/WireMock.Net/Serialization/MappingToFileSaver.cs @@ -10,11 +10,13 @@ internal class MappingToFileSaver { private readonly WireMockServerSettings _settings; private readonly MappingConverter _mappingConverter; + private readonly MappingFileNameSanitizer _fileNameSanitizer; public MappingToFileSaver(WireMockServerSettings settings, MappingConverter mappingConverter) { _settings = Guard.NotNull(settings); _mappingConverter = Guard.NotNull(mappingConverter); + _fileNameSanitizer = new MappingFileNameSanitizer(settings); } public void SaveMappingsToFile(IMapping[] mappings, string? folder = null) @@ -42,7 +44,7 @@ public void SaveMappingToFile(IMapping mapping, string? folder = null) var model = _mappingConverter.ToMappingModel(mapping); - var filename = BuildSanitizedFileName(mapping); + var filename = _fileNameSanitizer.BuildSanitizedFileName(mapping); var path = Path.Combine(folder, filename); Save(model, path); @@ -54,23 +56,4 @@ private void Save(object value, string path) _settings.FileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsDefault)); } - - private string BuildSanitizedFileName(IMapping mapping, char replaceChar = '_') - { - string name; - if (!string.IsNullOrEmpty(mapping.Title)) - { - name = mapping.Title!; - if (_settings.ProxyAndRecordSettings?.AppendGuidToSavedMappingFile == true) - { - name += $"{replaceChar}{mapping.Guid}"; - } - } - else - { - name = mapping.Guid.ToString(); - } - - return $"{Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, replaceChar))}.json"; - } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs index eb8631636..ed1b4febe 100644 --- a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs +++ b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs @@ -7,6 +7,11 @@ namespace WireMock.Settings; /// public class ProxyAndRecordSettings : HttpClientSettings { + /// + /// Default prefix value for saved mapping file + /// + public const string DefaultPrefixForSavedMappingFile = "Proxy Mapping for "; + /// /// The URL to proxy. /// @@ -95,9 +100,14 @@ public string SaveMappingForStatusCodePattern /// public bool AppendGuidToSavedMappingFile { get; set; } + /// + /// Set prefix for saved mapping file. + /// + public string PrefixForSavedMappingFile { get; set; } = DefaultPrefixForSavedMappingFile; + /// /// Proxy all Api calls, irrespective of any condition /// [PublicAPI] public bool ProxyAll { get; set; } = false; -} \ No newline at end of file +} diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index 962b94812..65a581200 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -120,6 +120,7 @@ private static void ParseProxyAndRecordSettings(WireMockServerSettings settings, SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), UseDefinedRequestMatchers = parser.GetBoolValue(nameof(ProxyAndRecordSettings.UseDefinedRequestMatchers)), AppendGuidToSavedMappingFile = parser.GetBoolValue(nameof(ProxyAndRecordSettings.AppendGuidToSavedMappingFile)), + PrefixForSavedMappingFile = parser.GetStringValue(nameof(ProxyAndRecordSettings.PrefixForSavedMappingFile), null), Url = proxyUrl!, SaveMappingSettings = new ProxySaveMappingSettings { diff --git a/test/WireMock.Net.Tests/Serialization/MappingFileNameSanitizerTests.cs b/test/WireMock.Net.Tests/Serialization/MappingFileNameSanitizerTests.cs new file mode 100644 index 000000000..2c0db1a42 --- /dev/null +++ b/test/WireMock.Net.Tests/Serialization/MappingFileNameSanitizerTests.cs @@ -0,0 +1,160 @@ +using System; +using Moq; +using WireMock.Serialization; +using WireMock.Settings; +using Xunit; + +namespace WireMock.Net.Tests.Serialization; + +public class MappingFileNameSanitizerTests +{ + private const string MappingGuid = "ce216a13-e7d6-42d7-91ac-8ae709e2add1"; + private const string MappingTitle = "Proxy Mapping for POST _ordermanagement_v1_orders_cancel"; + + [Fact] + public void BuildSanitizedFileName_WithTitleAndGuid_AppendsGuid() + { + // Arrange + var mappingMock = new Mock(); + mappingMock.Setup(m => m.Title).Returns(MappingTitle); + mappingMock.Setup(m => m.Guid).Returns(new Guid(MappingGuid)); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings + { + AppendGuidToSavedMappingFile = true + } + }; + + var sanitizer = new MappingFileNameSanitizer(settings); + + // Act + var result = sanitizer.BuildSanitizedFileName(mappingMock.Object); + + // Assert + Assert.Equal($"Proxy Mapping for _POST_ordermanagement_v1_orders_cancel_{MappingGuid}.json", result); + } + + [Fact] + public void BuildSanitizedFileName_WithoutTitle_UsesGuid() + { + // Arrange + var mappingMock = new Mock(); + mappingMock.Setup(m => m.Title).Returns((string?)null); + mappingMock.Setup(m => m.Guid).Returns(new Guid(MappingGuid)); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new () + }; + var sanitizer = new MappingFileNameSanitizer(settings); + + // Act + var result = sanitizer.BuildSanitizedFileName(mappingMock.Object); + + // Assert + Assert.Equal($"Proxy Mapping for _{MappingGuid}.json", result); + } + + [Fact] + public void BuildSanitizedFileName_WithTitleAndGuid_NoAppendGuidSetting() + { + // Arrange + var mappingMock = new Mock(); + mappingMock.Setup(m => m.Title).Returns(MappingTitle); + mappingMock.Setup(m => m.Guid).Returns(new Guid(MappingGuid)); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings + { + AppendGuidToSavedMappingFile = false + } + }; + + var sanitizer = new MappingFileNameSanitizer(settings); + + // Act + var result = sanitizer.BuildSanitizedFileName(mappingMock.Object); + + // Assert + Assert.Equal("Proxy Mapping for _POST_ordermanagement_v1_orders_cancel.json", result); + } + + [Fact] + public void BuildSanitizedFileName_WithPrefix_AddsPrefix() + { + // Arrange + var mappingMock = new Mock(); + mappingMock.Setup(m => m.Title).Returns(MappingTitle); + mappingMock.Setup(m => m.Guid).Returns(new Guid(MappingGuid)); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings + { + PrefixForSavedMappingFile = "Prefix" + } + }; + + var sanitizer = new MappingFileNameSanitizer(settings); + + // Act + var result = sanitizer.BuildSanitizedFileName(mappingMock.Object); + + // Assert + Assert.Equal($"Prefix_POST_ordermanagement_v1_orders_cancel.json", result); + } + + [Fact] + public void BuildSanitizedFileName_WithPrefix_AddsPrefixEmptyString() + { + // Arrange + var mappingMock = new Mock(); + mappingMock.Setup(m => m.Title).Returns(MappingTitle); + mappingMock.Setup(m => m.Guid).Returns(new Guid(MappingGuid)); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings + { + PrefixForSavedMappingFile = string.Empty + } + }; + + var sanitizer = new MappingFileNameSanitizer(settings); + + // Act + var result = sanitizer.BuildSanitizedFileName(mappingMock.Object); + + // Assert + Assert.Equal($"POST_ordermanagement_v1_orders_cancel.json", result); + } + + [Fact] + public void BuildSanitizedFileName_WithTitleAndGuid_WithPrefixAndAppendGuidSetting() + { + // Arrange + var mappingMock = new Mock(); + mappingMock.Setup(m => m.Title).Returns(MappingTitle); + mappingMock.Setup(m => m.Guid).Returns(new Guid(MappingGuid)); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings + { + PrefixForSavedMappingFile = "Prefix", + AppendGuidToSavedMappingFile = true + } + }; + + var sanitizer = new MappingFileNameSanitizer(settings); + + // Act + var result = sanitizer.BuildSanitizedFileName(mappingMock.Object); + + // Assert + Assert.Equal($"Prefix_POST_ordermanagement_v1_orders_cancel_{MappingGuid}.json", result); + } +} diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index 95f85e0cd..1d7240eca 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -174,7 +174,7 @@ public async Task WireMockServer_Proxy_With_SaveMappingToFile_Is_True_ShouldSave server.Mappings.Should().HaveCount(2); // Verify - fileSystemHandlerMock.Verify(f => f.WriteMappingFile($"m{System.IO.Path.DirectorySeparatorChar}{title}.json", It.IsRegex(stringBody)), Times.Once); + fileSystemHandlerMock.Verify(f => f.WriteMappingFile($"m{System.IO.Path.DirectorySeparatorChar}Proxy Mapping for _{title}.json", It.IsRegex(stringBody)), Times.Once); } [Fact]