using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using LehrerApp.Core.Services; using Xunit; namespace LehrerApp.Tests; public sealed class CompetencyCatalogImportServiceTests { private static readonly Guid SubjectId = Guid.NewGuid(); [Fact] public void Analyze_UngueltigeDatei_VeraendertDenKatalogNicht() { var repository = RepositoryWithExistingCatalog(); var service = new CompetencyCatalogImportService(repository); Assert.Throws(() => service.Analyze("{ kaputt", SubjectId, "Mathematik", 7)); Assert.False(repository.ReplaceWasCalled); Assert.Equal("Alte Beschreibung", repository.Current.Single().Items.Single().Description); } [Fact] public void Analyze_ErkenntNeueUnveraenderteUndKonflikte() { var service = new CompetencyCatalogImportService(RepositoryWithExistingCatalog()); var preview = service.Analyze(JsonWithConflictAndNewItem(), SubjectId, "Mathematik", 7); Assert.Equal(0, preview.NewDomains); Assert.Equal(1, preview.NewCompetencies); Assert.Single(preview.Conflicts); Assert.Equal("item:A1", preview.Conflicts[0].Id); } [Fact] public void Apply_MergeBehaeltBeiKonfliktStandardmaessigDenVorhandenenWert() { var repository = RepositoryWithExistingCatalog(); var service = new CompetencyCatalogImportService(repository); var preview = service.Analyze(JsonWithConflictAndNewItem(), SubjectId, "Mathematik", 7); service.Apply(preview, CompetencyCatalogImportMode.Merge); Assert.True(repository.ReplaceWasCalled); var items = repository.Current.Single().Items; Assert.Equal("Alte Beschreibung", items.Single(x => x.Code == "A1").Description); Assert.Equal("Neue Kompetenz", items.Single(x => x.Code == "A2").Description); } [Fact] public void Apply_MergeKannImportiertenKonfliktwertUebernehmen() { var repository = RepositoryWithExistingCatalog(); var service = new CompetencyCatalogImportService(repository); var preview = service.Analyze(JsonWithConflictAndNewItem(), SubjectId, "Mathematik", 7); service.Apply(preview, CompetencyCatalogImportMode.Merge, new HashSet { "item:A1" }); Assert.Equal("Importierte Beschreibung", repository.Current.Single().Items.Single(x => x.Code == "A1").Description); } [Fact] public void Apply_NeuerBereichMitVorhandenemCode_ErzeugtKeinenDoppeltenKompetenzcode() { var repository = RepositoryWithExistingCatalog(); var service = new CompetencyCatalogImportService(repository); const string json = """ { "domains": [ { "code": "B", "name": "Neuer Bereich", "competencies": [ { "code": "A1", "description": "Andere Fassung" } ] } ] } """; var preview = service.Analyze(json, SubjectId, "Mathematik", 7); service.Apply(preview, CompetencyCatalogImportMode.Merge); Assert.Single(repository.Current.SelectMany(x => x.Items), x => x.Code == "A1"); Assert.Equal("Alte Beschreibung", repository.Current.SelectMany(x => x.Items).Single(x => x.Code == "A1").Description); } [Fact] public void Apply_ReplaceErsetztDenGesamtenAusgewaehltenKatalog() { var repository = RepositoryWithExistingCatalog(); var service = new CompetencyCatalogImportService(repository); var preview = service.Analyze(JsonWithConflictAndNewItem(), SubjectId, "Mathematik", 7); service.Apply(preview, CompetencyCatalogImportMode.Replace); Assert.Equal(2, repository.Current.Single().Items.Count); Assert.Equal("Importierte Beschreibung", repository.Current.Single().Items.Single(x => x.Code == "A1").Description); } [Fact] public void Apply_NachZwischenzeitlicherAenderung_VerlangtNeueVorschau() { var repository = RepositoryWithExistingCatalog(); var service = new CompetencyCatalogImportService(repository); var preview = service.Analyze(JsonWithConflictAndNewItem(), SubjectId, "Mathematik", 7); repository.Current[0].UpdatedAt = repository.Current[0].UpdatedAt.AddSeconds(1); var exception = Assert.Throws(() => service.Apply(preview, CompetencyCatalogImportMode.Merge)); Assert.Contains("Vorschau", exception.Message); Assert.False(repository.ReplaceWasCalled); } [Fact] public void Analyze_AbweichendesFachUndKlassenstufe_ErzeugtWarnungen() { var service = new CompetencyCatalogImportService(RepositoryWithExistingCatalog()); var json = JsonWithConflictAndNewItem() .Replace("Mathematik", "Deutsch") .Replace("\"gradeLevel\": 7", "\"gradeLevel\": 8"); var preview = service.Analyze(json, SubjectId, "Mathematik", 7); Assert.Equal(2, preview.Warnings.Count); } [Fact] public void Analyze_DoppelterKompetenzcode_WirdAbgelehnt() { var service = new CompetencyCatalogImportService(RepositoryWithExistingCatalog()); const string json = """ { "domains": [ { "name": "A", "competencies": [{ "code": "X1", "description": "Eins" }] }, { "name": "B", "competencies": [{ "code": "X1", "description": "Zwei" }] } ] } """; Assert.Throws(() => service.Analyze(json, SubjectId, "Mathematik", 7)); } private static InMemoryCompetencyDomainRepository RepositoryWithExistingCatalog() => new( [ new CompetencyDomain { SubjectId = SubjectId, GradeLevel = 7, Code = "A", Name = "Arithmetik", Items = [ new CompetencyItem { Code = "A1", Description = "Alte Beschreibung" }, ], }, ]); private static string JsonWithConflictAndNewItem() => """ { "subject": "Mathematik", "gradeLevel": 7, "domains": [ { "code": "A", "name": "Arithmetik", "competencies": [ { "code": "A1", "description": "Importierte Beschreibung" }, { "code": "A2", "description": "Neue Kompetenz" } ] } ] } """; private sealed class InMemoryCompetencyDomainRepository(List initial) : ICompetencyDomainRepository { public List Current { get; private set; } = initial; public bool ReplaceWasCalled { get; private set; } public List GetBySubjectAndGrade(Guid subjectId, int gradeLevel) => Current.Where(x => x.SubjectId == subjectId && x.GradeLevel == gradeLevel).ToList(); public List GetBySubject(Guid subjectId) => Current.Where(x => x.SubjectId == subjectId).ToList(); public CompetencyDomain? GetById(Guid id) => Current.FirstOrDefault(x => x.Id == id); public void Save(CompetencyDomain domain) => Current.Add(domain); public void Delete(Guid id) => Current.RemoveAll(x => x.Id == id); public void DeleteBySubjectAndGrade(Guid subjectId, int gradeLevel) => Current.RemoveAll(x => x.SubjectId == subjectId && x.GradeLevel == gradeLevel); public void ReplaceForSubjectAndGrade(Guid subjectId, int gradeLevel, List domains) { ReplaceWasCalled = true; Current.RemoveAll(x => x.SubjectId == subjectId && x.GradeLevel == gradeLevel); Current.AddRange(domains); } } }