test/openagents/transparency_test.exs

58e6347eeb72 · 4 KB

defmodule OpenAgents.TransparencyTest do
  @moduledoc """
  Tests for `OpenAgents.Transparency` tier logic and revocation.
  """

  use OpenAgents.DataCase, async: true

  alias OpenAgents.Transparency.ArtifactLink

  import OpenAgents.Transparency,
    only: [
      allows?: 3,
      effective_tier: 2,
      redact_for_viewer: 3,
      tier_atoms: 0,
      viewer_tier: 1,
      revoke: 3
    ]

  describe "tier_atoms/0" do
    test "returns the tiers from least to most transparent" do
      assert tier_atoms() == [:dark, :pulse, :ledger, :glass]
    end
  end

  describe "effective_tier/2" do
    test "resolves tier atoms and strings" do
      assert effective_tier(:dark, nil) == :dark
      assert effective_tier("pulse", nil) == :pulse
      assert effective_tier(:ledger, nil) == :ledger
      assert effective_tier("glass", nil) == :glass
    end

    test "clamps to the lower of the artifact tier and the viewer tier" do
      assert effective_tier(:glass, "pulse") == :pulse
      assert effective_tier(:ledger, :glass) == :ledger
      assert effective_tier(:ledger, %{tier: "dark"}) == :dark
      assert effective_tier(:glass, %{tier: :pulse}) == :pulse
    end

    test "a revoked artifact link is always dark" do
      revoked = %ArtifactLink{tier: "glass", revoked_at: DateTime.utc_now()}

      assert effective_tier(revoked, :glass) == :dark
      assert effective_tier(revoked, nil) == :dark
    end

    test "an unrevoked artifact link is clamped by the viewer" do
      link = %ArtifactLink{tier: "glass", revoked_at: nil}

      assert effective_tier(link, :ledger) == :ledger
      assert effective_tier(link, "pulse") == :pulse
      assert effective_tier(link, nil) == :glass
    end
  end

  describe "allows?/3" do
    test "metadata requires at least pulse" do
      refute allows?(:dark, :metadata, nil)
      assert allows?(:pulse, :metadata, nil)
      assert allows?(:ledger, :metadata, nil)
      assert allows?(:glass, :metadata, nil)
    end

    test "content requires at least ledger" do
      refute allows?(:pulse, :content, nil)
      assert allows?(:ledger, :content, nil)
      assert allows?(:glass, :content, nil)
    end

    test "full requires glass" do
      refute allows?(:ledger, :full, nil)
      assert allows?(:glass, :full, nil)
    end

    test "a lower viewer tier restricts the result" do
      refute allows?(:glass, :full, :ledger)
      refute allows?(:ledger, :content, :pulse)
      refute allows?(:glass, :metadata, :dark)
    end
  end

  describe "revoke/3" do
    test "sets revoked_at and a revocation tombstone" do
      link = %ArtifactLink{tier: "ledger"}
      revoker_id = Ecto.UUID.generate()

      changeset = revoke(link, "expired", revoker_id)

      assert %DateTime{} = get_field(changeset, :revoked_at)

      assert %{
               reason: "expired",
               revoked_by_id: ^revoker_id,
               revoked_at: _iso
             } = get_field(changeset, :revocation_tombstone)
    end
  end

  describe "viewer_tier/1" do
    test "recognizes admin maps, user accounts, and tier maps" do
      assert viewer_tier(%{admin: true}) == :glass
      assert viewer_tier(%{account_id: Ecto.UUID.generate()}) == nil
      assert viewer_tier(nil) == nil
      assert viewer_tier(:glass) == :glass
      assert viewer_tier("pulse") == :pulse
    end
  end

  describe "redact_for_viewer/3" do
    test "an owner sees detail, trace_ref, and trace_digest while a non-owner is redacted" do
      owner = repository_user_fixture("owner")
      other = repository_user_fixture("other")
      repository = repository_fixture()

      {:ok, link} =
        %ArtifactLink{}
        |> ArtifactLink.changeset(%{
          account_id: owner.id,
          repository_id: repository.id,
          artifact_type: "changelog",
          artifact_ref: "sha",
          tier: "dark"
        })
        |> Repo.insert()

      data = %{
        trace_ref: "trace:v1:owner",
        trace_digest: "sha256:owner",
        detail: %{"note" => "visible"}
      }

      redacted_owner = redact_for_viewer(data, link, owner)
      assert redacted_owner.trace_ref == "trace:v1:owner"
      assert redacted_owner.trace_digest == "sha256:owner"
      assert redacted_owner.detail == %{"note" => "visible"}

      redacted_other = redact_for_viewer(data, link, other)
      assert redacted_other.trace_ref == nil
      assert redacted_other.trace_digest == nil
      assert redacted_other.detail == %{}
    end
  end
end