All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
进位区间改为左开右闭且禁止空洞,邮编 range 按数字/字母数字比较,查价仅国家时不再串用别国 zone。 Co-authored-by: Cursor <cursoragent@cursor.com>
285 lines
11 KiB
C#
285 lines
11 KiB
C#
using FluentAssertions;
|
|
using Oms.Application.Dtos.Orders;
|
|
using Oms.Application.Shared;
|
|
using Oms.Domain.ChannelsAggregate.Entities;
|
|
|
|
namespace Oms.Tests;
|
|
|
|
public class GeoPartitionMatcherTests
|
|
{
|
|
[Fact]
|
|
public void ZipPrefix_LongerPrefixWins()
|
|
{
|
|
var rules = CompileEntities(
|
|
Zip("US", "9"),
|
|
Zip("US", "90"));
|
|
|
|
var hit = GeoPartitionMatcher.MatchEntity(rules, "US", "CA", "LA", "90001");
|
|
hit!.LeftPostcode.Should().Be("90");
|
|
}
|
|
|
|
[Fact]
|
|
public void ZipPresent_ShouldSkipStateAndCity()
|
|
{
|
|
var rules = CompileEntities(
|
|
State("US", "CA"),
|
|
City("US", "CA", "LA"));
|
|
|
|
GeoPartitionMatcher.MatchEntity(rules, "US", "CA", "LA", "90001").Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void ZipEmpty_StateExact_ShouldHit()
|
|
{
|
|
var rules = CompileEntities(
|
|
Zip("US", "90"),
|
|
State("US", "CA"));
|
|
|
|
var hit = GeoPartitionMatcher.MatchEntity(rules, "US", "CA", "LA", null);
|
|
hit!.Province.Should().Be("CA");
|
|
hit.LeftPostcode.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void ZipEmpty_CityRequiresState()
|
|
{
|
|
var rules = CompileEntities(City("US", "CA", "LOSANGELES"));
|
|
|
|
GeoPartitionMatcher.MatchEntity(rules, "US", "NY", "LOSANGELES", null).Should().BeNull();
|
|
GeoPartitionMatcher.MatchEntity(rules, "US", "CA", "LOSANGELES", null).Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void ZipRange_ShouldBeClosedInterval()
|
|
{
|
|
var rules = CompileEntities(Range("US", "90000", "90199"));
|
|
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "90000").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "90199").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "90200").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ZipRange_NumericDifferentLength_ShouldCompareAsNumbers()
|
|
{
|
|
var rules = CompileEntities(Range("US", "9", "10"));
|
|
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "9").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "10").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "8").Should().BeFalse();
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "11").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ZipRange_LeadingZeros_ShouldKeepZipWidth()
|
|
{
|
|
var rules = CompileEntities(Range("US", "02100", "02200"));
|
|
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "02115").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "02100").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "02200").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "US", null, null, "02099").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ZipRange_AlphaNumericPrefix_ShouldCompareNumericTail()
|
|
{
|
|
var rules = CompileEntities(Range("GB", "YO0", "YO9"));
|
|
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "YO0").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "YO9").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "YO10").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void CountryFallback_ShouldHitWhenZipMisses()
|
|
{
|
|
var rules = CompileEntities(
|
|
Range("US", "10000", "19999"),
|
|
CountryOnly("US"));
|
|
|
|
var hit = GeoPartitionMatcher.MatchEntity(rules, "US", "CA", null, "90001");
|
|
hit!.LeftPostcode.Should().BeNull();
|
|
hit.Province.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void ThreePartitions_ShouldNotInterfere()
|
|
{
|
|
var black = CompileEntities(Range("US", "90000", "90010"));
|
|
var remote = CompileEntities(Range("US", "05999", "06999"));
|
|
var price = CompileEntities(Zip("US", "90"));
|
|
|
|
GeoPartitionMatcher.IsHit(black, "US", null, null, "90005").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(remote, "US", null, null, "90005").Should().BeFalse();
|
|
GeoPartitionMatcher.MatchEntity(price, "US", null, null, "90005")!.LeftPostcode.Should().Be("90");
|
|
}
|
|
|
|
[Fact]
|
|
public void SnapshotLegacyLeftOpenFormat_ShouldParseAsClosed()
|
|
{
|
|
var infos = new List<KpiAreaSnapshotRuleDto>
|
|
{
|
|
new() { Id = 1, CountryCode = "US", PostcodeRange = "(00000,50000]" }
|
|
};
|
|
|
|
GeoPartitionMatcher.IsHit(infos, "US", null, null, "00000").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(infos, "US", null, null, "50000").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(infos, "US", null, null, "50001").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void PrepareEntity_PrefixKeepsRightEmpty()
|
|
{
|
|
var entity = new KpiAreaInfo { CountryCode = "us", LeftPostcode = "90" };
|
|
GeoPartitionMatcher.PrepareEntity(entity);
|
|
entity.LeftPostcode.Should().Be("90");
|
|
entity.RightPostcode.Should().BeNull();
|
|
entity.MatchDim.Should().Be("zip");
|
|
entity.MatchMode.Should().Be("prefix");
|
|
entity.PrefixLen.Should().Be(2);
|
|
GeoPartitionMatcher.Describe(entity).MatchDim.Should().Be("zip");
|
|
GeoPartitionMatcher.Describe(entity).MatchMode.Should().Be("prefix");
|
|
}
|
|
|
|
[Fact]
|
|
public void PrepareEntity_CityWithoutState_ShouldThrow()
|
|
{
|
|
var entity = new KpiAreaInfo { CountryCode = "US", City = "LA" };
|
|
var act = () => GeoPartitionMatcher.PrepareEntity(entity);
|
|
act.Should().Throw<InvalidOperationException>().WithMessage("*州*");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("BT9 7AA", "BT97AA")]
|
|
[InlineData("bt97aa", "BT97AA")]
|
|
[InlineData("BT9-7AA", "BT97AA")]
|
|
[InlineData("B46 3AB", "B463AB")]
|
|
[InlineData(" b46 3ab ", "B463AB")]
|
|
public void NormalizeZip_AlphanumericWithSpaces_ShouldStripAndUpper(string raw, string expected)
|
|
{
|
|
GeoPartitionMatcher.NormalizeZip(raw).Should().Be(expected);
|
|
}
|
|
|
|
[Fact]
|
|
public void Gb_Bt97aa_LongerOutwardPrefixWins()
|
|
{
|
|
var rules = CompileEntities(
|
|
Zip("GB", "BT"),
|
|
Zip("GB", "BT9"),
|
|
Zip("GB", "BT97"),
|
|
Zip("GB", "B"));
|
|
|
|
var hit = GeoPartitionMatcher.MatchEntity(rules, "GB", "NIR", "Belfast", "BT9 7AA");
|
|
hit!.LeftPostcode.Should().Be("BT97");
|
|
GeoPartitionMatcher.MatchEntity(rules, "gb", null, null, "BT97AA")!.LeftPostcode.Should().Be("BT97");
|
|
}
|
|
|
|
[Fact]
|
|
public void Gb_Bt97aa_ExactIgnoresSpaces()
|
|
{
|
|
var rules = CompileEntities(Exact("GB", "BT9 7AA"));
|
|
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT97AA").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT9 7AA").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT9 7AB").Should().BeFalse();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT98AA").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Gb_Bt97aa_ClosedAlphanumericRange()
|
|
{
|
|
var rules = CompileEntities(Range("GB", "BT90AA", "BT99ZZ"));
|
|
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT97AA").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT9 7AA").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT90AA").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT99ZZ").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT8 1AA").Should().BeFalse();
|
|
GeoPartitionMatcher.IsHit(rules, "GB", null, null, "E1 6AN").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Ca_B463ab_PrefixAndSpaceVariants()
|
|
{
|
|
var rules = CompileEntities(
|
|
Zip("CA", "B"),
|
|
Zip("CA", "B4"),
|
|
Zip("CA", "B46"),
|
|
Zip("CA", "B463"));
|
|
|
|
var hit = GeoPartitionMatcher.MatchEntity(rules, "CA", "ON", "Toronto", "B46 3AB");
|
|
hit!.LeftPostcode.Should().Be("B463");
|
|
GeoPartitionMatcher.IsHit(rules, "CA", null, null, "B463AB").Should().BeTrue();
|
|
GeoPartitionMatcher.MatchEntity(rules, "CA", null, null, "B47 3AB")!.LeftPostcode.Should().Be("B4");
|
|
GeoPartitionMatcher.IsHit(rules, "CA", null, null, "M5V 2T6").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Ca_B463ab_ExactAndRange()
|
|
{
|
|
var exact = CompileEntities(Exact("CA", "B46 3AB"));
|
|
var range = CompileEntities(Range("CA", "B460AA", "B469ZZ"));
|
|
|
|
GeoPartitionMatcher.IsHit(exact, "CA", null, null, "B46 3AB").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(exact, "CA", null, null, "B463AB").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(exact, "CA", null, null, "B46 3AC").Should().BeFalse();
|
|
|
|
GeoPartitionMatcher.IsHit(range, "CA", null, null, "B46 3AB").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(range, "CA", null, null, "B46 0AA").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(range, "CA", null, null, "B47 3AB").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void GbAndCa_SameLookingPostcode_CountryMustMatch()
|
|
{
|
|
var gb = CompileEntities(Zip("GB", "B46"), Exact("GB", "BT97AA"));
|
|
var ca = CompileEntities(Zip("CA", "B46"));
|
|
|
|
GeoPartitionMatcher.IsHit(gb, "GB", null, null, "B46 3AB").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(gb, "CA", null, null, "B46 3AB").Should().BeFalse();
|
|
GeoPartitionMatcher.IsHit(ca, "CA", null, null, "B46 3AB").Should().BeTrue();
|
|
GeoPartitionMatcher.IsHit(ca, "GB", null, null, "BT97AA").Should().BeFalse();
|
|
GeoPartitionMatcher.IsHit(gb, "GB", null, null, "BT97AA").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Gb_Bt97aa_ZipPresent_ShouldSkipState()
|
|
{
|
|
var rules = CompileEntities(
|
|
State("GB", "NIR"),
|
|
Zip("GB", "BT1"));
|
|
|
|
GeoPartitionMatcher.MatchEntity(rules, "GB", "NIR", "Belfast", "BT97AA").Should().BeNull();
|
|
GeoPartitionMatcher.MatchEntity(rules, "GB", "NIR", "Belfast", null)!.Province.Should().Be("NIR");
|
|
}
|
|
|
|
private static List<KpiAreaInfo> CompileEntities(params KpiAreaInfo[] items)
|
|
{
|
|
foreach (var item in items)
|
|
{
|
|
GeoPartitionMatcher.PrepareEntity(item);
|
|
}
|
|
|
|
return items.ToList();
|
|
}
|
|
|
|
private static KpiAreaInfo Zip(string country, string prefix) =>
|
|
new() { CountryCode = country, LeftPostcode = prefix };
|
|
|
|
private static KpiAreaInfo Exact(string country, string postcode) =>
|
|
new() { CountryCode = country, LeftPostcode = postcode, RightPostcode = postcode };
|
|
|
|
private static KpiAreaInfo Range(string country, string left, string right) =>
|
|
new() { CountryCode = country, LeftPostcode = left, RightPostcode = right };
|
|
|
|
private static KpiAreaInfo State(string country, string state) =>
|
|
new() { CountryCode = country, Province = state };
|
|
|
|
private static KpiAreaInfo City(string country, string state, string city) =>
|
|
new() { CountryCode = country, Province = state, City = city };
|
|
|
|
private static KpiAreaInfo CountryOnly(string country) =>
|
|
new() { CountryCode = country };
|
|
}
|