Datensicherheit: Backup, Verschlüsselung, App-Sperre (Kapitel 13.3)
Automatisches rollierendes Backup der Datenbank beim Start mit Wiederherstellung über die Einstellungen, versionierte Schema-Migration, optionale Passwort-Verschlüsselung der LiteDB-Datei und eine App-Sperre nach Inaktivität mit eigenem Passwort. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
using LehrerApp.Core.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Tests;
|
||||
|
||||
public sealed class AppLockServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void NeueKonfiguration_IstStandardmaessigDeaktiviert()
|
||||
{
|
||||
using var temp = new TempAppData();
|
||||
var service = new AppLockService(temp.Path);
|
||||
|
||||
Assert.False(service.IsEnabled);
|
||||
Assert.False(service.HasPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetPassword_VerifyPassword_KorrektesPasswortWirdAkzeptiert()
|
||||
{
|
||||
using var temp = new TempAppData();
|
||||
var service = new AppLockService(temp.Path);
|
||||
|
||||
service.SetPassword("geheim123");
|
||||
|
||||
Assert.True(service.HasPassword);
|
||||
Assert.True(service.VerifyPassword("geheim123"));
|
||||
Assert.False(service.VerifyPassword("falsch"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WirdUeberNeueInstanzHinwegPersistiert()
|
||||
{
|
||||
using var temp = new TempAppData();
|
||||
var first = new AppLockService(temp.Path);
|
||||
first.SetPassword("geheim123");
|
||||
first.Configure(enabled: true, timeoutMinutes: 15);
|
||||
|
||||
var second = new AppLockService(temp.Path);
|
||||
|
||||
Assert.True(second.IsEnabled);
|
||||
Assert.Equal(15, second.TimeoutMinutes);
|
||||
Assert.True(second.VerifyPassword("geheim123"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyPassword_OhneGesetztesPasswort_GibtFalseZurueck()
|
||||
{
|
||||
using var temp = new TempAppData();
|
||||
var service = new AppLockService(temp.Path);
|
||||
|
||||
Assert.False(service.VerifyPassword("irgendwas"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_TimeoutUnterEinerMinute_WirdAufEineMinuteBegrenzt()
|
||||
{
|
||||
using var temp = new TempAppData();
|
||||
var service = new AppLockService(temp.Path);
|
||||
|
||||
service.Configure(enabled: true, timeoutMinutes: 0);
|
||||
|
||||
Assert.Equal(1, service.TimeoutMinutes);
|
||||
}
|
||||
|
||||
private sealed class TempAppData : IDisposable
|
||||
{
|
||||
public string Path { get; } = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(), $"lehrerapp-applock-tests-{Guid.NewGuid():N}");
|
||||
|
||||
public TempAppData() => Directory.CreateDirectory(Path);
|
||||
public void Dispose() { if (Directory.Exists(Path)) Directory.Delete(Path, recursive: true); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
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")));
|
||||
}
|
||||
|
||||
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); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user