Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ _ReSharper.Caches/
*.DotSettings.user
*.sln.DotSettings.user
riderModule.iml
.junie

# User-specific/legacy
*.user
Expand Down
48 changes: 48 additions & 0 deletions QsNet.Tests/DecodeOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,52 @@ public void CopyWith_PreservesAndOverrides_Decoders()
copy3.DecodeValue("v", Encoding.UTF8).Should().Be("K2:Value:v");
copy3.DecodeKey("k", Encoding.UTF8).Should().Be("K2:Key:k");
}

[Fact]
public void AllowDots_IsImpliedTrue_When_DecodeDotInKeys_True_And_NotExplicit()
{
var opts = new DecodeOptions { DecodeDotInKeys = true };
opts.AllowDots.Should().BeTrue();
}

[Fact]
public void AllowDots_ExplicitFalse_Wins_Even_When_DecodeDotInKeys_True()
{
var opts = new DecodeOptions { AllowDots = false, DecodeDotInKeys = true };
opts.AllowDots.Should().BeFalse();
}

[Fact]
public void DecodeKey_Throws_When_CustomKindAwareDecoder_Returns_NonString()
{
var opts = new DecodeOptions
{
DecoderWithKind = (_, _, kind) => kind == DecodeKind.Key ? 123 : "ok"
};

Action act = () => opts.DecodeKey("abc", Encoding.UTF8);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Key decoder must return a string or null*Int32*");
}

[Fact]
public void CopyWith_Overrides_StrictDepth_ThrowOnLimitExceeded_AllowSparseLists()
{
var original = new DecodeOptions
{
StrictDepth = false,
ThrowOnLimitExceeded = false,
AllowSparseLists = false
};

var copy = original.CopyWith(strictDepth: true, throwOnLimitExceeded: true, allowSparseLists: true);

copy.StrictDepth.Should().BeTrue();
copy.ThrowOnLimitExceeded.Should().BeTrue();
copy.AllowSparseLists.Should().BeTrue();

// Unchanged properties remain the same by default
copy.AllowEmptyLists.Should().Be(original.AllowEmptyLists);
copy.ListLimit.Should().Be(original.ListLimit);
}
}
36 changes: 18 additions & 18 deletions QsNet.Tests/DecodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4168,6 +4168,24 @@ public void Decode_BracketSingle_CommaSplit_YieldsNestedList()
inner.Select(x => x?.ToString()).Should().Equal("1", "2", "3");
}

#region Nested brackets

[Fact]
public void NestedBrackets_AreParsedCorrectly()
{
Qs.Decode("a[b[]]=c", new DecodeOptions())
.Should().BeEquivalentTo(
new Dictionary<string, object?>
{
["a"] = new Dictionary<string, object?>
{
["b[]"] = "c"
}
});
}

#endregion

#region Encoded dot behavior in keys (%2E / %2e)

[Fact]
Expand Down Expand Up @@ -4765,22 +4783,4 @@ public void Decode_CommaSplit_ThrowsWhenSumExceedsLimitAndThrowOn()
}

#endregion

#region Nested brackets

[Fact]
public void NestedBrackets_AreParsedCorrectly()
{
Qs.Decode("a[b[]]=c", new DecodeOptions())
.Should().BeEquivalentTo(
new Dictionary<string, object?>
{
["a"] = new Dictionary<string, object?>
{
["b[]"] = "c"
}
});
}

#endregion
}
138 changes: 137 additions & 1 deletion QsNet.Tests/EncodeOptionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using FluentAssertions;
Expand Down Expand Up @@ -112,4 +113,139 @@ public void CopyWith_WithModifications_ShouldReturnModifiedOptions()
newOptions.Filter.Should().NotBeNull();
newOptions.Filter.Should().BeOfType<FunctionFilter>();
}
}

[Fact]
public void AllowDots_IsImpliedTrue_When_EncodeDotInKeys_True_And_NotExplicit()
{
var opts = new EncodeOptions { EncodeDotInKeys = true };
opts.AllowDots.Should().BeTrue();
}

[Fact]
public void AllowDots_ExplicitFalse_Wins_Even_When_EncodeDotInKeys_True()
{
var opts = new EncodeOptions { AllowDots = false, EncodeDotInKeys = true };
opts.AllowDots.Should().BeFalse();
}

#pragma warning disable CS0618
[Fact]
public void ListFormat_Fallback_And_Override_Priority()
{
// Default: Indices when neither ListFormat nor Indices is set
var def = new EncodeOptions();
def.ListFormat.Should().Be(ListFormat.Indices);

// Indices=false => Repeat (fallback path)
var optsFalse = new EncodeOptions { Indices = false };
optsFalse.ListFormat.Should().Be(ListFormat.Repeat);

// Indices=true => Indices
var optsTrue = new EncodeOptions { Indices = true };
optsTrue.ListFormat.Should().Be(ListFormat.Indices);

// Explicit ListFormat overrides Indices
var optsOverride = new EncodeOptions { Indices = false, ListFormat = ListFormat.Brackets };
optsOverride.ListFormat.Should().Be(ListFormat.Brackets);
}
#pragma warning restore CS0618

[Fact]
public void GetEncoder_Uses_Custom_When_Present_And_Passes_Encoding_And_Format()
{
Encoding? seenEnc = null;
Format? seenFmt = null;
object? seenVal = null;

var opts = new EncodeOptions
{
Encoder = (v, e, f) =>
{
seenVal = v;
seenEnc = e;
seenFmt = f;
return "X";
},
Charset = Encoding.Latin1,
Format = Format.Rfc3986
};

var result = opts.GetEncoder("a b", Encoding.UTF8, Format.Rfc1738);
result.Should().Be("X");
seenVal.Should().Be("a b");
seenEnc.Should().Be(Encoding.UTF8); // override provided encoding is passed
seenFmt.Should().Be(Format.Rfc1738); // override provided format is passed

// When null overrides supplied, it should pass options.Charset/Format
opts.GetEncoder("y z");
seenEnc.Should().Be(Encoding.Latin1);
seenFmt.Should().Be(Format.Rfc3986);
}

[Fact]
public void GetEncoder_Falls_Back_To_Default_When_No_Custom()
{
var opts = new EncodeOptions { Format = Format.Rfc1738 };
// Utils.Encode returns %20; plus substitution happens later via Formatter, not in GetEncoder
opts.GetEncoder("a b", Encoding.UTF8).Should().Be("a%20b");
}

[Fact]
public void GetDateSerializer_Default_And_Custom()
{
var date = new DateTime(2020, 1, 2, 3, 4, 5, DateTimeKind.Utc);

var optsDefault = new EncodeOptions();
optsDefault.GetDateSerializer(date).Should().Be(date.ToString("O"));

var optsCustom = new EncodeOptions
{
DateSerializer = d => d.ToString("yyyyMMddHHmmss")
};
optsCustom.GetDateSerializer(date).Should().Be("20200102030405");
}

#pragma warning disable CS0618
[Fact]
public void CopyWith_Indices_Sort_Encoder_DateSerializer_Mapping()
{
var baseOpts = new EncodeOptions
{
Indices = true,
Sort = (_, _) => 0,
Encoder = (_, _, _) => "base",
DateSerializer = _ => "base"
};

var enc2Called = false;
var ds2Called = false;
var copy = baseOpts.CopyWith(
indices: false,
sort: (_, _) => 1,
encoder: (_, _, _) =>
{
enc2Called = true;
return "x";
},
dateSerializer: _ =>
{
ds2Called = true;
return "y";
}
);

// Because CopyWith resolves ListFormat from the source before setting Indices, it remains Indices here
copy.ListFormat.Should().Be(ListFormat.Indices);

// Ensure functions are the new ones
copy.GetEncoder("val").Should().Be("x");
enc2Called.Should().BeTrue();

copy.GetDateSerializer(DateTime.UtcNow).Should().Be("y");
ds2Called.Should().BeTrue();

// Sort exists (can't easily trigger usage here, but mapping should hold)
copy.Sort.Should().NotBeNull();
}
}
#pragma warning restore CS0618
Loading
Loading