using System.Text.Json;
using Omika.Core.Observability.External;
using Omika.Diagnostics.Import;
using Omika.Diagnostics.Models;
using Omika.Diagnostics.Queries;
using Omika.Diagnostics.Tests.Fakes;
namespace Omika.Diagnostics.Tests;
///
/// Tâche 13-A — Socle de stockage ClientCode / ServerInstance / Environment :
/// compatibilité additive (NULL), conservation des valeurs, lecture/écriture du
/// repository (fake) et filtres backend paramétrés (miroir du repository réel).
/// Aucun PostgreSQL réel requis (même convention que les autres tests, §51).
///
public class DiagnosticClientContextTests
{
private static readonly DateTimeOffset From = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
// --------------------------- A1 : ancien record sans contexte --------------
[Fact]
public void A1_RecordWithoutContext_AllContextFieldsAreNull()
{
DiagnosticEventRecord record = DiagnosticTestData.Event(Guid.NewGuid(), "OLD");
Assert.Null(record.ClientCode);
Assert.Null(record.ServerInstance);
Assert.Null(record.Environment);
}
// --------------------------- A2/A3/A4 : conservation d'une valeur ----------
[Fact]
public void A2_RecordWithClientCode_PreservesValue()
{
DiagnosticEventRecord record = EventWithContext(Guid.NewGuid(), clientCode: "CLIENT-A");
Assert.Equal("CLIENT-A", record.ClientCode);
}
[Fact]
public void A3_RecordWithServerInstance_PreservesValue()
{
DiagnosticEventRecord record = EventWithContext(Guid.NewGuid(), serverInstance: "API-SRV-01");
Assert.Equal("API-SRV-01", record.ServerInstance);
}
[Fact]
public void A4_RecordWithEnvironment_PreservesValue()
{
DiagnosticEventRecord record = EventWithContext(Guid.NewGuid(), environment: "Production");
Assert.Equal("Production", record.Environment);
}
// --------------------------- A5 : insert avec les trois valeurs -------------
[Fact]
public async Task A5_InsertWithAllThreeValues_StoresAndReadsThem()
{
var repo = new FakeDiagnosticEventRepository();
DiagnosticEventRecord record = EventWithContext(Guid.NewGuid(), "CLIENT-A", "API-SRV-01", "Production");
record.Id = 42;
int inserted = await repo.InsertBatchAsync(new[] { record });
Assert.Equal(1, inserted);
DiagnosticEventRecord? read = await repo.GetByIdAsync(42);
Assert.NotNull(read);
Assert.Equal("CLIENT-A", read!.ClientCode);
Assert.Equal("API-SRV-01", read.ServerInstance);
Assert.Equal("Production", read.Environment);
}
// --------------------------- A6 : insert NULL compatible --------------------
[Fact]
public async Task A6_InsertWithNullContext_IsAcceptedAndReadsNull()
{
var repo = new FakeDiagnosticEventRepository();
DiagnosticEventRecord record = EventWithContext(Guid.NewGuid(), null, null, null);
record.Id = 7;
int inserted = await repo.InsertBatchAsync(new[] { record });
Assert.Equal(1, inserted);
DiagnosticEventRecord? read = await repo.GetByIdAsync(7);
Assert.NotNull(read);
Assert.Null(read!.ClientCode);
Assert.Null(read.ServerInstance);
Assert.Null(read.Environment);
}
// --------------------------- A7 : lecture repository restitue les valeurs ----
[Fact]
public async Task A7_QueryAsync_RestoresContextValues()
{
var repo = new FakeDiagnosticEventRepository();
DiagnosticEventRecord record = EventWithContext(Guid.NewGuid(), "CLIENT-B", "API-SRV-02", "Staging");
record.Id = 10;
await repo.InsertBatchAsync(new[] { record });
DiagnosticQueryResult result = await repo.QueryAsync(new DiagnosticEventQuery());
DiagnosticEventRecord read = Assert.Single(result.Items);
Assert.Equal("CLIENT-B", read.ClientCode);
Assert.Equal("API-SRV-02", read.ServerInstance);
Assert.Equal("Staging", read.Environment);
}
// --------------------------- A8 : ancien JSONL / source absente → NULL ------
[Fact]
public void A8_Mapper_OldRecordWithoutContext_LeavesContextNull()
{
// RemoteDiagnosticRecord expose désormais ClientCode/ServerInstance/Environment
// (T13-B), mais un ancien enregistrement sans contexte porte ces champs à null :
// le mapper doit les laisser NULL (aucune valeur inventée).
RemoteDiagnosticRecord old = DiagnosticTestData.Record(Guid.NewGuid(), "OLD");
DiagnosticEventRecord? mapped = DiagnosticEventRecordMapper.ToRecord(old, "mobile");
Assert.NotNull(mapped);
Assert.Null(mapped!.ClientCode);
Assert.Null(mapped.ServerInstance);
Assert.Null(mapped.Environment);
}
[Fact]
public void A8_OldJsonlLineWithoutContextFields_DeserializesAndMapsToNull()
{
RemoteDiagnosticRecord old = DiagnosticTestData.Record(Guid.NewGuid(), "OLD");
string line = DiagnosticTestData.SerializeLine(old);
// Aucun champ « ClientCode/ServerInstance/Environment » requis : la ligne
// ancienne (sans ces champs) doit être acceptée telle quelle (§20).
RemoteDiagnosticRecord? parsed =
JsonSerializer.Deserialize(line, new JsonSerializerOptions(JsonSerializerDefaults.Web));
Assert.NotNull(parsed);
DiagnosticEventRecord? mapped = DiagnosticEventRecordMapper.ToRecord(parsed, "mobile");
Assert.NotNull(mapped);
Assert.Null(mapped!.ClientCode);
Assert.Null(mapped.ServerInstance);
Assert.Null(mapped.Environment);
}
// --------------------------- A9/A10/A11 : filtres backend -------------------
[Fact]
public async Task A9_ClientCodeFilter_ReturnsOnlyMatching()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
EventWithContext(Guid.NewGuid(), "CLIENT-A", "SRV-1", "Prod", From.AddMinutes(1)),
EventWithContext(Guid.NewGuid(), "CLIENT-B", "SRV-1", "Prod", From.AddMinutes(2))
});
DiagnosticQueryResult result =
await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = "CLIENT-A" });
Assert.Single(result.Items);
Assert.Equal("CLIENT-A", result.Items[0].ClientCode);
}
[Fact]
public async Task A10_ServerInstanceFilter_ReturnsOnlyMatching()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
EventWithContext(Guid.NewGuid(), "CLIENT-A", "SRV-1", "Prod", From.AddMinutes(1)),
EventWithContext(Guid.NewGuid(), "CLIENT-A", "SRV-2", "Prod", From.AddMinutes(2))
});
DiagnosticQueryResult result =
await repo.QueryAsync(new DiagnosticEventQuery { ServerInstance = "SRV-2" });
Assert.Single(result.Items);
Assert.Equal("SRV-2", result.Items[0].ServerInstance);
}
[Fact]
public async Task A11_EnvironmentFilter_ReturnsOnlyMatching()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
EventWithContext(Guid.NewGuid(), "CLIENT-A", "SRV-1", "Production", From.AddMinutes(1)),
EventWithContext(Guid.NewGuid(), "CLIENT-A", "SRV-1", "Staging", From.AddMinutes(2))
});
DiagnosticQueryResult result =
await repo.QueryAsync(new DiagnosticEventQuery { Environment = "Staging" });
Assert.Single(result.Items);
Assert.Equal("Staging", result.Items[0].Environment);
}
private static DiagnosticEventRecord EventWithContext(
Guid id,
string? clientCode = null,
string? serverInstance = null,
string? environment = null,
DateTimeOffset? timestamp = null)
{
DiagnosticEventRecord record = DiagnosticTestData.Event(id, "TEST", timestamp);
record.ClientCode = clientCode;
record.ServerInstance = serverInstance;
record.Environment = environment;
return record;
}
}