Skip to content

Commit 34ddedb

Browse files
refactoring to better support json arrays
1 parent 54a7d97 commit 34ddedb

6 files changed

Lines changed: 241 additions & 78 deletions

File tree

ECommerce.BusinessEvents.Tests/ECommerce.BusinessEvents.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1515
<PrivateAssets>all</PrivateAssets>
1616
</PackageReference>
17+
<PackageReference Include="JsonFlatten" Version="1.0.4" />
1718
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
1819
<PackageReference Include="Moq" Version="4.20.72" />
1920
<PackageReference Include="xunit" Version="2.5.3" />

ECommerce.BusinessEvents.Tests/Services/EventTrackingServiceTests.cs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
using ECommerce.Contracts.DTOs;
88
using ECommerce.Contracts.Interfaces;
99
using ECommerce.BusinessEvents.Infrastructure;
10+
using ECommerce.Common;
11+
using Newtonsoft.Json.Linq;
12+
using JsonFlatten;
1013

1114
namespace ECommerce.BusinessEvents.Tests.Services
1215
{
@@ -16,7 +19,6 @@ public class EventTrackingServiceTests : IDisposable
1619
private readonly SchemaRegistryService _schemaRegistry;
1720
private readonly Mock<IJsonSchemaValidator> _schemaValidatorMock;
1821
private readonly EventTrackingService _eventTracker;
19-
private readonly ITransactionManager _transactionManager;
2022

2123
public EventTrackingServiceTests()
2224
{
@@ -28,8 +30,8 @@ public EventTrackingServiceTests()
2830
_schemaRegistry = new SchemaRegistryService(_context);
2931
_schemaValidatorMock = new Mock<IJsonSchemaValidator>();
3032
var loggerMock = new Mock<Microsoft.Extensions.Logging.ILogger<EventTrackingService>>();
31-
_transactionManager = new NoOpTransactionManager();
32-
_eventTracker = new EventTrackingService(_context, _schemaRegistry, _schemaValidatorMock.Object, loggerMock.Object, _transactionManager);
33+
ITransactionManager transactionManager = new NoOpTransactionManager();
34+
_eventTracker = new EventTrackingService(_context, _schemaRegistry, _schemaValidatorMock.Object, loggerMock.Object, transactionManager);
3335

3436
InitializeTestSchema().Wait();
3537
}
@@ -59,7 +61,7 @@ public async Task TrackEventAsync_ValidData_SavesEventToDatabase()
5961
Customer customer = new Customer("John Doe", "john.doe@example.com");
6062
_schemaValidatorMock
6163
.Setup(v => v.Validate(It.IsAny<string>(), It.IsAny<string>()))
62-
.Returns(ECommerce.Common.Result<ECommerce.Common.Unit, string>.Success(new ECommerce.Common.Unit()));
64+
.Returns(Result<Unit, string>.Success(new Unit()));
6365

6466
var dto = new BusinessEventDto
6567
{
@@ -98,7 +100,7 @@ public async Task TrackEventAsync_InvalidData_ReturnsFailure()
98100
var invalidCustomer = new Customer("john doe", "wrong-email-format");
99101
_schemaValidatorMock
100102
.Setup(v => v.Validate(It.IsAny<string>(), It.IsAny<string>()))
101-
.Returns(ECommerce.Common.Result<ECommerce.Common.Unit, string>.Failure("Entity data does not match schema"));
103+
.Returns(Result<Unit, string>.Failure("Entity data does not match schema"));
102104

103105
var dto = new BusinessEventDto
104106
{
@@ -158,7 +160,7 @@ public async Task GetAllEventsAsync_ReturnsAllEvents()
158160
Customer customer1 = new Customer("Alice", "alice@example.com");
159161
Customer customer2 = new Customer("Bob", "bob@example.com");
160162
_schemaValidatorMock.Setup(v => v.Validate(It.IsAny<string>(), It.IsAny<string>()))
161-
.Returns(ECommerce.Common.Result<ECommerce.Common.Unit, string>.Success(new ECommerce.Common.Unit()));
163+
.Returns(Result<Unit, string>.Success(new Unit()));
162164

163165
var dto1 = new BusinessEventDto
164166
{
@@ -267,7 +269,7 @@ public async Task TrackEventAsync_WithNullAndEmptyFields_OnlySavesMetadataForFie
267269

268270
_schemaValidatorMock
269271
.Setup(v => v.Validate(It.IsAny<string>(), It.IsAny<string>()))
270-
.Returns(ECommerce.Common.Result<ECommerce.Common.Unit, string>.Success(new ECommerce.Common.Unit()));
272+
.Returns(Result<Unit, string>.Success(new Unit()));
271273

272274
var dto = new BusinessEventDto
273275
{
@@ -354,6 +356,14 @@ public async Task TrackEventAsync_WithEmbeddedPhoneNumbers_ExtractsMetadataCorre
354356

355357
await _schemaRegistry.AddSchemaAsync("CustomerWithPhones", 1, customerSchemaWithPhones);
356358

359+
// Debug: Test metadata config parsing directly
360+
var metadataConfig = _schemaRegistry.ParseMetadataConfig(customerSchemaWithPhones);
361+
Assert.Equal(2, metadataConfig.FieldsToExtract.Count); // Should have Id and Name
362+
Assert.Single(metadataConfig.ArrayPathsToExtract); // Should have PhoneNumbers array
363+
Assert.Contains("Id", metadataConfig.FieldsToExtract);
364+
Assert.Contains("Name", metadataConfig.FieldsToExtract);
365+
Assert.Contains("PhoneNumbers", metadataConfig.ArrayPathsToExtract.Keys);
366+
357367
var customerData = new {
358368
Id = "cust-001",
359369
Name = "Ent",
@@ -365,7 +375,7 @@ public async Task TrackEventAsync_WithEmbeddedPhoneNumbers_ExtractsMetadataCorre
365375

366376
_schemaValidatorMock
367377
.Setup(v => v.Validate(It.IsAny<string>(), It.IsAny<string>()))
368-
.Returns(ECommerce.Common.Result<ECommerce.Common.Unit, string>.Success(new ECommerce.Common.Unit()));
378+
.Returns(Result<Unit, string>.Success(new Unit()));
369379

370380
var dto = new BusinessEventDto
371381
{
@@ -392,6 +402,12 @@ public async Task TrackEventAsync_WithEmbeddedPhoneNumbers_ExtractsMetadataCorre
392402
.Where(m => m.EventId == savedEvent.EventId)
393403
.ToListAsync();
394404

405+
// Debug: Check what we actually got vs what we expected
406+
Assert.True(metadataRecords.Count > 0, "No metadata records were extracted at all!");
407+
408+
// Should have 6 total: Id, Name, PhoneNumbers[0].Number, PhoneNumbers[0].Prefix, PhoneNumbers[1].Number, PhoneNumbers[1].Prefix
409+
Assert.Equal(6, metadataRecords.Count);
410+
395411
// Should have Id and Name
396412
Assert.Contains(metadataRecords, m => m.MetadataKey == "Id" && m.MetadataValue == "cust-001");
397413
Assert.Contains(metadataRecords, m => m.MetadataKey == "Name" && m.MetadataValue == "Ent");
@@ -405,6 +421,35 @@ public async Task TrackEventAsync_WithEmbeddedPhoneNumbers_ExtractsMetadataCorre
405421
Assert.Contains(metadataRecords, m => m.MetadataKey == "PhoneNumbers[1].Prefix" && m.MetadataValue == "+1");
406422
}
407423

424+
[Fact]
425+
public void JsonFlatten_BasicTest_ShouldFlattenCorrectly()
426+
{
427+
// Arrange
428+
var testJson = @"{
429+
""Id"": ""cust-001"",
430+
""Name"": ""Ent"",
431+
""PhoneNumbers"": [
432+
{""Number"": ""123456789"", ""Prefix"": ""+44""},
433+
{""Number"": ""987654321"", ""Prefix"": ""+1""}
434+
]
435+
}";
436+
437+
// Act
438+
var jObject = JObject.Parse(testJson);
439+
var flattened = jObject.Flatten();
440+
441+
// Assert - Let's see what keys JsonFlatten actually produces
442+
var keys = flattened.Keys.ToList();
443+
Assert.Contains("Id", keys);
444+
Assert.Contains("Name", keys);
445+
446+
// Check if JsonFlatten produces the expected array keys
447+
Assert.Contains("PhoneNumbers[0].Number", keys);
448+
Assert.Contains("PhoneNumbers[0].Prefix", keys);
449+
Assert.Contains("PhoneNumbers[1].Number", keys);
450+
Assert.Contains("PhoneNumbers[1].Prefix", keys);
451+
}
452+
408453
public void Dispose()
409454
{
410455
_context.Dispose();

ECommerce.BusinessEvents/Domain/MetadataExtractionConfig.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
2+
13
namespace ECommerce.BusinessEvents.Domain
24
{
35
/// <summary>
@@ -10,17 +12,23 @@ public class MetadataExtractionConfig
1012
/// List of field paths that should be extracted as metadata.
1113
/// Supports nested paths using dot notation (e.g., "Address.PostCode").
1214
/// </summary>
13-
public List<string> FieldsToExtract { get; set; } = new();
15+
public List<string> FieldsToExtract { get; set; } = [];
1416

1517
/// <summary>
1618
/// Maps field paths to their expected data types for proper storage.
1719
/// Valid types: 'string', 'number', 'boolean', 'date'
1820
/// </summary>
1921
public Dictionary<string, string> FieldTypes { get; set; } = new();
2022

23+
/// <summary>
24+
/// Maps array paths to their item schema definitions for dynamic extraction.
25+
/// Stores the schema as JSON string to avoid JsonElement disposal issues.
26+
/// </summary>
27+
public Dictionary<string, string> ArrayPathsToExtract { get; set; } = new();
28+
2129
/// <summary>
2230
/// Indicates whether any metadata fields are configured for extraction.
2331
/// </summary>
24-
public bool HasMetadata => FieldsToExtract.Any();
32+
public bool HasMetadata => FieldsToExtract.Any() || ArrayPathsToExtract.Any();
2533
}
2634
}

ECommerce.BusinessEvents/ECommerce.BusinessEvents.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="JsonFlatten" Version="1.0.4" />
1011
<PackageReference Include="JsonSchema.Net" Version="7.3.4" />
1112
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="9.0.7" />
1213
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.16" />

0 commit comments

Comments
 (0)