defmodule GitGud.Workflows.ExprTest do
use ExUnit.Case, async: true
alias GitGud.Workflows.Expr
@contexts %{
"github" => %{
"workflow" => "ci",
"ref" => "refs/heads/main",
"head_ref" => "",
"run_id" => "42",
"event" => %{"pull_request" => %{"number" => 7}, "forced" => false}
},
"inputs" => %{"env" => "prod"},
"vars" => %{"REGION" => "eu"},
"matrix" => %{"target" => "arm64", "n" => 2},
"needs" => %{"build" => %{"result" => "success", "outputs" => %{"tag" => "v1"}}}
}
defp i(string), do: Expr.interpolate(string, @contexts)
describe "the patterns concurrency groups are made of" do
test "workflow and ref" do
assert i("${{ github.workflow }}-${{ github.ref }}") == {:ok, "ci-refs/heads/main"}
end
test "a pull request's number, else the ref" do
assert i("${{ github.event.pull_request.number || github.ref }}") == {:ok, "7"}
assert Expr.interpolate(
"${{ github.event.pull_request.number || github.ref }}",
put_in(@contexts, ["github", "event"], %{})
) == {:ok, "refs/heads/main"}
end
test "head_ref, else the run id: an empty string is falsy" do
assert i("${{ github.head_ref || github.run_id }}") == {:ok, "42"}
end
test "inputs, vars, matrix and needs" do
assert i("deploy-${{ inputs.env }}") == {:ok, "deploy-prod"}
assert i("${{ vars.REGION }}") == {:ok, "eu"}
assert i("${{ matrix.target }}-${{ matrix.n }}") == {:ok, "arm64-2"}
assert i("${{ needs.build.outputs.tag }}") == {:ok, "v1"}
end
test "index syntax, case-insensitively" do
assert i("${{ inputs['env'] }}") == {:ok, "prod"}
assert i("${{ GitHub.Workflow }}") == {:ok, "ci"}
assert i("${{ github['event']['pull_request'].number }}") == {:ok, "7"}
end
test "format" do
assert i("${{ format('{0}-{1}', github.workflow, inputs.env) }}") == {:ok, "ci-prod"}
assert i("${{ format('{{literal}} {0}', 'x') }}") == {:ok, "{literal} x"}
end
test "text without an expression is itself" do
assert i("deploy-prod") == {:ok, "deploy-prod"}
end
end
describe "operators and values" do
test "|| falls back over null and empty string, && keeps the falsy operand" do
assert i("${{ github.nothing || 'fallback' }}") == {:ok, "fallback"}
assert i("${{ '' || null || 'x' }}") == {:ok, "x"}
assert i("${{ github.head_ref && 'yes' }}") == {:ok, ""}
assert i("${{ github.workflow && 'yes' }}") == {:ok, "yes"}
end
test "== and != compare strings ignoring case, and loosely across types" do
assert Expr.evaluate("github.ref == 'REFS/HEADS/MAIN'", @contexts) == {:ok, true}
assert Expr.evaluate("github.ref != 'refs/heads/main'", @contexts) == {:ok, false}
assert Expr.evaluate("github.run_id == 42", @contexts) == {:ok, true}
assert Expr.evaluate("null == 0", @contexts) == {:ok, true}
assert Expr.evaluate("'abc' == 0", @contexts) == {:ok, false}
end
test "!, parentheses and precedence" do
assert Expr.evaluate("!github.event.forced", @contexts) == {:ok, true}
assert Expr.evaluate("!(true && false) || false", @contexts) == {:ok, true}
assert Expr.evaluate("false || true && false", @contexts) == {:ok, false}
end
test "values stringify as GitHub does" do
assert i("${{ null }}|${{ true }}|${{ false }}|${{ 1.0 }}|${{ 0x10 }}|${{ 'it''s' }}") ==
{:ok, "|true|false|1|16|it's"}
end
test "a path through something missing is null" do
assert i("${{ github.event.nope.deeper }}") == {:ok, ""}
end
end
describe "what isn't supported is an error, not a guess" do
test "other functions" do
assert i("${{ startsWith(github.ref, 'refs/') }}") == :error
assert i("${{ contains(github.ref, 'main') }}") == :error
end
test "a context nobody supplied" do
assert i("${{ env.FOO }}") == :error
end
test "unknown syntax" do
assert i("${{ github.* }}") == :error
assert i("${{ github.ref < 'x' }}") == :error
assert i("${{ \"double\" }}") == :error
assert i("${{ github.ref") == :error
end
test "an object where a string was wanted" do
assert i("${{ github.event }}") == :error
end
end
describe "a matrix computed by an earlier job" do
defp r(string, contexts \\ @contexts), do: Expr.resolve(string, contexts)
test "fromJSON yields the value, not its text" do
contexts = put_in(@contexts, ["needs", "build", "outputs", "m"], ~s({"os":["a","b"]}))
assert r("${{ fromJSON(needs.build.outputs.m) }}", contexts) ==
{:ok, %{"os" => ["a", "b"]}}
assert r(" ${{ fromJSON('[1, 2]') }} ") == {:ok, [1, 2]}
end
test "bad JSON is an error" do
assert r("${{ fromJSON('nope') }}") == :error
assert r("${{ fromJSON(needs.build.outputs.missing) }}") == :error
end
test "toJSON is the value's JSON" do
assert {:ok, json} = r("${{ toJSON(matrix) }}")
assert Jason.decode!(json) == %{"target" => "arm64", "n" => 2}
end
test "anything but one whole expression is a string" do
assert r("${{ matrix.target }}-${{ matrix.n }}") == {:ok, "arm64-2"}
assert r("plain") == {:ok, "plain"}
assert r("${{ format('{{0}}') }}") == {:ok, "{0}"}
end
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
5.3 KiB · text
History
6b0b893