3.2 KiB · text History b868907
defmodule GitGud.Workflows.RunnerPayloadTest do
  @moduledoc """
  The workflow document handed to forgejo-runner is the run's own, cut down
  to the one job.

  It used to be rebuilt from the name, `on: push` and the job, so a
  workflow-level `env:` or `defaults.run.working-directory` never reached
  the runner — steps ran with the variable unset, in the wrong directory —
  and act lost the real `on:`, where it finds `workflow_dispatch` input
  defaults.
  """
  use GitGud.DataCase, async: true

  alias GitGud.Workflows.RunnerPayload
  alias GitGud.Workflows.WorkflowJob
  alias GitGud.Workflows.WorkflowRun

  import GitGud.ForgeFixtures

  @doc_with_everything %{
    "name" => "ci",
    "run-name" => "ci for ${{ github.ref }}",
    "on" => %{
      "workflow_dispatch" => %{
        "inputs" => %{"level" => %{"type" => "string", "default" => "info"}}
      }
    },
    "env" => %{"FOO" => "bar"},
    "defaults" => %{"run" => %{"working-directory" => "app"}},
    "permissions" => %{"contents" => "read"},
    "concurrency" => %{"group" => "deploy", "cancel-in-progress" => true},
    "jobs" => %{
      "test" => %{"runs-on" => "ubuntu-latest", "steps" => [%{"run" => "true"}]},
      "build" => %{
        "runs-on" => "ubuntu-latest",
        "needs" => ["test"],
        "concurrency" => "build-${{ github.ref }}",
        "steps" => [%{"run" => "echo $FOO"}]
      }
    }
  }

  defp job!(workflow_doc) do
    {_user, repo} = repository_fixture()

    run =
      %WorkflowRun{repository_id: repo.id}
      |> WorkflowRun.create_changeset(%{
        number: 1,
        workflow_path: ".forgejo/workflows/ci.yml",
        workflow_name: "ci",
        trigger: "workflow_dispatch",
        head_sha: :crypto.strong_rand_bytes(20),
        workflow_doc: workflow_doc
      })
      |> Repo.insert!()

    %WorkflowJob{workflow_run_id: run.id}
    |> WorkflowJob.create_changeset(%{
      job_id: "build",
      definition: @doc_with_everything["jobs"]["build"]
    })
    |> Repo.insert!()
  end

  defp decode(job) do
    {:ok, doc} = job |> RunnerPayload.encode() |> YamlElixir.read_from_string()
    doc
  end

  test "keeps the workflow's env, defaults, permissions, run-name and on" do
    doc = decode(job!(@doc_with_everything))

    assert doc["env"] == %{"FOO" => "bar"}
    assert doc["defaults"] == %{"run" => %{"working-directory" => "app"}}
    assert doc["permissions"] == %{"contents" => "read"}
    assert doc["run-name"] == "ci for ${{ github.ref }}"
    assert doc["on"]["workflow_dispatch"]["inputs"]["level"]["default"] == "info"
    assert doc["name"] == "ci"
  end

  test "carries exactly the one job, without needs, and drops concurrency" do
    doc = decode(job!(@doc_with_everything))

    assert Map.keys(doc["jobs"]) == ["build"]
    refute Map.has_key?(doc["jobs"]["build"], "needs")
    refute Map.has_key?(doc["jobs"]["build"], "concurrency")
    assert doc["jobs"]["build"]["steps"] == [%{"run" => "echo $FOO"}]
    refute Map.has_key?(doc, "concurrency")
  end

  test "a run without a stored document gets the minimal one" do
    doc = decode(job!(nil))

    assert doc["on"] == "push"
    assert doc["name"] == "ci"
    assert Map.keys(doc["jobs"]) == ["build"]
  end
end