test/openagents/migration_lineage_test.exs

58e6347eeb72 · 4 KB

defmodule OpenAgents.MigrationLineageTest do
  use OpenAgents.DataCase, async: false

  import OpenAgents.AccountsFixtures

  alias OpenAgents.MigrationLineage
  alias OpenAgents.Repo

  @snapshot_ref "local-snapshot-20260820"

  test "the baseline map partitions every current migration" do
    %{data: map, digest: digest} = MigrationLineage.map!()

    current_versions =
      "priv/repo/migrations/*.exs"
      |> Path.wildcard()
      |> Enum.map(fn path ->
        path |> Path.basename() |> String.slice(0, 14) |> String.to_integer()
      end)
      |> MapSet.new()

    baseline_versions = Enum.map(map["baseline_entries"], & &1["current_version"])

    classified_versions =
      map["shared_versions"] ++
        baseline_versions ++
        map["reconciliation_versions_to_run"] ++ map["new_versions_to_run"]

    assert MapSet.new(classified_versions) == current_versions
    assert length(classified_versions) == MapSet.size(current_versions)
    assert digest =~ ~r/\A[0-9a-f]{64}\z/
  end

  test "a current-lineage database is classified without mutation" do
    assert {:ok, result} = MigrationLineage.classify(Repo)
    assert result.classification == "current"
    assert result.bridge_state == "complete"
  end

  test "the known prior lineage is baselined once without deleting account data" do
    user = repository_user_fixture("lineage-account")
    prepare_prior_lineage!()

    assert {:ok, before} = MigrationLineage.classify(Repo)
    assert before.classification == "prior"
    assert before.required_facts_missing == 0

    assert {:ok, result} = MigrationLineage.baseline(Repo, @snapshot_ref)
    assert result.classification == "prior_baselined"
    assert result.changed
    assert result.snapshot_ref == @snapshot_ref
    assert user_exists?(user.id)
    assert column_exists?("users", "browser_key_hash")
    assert index_exists?("users_browser_key_hash_index")

    assert {:ok, repeated} = MigrationLineage.baseline(Repo, @snapshot_ref)
    refute repeated.changed

    encoded = MigrationLineage.command!(Repo, "check", nil)
    refute encoded =~ user.github_login
    assert Jason.decode!(encoded)["classification"] == "prior_baselined"
  end

  test "an unknown or partially modified lineage is refused" do
    prepare_prior_lineage!()
    [first_baseline | _rest] = baseline_versions()
    insert_version!(first_baseline)

    assert {:ok, %{classification: "prior_partial"}} = MigrationLineage.classify(Repo)

    assert {:error, :partial_lineage_forbidden} =
             MigrationLineage.baseline(Repo, @snapshot_ref)

    Repo.query!("DELETE FROM schema_migrations")
    insert_version!(20_260_101_000_000)

    assert {:ok, %{classification: "unknown"}} = MigrationLineage.classify(Repo)

    assert {:error, :lineage_not_baselineable} =
             MigrationLineage.baseline(Repo, @snapshot_ref)
  end

  test "baseline mutation requires a bounded snapshot reference" do
    assert {:error, :invalid_snapshot_reference} = MigrationLineage.baseline(Repo, "missing")

    assert {:error, :invalid_snapshot_reference} =
             MigrationLineage.baseline(Repo, "snapshot/contains/path")
  end

  defp prepare_prior_lineage! do
    Repo.query!("DELETE FROM schema_migrations")
    Repo.query!("DROP INDEX users_browser_key_hash_index")
    Repo.query!("ALTER TABLE users DROP COLUMN browser_key_hash")
    Repo.query!("ALTER TABLE users ALTER COLUMN github_avatar_url TYPE text")

    Repo.query!("""
    CREATE UNIQUE INDEX forge_builds_repo_sha_target_id_index
        ON forge_builds (repo, sha, target_id)
    """)

    %{data: map} = MigrationLineage.map!()
    Enum.each(map["prior_signature_versions"], &insert_version!/1)
  end

  defp baseline_versions do
    %{data: map} = MigrationLineage.map!()
    Enum.map(map["baseline_entries"], & &1["current_version"])
  end

  defp insert_version!(version) do
    Repo.query!(
      "INSERT INTO schema_migrations (version, inserted_at) VALUES ($1, NOW())",
      [version]
    )
  end

  defp column_exists?(table, column) do
    %{num_rows: count} =
      Repo.query!(
        "SELECT 1 FROM information_schema.columns WHERE table_name = $1 AND column_name = $2",
        [table, column]
      )

    count == 1
  end

  defp index_exists?(index) do
    %{num_rows: count} =
      Repo.query!("SELECT 1 FROM pg_indexes WHERE indexname = $1", [index])

    count == 1
  end

  defp user_exists?(id) do
    %{rows: [[count]]} = Repo.query!("SELECT count(*) FROM users WHERE id::text = $1", [id])
    count == 1
  end
end