WorkTracker/tests/WorkTracker.Tests/JsonDatabaseBackupServiceTests.cs

167 lines
7.1 KiB
C#
Raw Permalink Normal View History

using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Couchbase.Lite;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using WorkTracker.Configuration;
using WorkTracker.Domain;
using WorkTracker.Services.Settings;
using WorkTracker.Services.Storage;
using Xunit;
namespace WorkTracker.Tests;
public sealed class JsonDatabaseBackupServiceTests
{
[Fact]
public async Task ExportAndImport_RoundTripsManagedCollectionsAndVersions()
{
using var sourceDatabase = new TestDatabaseHandle();
await SeedAsync(sourceDatabase.Provider);
var backupService = new JsonDatabaseBackupService(sourceDatabase.Provider);
var backupFile = await backupService.ExportAsync();
using var backupJson = JsonDocument.Parse(backupFile.Content);
Assert.Equal(1, backupJson.RootElement.GetProperty("backupFormatVersion").GetInt32());
Assert.Equal(CouchbaseLiteDatabaseProvider.CurrentDatabaseSchemaVersion, backupJson.RootElement.GetProperty("databaseSchemaVersion").GetInt32());
Assert.True(backupJson.RootElement.GetProperty("collections").TryGetProperty("app_settings", out _));
Assert.True(backupJson.RootElement.GetProperty("collections").TryGetProperty("users", out _));
Assert.True(backupJson.RootElement.GetProperty("collections").TryGetProperty("workdays", out _));
using var targetDatabase = new TestDatabaseHandle();
var importService = new JsonDatabaseBackupService(targetDatabase.Provider);
await using (var stream = new MemoryStream(backupFile.Content, writable: false))
{
await importService.ImportAsync(stream);
}
var appSettingsService = new CouchbaseLiteAppSettingsService(targetDatabase.Provider);
var importedSettings = await appSettingsService.GetAsync();
Assert.Equal(AppThemeMode.Dark, importedSettings.ThemeMode);
Assert.Equal("USD", importedSettings.Currency);
Assert.Equal("en-US", importedSettings.Locale);
Assert.Equal(CouchbaseLiteDatabaseProvider.CurrentDatabaseSchemaVersion, targetDatabase.Provider.DatabaseSchemaVersion);
var importedUser = targetDatabase.Provider.Users.GetDocument("USER1");
Assert.NotNull(importedUser);
Assert.Equal("admin", importedUser!.GetString("username"));
var importedWorkDay = targetDatabase.Provider.WorkDays.GetDocument("2026-04-24");
Assert.NotNull(importedWorkDay);
Assert.Equal("2026-04-24", importedWorkDay!.GetString("date"));
}
[Fact]
public async Task Import_WhenSchemaVersionDoesNotMatch_RejectsWithoutOverwritingDatabase()
{
using var sourceDatabase = new TestDatabaseHandle();
await SeedAsync(sourceDatabase.Provider);
var backupService = new JsonDatabaseBackupService(sourceDatabase.Provider);
var backupFile = await backupService.ExportAsync();
var modifiedBackup = JsonNode.Parse(backupFile.Content)!;
modifiedBackup["databaseSchemaVersion"] = CouchbaseLiteDatabaseProvider.CurrentDatabaseSchemaVersion + 1;
var modifiedContent = Encoding.UTF8.GetBytes(modifiedBackup.ToJsonString(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
using var targetDatabase = new TestDatabaseHandle();
SeedExistingTargetData(targetDatabase.Provider);
var importService = new JsonDatabaseBackupService(targetDatabase.Provider);
await using var stream = new MemoryStream(modifiedContent, writable: false);
var exception = await Assert.ThrowsAsync<DatabaseBackupException>(() => importService.ImportAsync(stream));
Assert.Contains("not supported", exception.Message, StringComparison.OrdinalIgnoreCase);
Assert.NotNull(targetDatabase.Provider.Users.GetDocument("KEEP"));
Assert.Null(targetDatabase.Provider.Users.GetDocument("USER1"));
}
private static async Task SeedAsync(CouchbaseLiteDatabaseProvider provider)
{
var appSettingsService = new CouchbaseLiteAppSettingsService(provider);
await appSettingsService.SaveAsync(new AppSettingsDocument
{
ThemeMode = AppThemeMode.Dark,
Currency = "USD",
Locale = "en-US",
StandardWorkHoursPerDay = 7.5m,
HourlyGrossRate = 30m,
ProfitabilityCoefficient = 0.8m,
InpsRate = 0.2m,
SubstituteTaxRate = 0.1m
});
var userDocument = new MutableDocument("USER1");
userDocument.SetString("username", "admin");
userDocument.SetString("usernameNormalized", "ADMIN");
userDocument.SetString("passwordHash", "hash");
userDocument.SetBoolean("mustChangePassword", false);
provider.Users.Save(userDocument);
var workDayDocument = new MutableDocument("2026-04-24");
workDayDocument.SetString("date", "2026-04-24");
workDayDocument.SetBoolean("isWeekend", false);
workDayDocument.SetBoolean("isItalianFestivity", false);
workDayDocument.SetArray("workUnits", new MutableArrayObject());
workDayDocument.SetArray("calendarEvents", new MutableArrayObject());
workDayDocument.SetString("createdAtUtc", "2026-04-24T08:00:00.0000000+00:00");
workDayDocument.SetString("updatedAtUtc", "2026-04-24T08:00:00.0000000+00:00");
provider.WorkDays.Save(workDayDocument);
}
private static void SeedExistingTargetData(CouchbaseLiteDatabaseProvider provider)
{
var existingUser = new MutableDocument("KEEP");
existingUser.SetString("username", "keep-me");
existingUser.SetString("usernameNormalized", "KEEP-ME");
existingUser.SetString("passwordHash", "keep-hash");
existingUser.SetBoolean("mustChangePassword", false);
provider.Users.Save(existingUser);
}
private sealed class TestDatabaseHandle : IDisposable
{
private readonly string directoryPath;
public TestDatabaseHandle()
{
directoryPath = Path.Combine(Path.GetTempPath(), "WorkTracker.Tests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directoryPath);
Provider = new CouchbaseLiteDatabaseProvider(
Options.Create(new CouchbaseLiteOptions
{
Directory = directoryPath,
DatabaseName = $"worktracker-tests-{Guid.NewGuid():N}"
}),
new TestHostEnvironment());
}
public CouchbaseLiteDatabaseProvider Provider { get; }
public void Dispose()
{
Provider.Dispose();
if (Directory.Exists(directoryPath))
{
Directory.Delete(directoryPath, recursive: true);
}
}
}
private sealed class TestHostEnvironment : IHostEnvironment
{
public string EnvironmentName { get; set; } = "Development";
public string ApplicationName { get; set; } = "WorkTracker.Tests";
public string ContentRootPath { get; set; } = Directory.GetCurrentDirectory();
public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = null!;
}
}