Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
using Omika.App.Local.Abstractions;
|
||||
using Omika.App.Local.Infrastructure;
|
||||
using Omika.App.Local.Models;
|
||||
using Omika.App.Local.Services;
|
||||
using Omika.Core.Contracts.Common;
|
||||
using Omika.Core.Contracts.Security;
|
||||
|
||||
namespace Omika.Tests.Mobile.Local;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de la préférence LOCALE « pause déjeuner » : identification métier du
|
||||
/// type de temps, stockage SQLite par utilisateur, isolation entre utilisateurs
|
||||
/// et valeurs par défaut. Aucun appel réseau n'est impliqué.
|
||||
/// </summary>
|
||||
public class PauseDejeunerPreferenceTests : LocalTestBase
|
||||
{
|
||||
// ===== Identification métier du type de temps « pause déjeuner =====
|
||||
|
||||
/// <summary>
|
||||
/// L'identification privilégie la clé métier stable. Le repli sur le libellé
|
||||
/// n'existe que pour la génération Batik (sans PA_CLE) et ne doit JAMAIS
|
||||
/// confondre la pause déjeuner avec « Temps de pause », qui est un autre type.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("T.TEMPS.DJNER", true)] // clé métier Omika
|
||||
[InlineData("t.temps.djner", true)] // insensible à la casse
|
||||
[InlineData("Temps pause déjeuner", true)] // libellé Batik
|
||||
[InlineData("Temps pause dejeuner", true)] // libellé sans accent
|
||||
[InlineData("Pause déjeuner", true)] // libellé personnalisé
|
||||
[InlineData("Temps de pause", false)] // type métier DIFFÉRENT
|
||||
[InlineData("Temps de préparation", false)]
|
||||
[InlineData("T.TEMPS.INTER", false)]
|
||||
[InlineData("Temps d'intervention", false)]
|
||||
[InlineData("Temps de trajet", false)]
|
||||
[InlineData("T.TEMPS.GLB", false)]
|
||||
[InlineData("T.TEMPS.TMPSNNFF", false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData(null, false)]
|
||||
public void IsPauseDejeunerTypeTemps_IdentifieLeTypeSansDependreDuLibelleExact(
|
||||
string? identifier, bool attendu)
|
||||
=> Assert.Equal(attendu, SuiviTempsConstants.IsPauseDejeunerTypeTemps(identifier));
|
||||
|
||||
[Fact]
|
||||
public void FindPauseDejeunerTypeTemps_ParCleMetier_Omika()
|
||||
{
|
||||
IReadOnlyList<ReferenceValueDto> types =
|
||||
[
|
||||
new() { Key = "T.TEMPS.INTER", Label = "Temps d'intervention" },
|
||||
new() { Key = SuiviTempsConstants.PauseDejeunerTypeTempsKey, Label = "Temps pause déjeuner" },
|
||||
new() { Key = "T.TEMPS.TMPSNNFF", Label = "Temps non affecté" }
|
||||
];
|
||||
|
||||
ReferenceValueDto? trouve = SuiviTempsConstants.FindPauseDejeunerTypeTemps(types);
|
||||
|
||||
Assert.NotNull(trouve);
|
||||
Assert.Equal(SuiviTempsConstants.PauseDejeunerTypeTempsKey, trouve!.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindPauseDejeunerTypeTemps_ParLibelle_Batik()
|
||||
{
|
||||
// Génération Batik : aucune PA_CLE, le libellé tient lieu de clé.
|
||||
IReadOnlyList<ReferenceValueDto> types =
|
||||
[
|
||||
new() { Label = "Temps d'intervention" },
|
||||
new() { Label = "Temps pause déjeuner" },
|
||||
new() { Label = "Temps de pause" }
|
||||
];
|
||||
|
||||
ReferenceValueDto? trouve = SuiviTempsConstants.FindPauseDejeunerTypeTemps(types);
|
||||
|
||||
Assert.NotNull(trouve);
|
||||
Assert.Equal("Temps pause déjeuner", trouve!.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindPauseDejeunerTypeTemps_SansPauseDejeuner_RetourneNull()
|
||||
{
|
||||
IReadOnlyList<ReferenceValueDto> types =
|
||||
[
|
||||
new() { Label = "Temps d'intervention" },
|
||||
new() { Label = "Temps de pause" }
|
||||
];
|
||||
|
||||
Assert.Null(SuiviTempsConstants.FindPauseDejeunerTypeTemps(types));
|
||||
}
|
||||
|
||||
// ===== Repository : stockage local par utilisateur =====
|
||||
|
||||
[Fact]
|
||||
public async Task Repository_AllerRetour_ConserveLaPrecisionAMinute()
|
||||
{
|
||||
await PreferencesUtilisateurRepository.SavePauseDejeunerAsync(
|
||||
"U1", new TimeSpan(12, 15, 0), new TimeSpan(13, 15, 0));
|
||||
|
||||
PauseDejeunerPreference? relue =
|
||||
await PreferencesUtilisateurRepository.GetPauseDejeunerAsync("U1");
|
||||
|
||||
Assert.NotNull(relue);
|
||||
Assert.Equal(new TimeSpan(12, 15, 0), relue!.Debut);
|
||||
Assert.Equal(new TimeSpan(13, 15, 0), relue.Fin);
|
||||
Assert.True(relue.EstValide);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Repository_AucunePreference_RetourneNull()
|
||||
=> Assert.Null(await PreferencesUtilisateurRepository.GetPauseDejeunerAsync("INCONNU"));
|
||||
|
||||
[Fact]
|
||||
public async Task Repository_DeuxUtilisateurs_NeSeMelangentPas()
|
||||
{
|
||||
await PreferencesUtilisateurRepository.SavePauseDejeunerAsync(
|
||||
"UTILISATEUR-A", new TimeSpan(12, 0, 0), new TimeSpan(13, 0, 0));
|
||||
await PreferencesUtilisateurRepository.SavePauseDejeunerAsync(
|
||||
"UTILISATEUR-B", new TimeSpan(12, 30, 0), new TimeSpan(13, 30, 0));
|
||||
|
||||
PauseDejeunerPreference? a = await PreferencesUtilisateurRepository.GetPauseDejeunerAsync("UTILISATEUR-A");
|
||||
PauseDejeunerPreference? b = await PreferencesUtilisateurRepository.GetPauseDejeunerAsync("UTILISATEUR-B");
|
||||
|
||||
Assert.Equal(new TimeSpan(12, 0, 0), a!.Debut);
|
||||
Assert.Equal(new TimeSpan(13, 0, 0), a.Fin);
|
||||
Assert.Equal(new TimeSpan(12, 30, 0), b!.Debut);
|
||||
Assert.Equal(new TimeSpan(13, 30, 0), b.Fin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Repository_EnregistrementDeuxFois_MetAJourLaMemeLigne()
|
||||
{
|
||||
await PreferencesUtilisateurRepository.SavePauseDejeunerAsync(
|
||||
"U1", new TimeSpan(12, 0, 0), new TimeSpan(13, 0, 0));
|
||||
await PreferencesUtilisateurRepository.SavePauseDejeunerAsync(
|
||||
"U1", new TimeSpan(11, 45, 0), new TimeSpan(12, 45, 0));
|
||||
|
||||
object? nombre = await Database.ExecuteScalarAsync(
|
||||
"SELECT COUNT(*) FROM \"PreferencesUtilisateur\" WHERE \"UtilisateurId\" = 'U1'");
|
||||
Assert.Equal(1L, Convert.ToInt64(nombre));
|
||||
|
||||
PauseDejeunerPreference? relue = await PreferencesUtilisateurRepository.GetPauseDejeunerAsync("U1");
|
||||
Assert.Equal(new TimeSpan(11, 45, 0), relue!.Debut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Repository_ValeurIncoherenteEnBase_EstIgnoreeSansErreur()
|
||||
{
|
||||
// Fin ANTÉRIEURE au début : une valeur corrompue ne doit jamais produire
|
||||
// une pause aberrante, ni faire échouer la lecture.
|
||||
await Database.ExecuteNonQueryAsync(
|
||||
"INSERT INTO \"PreferencesUtilisateur\" "
|
||||
+ "(\"UtilisateurId\", \"PauseDejeunerDebut\", \"PauseDejeunerFin\", \"UpdatedAtUtc\") "
|
||||
+ "VALUES ('U1', '13:00', '12:00', '2026-01-01T00:00:00Z')");
|
||||
|
||||
Assert.Null(await PreferencesUtilisateurRepository.GetPauseDejeunerAsync("U1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Repository_ValeurIllisible_EstIgnoreeSansErreur()
|
||||
{
|
||||
await Database.ExecuteNonQueryAsync(
|
||||
"INSERT INTO \"PreferencesUtilisateur\" "
|
||||
+ "(\"UtilisateurId\", \"PauseDejeunerDebut\", \"PauseDejeunerFin\", \"UpdatedAtUtc\") "
|
||||
+ "VALUES ('U1', 'midi', '13h00', '2026-01-01T00:00:00Z')");
|
||||
|
||||
Assert.Null(await PreferencesUtilisateurRepository.GetPauseDejeunerAsync("U1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Repository_HeuresNulles_EstIgnoreSansErreur()
|
||||
{
|
||||
await Database.ExecuteNonQueryAsync(
|
||||
"INSERT INTO \"PreferencesUtilisateur\" "
|
||||
+ "(\"UtilisateurId\", \"PauseDejeunerDebut\", \"PauseDejeunerFin\", \"UpdatedAtUtc\") "
|
||||
+ "VALUES ('U1', NULL, NULL, '2026-01-01T00:00:00Z')");
|
||||
|
||||
Assert.Null(await PreferencesUtilisateurRepository.GetPauseDejeunerAsync("U1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Repository_IdentifiantVide_NeLevePasEtRetourneNull()
|
||||
=> Assert.Null(await PreferencesUtilisateurRepository.GetPauseDejeunerAsync(" "));
|
||||
|
||||
// ===== Service : identité de l'utilisateur connecté =====
|
||||
|
||||
[Fact]
|
||||
public async Task Service_SansPreferenceEnregistree_RetourneLesValeursParDefautSansEcrire()
|
||||
{
|
||||
var service = CreateService();
|
||||
|
||||
PauseDejeunerPreference preference = await service.GetAsync();
|
||||
|
||||
Assert.Equal(PauseDejeunerPreference.DebutParDefaut, preference.Debut);
|
||||
Assert.Equal(PauseDejeunerPreference.FinParDefaut, preference.Fin);
|
||||
|
||||
// La lecture seule ne crée aucune préférence.
|
||||
object? nombre = await Database.ExecuteScalarAsync(
|
||||
"SELECT COUNT(*) FROM \"PreferencesUtilisateur\"");
|
||||
Assert.Equal(0L, Convert.ToInt64(nombre));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Service_UtilisateurA_PuisUtilisateurB_ChacunRetrouveSesHoraires()
|
||||
{
|
||||
// Utilisateur A configure 12:00 → 13:00.
|
||||
Connecte("Utilisateur-A");
|
||||
var serviceA = CreateService();
|
||||
await serviceA.SaveAsync(new TimeSpan(12, 0, 0), new TimeSpan(13, 0, 0));
|
||||
|
||||
// Déconnexion / reconnexion : utilisateur B configure 12:30 → 13:30.
|
||||
Connecte("Utilisateur-B");
|
||||
var serviceB = CreateService();
|
||||
await serviceB.SaveAsync(new TimeSpan(12, 30, 0), new TimeSpan(13, 30, 0));
|
||||
|
||||
// B retrouve ses horaires.
|
||||
PauseDejeunerPreference pourB = await serviceB.GetAsync();
|
||||
Assert.Equal(new TimeSpan(12, 30, 0), pourB.Debut);
|
||||
Assert.Equal(new TimeSpan(13, 30, 0), pourB.Fin);
|
||||
|
||||
// A retrouve les siens, intacts.
|
||||
Connecte("Utilisateur-A");
|
||||
PauseDejeunerPreference pourA = await CreateService().GetAsync();
|
||||
Assert.Equal(new TimeSpan(12, 0, 0), pourA.Debut);
|
||||
Assert.Equal(new TimeSpan(13, 0, 0), pourA.Fin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Service_PreferencePersistante_SurvitAUneNouvelleInstance()
|
||||
{
|
||||
Connecte("Utilisateur-A");
|
||||
await CreateService().SaveAsync(new TimeSpan(12, 15, 0), new TimeSpan(13, 15, 0));
|
||||
|
||||
// Simule un redémarrage complet : nouvelle instance de service sur la
|
||||
// MÊME base locale (aucun réseau, aucune synchronisation).
|
||||
PauseDejeunerPreference relue = await CreateService().GetAsync();
|
||||
|
||||
Assert.Equal(new TimeSpan(12, 15, 0), relue.Debut);
|
||||
Assert.Equal(new TimeSpan(13, 15, 0), relue.Fin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Service_FinAnterieureOuEgaleAuDebut_RefuseLEnregistrement()
|
||||
{
|
||||
var service = CreateService();
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => service.SaveAsync(new TimeSpan(13, 30, 0), new TimeSpan(12, 0, 0)));
|
||||
await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => service.SaveAsync(new TimeSpan(12, 0, 0), new TimeSpan(12, 0, 0)));
|
||||
|
||||
// Aucune configuration incohérente n'a été enregistrée.
|
||||
Assert.Null(await PreferencesUtilisateurRepository.GetPauseDejeunerAsync(
|
||||
PauseDejeunerPreferenceService.CleSansUtilisateur));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Service_SansBootstrap_UtiliseLaCleDeRepliSansLeve()
|
||||
{
|
||||
SessionService.Bootstrap = null;
|
||||
var service = CreateService();
|
||||
|
||||
await service.SaveAsync(new TimeSpan(12, 0, 0), new TimeSpan(13, 0, 0));
|
||||
PauseDejeunerPreference relue = await service.GetAsync();
|
||||
|
||||
Assert.Equal(new TimeSpan(12, 0, 0), relue.Debut);
|
||||
Assert.NotNull(await PreferencesUtilisateurRepository.GetPauseDejeunerAsync(
|
||||
PauseDejeunerPreferenceService.CleSansUtilisateur));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Service_BootstrapSansUtilisateur_UtiliseLaCleDeRepli()
|
||||
{
|
||||
SessionService.Bootstrap = new LocalBootstrapState { Utilisateur = null };
|
||||
var service = CreateService();
|
||||
|
||||
await service.SaveAsync(new TimeSpan(12, 30, 0), new TimeSpan(13, 30, 0));
|
||||
|
||||
Assert.NotNull(await PreferencesUtilisateurRepository.GetPauseDejeunerAsync(
|
||||
PauseDejeunerPreferenceService.CleSansUtilisateur));
|
||||
}
|
||||
|
||||
/// <summary>Branche un utilisateur connecté dans le bootstrap local.</summary>
|
||||
private void Connecte(string utilisateurId)
|
||||
=> SessionService.Bootstrap = new LocalBootstrapState
|
||||
{
|
||||
Utilisateur = new CurrentUserDto { Id = utilisateurId, Login = utilisateurId }
|
||||
};
|
||||
|
||||
private IPauseDejeunerPreferenceService CreateService()
|
||||
=> new PauseDejeunerPreferenceService(PreferencesUtilisateurRepository, SessionService);
|
||||
}
|
||||
Reference in New Issue
Block a user