471 lines
22 KiB
C#
471 lines
22 KiB
C#
using Omika.Diagnostics.Models;
|
|
using Omika.Diagnostics.Queries;
|
|
using Omika.Diagnostics.Tests.Fakes;
|
|
|
|
namespace Omika.Diagnostics.Tests;
|
|
|
|
/// <summary>
|
|
/// Tâche T13-G — Tests multi-clients backend (G1-G24) : validation du comportement
|
|
/// de filtrage/agrégation du contexte de déploiement sur un jeu de données synthétique
|
|
/// représentatif du seed (DEMO-CLIENT-A / B / C, plusieurs ServerInstance, plusieurs
|
|
/// environnements, erreurs multi-clients, NULL historiques, synchronisation
|
|
/// API/Worker). Le tout sur <see cref="FakeDiagnosticEventRepository"/> (in-memory),
|
|
/// miroir du repository PostgreSQL réel.
|
|
/// </summary>
|
|
public class DiagnosticMultiClientTests
|
|
{
|
|
private const string ClientA = "DEMO-CLIENT-A";
|
|
private const string ClientB = "DEMO-CLIENT-B";
|
|
private const string ClientC = "DEMO-CLIENT-C";
|
|
|
|
private const string ApiA = "API-A-01";
|
|
private const string WkrA = "WORKER-A-01";
|
|
private const string ApiB = "API-B-01";
|
|
private const string WkrB = "WORKER-B-01";
|
|
private const string ApiBStg = "API-B-STG-01";
|
|
private const string ApiCDev = "API-C-DEV-01";
|
|
|
|
private static readonly DateTimeOffset From = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
|
private static readonly DateTimeOffset To = From.AddDays(30);
|
|
|
|
/// <summary>Fabrique un enregistrement avec contexte, version et composant.</summary>
|
|
private static DiagnosticEventRecord Ev(
|
|
long id,
|
|
string eventCode,
|
|
string? client,
|
|
string? server,
|
|
string? environment,
|
|
DateTimeOffset? ts = null,
|
|
string level = "Info",
|
|
string? component = null,
|
|
string? errorCode = null,
|
|
string? exceptionType = null,
|
|
string? appVersion = null,
|
|
string? platform = null,
|
|
Guid? operationId = null)
|
|
{
|
|
DiagnosticEventRecord record = DiagnosticTestData.Event(
|
|
Guid.NewGuid(), eventCode, ts ?? From, component: component, level: level, errorCode: errorCode);
|
|
record.Id = id;
|
|
record.ClientCode = client;
|
|
record.ServerInstance = server;
|
|
record.Environment = environment;
|
|
record.ExceptionType = exceptionType;
|
|
record.AppVersion = appVersion;
|
|
record.Platform = platform;
|
|
record.OperationId = operationId;
|
|
return record;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Insère le jeu de données multi-clients représentatif du seed SQL (T13-G) :
|
|
/// volumes décroissants A > B > C, erreur partagée A+B, erreur spécifique C,
|
|
/// environnements Production/Staging/Development, NULL historiques, opérations de
|
|
/// synchronisation API/Worker, versions par client.
|
|
/// </summary>
|
|
private static async Task<Guid> InsertSeedAsync(FakeDiagnosticEventRepository repo)
|
|
{
|
|
Guid opA = Guid.NewGuid();
|
|
Guid opB = Guid.NewGuid();
|
|
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
// --- Volume de fond (A > B > C) ---
|
|
Ev(1, "OMK-API-HTTP-001", ClientA, ApiA, "Production", appVersion: "1.4.0.0", platform: "api", component: "Omika.Api"),
|
|
Ev(2, "OMK-API-HTTP-002", ClientA, ApiA, "Production", appVersion: "1.4.1.0", platform: "api", component: "Omika.Api"),
|
|
Ev(3, "OMK-WKR-SYNC-001", ClientA, WkrA, "Production", appVersion: "1.4.0.0", platform: "worker", component: "Omika.Worker"),
|
|
Ev(4, "OMK-API-HTTP-001", ClientB, ApiB, "Production", appVersion: "1.3.8.0", platform: "api", component: "Omika.Api"),
|
|
Ev(5, "OMK-WKR-SYNC-001", ClientB, WkrB, "Production", appVersion: "1.4.1.0", platform: "worker", component: "Omika.Worker"),
|
|
Ev(6, "OMK-API-HTTP-001", ClientB, ApiBStg, "Staging", appVersion: "1.4.1.0", platform: "api", component: "Omika.Api"),
|
|
Ev(7, "OMK-API-HTTP-001", ClientC, ApiCDev, "Development", appVersion: "1.5.0.0", platform: "api", component: "Omika.Api"),
|
|
|
|
// --- Erreur partagée A+B : même signature (TIMEOUT / TimeoutException / Omika.Api) ---
|
|
Ev(8, "OMK-API-HTTP-003", ClientA, ApiA, "Production", level: "Error", component: "Omika.Api", errorCode: "TIMEOUT", exceptionType: "System.TimeoutException", appVersion: "1.4.0.0", platform: "api"),
|
|
Ev(9, "OMK-API-HTTP-003", ClientA, ApiA, "Production", level: "Error", component: "Omika.Api", errorCode: "TIMEOUT", exceptionType: "System.TimeoutException", appVersion: "1.4.1.0", platform: "api"),
|
|
Ev(10, "OMK-API-HTTP-003", ClientB, ApiB, "Production", level: "Error", component: "Omika.Api", errorCode: "TIMEOUT", exceptionType: "System.TimeoutException", appVersion: "1.3.8.0", platform: "api"),
|
|
Ev(11, "OMK-API-HTTP-003", ClientB, ApiBStg, "Staging", level: "Error", component: "Omika.Api", errorCode: "TIMEOUT", exceptionType: "System.TimeoutException", appVersion: "1.4.1.0", platform: "api"),
|
|
|
|
// --- Erreur spécifique à C (CRASH / OutOfMemoryException / Omika.Api) ---
|
|
Ev(12, "OMK-API-HTTP-003", ClientC, ApiCDev, "Development", level: "Critical", component: "Omika.Api", errorCode: "CRASH", exceptionType: "System.OutOfMemoryException", appVersion: "1.5.0.0", platform: "api"),
|
|
|
|
// --- NULL historiques (sans contexte) ---
|
|
Ev(13, "OMK-MOB-APP-001", null, null, null, appVersion: "1.0.0.0", platform: "Android", component: "Omika.App"),
|
|
Ev(14, "OMK-MOB-APP-002", null, null, null, level: "Warning", appVersion: "1.1.0.0", platform: "iOS", component: "Omika.App"),
|
|
|
|
// --- Synchronisation API/Worker : même OperationId, même Client, serveurs différents ---
|
|
Ev(15, "OMK-MOB-SYNC-001", ClientA, ApiA, "Production", operationId: opA, appVersion: "1.4.1.0", platform: "Android", component: "Omika.App"),
|
|
Ev(16, "OMK-WKR-SYNC-001", ClientA, WkrA, "Production", operationId: opA, appVersion: "1.4.1.0", platform: "worker", component: "Omika.Worker"),
|
|
Ev(17, "OMK-MOB-SYNC-001", ClientB, ApiB, "Production", operationId: opB, appVersion: "1.3.8.0", platform: "iOS", component: "Omika.App"),
|
|
Ev(18, "OMK-WKR-SYNC-002", ClientB, WkrB, "Production", operationId: opB, level: "Error", errorCode: "SYNC_FAILED", appVersion: "1.4.1.0", platform: "worker", component: "Omika.Worker")
|
|
});
|
|
|
|
return opA;
|
|
}
|
|
|
|
// --------------------------- G1 : filtre Client A --------------------------
|
|
[Fact]
|
|
public async Task G1_ClientAFilter_ExcludesBAndC()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = ClientA });
|
|
|
|
Assert.NotEmpty(result.Items);
|
|
Assert.All(result.Items, e => Assert.Equal(ClientA, e.ClientCode));
|
|
Assert.DoesNotContain(result.Items, e => e.ClientCode is ClientB or ClientC);
|
|
}
|
|
|
|
// --------------------------- G2 : filtre Client B --------------------------
|
|
[Fact]
|
|
public async Task G2_ClientBFilter_ExcludesAAndC()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = ClientB });
|
|
|
|
Assert.NotEmpty(result.Items);
|
|
Assert.All(result.Items, e => Assert.Equal(ClientB, e.ClientCode));
|
|
}
|
|
|
|
// --------------------------- G3 : Client + Environment ---------------------
|
|
[Fact]
|
|
public async Task G3_ClientBStagingFilter_ReturnsOnlyThatCross()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.QueryAsync(new DiagnosticEventQuery
|
|
{
|
|
ClientCode = ClientB,
|
|
Environment = "Staging"
|
|
});
|
|
|
|
Assert.NotEmpty(result.Items);
|
|
Assert.All(result.Items, e =>
|
|
{
|
|
Assert.Equal(ClientB, e.ClientCode);
|
|
Assert.Equal("Staging", e.Environment);
|
|
});
|
|
}
|
|
|
|
// --------------------------- G4 : Client + Server --------------------------
|
|
[Fact]
|
|
public async Task G4_ClientAWorkerFilter_ReturnsOnlyWorkerEvents()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.QueryAsync(new DiagnosticEventQuery
|
|
{
|
|
ClientCode = ClientA,
|
|
ServerInstance = WkrA
|
|
});
|
|
|
|
Assert.NotEmpty(result.Items);
|
|
Assert.All(result.Items, e =>
|
|
{
|
|
Assert.Equal(ClientA, e.ClientCode);
|
|
Assert.Equal(WkrA, e.ServerInstance);
|
|
Assert.Equal("Omika.Worker", e.Component);
|
|
});
|
|
}
|
|
|
|
// --------------------------- G5 : trois filtres combinés -------------------
|
|
[Fact]
|
|
public async Task G5_ThreeFiltersCombined_ReturnOnlyMatchingEvents()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.QueryAsync(new DiagnosticEventQuery
|
|
{
|
|
ClientCode = ClientB,
|
|
ServerInstance = ApiBStg,
|
|
Environment = "Staging"
|
|
});
|
|
|
|
Assert.NotEmpty(result.Items);
|
|
Assert.All(result.Items, e =>
|
|
{
|
|
Assert.Equal(ClientB, e.ClientCode);
|
|
Assert.Equal(ApiBStg, e.ServerInstance);
|
|
Assert.Equal("Staging", e.Environment);
|
|
});
|
|
}
|
|
|
|
// --------------------------- G6 : NULL historiques -------------------------
|
|
[Fact]
|
|
public async Task G6_HistoricalNull_VisibleWithoutFilter_ExcludedByClientFilter()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
// Sans filtre : les NULL sont visibles.
|
|
DiagnosticQueryResult<DiagnosticEventRecord> all = await repo.QueryAsync(new DiagnosticEventQuery());
|
|
Assert.Contains(all.Items, e => e.ClientCode is null && e.ServerInstance is null && e.Environment is null);
|
|
|
|
// Avec un filtre Client : les NULL ne sont PAS inclus par erreur.
|
|
DiagnosticQueryResult<DiagnosticEventRecord> aOnly =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = ClientA });
|
|
Assert.DoesNotContain(aOnly.Items, e => e.ClientCode is null);
|
|
}
|
|
|
|
// --------------------------- G7/G8/G9 : groupes d'erreurs multi-clients ----
|
|
[Fact]
|
|
public async Task G7_G8_G9_SharedErrorGroup_CountsPerClient()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticErrorGroupQuery allQuery = new() { FromUtc = From, ToUtc = To, ErrorsOnly = true };
|
|
IReadOnlyList<DiagnosticErrorGroup> all = await repo.GroupErrorsAsync(allQuery);
|
|
|
|
// G7 : sans filtre, le groupe TIMEOUT agrège A + B (4 occurrences).
|
|
DiagnosticErrorGroup timeoutGroup = Assert.Single(all, g => g.ErrorCode == "TIMEOUT");
|
|
Assert.Equal(4, timeoutGroup.OccurrenceCount);
|
|
|
|
// G8 : filtre A → mêmes groupe, occurrences = A uniquement (2).
|
|
IReadOnlyList<DiagnosticErrorGroup> aGroups = await repo.GroupErrorsAsync(new DiagnosticErrorGroupQuery
|
|
{
|
|
FromUtc = From, ToUtc = To, ErrorsOnly = true, ClientCode = ClientA
|
|
});
|
|
DiagnosticErrorGroup aTimeout = Assert.Single(aGroups, g => g.ErrorCode == "TIMEOUT");
|
|
Assert.Equal(2, aTimeout.OccurrenceCount);
|
|
|
|
// G9 : filtre B → occurrences = B uniquement (2).
|
|
IReadOnlyList<DiagnosticErrorGroup> bGroups = await repo.GroupErrorsAsync(new DiagnosticErrorGroupQuery
|
|
{
|
|
FromUtc = From, ToUtc = To, ErrorsOnly = true, ClientCode = ClientB
|
|
});
|
|
DiagnosticErrorGroup bTimeout = Assert.Single(bGroups, g => g.ErrorCode == "TIMEOUT");
|
|
Assert.Equal(2, bTimeout.OccurrenceCount);
|
|
}
|
|
|
|
// --------------------------- G10 : erreur spécifique C ---------------------
|
|
[Fact]
|
|
public async Task G10_CrashErrorGroup_IsAbsentForClientA()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
IReadOnlyList<DiagnosticErrorGroup> aGroups = await repo.GroupErrorsAsync(new DiagnosticErrorGroupQuery
|
|
{
|
|
FromUtc = From, ToUtc = To, ErrorsOnly = true, ClientCode = ClientA
|
|
});
|
|
|
|
Assert.DoesNotContain(aGroups, g => g.ErrorCode == "CRASH");
|
|
}
|
|
|
|
// --------------------------- G11/G12/G13 : environnements ------------------
|
|
[Fact]
|
|
public async Task G11_ProductionEnvironment_ExcludesStagingAndDevelopment()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { Environment = "Production" });
|
|
|
|
Assert.NotEmpty(result.Items);
|
|
Assert.All(result.Items, e => Assert.Equal("Production", e.Environment));
|
|
Assert.DoesNotContain(result.Items, e => e.Environment is "Staging" or "Development");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task G12_StagingEnvironment_ExcludesProduction()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { Environment = "Staging" });
|
|
|
|
Assert.NotEmpty(result.Items);
|
|
Assert.All(result.Items, e => Assert.Equal("Staging", e.Environment));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task G13_UnknownEnvironment_IsTechnicallySupported()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
// Aucune énumération fermée : une valeur inconnue est acceptée et retourne vide.
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { Environment = "Custom-Test" });
|
|
|
|
Assert.Empty(result.Items);
|
|
Assert.Equal(0, result.TotalCount);
|
|
}
|
|
|
|
// --------------------------- G14/G15 : serveurs ----------------------------
|
|
[Fact]
|
|
public async Task G14_ApiAndWorkerServers_AreSeparableWithinSameClient()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> api =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = ClientA, ServerInstance = ApiA });
|
|
DiagnosticQueryResult<DiagnosticEventRecord> worker =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = ClientA, ServerInstance = WkrA });
|
|
|
|
Assert.NotEmpty(api.Items);
|
|
Assert.NotEmpty(worker.Items);
|
|
Assert.All(api.Items, e => Assert.Equal(ApiA, e.ServerInstance));
|
|
Assert.All(worker.Items, e => Assert.Equal(WkrA, e.ServerInstance));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task G15_ServersAreNotSharedBetweenClients()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
// Aucun serveur ne doit être partagé entre deux clients.
|
|
string[] aServers = { ApiA, WkrA };
|
|
string[] bServers = { ApiB, WkrB, ApiBStg };
|
|
string[] cServers = { ApiCDev };
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> a =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = ClientA, PageSize = 100 });
|
|
Assert.All(a.Items, e => Assert.Contains(e.ServerInstance, aServers));
|
|
Assert.DoesNotContain(a.Items, e => bServers.Contains(e.ServerInstance) || cServers.Contains(e.ServerInstance));
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> b =
|
|
await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = ClientB, PageSize = 100 });
|
|
Assert.All(b.Items, e => Assert.Contains(e.ServerInstance, bServers));
|
|
Assert.DoesNotContain(b.Items, e => aServers.Contains(e.ServerInstance) || cServers.Contains(e.ServerInstance));
|
|
}
|
|
|
|
// --------------------------- G16/G17/G18 : synchronisation -----------------
|
|
[Fact]
|
|
public async Task G16_SharedOperationId_KeepsClientEnvAndDistinctServers()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
Guid opA = await InsertSeedAsync(repo);
|
|
|
|
IReadOnlyList<DiagnosticEventRecord> events = await repo.GetByOperationIdAsync(opA);
|
|
Assert.Equal(2, events.Count);
|
|
|
|
DiagnosticEventRecord api = Assert.Single(events, e => e.Component == "Omika.App");
|
|
DiagnosticEventRecord worker = Assert.Single(events, e => e.Component == "Omika.Worker");
|
|
|
|
Assert.Equal(ClientA, api.ClientCode);
|
|
Assert.Equal(ClientA, worker.ClientCode);
|
|
Assert.Equal("Production", api.Environment);
|
|
Assert.Equal("Production", worker.Environment);
|
|
Assert.Equal(opA, api.OperationId);
|
|
Assert.Equal(opA, worker.OperationId);
|
|
Assert.Equal(ApiA, api.ServerInstance);
|
|
Assert.Equal(WkrA, worker.ServerInstance);
|
|
Assert.NotEqual(api.ServerInstance, worker.ServerInstance);
|
|
Assert.NotEqual(api.Component, worker.Component);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task G17_ClientFilter_KeepsAllEventsOfSameClientOperation()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
Guid opA = await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.QueryAsync(new DiagnosticEventQuery
|
|
{
|
|
ClientCode = ClientA,
|
|
SyncOnly = true
|
|
});
|
|
|
|
// Les deux composants de l'opération A (mobile + worker) sont conservés.
|
|
Assert.Contains(result.Items, e => e.OperationId == opA && e.Component == "Omika.App");
|
|
Assert.Contains(result.Items, e => e.OperationId == opA && e.Component == "Omika.Worker");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task G18_ClientFilter_ExcludesOtherClientOperation()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
Guid opA = await InsertSeedAsync(repo);
|
|
|
|
DiagnosticQueryResult<DiagnosticEventRecord> bSync = await repo.QueryAsync(new DiagnosticEventQuery
|
|
{
|
|
ClientCode = ClientB,
|
|
SyncOnly = true
|
|
});
|
|
|
|
Assert.NotEmpty(bSync.Items);
|
|
Assert.DoesNotContain(bSync.Items, e => e.OperationId == opA);
|
|
Assert.All(bSync.Items, e => Assert.Equal(ClientB, e.ClientCode));
|
|
}
|
|
|
|
// --------------------------- G19/G20 : versions ----------------------------
|
|
[Fact]
|
|
public async Task G19_ClientAFilter_ReturnsOnlyClientAVersions()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
IReadOnlyList<DiagnosticVersionSummary> summaries = await repo.GetVersionSummariesAsync(
|
|
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, ClientCode = ClientA });
|
|
|
|
Assert.NotEmpty(summaries);
|
|
Assert.All(summaries, s => Assert.Contains(s.AppVersion, new[] { "1.4.0.0", "1.4.1.0" }));
|
|
Assert.DoesNotContain(summaries, s => s.AppVersion is "1.3.8.0" or "1.5.0.0");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task G20_ClientBStagingFilter_ReducesVersionSet()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
IReadOnlyList<DiagnosticVersionSummary> summaries = await repo.GetVersionSummariesAsync(
|
|
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, ClientCode = ClientB, Environment = "Staging" });
|
|
|
|
// Seul 1.4.1.0 est observé chez B en Staging (1.3.8.0 est en Production uniquement).
|
|
DiagnosticVersionSummary summary = Assert.Single(summaries);
|
|
Assert.Equal("1.4.1.0", summary.AppVersion);
|
|
}
|
|
|
|
// --------------------------- G21/G22/G23 : options distinctes --------------
|
|
[Fact]
|
|
public async Task G21_G22_G23_DistinctOptions_AreCompleteAndUnique()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await InsertSeedAsync(repo);
|
|
|
|
IReadOnlyList<string> clients = await repo.GetFilterOptionsAsync(
|
|
DiagnosticFilterDimension.ClientCode, From, DiagnosticFilterDefaults.ClientCodeOptionsLimit);
|
|
IReadOnlyList<string> servers = await repo.GetFilterOptionsAsync(
|
|
DiagnosticFilterDimension.ServerInstance, From, DiagnosticFilterDefaults.ServerInstanceOptionsLimit);
|
|
IReadOnlyList<string> environments = await repo.GetFilterOptionsAsync(
|
|
DiagnosticFilterDimension.Environment, From, DiagnosticFilterDefaults.EnvironmentOptionsLimit);
|
|
|
|
// G21 : trois clients, sans doublon.
|
|
Assert.Equal(new[] { ClientA, ClientB, ClientC }, clients);
|
|
|
|
// G22 : les instances prévues, sans doublon (tri ordinal).
|
|
Assert.Equal(new[] { ApiA, ApiB, ApiBStg, ApiCDev, WkrA, WkrB }, servers);
|
|
|
|
// G23 : les environnements seed utilisés.
|
|
Assert.Equal(new[] { "Development", "Production", "Staging" }, environments);
|
|
}
|
|
|
|
// --------------------------- G24 : limites bornées -------------------------
|
|
[Fact]
|
|
public void G24_DimensionLimits_RemainBounded()
|
|
{
|
|
// Les limites de T13-F restent bornées et respectent la borne commune.
|
|
Assert.InRange(DiagnosticFilterDefaults.ClientCodeOptionsLimit, 1, DiagnosticFilterDefaults.MaxOptionsLimit);
|
|
Assert.InRange(DiagnosticFilterDefaults.ServerInstanceOptionsLimit, 1, DiagnosticFilterDefaults.MaxOptionsLimit);
|
|
Assert.InRange(DiagnosticFilterDefaults.EnvironmentOptionsLimit, 1, DiagnosticFilterDefaults.MaxOptionsLimit);
|
|
|
|
Assert.Equal(DiagnosticFilterDefaults.ClientCodeOptionsLimit, DiagnosticFilterDefaults.LimitFor(DiagnosticFilterDimension.ClientCode));
|
|
Assert.Equal(DiagnosticFilterDefaults.ServerInstanceOptionsLimit, DiagnosticFilterDefaults.LimitFor(DiagnosticFilterDimension.ServerInstance));
|
|
Assert.Equal(DiagnosticFilterDefaults.EnvironmentOptionsLimit, DiagnosticFilterDefaults.LimitFor(DiagnosticFilterDimension.Environment));
|
|
}
|
|
}
|