defmodule GitGud.Workflows.ScheduleTest do
@moduledoc """
`on: schedule`: crons are read from the default branch as it moves, and
the minute tick fires a due one once.
"""
use GitGud.DataCase, async: false
use Oban.Testing, repo: GitGud.Repo
alias GitGud.Repositories
alias GitGud.Repositories.Repository
alias GitGud.Workflows.Schedules
alias GitGud.Workflows.WorkflowRun
alias GitGud.Workflows.WorkflowSchedule
alias GitGud.Workflows.Workers.ScheduleTick
import GitGud.ForgeFixtures
import GitGud.WorkflowGitHelpers
@moduletag :capture_log
@wf ".forgejo/workflows/nightly.yml"
defp workflow(crons) do
entries = Enum.map_join(crons, "", &" - cron: \"#{&1}\"\n")
"""
name: nightly
on:
schedule:
#{entries}
jobs:
build:
runs-on: self-hosted
steps:
- run: echo hello
"""
end
setup do
{user, repo} = repository_fixture()
{:ok, repo: repo, user: user}
end
defp schedules(%Repository{id: rid}) do
from(s in WorkflowSchedule, where: s.repository_id == ^rid, order_by: s.cron)
|> Repo.all()
|> Enum.map(&{&1.workflow_path, &1.cron})
end
defp scheduled_runs(%Repository{id: rid}) do
from(r in WorkflowRun, where: r.repository_id == ^rid and r.trigger == "schedule")
|> Repo.all()
end
defp tick!(at), do: :ok = perform_job(ScheduleTick, %{"at" => DateTime.to_iso8601(at)})
describe "sync" do
test "a push to the default branch records its crons; another branch doesn't", %{
repo: repo
} do
main = commit!(repo, %{@wf => workflow(["0 3 * * *", "30 12 * * 1"])}, [])
push_ref!(repo, "refs/heads/main", nil, main)
assert schedules(repo) == [{@wf, "0 3 * * *"}, {@wf, "30 12 * * 1"}]
other = commit!(repo, %{@wf => workflow(["0 4 * * *"])}, [main])
push_ref!(repo, "refs/heads/feature", nil, other)
assert schedules(repo) == [{@wf, "0 3 * * *"}, {@wf, "30 12 * * 1"}]
end
test "dropping a cron from the file removes it", %{repo: repo} do
main = commit!(repo, %{@wf => workflow(["0 3 * * *", "30 12 * * 1"])}, [])
push_ref!(repo, "refs/heads/main", nil, main)
next = commit!(repo, %{@wf => workflow(["30 12 * * 1"])}, [main])
push_ref!(repo, "refs/heads/main", main, next)
assert schedules(repo) == [{@wf, "30 12 * * 1"}]
gone = commit!(repo, %{"README" => "x\n"}, [next])
push_ref!(repo, "refs/heads/main", next, gone)
assert schedules(repo) == []
end
test "changing the default branch re-reads them", %{repo: repo} do
main = commit!(repo, %{"README" => "x\n"}, [])
push_ref!(repo, "refs/heads/main", nil, main)
dev = commit!(repo, %{@wf => workflow(["0 3 * * *"])}, [main])
push_ref!(repo, "refs/heads/dev", nil, dev)
assert schedules(repo) == []
{:ok, _repo} = Repositories.update_repository(repo, %{"default_branch" => "dev"})
assert schedules(repo) == [{@wf, "0 3 * * *"}]
end
test "too frequent, @reboot and unparseable crons are refused", %{repo: repo} do
crons = ["* * * * *", "*/2 * * * *", "58 * * * *", "@reboot", "nope", "*/5 * * * *"]
main = commit!(repo, %{@wf => workflow(crons)}, [])
push_ref!(repo, "refs/heads/main", nil, main)
assert schedules(repo) == [{@wf, "*/5 * * * *"}, {@wf, "58 * * * *"}]
end
end
describe "parse/1" do
test "the five-minute minimum" do
assert {:ok, _} = Schedules.parse("*/5 * * * *")
assert {:ok, _} = Schedules.parse("@hourly")
assert {:error, _} = Schedules.parse("0 0 31 2 *")
assert {:error, _} = Schedules.parse("*/4 * * * *")
assert {:error, _} = Schedules.parse("0,3 * * * *")
# 12:58 then 13:01.
assert {:error, _} = Schedules.parse("1,58 12,13 * * *")
assert {:ok, _} = Schedules.parse("1,58 12,14 * * *")
end
end
describe "the tick" do
setup %{repo: repo} do
main = commit!(repo, %{@wf => workflow(["0 3 * * *"])}, [])
push_ref!(repo, "refs/heads/main", nil, main)
{:ok, main: main}
end
test "fires a due schedule once a minute, on the default branch", %{repo: repo, main: main} do
tick!(~U[2026-10-17 03:00:05Z])
tick!(~U[2026-10-17 03:00:40Z])
assert [run] = scheduled_runs(repo)
assert run.head_ref == "refs/heads/main"
assert run.head_sha == sha!(main)
assert run.workflow_path == @wf
assert run.event_payload["schedule"] == "0 3 * * *"
tick!(~U[2026-10-18 03:00:00Z])
assert [_, _] = scheduled_runs(repo)
end
test "doesn't fire one that isn't due", %{repo: repo} do
tick!(~U[2026-10-17 03:01:00Z])
assert scheduled_runs(repo) == []
end
test "a workflow edited to drop the cron doesn't fire it", %{repo: repo, main: main} do
# The row is left behind (as if sync hadn't run yet): the tick
# re-reads the file before running it.
edited = commit!(repo, %{@wf => workflow(["0 4 * * *"])}, [main])
git!(repo.disk_path, ["update-ref", "refs/heads/main", edited])
tick!(~U[2026-10-17 03:00:00Z])
assert scheduled_runs(repo) == []
end
test "an archived repository doesn't run", %{repo: repo} do
{:ok, _} = Repositories.update_repository(repo, %{"archived" => true})
tick!(~U[2026-10-17 03:00:00Z])
assert scheduled_runs(repo) == []
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
ad37081