Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e626e73
feat(policies): a second host may configure the same posture, and a p…
Sajadh92 Sep 20, 2026
f178ba0
docs: 3.3.0 — configuring twice, a path the query cannot compute, pub…
Sajadh92 Sep 20, 2026
757e468
test(policies): a composed clause reads the source too, and every pos…
Sajadh92 Sep 20, 2026
073614c
test: stop blocking the thread pool, and give the store watch a budge…
Sajadh92 Sep 20, 2026
68bc021
fix(policies): a column only a subtype maps is not one the queried ty…
Sajadh92 Sep 20, 2026
fb2658c
fix(policies): only EF Core's rows are held to what EF Core can compute
Sajadh92 Sep 20, 2026
45f1392
fix(policies): a second call adding a source is refused, the catalogu…
Sajadh92 Sep 20, 2026
652f3b1
fix(policies): a projection is not held to what the database can comp…
Sajadh92 Sep 20, 2026
ae60b45
docs: what the review found in the text, and what the fixes changed u…
Sajadh92 Sep 20, 2026
371a878
test(policies): keep the review's probes, so what was ruled out stays…
Sajadh92 Sep 20, 2026
2735a72
fix(policies): the provider that translates decides, and a conditiona…
Sajadh92 Sep 20, 2026
d29ce52
fix(policies): the trace flag is compared by the value that applies, …
Sajadh92 Sep 20, 2026
94cf5dc
docs: what the second review found in the text, and what the fixes ch…
Sajadh92 Sep 20, 2026
1cbe928
test(policies): keep the second review's probes, so what was ruled ou…
Sajadh92 Sep 20, 2026
bc21ca6
fix(policies): EF Core's own provider is that type, and a member read…
Sajadh92 Sep 20, 2026
7cb2866
docs: which provider the rule belongs to, and how far a projection is…
Sajadh92 Sep 20, 2026
56d169d
docs: the third review's corrections, and the probes that hold them
Sajadh92 Sep 20, 2026
3ef4029
fix(policies): a member a subquery builds is not the navigation it re…
Sajadh92 Sep 20, 2026
c8f7538
docs: the cookbook is twelve examples, and a null on its own is not read
Sajadh92 Sep 20, 2026
f1c7d95
fix(policies): the audit cap refuses like any other field, and the pr…
Sajadh92 Sep 20, 2026
692dd11
docs: a ninth inference channel, and which providers the refusal leav…
Sajadh92 Sep 20, 2026
0e2f2c9
docs: the audit cap's new refusal everywhere it is described, not onl…
Sajadh92 Sep 20, 2026
78ffa8a
fix(policies): four refusals that named a field under Strict name the…
Sajadh92 Sep 20, 2026
7034717
docs: a tenth channel, what [DwAudit] records, and the member a seque…
Sajadh92 Sep 20, 2026
63d75bd
fix(policies): an audited field a request does not name is still a fi…
Sajadh92 Sep 20, 2026
e03d856
docs: an eleventh entry on the security page, and what a use is
Sajadh92 Sep 20, 2026
0333c49
fix(policies): a dry run is either switch, and Apply keeps its docume…
Sajadh92 Sep 20, 2026
97fb222
docs: what each of the four refusals actually says, in the reference …
Sajadh92 Sep 20, 2026
fe4745d
fix(policies): the audit keeps the field the caller is not told, and …
Sajadh92 Sep 20, 2026
893cadc
docs: the audit keeps the field, a blank name fails alike, and an ali…
Sajadh92 Sep 20, 2026
fc4ac71
docs: the ASP.NET Core package says which of the core changes reach i…
Sajadh92 Sep 20, 2026
bd85d50
fix(policies): what the caller receives is what the audit records, an…
Sajadh92 Sep 20, 2026
692a4a4
docs: what "hands back" means, and the navigation cap on a name writt…
Sajadh92 Sep 20, 2026
09386e0
docs: the posture sweep is a suite test, and its comment says so
Sajadh92 Sep 21, 2026
89fd6bf
test: the in-memory watch gets the budget its conformance twin alread…
Sajadh92 Sep 21, 2026
7e77f81
fix(core): a page number whose offset passes Int32 is an empty page
Sajadh92 Sep 21, 2026
bd9ac24
fix(policies): a path or a member the attribute walk cannot name is s…
Sajadh92 Sep 21, 2026
8743f65
fix(aspnetcore): a caller who disconnects does not cancel the record …
Sajadh92 Sep 21, 2026
7ee4916
fix(redis): writers moving one rule leave one copy of it
Sajadh92 Sep 21, 2026
f8b9376
build: name the patched Microsoft.Extensions.Caching.Memory
Sajadh92 Sep 21, 2026
b24239c
perf(cache): a property lookup takes no lock and allocates nothing on…
Sajadh92 Sep 21, 2026
ef68f00
fix(policies): a having clause with a null list runs guarded as it ru…
Sajadh92 Sep 21, 2026
48cbcb3
refactor(policies): a single-member path asks nothing of the walk, an…
Sajadh92 Sep 21, 2026
1cc8068
fix(policies): a masked member past the walk comes back masked, and t…
Sajadh92 Sep 21, 2026
967b464
fix(policies): a query the caller runs is refused where only an unnam…
Sajadh92 Sep 21, 2026
8ff87f3
fix(policies): [DwAudit] records a member no path names, once the row…
Sajadh92 Sep 21, 2026
aec64cb
test(policies): a store rule covers the paths beneath its member with…
Sajadh92 Sep 21, 2026
414f587
docs: what round eight changed, in the reference, the site, DOC.md, t…
Sajadh92 Sep 21, 2026
0452005
build(tests): the test project restores with no open advisory
Sajadh92 Sep 21, 2026
d6284a9
fix(core): a number value is read as the parser reads it, not as the …
Sajadh92 Sep 21, 2026
7e9b1b4
fix(core): a null entry in a request's list is a malformed request, r…
Sajadh92 Sep 21, 2026
b8e2eb6
feat(policies): a token vault can hold a key, so a copy of the store …
Sajadh92 Sep 21, 2026
fd15ca1
docs(core): Select names the refusal a null or blank field name now gets
Sajadh92 Sep 21, 2026
9ce3016
docs: number values, null entries and the vault key, in the reference…
Sajadh92 Sep 21, 2026
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
182 changes: 182 additions & 0 deletions DynamicWhere.Tests/CloneTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using DynamicWhere.ex.Classes.Complex;
using DynamicWhere.ex.Classes.Core;
using DynamicWhere.ex.Enums;

namespace DynamicWhere.Tests
{
/// <summary>
/// <c>Clone</c> on the three request types a caller builds.
/// </summary>
/// <remarks>
/// Public since 3.3.0. A caller reading the same request again with one part changed — the next
/// page, another order — used to rebuild the request around the caller's own clauses, which
/// leaves both requests holding one condition tree. The bug that follows is the one the library
/// already avoids internally by cloning before it rewrites anything.
/// </remarks>
public class CloneTests
{
private static Filter Filled() => new()
{
ConditionGroup = new ConditionGroup
{
Conditions =
{
new Condition { Field = "Name", DataType = DataType.Text, Operator = Operator.Equal, Values = { "a" } }
},
SubConditionGroups = new List<ConditionGroup>
{
new()
{
Conditions =
{
new Condition { Field = "Age", DataType = DataType.Number, Operator = Operator.GreaterThan, Values = { "1" } }
}
}
}
},
Selects = new List<string> { "Id", "Name" },
Orders = new List<OrderBy> { new() { Field = "Name", Direction = Direction.Ascending } },
Page = new PageBy { PageNumber = 1, PageSize = 10 }
};

[Fact]
public void A_filter_clone_shares_nothing_with_the_original()
{
Filter original = Filled();
Filter copy = original.Clone();

Assert.NotSame(original, copy);
Assert.NotSame(original.ConditionGroup, copy.ConditionGroup);
Assert.NotSame(original.ConditionGroup!.Conditions[0], copy.ConditionGroup!.Conditions[0]);
Assert.NotSame(original.ConditionGroup.SubConditionGroups![0], copy.ConditionGroup.SubConditionGroups![0]);
Assert.NotSame(original.Selects, copy.Selects);
Assert.NotSame(original.Orders, copy.Orders);
Assert.NotSame(original.Orders![0], copy.Orders![0]);
Assert.NotSame(original.Page, copy.Page);
}

[Fact]
public void Changing_the_copy_leaves_the_caller_s_request_alone()
{
Filter original = Filled();
Filter copy = original.Clone();

copy.Page!.PageNumber = 2;
copy.Orders![0].Direction = Direction.Descending;
copy.Selects!.Add("Age");
copy.ConditionGroup!.Conditions[0].Values[0] = "b";

Assert.Equal(1, original.Page!.PageNumber);
Assert.Equal(Direction.Ascending, original.Orders![0].Direction);
Assert.Equal(2, original.Selects!.Count);
Assert.Equal("a", original.ConditionGroup!.Conditions[0].Values[0]);
}

[Fact]
public void The_copy_carries_every_value()
{
Filter copy = Filled().Clone();

Assert.Equal("Name", copy.ConditionGroup!.Conditions[0].Field);
Assert.Equal("Age", copy.ConditionGroup.SubConditionGroups![0].Conditions[0].Field);
Assert.Equal(new[] { "Id", "Name" }, copy.Selects!);
Assert.Equal("Name", copy.Orders![0].Field);
Assert.Equal(10, copy.Page!.PageSize);
}

[Fact]
public void A_branch_the_caller_left_null_stays_null()
{
Filter copy = new Filter().Clone();

Assert.Null(copy.ConditionGroup);
Assert.Null(copy.Selects);
Assert.Null(copy.Orders);
Assert.Null(copy.Page);
}

[Fact]
public void A_segment_clone_copies_every_set()
{
Segment original = new()
{
ConditionSets =
{
new ConditionSet
{
Sort = 1,
ConditionGroup = new ConditionGroup
{
Conditions =
{
new Condition { Field = "Name", DataType = DataType.Text, Operator = Operator.Equal, Values = { "a" } }
}
}
}
},
Orders = new List<OrderBy> { new() { Field = "Name", Direction = Direction.Ascending } },
Page = new PageBy { PageNumber = 1, PageSize = 5 }
};

Segment copy = original.Clone();

copy.ConditionSets[0].ConditionGroup!.Conditions[0].Values[0] = "b";
copy.Page!.PageSize = 50;

Assert.NotSame(original.ConditionSets[0], copy.ConditionSets[0]);
Assert.Equal("a", original.ConditionSets[0].ConditionGroup!.Conditions[0].Values[0]);
Assert.Equal(5, original.Page!.PageSize);
}

[Fact]
public void A_summary_clone_copies_the_having_clause_as_well()
{
Summary original = new()
{
ConditionGroup = new ConditionGroup
{
Conditions =
{
new Condition { Field = "Name", DataType = DataType.Text, Operator = Operator.Equal, Values = { "a" } }
}
},
GroupBy = new GroupBy
{
Fields = new List<string> { "Name" },
AggregateBy = new List<AggregateBy> { new() { Alias = "Total", Aggregator = Aggregator.Count } }
},
Having = new ConditionGroup
{
Conditions =
{
new Condition { Field = "Total", DataType = DataType.Number, Operator = Operator.GreaterThan, Values = { "1" } }
}
},
Page = new PageBy { PageNumber = 1, PageSize = 5 }
};

Summary copy = original.Clone();

copy.Having!.Conditions[0].Values[0] = "9";
copy.GroupBy!.Fields[0] = "Age";

Assert.NotSame(original.Having, copy.Having);
Assert.NotSame(original.GroupBy, copy.GroupBy);
Assert.Equal("1", original.Having!.Conditions[0].Values[0]);
Assert.Equal("Name", original.GroupBy!.Fields[0]);
}

[Fact]
public void The_next_page_is_what_this_exists_for()
{
Filter caller = Filled();

Filter page2 = caller.Clone();
page2.Page!.PageNumber = 2;

Assert.Equal(1, caller.Page!.PageNumber);
Assert.Equal(2, page2.Page.PageNumber);
Assert.Equal(caller.Page.PageSize, page2.Page.PageSize);
}
}
}
32 changes: 30 additions & 2 deletions DynamicWhere.Tests/DynamicWhere.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,29 @@
<!-- Lazy-loading proxies, for the test that a navigation a proxy can fill after the query counts as
loaded. Same version as EF Core, on both legs. -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="$(DwEfCoreVersion)" />
<!-- EF Core's own in-memory provider, for the probe that the refusal fires there too: it is EF
Core's provider, so the rows are EF Core's to speak for, and the getter fails there as it
fails in SQLite. Same version as EF Core, on both legs. -->
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(DwEfCoreVersion)" />
<!-- ConfigurationBuilder, for the posture-binding tests. In the always-on group rather
than beside Npgsql, because binding is a core feature and the floor leg has to run
its tests too. An Extensions package at 6.0.0 pulls no EF Core graph with it. -->
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />

<!-- Not used directly. Each names the patched version of a package the graph above resolved to
a version with an open advisory: System.Net.Http 4.3.0 (GHSA-7jgj-8wvc-jh57) and
System.Text.RegularExpressions 4.3.0 (GHSA-cmhx-cq75-c4mj), both through xunit's
NETStandard.Library graph, and the native SQLite build EF Core's provider asks for,
SQLitePCLRaw.lib.e_sqlite3 2.1.6 on the 8 leg and 2.0.6 on the floor leg
(GHSA-2m69-gcr7-jv3q, every version up to 2.1.11). Nothing here ships: the four packable
projects were already clean, and this keeps the test project's own restore clean too, so a
new advisory is not lost among old ones. -->
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.13" />
</ItemGroup>

<!-- Npgsql 8 and Mvc.Testing 8 both depend on EF Core 8, so leaving them referenced on the floor
Expand All @@ -54,8 +70,8 @@
API's design-time tooling compiles that project against; left to resolve on their own,
Npgsql pulls 8.0.11 and the assembly versions cannot be unified (MSB3277). -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.21" />
<PackageReference Include="Testcontainers.PostgreSql" Version="4.0.0" />
<PackageReference Include="Testcontainers.Redis" Version="4.0.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="4.15.0" />
<PackageReference Include="Testcontainers.Redis" Version="4.15.0" />
<!-- Drives the administrative endpoints through a real request pipeline: routing,
authorization and model binding all take part, and a test that mounted the handlers
directly would prove none of them. -->
Expand All @@ -73,6 +89,10 @@
where the expression-tree differences between EF Core 6 and 8 would surface. The sales
fixture is excluded because it maps DateOnly/TimeOnly, which EF Core 6 cannot. -->
<ItemGroup Condition="'$(DwFloorLeg)' == 'true'">
<!-- Not used directly. EF Core 6's SQLite provider asks for System.Text.Json 6.0.0 through
Microsoft.Extensions.DependencyModel, which has an open advisory (GHSA-8g4q-xg66-9fp4);
6.0.10 is the patched version. The 8 leg resolves a patched one on its own. -->
<PackageReference Include="System.Text.Json" Version="6.0.10" />
<Compile Remove="*.cs" />
<Compile Remove="Domain/**/*.cs" />
<!-- Kept: AttributeProviderTests reflects over the Blog graph declared here, and it carries no
Expand All @@ -97,6 +117,14 @@
<Compile Remove="Policies/PolicyEndpointTests.cs" />
<Compile Remove="Policies/PolicyEndpointHardeningTests.cs" />
<Compile Remove="Policies/PolicyAdminControllerTests.cs" />
<!-- Kept off the floor leg: the mapping probes map an owned type with ToJson and a primitive
collection, neither of which EF Core 6 has. What they rule out — that the refusal reads no
mapping shape wrongly — is an EF Core 8 question in the first place. -->
<Compile Remove="Policies/ReviewMappingProbes.cs" />
<!-- Same reason: the complex-type probes map complex properties, which EF Core 6 does not have. -->
<Compile Remove="Policies/Ob4ComplexProbes.cs" />
<!-- Same reason: the mapping probes map ToJson, a complex property and a primitive collection. -->
<Compile Remove="Policies/Pr6MappingProbes.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading
Loading