85 lines
3.6 KiB
C#
85 lines
3.6 KiB
C#
using Duempelkas.Domain.Enums;
|
|
using Duempelkas.Infrastructure;
|
|
using Duempelkas.Infrastructure.Persistence;
|
|
using Duempelkas.Infrastructure.Services;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Xunit;
|
|
|
|
namespace Duempelkas.Tests;
|
|
|
|
public sealed class BackupServiceTests : IDisposable
|
|
{
|
|
private readonly string folder = Path.Combine(Path.GetTempPath(), "duempelkas-tests-" + Guid.NewGuid().ToString("N"));
|
|
private readonly ServiceProvider services;
|
|
private readonly BackupService backup;
|
|
private readonly EntryService entries;
|
|
private readonly int accountId;
|
|
|
|
public BackupServiceTests()
|
|
{
|
|
Directory.CreateDirectory(folder);
|
|
services = new ServiceCollection().AddInfrastructure($"Data Source={Path.Combine(folder, "active.db")};Pooling=False").BuildServiceProvider();
|
|
using var db = services.GetRequiredService<IDbContextFactory<FinanceDbContext>>().CreateDbContext();
|
|
db.Database.Migrate();
|
|
db.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL;");
|
|
db.Accounts.Add(new() { Name = "Isolierte Testkasse" });
|
|
db.SaveChanges();
|
|
accountId = db.Accounts.Single().Id;
|
|
backup = new BackupService(services.GetRequiredService<DatabaseLocation>());
|
|
entries = (EntryService)services.GetRequiredService<Duempelkas.App.Services.IEntryService>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WalSnapshot_RestoresExactState_AndKeepsFallback()
|
|
{
|
|
var first = await entries.CreateEntryAsync(accountId, EntryType.Income, new(2026, 1, 1), "Gesichert", 12.50m);
|
|
var path = Path.Combine(folder, "snapshot.bak");
|
|
await backup.CreateBackupToAsync(path);
|
|
await entries.CreateEntryAsync(accountId, EntryType.Expense, new(2026, 1, 2), "Später", 4.20m);
|
|
var fallback = await backup.RestoreFromAsync(path);
|
|
var restored = await entries.GetEntriesAsync(accountId, (int?)null);
|
|
Assert.Single(restored);
|
|
Assert.Equal(first, restored[0]);
|
|
using var old = new SqliteConnection($"Data Source={fallback};Mode=ReadOnly;Pooling=False");
|
|
old.Open();
|
|
using var command = old.CreateCommand();
|
|
command.CommandText = "SELECT COUNT(*) FROM Entries";
|
|
Assert.Equal(2L, command.ExecuteScalar());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public async Task InvalidBackup_DoesNotChangeActiveData(bool sqlite)
|
|
{
|
|
await entries.CreateEntryAsync(accountId, EntryType.Income, new(2026, 1, 1), "Bleibt", 10m);
|
|
var path = Path.Combine(folder, "invalid.bak");
|
|
if (sqlite)
|
|
{
|
|
using var connection = new SqliteConnection($"Data Source={path};Pooling=False");
|
|
connection.Open();
|
|
using var command = connection.CreateCommand();
|
|
command.CommandText = "CREATE TABLE Unrelated (Id INTEGER)";
|
|
command.ExecuteNonQuery();
|
|
}
|
|
else await File.WriteAllTextAsync(path, "not a database");
|
|
await Assert.ThrowsAnyAsync<Exception>(() => backup.RestoreFromAsync(path));
|
|
Assert.Single(await entries.GetEntriesAsync(accountId, (int?)null));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MissingSource_AndActiveDestination_AreRejected()
|
|
{
|
|
await Assert.ThrowsAnyAsync<Exception>(() => backup.RestoreFromAsync(Path.Combine(folder, "missing.bak")));
|
|
await Assert.ThrowsAnyAsync<Exception>(() => backup.CreateBackupToAsync(Path.Combine(folder, "active.db")));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
services.Dispose();
|
|
Directory.Delete(folder, true);
|
|
}
|
|
}
|