- 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>
140 lines
5.1 KiB
C#
140 lines
5.1 KiB
C#
using LiteDB;
|
|
using Xunit;
|
|
|
|
namespace LehrerApp.Data.Tests;
|
|
|
|
public sealed class DatabaseEncryptionServiceTests
|
|
{
|
|
[Fact]
|
|
public void IsEncrypted_OhneVorhandeneDatei_GibtFalseZurueck()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
var service = new DatabaseEncryptionService();
|
|
|
|
Assert.False(service.IsEncrypted(temp.Path));
|
|
}
|
|
|
|
[Fact]
|
|
public void IsEncrypted_UnverschluesselteDatenbank_GibtFalseZurueck()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
using (var db = new LiteDatabase(temp.Path))
|
|
db.GetCollection<BsonDocument>("t").Insert(new BsonDocument { ["x"] = 1 });
|
|
|
|
var service = new DatabaseEncryptionService();
|
|
|
|
Assert.False(service.IsEncrypted(temp.Path));
|
|
}
|
|
|
|
[Fact]
|
|
public void SetPassword_VerschluesseltDieDatenbankUndBehaeltDieDaten()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
using (var db = new LiteDatabase(temp.Path))
|
|
db.GetCollection<BsonDocument>("t").Insert(new BsonDocument { ["Name"] = "Hallo" });
|
|
|
|
var service = new DatabaseEncryptionService();
|
|
service.SetPassword(temp.Path, currentPassword: null, newPassword: "geheim123");
|
|
|
|
Assert.True(service.IsEncrypted(temp.Path));
|
|
Assert.True(service.VerifyPassword(temp.Path, "geheim123"));
|
|
Assert.False(service.VerifyPassword(temp.Path, "falsch"));
|
|
|
|
using var reopened = new LiteDatabase(new ConnectionString(temp.Path) { Password = "geheim123" });
|
|
var doc = reopened.GetCollection<BsonDocument>("t").FindAll().First();
|
|
Assert.Equal("Hallo", doc["Name"].AsString);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetPassword_EntferntDasPasswortWiederWennNeuesPasswortNullIst()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
var service = new DatabaseEncryptionService();
|
|
using (var db = new LiteDatabase(new ConnectionString(temp.Path) { Password = "geheim123" }))
|
|
db.GetCollection<BsonDocument>("t").Insert(new BsonDocument { ["x"] = 1 });
|
|
|
|
service.SetPassword(temp.Path, currentPassword: "geheim123", newPassword: null);
|
|
|
|
Assert.False(service.IsEncrypted(temp.Path));
|
|
using var reopened = new LiteDatabase(temp.Path);
|
|
Assert.Single(reopened.GetCollection<BsonDocument>("t").FindAll());
|
|
}
|
|
|
|
[Fact]
|
|
public void CanOpenAndRead_OhneVorhandeneDatei_GibtFalseZurueck()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
var service = new DatabaseEncryptionService();
|
|
|
|
Assert.False(service.CanOpenAndRead(temp.Path, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanOpenAndRead_GueltigeUnverschluesselteDatenbank_GibtTrueZurueck()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
using (var db = new LiteDatabase(temp.Path))
|
|
db.GetCollection<BsonDocument>("t").Insert(new BsonDocument { ["x"] = 1 });
|
|
|
|
var service = new DatabaseEncryptionService();
|
|
|
|
Assert.True(service.CanOpenAndRead(temp.Path, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanOpenAndRead_GueltigeVerschluesselteDatenbankMitRichtigemPasswort_GibtTrueZurueck()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
using (var db = new LiteDatabase(new ConnectionString(temp.Path) { Password = "geheim123" }))
|
|
db.GetCollection<BsonDocument>("t").Insert(new BsonDocument { ["x"] = 1 });
|
|
|
|
var service = new DatabaseEncryptionService();
|
|
|
|
Assert.True(service.CanOpenAndRead(temp.Path, "geheim123"));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanOpenAndRead_FalschesPasswort_GibtFalseZurueck()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
using (var db = new LiteDatabase(new ConnectionString(temp.Path) { Password = "geheim123" }))
|
|
db.GetCollection<BsonDocument>("t").Insert(new BsonDocument { ["x"] = 1 });
|
|
|
|
var service = new DatabaseEncryptionService();
|
|
|
|
Assert.False(service.CanOpenAndRead(temp.Path, "falsch"));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanOpenAndRead_BeschaedigteDatei_GibtFalseZurueck()
|
|
{
|
|
using var temp = new TempDatabase();
|
|
// Eine kurze Textdatei initialisiert LiteDB stillschweigend als neue, leere Datenbank
|
|
// (kleiner als eine Seite) — erst genug Datenmüll jenseits der Seitengröße lässt das
|
|
// Datei-Layout tatsächlich fehlschlagen und simuliert eine echte Beschädigung
|
|
// (z.B. durch einen vollen Datenträger während der Backup-Kopie).
|
|
File.WriteAllBytes(temp.Path, Enumerable.Repeat((byte)0x42, 32_768).ToArray());
|
|
var service = new DatabaseEncryptionService();
|
|
|
|
Assert.False(service.CanOpenAndRead(temp.Path, null));
|
|
}
|
|
|
|
private sealed class TempDatabase : IDisposable
|
|
{
|
|
private readonly string _directory = System.IO.Path.Combine(
|
|
System.IO.Path.GetTempPath(), $"lehrerapp-dbenc-tests-{Guid.NewGuid():N}");
|
|
public string Path { get; }
|
|
|
|
public TempDatabase()
|
|
{
|
|
Directory.CreateDirectory(_directory);
|
|
Path = System.IO.Path.Combine(_directory, "test.db");
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Directory.Exists(_directory)) Directory.Delete(_directory, recursive: true);
|
|
}
|
|
}
|
|
}
|