3.8 KiB · text History b868907
defmodule GitGud.Workflows.DispatchInputsTest do
  use ExUnit.Case, async: true

  alias GitGud.Workflows.DispatchInputs
  alias GitGud.Workflows.Yaml

  defp doc(inputs_yaml) do
    {:ok, doc} =
      Yaml.parse("""
      on:
        workflow_dispatch:
          inputs:
      #{inputs_yaml}
      jobs:
        a:
          steps: [{run: "true"}]
      """)

    doc
  end

  @inputs """
        environment:
          description: Where to
          type: choice
          options: [staging, production]
          default: staging
        dry_run:
          type: boolean
          default: true
        verbose:
          type: boolean
        replicas:
          type: number
        reason:
          required: true
        note:
          description: free text
        target:
          type: environment
  """

  test "defaults fill what isn't given; values are strings" do
    assert {:ok, inputs} = DispatchInputs.resolve(doc(@inputs), %{"reason" => "x"})

    assert inputs == %{
             "environment" => "staging",
             "dry_run" => "true",
             "verbose" => "false",
             "replicas" => "",
             "reason" => "x",
             "note" => "",
             "target" => ""
           }
  end

  test "given values win, booleans and numbers normalised" do
    given = %{
      "reason" => "x",
      "environment" => "production",
      "dry_run" => "FALSE",
      "verbose" => true,
      "replicas" => " 3 ",
      "target" => "prod"
    }

    assert {:ok, inputs} = DispatchInputs.resolve(doc(@inputs), given)
    assert inputs["environment"] == "production"
    assert inputs["dry_run"] == "false"
    assert inputs["verbose"] == "true"
    assert inputs["replicas"] == "3"
    assert inputs["target"] == "prod"
  end

  test "an undeclared input is refused" do
    assert {:error, [{"colour", "is not an input of this workflow"}]} =
             DispatchInputs.resolve(doc(@inputs), %{"reason" => "x", "colour" => "red"})
  end

  test "a required input with no default must be given" do
    assert {:error, [{"reason", "is required"}]} = DispatchInputs.resolve(doc(@inputs), %{})
  end

  test "a required input with a default needn't be" do
    doc = doc("      level:\n        required: true\n        default: info\n")
    assert {:ok, %{"level" => "info"}} = DispatchInputs.resolve(doc, %{})
  end

  test "a choice must be one of its options" do
    assert {:error, [{"environment", "must be one of: staging, production"}]} =
             DispatchInputs.resolve(doc(@inputs), %{"reason" => "x", "environment" => "dev"})
  end

  test "a boolean must be true or false" do
    assert {:error, [{"dry_run", "must be true or false"}]} =
             DispatchInputs.resolve(doc(@inputs), %{"reason" => "x", "dry_run" => "yes"})
  end

  test "a number must be numeric" do
    assert {:error, [{"replicas", "must be a number"}]} =
             DispatchInputs.resolve(doc(@inputs), %{"reason" => "x", "replicas" => "3x"})

    assert {:ok, %{"replicas" => "2.5"}} =
             DispatchInputs.resolve(doc(@inputs), %{"reason" => "x", "replicas" => "2.5"})
  end

  test "every error at once, by name" do
    assert {:error, errors} =
             DispatchInputs.resolve(doc(@inputs), %{"dry_run" => "no", "zz" => "1"})

    assert Enum.map(errors, &elem(&1, 0)) == ["dry_run", "reason", "zz"]
    assert DispatchInputs.describe(errors) =~ "input reason is required"
  end

  test "no declared inputs: nothing comes out, and nothing may go in" do
    {:ok, doc} = Yaml.parse("on: workflow_dispatch\njobs: {a: {steps: []}}\n")
    assert {:ok, %{}} = DispatchInputs.resolve(doc, %{})
    assert {:error, [{"x", _}]} = DispatchInputs.resolve(doc, %{"x" => "1"})
  end

  test "declared/1 reads a stored raw doc too" do
    assert %{"reason" => %{"required" => true}} = DispatchInputs.declared(doc(@inputs).raw)
  end
end