Ctrl K
ComunidadGuías…

Nostos.Backend.Tests/Portability/SelfHostedHostStateCarryOverTests.cs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Nostos.Backend.Data;
using Nostos.Backend.Services.Portability.Activation;
using Xunit;

namespace Nostos.Backend.Tests.Portability;

public sealed class SelfHostedHostStateCarryOverTests
{
    [Fact]
    public void Every_excluded_inventory_entity_has_exactly_one_documented_carry_over_decision()
    {
        var excluded = PortableCompletenessInventoryTests.ExcludedEntityNames;
        var decisions = SelfHostedHostStateCarryOver.Decisions;

        decisions.Select(decision => decision.EntityName)
            .Should().OnlyHaveUniqueItems("a carry-over decision may only appear once");

        // The completeness inventory and the candidate finalizer's copy list are
        // two views of the same fact. A new excluded entity without a decision
        // fails the first assertion; a stale decision fails the second.
        decisions.Select(decision => decision.EntityName)
            .Should().BeSubsetOf(excluded,
                "every carry-over decision must correspond to an excluded inventory entity");
        excluded.Should().BeSubsetOf(decisions.Select(decision => decision.EntityName),
            "a newly excluded host-state entity must be given an explicit carry-over decision");

        foreach (var decision in decisions)
        {
            decision.Rationale.Should().NotBeNullOrWhiteSpace(
                $"{decision.EntityName} must document why it is carried or cleared");
        }
    }

    [Fact]
    public void Every_carry_over_entity_is_mapped_by_the_ef_model()
    {
        using var db = new NostosDbContext(
            new DbContextOptionsBuilder<NostosDbContext>().UseSqlite("Data Source=:memory:").Options);

        foreach (var decision in SelfHostedHostStateCarryOver.Decisions)
        {
            db.Model.FindEntityType(decision.EntityType).Should().NotBeNull(
                $"{decision.EntityName} must remain an EF-mapped entity");
        }
    }

    [Fact]
    public void Carried_entities_are_the_expected_host_operational_state()
    {
        var carried = SelfHostedHostStateCarryOver.CarryDecisions
            .Select(decision => decision.EntityName)
            .ToHashSet(StringComparer.Ordinal);

        carried.Should().BeEquivalentTo(
        [
            nameof(Nostos.Backend.Data.Models.AiProviderSettingsModel),
            nameof(Nostos.Backend.Data.Models.ProviderPreferenceModel),
            nameof(Nostos.Backend.Data.Models.McpAccessTokenModel),
            nameof(Nostos.Backend.Data.Models.McpOAuthGrantModel),
            nameof(Nostos.Backend.Data.Models.McpOAuthTokenModel),
            nameof(Nostos.Backend.Data.Models.BackupRecord),
            nameof(Nostos.Backend.Data.Models.LibraryCommandReceipt),
            nameof(Nostos.Backend.Data.Models.NoteCommandReceipt),
            nameof(Nostos.Backend.Data.Models.LibraryState),
            nameof(Nostos.Backend.Data.Models.MigrationJobRecord),
            nameof(Nostos.Backend.Data.Models.MigrationSessionRecord),
            nameof(Nostos.Backend.Data.Models.MigrationChunkReceiptRecord),
            nameof(Nostos.Backend.Data.Models.MigrationExportArtifactRecord),
            nameof(Nostos.Backend.Data.Models.MigrationStorageReservationRecord),
        ]);
    }

    [Fact]
    public void Cleared_entities_are_the_import_undo_history_alignment_anchors_external_media_references_and_writing_citations()
    {
        var cleared = SelfHostedHostStateCarryOver.Decisions
            .Where(decision => decision.Kind == HostStateCarryOverKind.Clear)
            .Select(decision => decision.EntityName)
            .ToHashSet(StringComparer.Ordinal);

        cleared.Should().BeEquivalentTo(
        [
            nameof(Nostos.Backend.Data.Models.NoteImportBatch),
            nameof(Nostos.Backend.Data.Models.NoteImportBatchNote),
            nameof(Nostos.Backend.Data.Models.BookImportBatch),
            nameof(Nostos.Backend.Data.Models.BookImportBatchBook),
            nameof(Nostos.Backend.Data.Models.AlignmentAnchorModel),
            nameof(Nostos.Backend.Data.Models.ExternalMediaLinkModel),
            nameof(Nostos.Backend.Data.Models.WritingCitationModel),
            nameof(Nostos.Backend.Data.Models.BookUploadSessionRecord),
            nameof(Nostos.Backend.Data.Models.BookUploadPartReceiptRecord),
        ]);
    }

    [Fact]
    public void No_carried_entity_declares_a_foreign_key_into_portable_or_cleared_state()
    {
        using var db = new NostosDbContext(
            new DbContextOptionsBuilder<NostosDbContext>().UseSqlite("Data Source=:memory:").Options);
        var carried = SelfHostedHostStateCarryOver.CarryDecisions
            .Select(decision => decision.EntityType)
            .ToHashSet();

        foreach (var decision in SelfHostedHostStateCarryOver.CarryDecisions)
        {
            var entity = db.Model.FindEntityType(decision.EntityType)!;
            var principals = entity.GetForeignKeys()
                .Select(foreignKey => foreignKey.PrincipalEntityType.ClrType)
                .ToArray();
            principals.Should().BeSubsetOf(carried,
                $"{decision.EntityName} must not reference portable or cleared state through a foreign key");
        }
    }
}