From b69b63942ec45e7870857178fbdc42665ffe73a4 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 10 Nov 2024 21:36:42 +1100 Subject: [PATCH] Apply scrubbing to dictionary keys (#1342) * Update SerializationTests.cs * Update SerializationTests.cs * . * . --- docs/dates.md | 26 ++--- docs/guids.md | 14 +-- docs/named-tuples.md | 4 +- docs/scrubbers.md | 12 +- docs/serializer-settings.md | 58 +++++----- ...ctionary_ScrubDictionaryKeys.verified.json | 4 + ...llection_ScrubDictionaryKeys.verified.json | 4 + ...ctionary_ScrubDictionaryKeys.verified.json | 4 + ...ictionary_ScrubDictionaryKeys.verified.txt | 4 + ...ollection_ScrubDictionaryKeys.verified.txt | 4 + ...ictionary_ScrubDictionaryKeys.verified.txt | 4 + .../Serialization/SerializationTests.cs | 104 +++++++++++++++--- .../CustomContractResolver_Dictionary.cs | 9 +- 13 files changed, 175 insertions(+), 76 deletions(-) create mode 100644 src/StrictJsonTests/SerializationTests.Dictionary_ScrubDictionaryKeys.verified.json create mode 100644 src/StrictJsonTests/SerializationTests.NameValueCollection_ScrubDictionaryKeys.verified.json create mode 100644 src/StrictJsonTests/SerializationTests.StringDictionary_ScrubDictionaryKeys.verified.json create mode 100644 src/Verify.Tests/Serialization/SerializationTests.Dictionary_ScrubDictionaryKeys.verified.txt create mode 100644 src/Verify.Tests/Serialization/SerializationTests.NameValueCollection_ScrubDictionaryKeys.verified.txt create mode 100644 src/Verify.Tests/Serialization/SerializationTests.StringDictionary_ScrubDictionaryKeys.verified.txt diff --git a/docs/dates.md b/docs/dates.md index 214f111a36..1e9d1a2446 100644 --- a/docs/dates.md +++ b/docs/dates.md @@ -29,7 +29,7 @@ var target = new DateTimeTarget await Verify(target); ``` -snippet source | anchor +snippet source | anchor Results in the following: @@ -70,7 +70,7 @@ settings.DontScrubDateTimes(); return Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor @@ -87,7 +87,7 @@ var target = new return Verify(target) .DontScrubDateTimes(); ``` -snippet source | anchor +snippet source | anchor @@ -100,7 +100,7 @@ return Verify(target) public static void ModuleInitializer() => VerifierSettings.DontScrubDateTimes(); ``` -snippet source | anchor +snippet source | anchor @@ -124,7 +124,7 @@ settings.DisableDateCounting(); return Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor @@ -141,7 +141,7 @@ var target = new return Verify(target) .DisableDateCounting(); ``` -snippet source | anchor +snippet source | anchor @@ -154,7 +154,7 @@ return Verify(target) public static void ModuleInitializer() => VerifierSettings.DisableDateCounting(); ``` -snippet source | anchor +snippet source | anchor @@ -201,7 +201,7 @@ public Task ScrubInlineDateTimesInstance() settings); } ``` -snippet source | anchor +snippet source | anchor @@ -215,7 +215,7 @@ public Task ScrubInlineDateTimesFluent() => Verify("content 2020-10-20 content") .ScrubInlineDateTimes("yyyy-MM-dd"); ``` -snippet source | anchor +snippet source | anchor @@ -231,7 +231,7 @@ public static class ModuleInitializer VerifierSettings.ScrubInlineDateTimes("yyyy-MM-dd"); } ``` -snippet source | anchor +snippet source | anchor @@ -252,7 +252,7 @@ settings.AddNamedDateTime(new(2030, 1, 2), "instanceNamedDateTime"); settings.AddNamedDateTimeOffset(new DateTime(2030, 1, 2), "instanceNamedTimeOffset"); await Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor @@ -267,7 +267,7 @@ await Verify(target) .AddNamedDateTime(new(2030, 1, 2), "instanceNamedDateTime") .AddNamedDateTimeOffset(new DateTime(2030, 1, 2), "instanceNamedTimeOffset"); ``` -snippet source | anchor +snippet source | anchor @@ -285,7 +285,7 @@ public static void NamedDatesAndTimesGlobal() VerifierSettings.AddNamedDateTimeOffset(new(new(2030, 1, 1)), "namedDateTimeOffset"); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guids.md b/docs/guids.md index 2538e1f53b..7d4f45e84c 100644 --- a/docs/guids.md +++ b/docs/guids.md @@ -23,7 +23,7 @@ var target = new GuidTarget await Verify(target); ``` -snippet source | anchor +snippet source | anchor Results in the following: @@ -56,7 +56,7 @@ var settings = new VerifySettings(); settings.DontScrubGuids(); await Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor @@ -68,7 +68,7 @@ await Verify(target, settings); await Verify(target) .DontScrubGuids(); ``` -snippet source | anchor +snippet source | anchor @@ -79,7 +79,7 @@ await Verify(target) ```cs VerifierSettings.DontScrubGuids(); ``` -snippet source | anchor +snippet source | anchor @@ -103,7 +103,7 @@ public Task ScrubInlineGuidsInstance() settings); } ``` -snippet source | anchor +snippet source | anchor @@ -117,7 +117,7 @@ public Task ScrubInlineGuidsFluent() => Verify("content 651ad409-fc30-4b12-a47e-616d3f953e4c content") .ScrubInlineGuids(); ``` -snippet source | anchor +snippet source | anchor @@ -133,7 +133,7 @@ public static class ModuleInitializer VerifierSettings.ScrubInlineGuids(); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/named-tuples.md b/docs/named-tuples.md index 835be2934f..151cc98227 100644 --- a/docs/named-tuples.md +++ b/docs/named-tuples.md @@ -19,7 +19,7 @@ Given a method that returns a named tuple: static (bool Member1, string Member2, string Member3) MethodWithNamedTuple() => (true, "A", "B"); ``` -snippet source | anchor +snippet source | anchor Can be verified: @@ -29,7 +29,7 @@ Can be verified: ```cs await VerifyTuple(() => MethodWithNamedTuple()); ``` -snippet source | anchor +snippet source | anchor Resulting in: diff --git a/docs/scrubbers.md b/docs/scrubbers.md index 2981364acf..6458f9e2f6 100644 --- a/docs/scrubbers.md +++ b/docs/scrubbers.md @@ -59,7 +59,7 @@ For example remove lines containing `text`: ```cs verifySettings.ScrubLines(line => line.Contains("text")); ``` -snippet source | anchor +snippet source | anchor @@ -74,7 +74,7 @@ For example remove lines containing `text1` or `text2` ```cs verifySettings.ScrubLinesContaining("text1", "text2"); ``` -snippet source | anchor +snippet source | anchor Case insensitive by default (StringComparison.OrdinalIgnoreCase). @@ -86,7 +86,7 @@ Case insensitive by default (StringComparison.OrdinalIgnoreCase). ```cs verifySettings.ScrubLinesContaining(StringComparison.Ordinal, "text1", "text2"); ``` -snippet source | anchor +snippet source | anchor @@ -101,7 +101,7 @@ For example converts lines to upper case: ```cs verifySettings.ScrubLinesWithReplace(line => line.ToUpper()); ``` -snippet source | anchor +snippet source | anchor @@ -114,7 +114,7 @@ Replaces `Environment.MachineName` with `TheMachineName`. ```cs verifySettings.ScrubMachineName(); ``` -snippet source | anchor +snippet source | anchor @@ -127,7 +127,7 @@ Replaces `Environment.UserName` with `TheUserName`. ```cs verifySettings.ScrubUserName(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/serializer-settings.md b/docs/serializer-settings.md index d83965ab5a..c8baaa7579 100644 --- a/docs/serializer-settings.md +++ b/docs/serializer-settings.md @@ -57,7 +57,7 @@ var settings = new VerifySettings(); settings.UseStrictJson(); await Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor @@ -73,7 +73,7 @@ var target = new TheTarget await Verify(target) .UseStrictJson(); ``` -snippet source | anchor +snippet source | anchor @@ -190,7 +190,7 @@ VerifierSettings .AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All); ``` -snippet source | anchor +snippet source | anchor @@ -246,7 +246,7 @@ To disable this behavior globally use: ```cs VerifierSettings.DontIgnoreEmptyCollections(); ``` -snippet source | anchor +snippet source | anchor @@ -347,7 +347,7 @@ public Task ScopedSerializerFluent() .AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All); } ``` -snippet source | anchor +snippet source | anchor Result: @@ -475,7 +475,7 @@ public Task IgnoreTypeFluent() .IgnoreMembersWithType(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -485,7 +485,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersWithType(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -622,7 +622,7 @@ public Task ScrubTypeFluent() .ScrubMembersWithType(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -632,7 +632,7 @@ Or globally: ```cs VerifierSettings.ScrubMembersWithType(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -711,7 +711,7 @@ public Task AddIgnoreInstanceFluent() .IgnoreInstance(_ => _.Property == "Ignore"); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -721,7 +721,7 @@ Or globally: ```cs VerifierSettings.IgnoreInstance(_ => _.Property == "Ignore"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -783,7 +783,7 @@ public Task AddScrubInstanceFluent() .ScrubInstance(_ => _.Property == "Ignore"); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -793,7 +793,7 @@ Or globally: ```cs VerifierSettings.ScrubInstance(_ => _.Property == "Ignore"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -838,7 +838,7 @@ public Task WithObsoleteProp() return Verify(target); } ``` -snippet source | anchor +snippet source | anchor Result: @@ -886,7 +886,7 @@ public Task WithObsoletePropIncludedFluent() .IncludeObsoletes(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -896,7 +896,7 @@ Or globally: ```cs VerifierSettings.IncludeObsoletes(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -957,7 +957,7 @@ public Task IgnoreMemberByExpressionFluent() _ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally @@ -972,7 +972,7 @@ VerifierSettings.IgnoreMembers( _ => _.GetOnlyProperty, _ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1032,7 +1032,7 @@ public Task ScrubMemberByExpressionFluent() _ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally @@ -1047,7 +1047,7 @@ VerifierSettings.ScrubMembers( _ => _.GetOnlyProperty, _ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1126,7 +1126,7 @@ public Task IgnoreMemberByNameFluent() .IgnoreMember(_ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1146,7 +1146,7 @@ VerifierSettings.IgnoreMember("Field"); // For a specific type with expression VerifierSettings.IgnoreMember(_ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1221,7 +1221,7 @@ public Task ScrubMemberByNameFluent() .ScrubMember(_ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1241,7 +1241,7 @@ VerifierSettings.ScrubMember("Field"); // For a specific type with expression VerifierSettings.ScrubMember(_ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1292,7 +1292,7 @@ public Task CustomExceptionPropFluent() .IgnoreMembersThatThrow(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1302,7 +1302,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersThatThrow(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1339,7 +1339,7 @@ public Task ExceptionMessagePropFluent() .IgnoreMembersThatThrow(_ => _.Message == "Ignore"); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1349,7 +1349,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersThatThrow(_ => _.Message == "Ignore"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1507,7 +1507,7 @@ public Task MemberConverterByExpression() return Verify(input); } ``` -snippet source | anchor +snippet source | anchor diff --git a/src/StrictJsonTests/SerializationTests.Dictionary_ScrubDictionaryKeys.verified.json b/src/StrictJsonTests/SerializationTests.Dictionary_ScrubDictionaryKeys.verified.json new file mode 100644 index 0000000000..95687d94c9 --- /dev/null +++ b/src/StrictJsonTests/SerializationTests.Dictionary_ScrubDictionaryKeys.verified.json @@ -0,0 +1,4 @@ +{ + "Guid_1": "value", + "scrubbed": "value" +} \ No newline at end of file diff --git a/src/StrictJsonTests/SerializationTests.NameValueCollection_ScrubDictionaryKeys.verified.json b/src/StrictJsonTests/SerializationTests.NameValueCollection_ScrubDictionaryKeys.verified.json new file mode 100644 index 0000000000..95687d94c9 --- /dev/null +++ b/src/StrictJsonTests/SerializationTests.NameValueCollection_ScrubDictionaryKeys.verified.json @@ -0,0 +1,4 @@ +{ + "Guid_1": "value", + "scrubbed": "value" +} \ No newline at end of file diff --git a/src/StrictJsonTests/SerializationTests.StringDictionary_ScrubDictionaryKeys.verified.json b/src/StrictJsonTests/SerializationTests.StringDictionary_ScrubDictionaryKeys.verified.json new file mode 100644 index 0000000000..95687d94c9 --- /dev/null +++ b/src/StrictJsonTests/SerializationTests.StringDictionary_ScrubDictionaryKeys.verified.json @@ -0,0 +1,4 @@ +{ + "Guid_1": "value", + "scrubbed": "value" +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.Dictionary_ScrubDictionaryKeys.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.Dictionary_ScrubDictionaryKeys.verified.txt new file mode 100644 index 0000000000..c6073562fa --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.Dictionary_ScrubDictionaryKeys.verified.txt @@ -0,0 +1,4 @@ +{ + Guid_1: value, + scrubbed: value +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.NameValueCollection_ScrubDictionaryKeys.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.NameValueCollection_ScrubDictionaryKeys.verified.txt new file mode 100644 index 0000000000..c6073562fa --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.NameValueCollection_ScrubDictionaryKeys.verified.txt @@ -0,0 +1,4 @@ +{ + Guid_1: value, + scrubbed: value +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.StringDictionary_ScrubDictionaryKeys.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.StringDictionary_ScrubDictionaryKeys.verified.txt new file mode 100644 index 0000000000..c6073562fa --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.StringDictionary_ScrubDictionaryKeys.verified.txt @@ -0,0 +1,4 @@ +{ + Guid_1: value, + scrubbed: value +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.cs b/src/Verify.Tests/Serialization/SerializationTests.cs index 0e5919d283..3f36a52382 100644 --- a/src/Verify.Tests/Serialization/SerializationTests.cs +++ b/src/Verify.Tests/Serialization/SerializationTests.cs @@ -502,10 +502,13 @@ public Task AddExtraSettingsFluent() => // ReSharper disable once UnusedMember.Local static void AddExtraSettingsGlobal() => - #region AddExtraSettingsGlobal + + #region AddExtraSettingsGlobal + VerifierSettings .AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All); + #endregion [Theory] @@ -642,12 +645,12 @@ public async Task VerifyJsonUseStrictJson() var settings = new VerifySettings(); settings.UseStrictJson(); await VerifyJson( - """ - { - "fruit": "Apple" - } - """, - settings); + """ + { + "fruit": "Apple" + } + """, + settings); } [Fact] @@ -1407,7 +1410,7 @@ public async Task ScrubInlineDateTimesInValidFormat() { var exception = await Assert.ThrowsAsync(() => Verify("2020 10 01") .ScrubInlineDates("dddd d MMM yyyy 'at' h:mm tt")); - Assert.Equal("Format 'dddd d MMM yyyy 'at' h:mm tt' is not valid for DateOnly.ToString(format, culture).",exception.Message); + Assert.Equal("Format 'dddd d MMM yyyy 'at' h:mm tt' is not valid for DateOnly.ToString(format, culture).", exception.Message); } #endif @@ -1522,34 +1525,49 @@ public Task ScrubInlineDateTimes() // ReSharper disable once UnusedMember.Local static void DontIgnoreEmptyCollections() => - #region DontIgnoreEmptyCollections + + #region DontIgnoreEmptyCollections + VerifierSettings.DontIgnoreEmptyCollections(); + #endregion // ReSharper disable once UnusedMember.Local static void DontScrubGuids() => - #region DontScrubGuidsGlobal + + #region DontScrubGuidsGlobal + VerifierSettings.DontScrubGuids(); + #endregion // ReSharper disable once UnusedMember.Local static void DontScrubUserProfile() => - #region DontScrubUserProfile + + #region DontScrubUserProfile + VerifierSettings.DontScrubUserProfile(); + #endregion // ReSharper disable once UnusedMember.Local static void DontScrubProjectDirectory() => - #region DontScrubProjectDirectory + + #region DontScrubProjectDirectory + VerifierSettings.DontScrubProjectDirectory(); + #endregion // ReSharper disable once UnusedMember.Local static void DontScrubSolutionDirectory() => - #region DontScrubSolutionDirectory + + #region DontScrubSolutionDirectory + VerifierSettings.DontScrubSolutionDirectory(); + #endregion /* @@ -1917,8 +1935,11 @@ public async Task UseShortTypeName() [Fact] public async Task NamedTuple() => - #region VerifyTuple + + #region VerifyTuple + await VerifyTuple(() => MethodWithNamedTuple()); + #endregion #region MethodWithNamedTuple @@ -2321,8 +2342,11 @@ class CollectionTarget // ReSharper disable once UnusedMember.Local static void ExceptionMessagePropGlobal() => - #region IgnoreMembersThatThrowExpressionGlobal + + #region IgnoreMembersThatThrowExpressionGlobal + VerifierSettings.IgnoreMembersThatThrow(_ => _.Message == "Ignore"); + #endregion @@ -3500,8 +3524,11 @@ class IgnoreExplicitTarget // ReSharper disable once UnusedMember.Local static void CustomExceptionPropGlobal() => - #region IgnoreMembersThatThrowGlobal + + #region IgnoreMembersThatThrowGlobal + VerifierSettings.IgnoreMembersThatThrow(); + #endregion #region IgnoreMembersThatThrow @@ -3615,8 +3642,11 @@ class WithNotSupportedException // ReSharper disable once UnusedMember.Local static void WithObsoletePropIncludedGlobally() => - #region WithObsoletePropIncludedGlobally + + #region WithObsoletePropIncludedGlobally + VerifierSettings.IncludeObsoletes(); + #endregion #region WithObsoletePropIncluded @@ -3772,6 +3802,7 @@ public override void Write(VerifyJsonWriter writer, ConverterIgnoreDefaultTarget writer.WriteEnd(); } } + class ConverterIgnoreDefaultTarget { public string Name { get; set; } = null!; @@ -3883,4 +3914,43 @@ public async Task CancellationToken_CancelledDisposed() cancelSource.Dispose(); await Verify(token); } + + [Fact] + public Task NameValueCollection_ScrubDictionaryKeys() => + Verify(new NameValueCollection + { + { + Guid.NewGuid().ToString(), "value" + }, + { + "key", "value" + }, + }) + .AddScrubber(_ => _.Replace("key", "scrubbed")); + + [Fact] + public Task StringDictionary_ScrubDictionaryKeys() => + Verify(new StringDictionary + { + { + Guid.NewGuid().ToString(), "value" + }, + { + "key", "value" + }, + }) + .AddScrubber(_ => _.Replace("key", "scrubbed")); + + [Fact] + public Task Dictionary_ScrubDictionaryKeys() => + Verify(new Dictionary + { + { + Guid.NewGuid().ToString(), "value" + }, + { + "key", "value" + }, + }) + .AddScrubber(_ => _.Replace("key", "scrubbed")); } \ No newline at end of file diff --git a/src/Verify/Serialization/CustomContractResolver_Dictionary.cs b/src/Verify/Serialization/CustomContractResolver_Dictionary.cs index d69dd907ae..50548b4bc4 100644 --- a/src/Verify/Serialization/CustomContractResolver_Dictionary.cs +++ b/src/Verify/Serialization/CustomContractResolver_Dictionary.cs @@ -27,7 +27,7 @@ KeyValueInterceptResult HandleDictionaryItem(JsonWriter writer, object key, obje return ToInterceptKeyValueResult(scrubOrIgnore.Value); } - if (TryConvertDictionaryKey(key, out var resultKey)) + if (TryConvertDictionaryKey(writer, key, out var resultKey)) { return KeyValueInterceptResult.ReplaceKey(resultKey); } @@ -45,7 +45,7 @@ static KeyValueInterceptResult ToInterceptKeyValueResult(ScrubOrIgnore scrubOrIg return KeyValueInterceptResult.ReplaceValue("{Scrubbed}"); } - bool TryConvertDictionaryKey(object original, [NotNullWhen(true)] out string? result) + bool TryConvertDictionaryKey(JsonWriter writer, object original, [NotNullWhen(true)] out string? result) { var counter = Counter.Current; @@ -83,6 +83,11 @@ bool TryConvertDictionaryKey(object original, [NotNullWhen(true)] out string? re { return true; } + + var verifyJsonWriter = (VerifyJsonWriter)writer; + result = ApplyScrubbers.ApplyForPropertyValue(stringValue.AsSpan(), verifyJsonWriter.settings, counter).ToString(); + + return true; } if (original is DateTime dateTime)