test/openagents/computer_projection_test.exs

58e6347eeb72 · 3 KB

defmodule OpenAgents.ComputerProjectionTest do
  @moduledoc """
  `machines.revoked_at` was written by both revocation paths and read by
  nothing: `ComputerProjection.project/1` omitted it, so the account export and
  the computers API reported that a computer was revoked without saying when.
  """

  use OpenAgents.DataCase, async: true

  alias OpenAgents.ComputerProjection
  alias OpenAgents.Machines
  alias OpenAgents.Machines.Machine
  alias OpenAgents.Repo

  defp user(key) do
    {:ok, user} =
      OpenAgents.Accounts.upsert_github_user(%{
        github_id: :erlang.phash2({__MODULE__, key}),
        github_login: "projection-#{key}",
        github_avatar_url: "https://avatars.githubusercontent.com/u/1?v=4"
      })

    user
  end

  defp computer(owner, key) do
    {:ok, %{code: code}} = Machines.start_pairing(%{"name" => "box-#{key}", "tier" => "probe"})
    {:ok, machine} = Machines.approve_pairing(owner, code)
    machine
  end

  test "an active computer reports no revocation" do
    projection = ComputerProjection.project(computer(user("active"), "active"))

    assert projection["status"] == "active"
    assert Map.fetch!(projection, "revoked_at") == nil
  end

  test "a revoked computer reports when it was revoked" do
    owner = user("revoked")
    machine = computer(owner, "revoked")

    {:ok, revoked} = Machines.revoke_machine(owner, machine.id)
    projection = ComputerProjection.project(revoked)

    assert projection["status"] == "revoked"
    assert projection["revoked_at"] == DateTime.to_iso8601(revoked.revoked_at)
  end

  test "the stamp the account export carries is the stamp the row holds" do
    owner = user("export")
    machine = computer(owner, "export")
    {:ok, _revoked} = Machines.revoke_machine(owner, machine.id)

    # Projected from a fresh read, the way every caller reaches it.
    [projected] = Enum.map(Machines.list_machines(owner.id), &ComputerProjection.project/1)
    stored = Repo.get!(Machine, machine.id)

    assert projected["revoked_at"] == DateTime.to_iso8601(stored.revoked_at)
  end

  test "a computer revoked by an elapsed pairing reports its stamp too" do
    owner = user("swept")
    machine = computer(owner, "swept")

    OpenAgents.Machines.Pairing
    |> Repo.get_by!(machine_id: machine.id)
    |> Ecto.Changeset.change(expires_at: DateTime.add(DateTime.utc_now(), -3_600, :second))
    |> Repo.update!()

    assert Machines.expire_elapsed_pairings() == 1

    projection = ComputerProjection.project(Repo.get!(Machine, machine.id))
    assert projection["status"] == "revoked"
    assert is_binary(projection["revoked_at"])
  end

  test "the projection still withholds the token, its digest, and the raw probe" do
    machine = computer(user("secrets"), "secrets")
    {:ok, machine} = Machines.store_probe(machine, %{"acp_agents" => [], "secret" => "x"})

    projection = ComputerProjection.project(machine)

    refute Map.has_key?(projection, "token_digest")
    refute Map.has_key?(projection, "token_expires_at")
    refute Map.has_key?(projection, "last_probe")
  end
end