test/openagents/scv/executions_test.exs

58e6347eeb72 · 4 KB

defmodule OpenAgents.SCV.ExecutionsTest do
  use OpenAgents.DataCase, async: false

  alias OpenAgents.Accounts
  alias OpenAgents.Repo
  alias OpenAgents.SCV.DriverAccount
  alias OpenAgents.SCV.ExecutionEvent
  alias OpenAgents.SCV.Executions

  setup do
    {:ok, operator} =
      Accounts.upsert_github_user(%{
        github_id: System.unique_integer([:positive]),
        github_login: "scv-execution-#{System.unique_integer([:positive])}",
        github_avatar_url: "https://avatars.githubusercontent.com/u/1?v=4"
      })

    account =
      %DriverAccount{}
      |> DriverAccount.create_changeset(%{
        operator_id: operator.id,
        label: "Execution account",
        secret_ref: "file:execution-#{System.unique_integer([:positive])}"
      })
      |> Repo.insert!()
      |> DriverAccount.ready_changeset(%{
        credential_version: 1,
        plan_type: "pro",
        available_models: ["gpt-5.6-luna"],
        reasoning_efforts: ["low", "none"],
        last_verified_at: DateTime.utc_now()
      })
      |> Repo.update!()

    {:ok, account: account, revision: String.duplicate("a", 40)}
  end

  test "fences one active SCV per account and persists a terminal receipt", context do
    assert {:ok, execution} =
             Executions.claim(context.account, context.revision, "Inspect the release.")

    assert execution.status == "running"
    assert execution.driver == "codex_app_server"
    assert execution.model == "gpt-5.6-luna"
    assert execution.reasoning_effort == "low"
    assert execution.principal == "scv:codex_app_server:#{context.account.id}"

    assert {:error, :account_capacity_unavailable} =
             Executions.claim(context.account, context.revision, "Competing inspection.")

    assert :ok =
             Executions.record_event(execution, %{
               schema: "openagents.scv.event.v1",
               run_id: execution.id,
               type: "tool_started",
               emitted_at: DateTime.utc_now() |> DateTime.to_iso8601(),
               activity_kind: "command",
               tool: "commandExecution",
               command: "must not persist",
               output: "must not persist"
             })

    assert [public] = Executions.public_projection()
    assert public["id"] =~ ~r/\Ascv-[0-9a-f]{8}\z/
    assert public["text"] == "Running a read-only repository command"
    refute inspect(public) =~ execution.id
    refute inspect(public) =~ "must not persist"

    [event] = Repo.all(ExecutionEvent)
    assert event.event_type == "tool_started"
    assert event.payload["activity_kind"] == "command"
    refute Map.has_key?(event.payload, "command")
    refute Map.has_key?(event.payload, "output")

    assert {:ok, updated} =
             Executions.record_session(execution, %{
               driver_thread_id: "thr_fixture",
               driver_turn_id: "turn_fixture"
             })

    assert updated.driver_thread_id == "thr_fixture"
    assert updated.driver_turn_id == "turn_fixture"

    report = "{\"summary\":\"bounded\"}"

    assert {:ok, completed} =
             Executions.finish(execution, %{
               status: "succeeded",
               report: %{text: report},
               usage: %{total_tokens: 21},
               resources: %{wall_time_ms: 50}
             })

    assert completed.status == "succeeded"
    assert completed.report == report
    assert completed.report_digest =~ ~r/\Asha256:[0-9a-f]{64}\z/
    assert completed.event_count == 1
    assert completed.completed_at

    assert {:error, :stale_execution_generation} =
             Executions.record_event(execution, %{
               schema: "openagents.scv.event.v1",
               run_id: execution.id,
               type: "heartbeat"
             })
  end

  test "refuses an account without the admitted model or reasoning effort", context do
    unavailable =
      context.account
      |> Ecto.Changeset.change(available_models: [], reasoning_efforts: [])
      |> Repo.update!()

    assert {:error, :required_model_unavailable} =
             Executions.claim(unavailable, context.revision, "Inspect the release.")
  end

  test "expires a stale lease and releases the account slot", context do
    assert {:ok, execution} =
             Executions.claim(context.account, context.revision, "Inspect the release.")

    execution
    |> Ecto.Changeset.change(lease_expires_at: DateTime.add(DateTime.utc_now(), -1, :second))
    |> Repo.update!()

    assert Executions.expire_stale() == 1
    assert Executions.get!(execution.id).status == "uncertain"
    assert Executions.public_projection() == []

    assert {:ok, replacement} =
             Executions.claim(context.account, context.revision, "Inspect it again.")

    assert replacement.generation == execution.generation + 1
  end
end