- Dashboard: Fehlzeiten-Warnung lädt Mitarbeitssitzungen einmal vorab statt pro Schüler/Eintrag einzeln nachzuschlagen (N+1 vermieden); neues IParticipationSessionRepository.GetAll() dafür. - CI: .gitea/workflows/ci.yml baut und testet bei jedem Push/PR. Dabei fehlende Release|Any CPU-Konfiguration für 6 Projekte in der .sln behoben (LehrerApp.Data.Tests wurde bei Release-Builds der Solution bislang stillschweigend übersprungen). TreatWarningsAsErrors jetzt aktiv. - SettingsViewModel (1986 Zeilen) als partial class auf 20 Themen- dateien aufgeteilt, Verhalten unverändert. - Backup: optionaler zweiter Sicherungsordner (USB-Stick/Netzlaufwerk, best-effort) und Integritätsprüfung nach jedem Backup (DatabaseEncryptionService.CanOpenAndRead). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
160 lines
5.6 KiB
C#
160 lines
5.6 KiB
C#
using LehrerApp.Core.Services;
|
|
using Xunit;
|
|
|
|
namespace LehrerApp.Tests;
|
|
|
|
public sealed class BackupServiceTests
|
|
{
|
|
[Fact]
|
|
public void CreateBackup_OhneVorhandeneDatenbank_GibtNullZurueck()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var service = new BackupService(temp.Path);
|
|
|
|
var result = service.CreateBackup(Path.Combine(temp.Path, "fehlt.db"));
|
|
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateBackup_KopiertDieDatenbankdatei()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var dbPath = Path.Combine(temp.Path, "lehrerapp.db");
|
|
File.WriteAllText(dbPath, "Testinhalt");
|
|
var service = new BackupService(temp.Path);
|
|
|
|
var backupPath = service.CreateBackup(dbPath);
|
|
|
|
Assert.NotNull(backupPath);
|
|
Assert.True(File.Exists(backupPath));
|
|
Assert.Equal("Testinhalt", File.ReadAllText(backupPath!));
|
|
}
|
|
|
|
[Fact]
|
|
public void ListBackups_SortiertNeuesteZuerst()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var dbPath = Path.Combine(temp.Path, "lehrerapp.db");
|
|
File.WriteAllText(dbPath, "v1");
|
|
var service = new BackupService(temp.Path);
|
|
|
|
var first = service.CreateBackup(dbPath)!;
|
|
File.SetLastWriteTime(first, DateTime.Now.AddMinutes(-5));
|
|
File.WriteAllText(dbPath, "v2");
|
|
var second = service.CreateBackup(dbPath)!;
|
|
|
|
var backups = service.ListBackups();
|
|
|
|
Assert.Equal(2, backups.Count);
|
|
Assert.Equal(second, backups[0].Path);
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateBackup_BehaeltNurDieLetztenNBackups()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var dbPath = Path.Combine(temp.Path, "lehrerapp.db");
|
|
var service = new BackupService(temp.Path);
|
|
|
|
for (var i = 0; i < 5; i++)
|
|
{
|
|
File.WriteAllText(dbPath, $"v{i}");
|
|
var backup = service.CreateBackup(dbPath, keepCount: 3)!;
|
|
File.SetLastWriteTime(backup, DateTime.Now.AddMinutes(-5 + i));
|
|
}
|
|
|
|
Assert.Equal(3, service.ListBackups().Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void RestoreBackup_KopiertBackupUeberDieZieldatei()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var dbPath = Path.Combine(temp.Path, "lehrerapp.db");
|
|
File.WriteAllText(dbPath, "alt");
|
|
var service = new BackupService(temp.Path);
|
|
var backupPath = service.CreateBackup(dbPath)!;
|
|
File.WriteAllText(dbPath, "neu-kaputt");
|
|
|
|
service.RestoreBackup(backupPath, dbPath);
|
|
|
|
Assert.Equal("alt", File.ReadAllText(dbPath));
|
|
}
|
|
|
|
[Fact]
|
|
public void RestoreBackup_OhneVorhandenesBackup_WirftException()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var service = new BackupService(temp.Path);
|
|
|
|
Assert.Throws<FileNotFoundException>(() =>
|
|
service.RestoreBackup(Path.Combine(temp.Path, "backups", "fehlt.db"), Path.Combine(temp.Path, "lehrerapp.db")));
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateBackup_MitSecondaryDirectory_SpiegeltDasBackupDorthin()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var dbPath = Path.Combine(temp.Path, "lehrerapp.db");
|
|
File.WriteAllText(dbPath, "Testinhalt");
|
|
var secondaryDir = Path.Combine(temp.Path, "secondary");
|
|
var service = new BackupService(temp.Path) { SecondaryBackupDirectory = secondaryDir };
|
|
|
|
var backupPath = service.CreateBackup(dbPath);
|
|
|
|
Assert.NotNull(backupPath);
|
|
var mirrored = Path.Combine(secondaryDir, Path.GetFileName(backupPath!));
|
|
Assert.True(File.Exists(mirrored));
|
|
Assert.Equal("Testinhalt", File.ReadAllText(mirrored));
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateBackup_SecondaryDirectoryNichtErreichbar_HauptbackupBleibtErhalten()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var dbPath = Path.Combine(temp.Path, "lehrerapp.db");
|
|
File.WriteAllText(dbPath, "Testinhalt");
|
|
// Eine gewöhnliche Datei blockiert den Pfad, sodass darunter kein Verzeichnis angelegt
|
|
// werden kann — simuliert plattformunabhängig ein nicht eingestecktes USB-Ziel bzw. eine
|
|
// nicht erreichbare Netzwerkfreigabe.
|
|
var blockingFile = Path.Combine(temp.Path, "blockiert");
|
|
File.WriteAllText(blockingFile, "x");
|
|
var unreachable = Path.Combine(blockingFile, "secondary");
|
|
var service = new BackupService(temp.Path) { SecondaryBackupDirectory = unreachable };
|
|
|
|
var backupPath = service.CreateBackup(dbPath);
|
|
|
|
Assert.NotNull(backupPath);
|
|
Assert.True(File.Exists(backupPath));
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateBackup_MitSecondaryDirectory_BehaeltNurDieLetztenNAuchDort()
|
|
{
|
|
using var temp = new TempAppData();
|
|
var dbPath = Path.Combine(temp.Path, "lehrerapp.db");
|
|
var secondaryDir = Path.Combine(temp.Path, "secondary");
|
|
var service = new BackupService(temp.Path) { SecondaryBackupDirectory = secondaryDir };
|
|
|
|
for (var i = 0; i < 5; i++)
|
|
{
|
|
File.WriteAllText(dbPath, $"v{i}");
|
|
var backup = service.CreateBackup(dbPath, keepCount: 3)!;
|
|
var mirrored = Path.Combine(secondaryDir, Path.GetFileName(backup));
|
|
File.SetLastWriteTime(mirrored, DateTime.Now.AddMinutes(-5 + i));
|
|
}
|
|
|
|
Assert.Equal(3, Directory.GetFiles(secondaryDir, "lehrerapp-*.db").Length);
|
|
}
|
|
|
|
private sealed class TempAppData : IDisposable
|
|
{
|
|
public string Path { get; } = System.IO.Path.Combine(
|
|
System.IO.Path.GetTempPath(), $"lehrerapp-backup-tests-{Guid.NewGuid():N}");
|
|
|
|
public TempAppData() => Directory.CreateDirectory(Path);
|
|
public void Dispose() { if (Directory.Exists(Path)) Directory.Delete(Path, recursive: true); }
|
|
}
|
|
}
|