182 lines
6.9 KiB
Ruby
182 lines
6.9 KiB
Ruby
|
|
require_relative "test_helper"
|
||
|
|
require "tmpdir"
|
||
|
|
|
||
|
|
class FakeIssuesRunner
|
||
|
|
attr_reader :calls
|
||
|
|
|
||
|
|
def initialize(responses)
|
||
|
|
@responses = responses.dup
|
||
|
|
@calls = []
|
||
|
|
end
|
||
|
|
|
||
|
|
def capture(*cmd)
|
||
|
|
@calls << cmd
|
||
|
|
@responses.shift || ["", true]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
class IssuesCreateTest < Minitest::Test
|
||
|
|
def test_forgejo_create_shells_out_to_tea_with_priority_label
|
||
|
|
runner = FakeIssuesRunner.new([["Created issue #7: https://forgejo.example/jared/cc-os/issues/7\n", true]])
|
||
|
|
issues = Backlog::Issues.new(runner: runner)
|
||
|
|
|
||
|
|
result = issues.create(tracker: "forgejo:jared/cc-os", title: "Fix the thing", body: "context here", priority: "P1")
|
||
|
|
|
||
|
|
assert_equal 7, result[:number]
|
||
|
|
assert_equal "https://forgejo.example/jared/cc-os/issues/7", result[:url]
|
||
|
|
cmd = runner.calls.first
|
||
|
|
assert_equal "tea", cmd[0]
|
||
|
|
assert_includes cmd, "--repo"
|
||
|
|
assert_includes cmd, "jared/cc-os"
|
||
|
|
assert_includes cmd, "--title"
|
||
|
|
assert_includes cmd, "Fix the thing"
|
||
|
|
assert_includes cmd, "--labels"
|
||
|
|
assert_includes cmd, "P1"
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_forgejo_create_omits_labels_flag_without_priority
|
||
|
|
runner = FakeIssuesRunner.new([["Created issue #1: https://x/issues/1\n", true]])
|
||
|
|
Backlog::Issues.new(runner: runner).create(tracker: "forgejo:jared/cc-os", title: "T", body: "B")
|
||
|
|
|
||
|
|
refute_includes runner.calls.first, "--labels"
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_github_create_shells_out_to_gh
|
||
|
|
runner = FakeIssuesRunner.new([["https://github.com/acme/widget/issues/42\n", true]])
|
||
|
|
issues = Backlog::Issues.new(runner: runner)
|
||
|
|
|
||
|
|
result = issues.create(tracker: "github:acme/widget", title: "Fix", body: "ctx", priority: "P0")
|
||
|
|
|
||
|
|
assert_equal 42, result[:number]
|
||
|
|
assert_equal "https://github.com/acme/widget/issues/42", result[:url]
|
||
|
|
cmd = runner.calls.first
|
||
|
|
assert_equal "gh", cmd[0]
|
||
|
|
assert_includes cmd, "--label"
|
||
|
|
assert_includes cmd, "P0"
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_create_raises_command_failed_on_nonzero_exit
|
||
|
|
runner = FakeIssuesRunner.new([["auth error", false]])
|
||
|
|
issues = Backlog::Issues.new(runner: runner)
|
||
|
|
|
||
|
|
assert_raises(Backlog::Issues::CommandFailed) do
|
||
|
|
issues.create(tracker: "forgejo:jared/cc-os", title: "T", body: "B")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_create_rejects_invalid_priority
|
||
|
|
issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]))
|
||
|
|
|
||
|
|
assert_raises(ArgumentError) do
|
||
|
|
issues.create(tracker: "forgejo:jared/cc-os", title: "T", body: "B", priority: "P9")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_create_can_never_apply_the_next_label
|
||
|
|
# There is no `labels:`/state-label parameter on #create at all — the
|
||
|
|
# only label surface is `priority:`, restricted to P0-P3. This test
|
||
|
|
# documents that constraint as executable spec.
|
||
|
|
issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]))
|
||
|
|
|
||
|
|
assert_raises(ArgumentError) do
|
||
|
|
issues.create(tracker: "forgejo:jared/cc-os", title: "T", body: "B", priority: "next")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_repo_create_writes_a_frontmattered_markdown_file
|
||
|
|
Dir.mktmpdir do |dir|
|
||
|
|
issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]), repo_root: dir,
|
||
|
|
clock: -> { Time.new(2026, 7, 16) })
|
||
|
|
result = issues.create(tracker: "repo:docs/issues", title: "Fix the Thing!", body: "details here",
|
||
|
|
priority: "P2")
|
||
|
|
|
||
|
|
assert_equal 1, result[:number]
|
||
|
|
assert_equal File.join(dir, "docs/issues/001-fix-the-thing.md"), result[:path]
|
||
|
|
contents = File.read(result[:path])
|
||
|
|
assert_includes contents, "number: 1"
|
||
|
|
assert_includes contents, "title: Fix the Thing!"
|
||
|
|
assert_includes contents, "labels: P2"
|
||
|
|
assert_includes contents, "state: open"
|
||
|
|
assert_includes contents, "created: 2026-07-16"
|
||
|
|
assert_includes contents, "details here"
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_repo_create_numbers_sequentially
|
||
|
|
Dir.mktmpdir do |dir|
|
||
|
|
issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]), repo_root: dir)
|
||
|
|
issues.create(tracker: "repo:docs/issues", title: "First", body: "b")
|
||
|
|
second = issues.create(tracker: "repo:docs/issues", title: "Second", body: "b")
|
||
|
|
|
||
|
|
assert_equal 2, second[:number]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
class IssuesListTest < Minitest::Test
|
||
|
|
def test_forgejo_list_groups_by_state_label
|
||
|
|
tea_json = JSON.generate([
|
||
|
|
{ "index" => 1, "title" => "Next thing", "labels" => "P1,next", "assignees" => "jared" },
|
||
|
|
{ "index" => 2, "title" => "Blocked thing", "labels" => "waiting", "assignees" => "" },
|
||
|
|
{ "index" => 3, "title" => "Shipped semi", "labels" => "review", "assignees" => "" },
|
||
|
|
{ "index" => 4, "title" => "Ad hoc", "labels" => "P3", "assignees" => "" }
|
||
|
|
])
|
||
|
|
runner = FakeIssuesRunner.new([[tea_json, true]])
|
||
|
|
grouped = Backlog::Issues.new(runner: runner).list(tracker: "forgejo:jared/cc-os")
|
||
|
|
|
||
|
|
assert_equal [1], grouped[:next].map { |i| i[:number] }
|
||
|
|
assert_equal [2], grouped[:waiting].map { |i| i[:number] }
|
||
|
|
assert_equal [3], grouped[:review].map { |i| i[:number] }
|
||
|
|
assert_equal [4], grouped[:other].map { |i| i[:number] }
|
||
|
|
assert_equal "jared", grouped[:next].first[:assignee]
|
||
|
|
cmd = runner.calls.first
|
||
|
|
assert_equal "tea", cmd[0]
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_github_list_groups_by_state_label
|
||
|
|
gh_json = JSON.generate([
|
||
|
|
{ "number" => 5, "title" => "Ship it", "labels" => [{ "name" => "review" }],
|
||
|
|
"assignees" => [{ "login" => "jared" }] }
|
||
|
|
])
|
||
|
|
runner = FakeIssuesRunner.new([[gh_json, true]])
|
||
|
|
grouped = Backlog::Issues.new(runner: runner).list(tracker: "github:acme/widget")
|
||
|
|
|
||
|
|
assert_equal [5], grouped[:review].map { |i| i[:number] }
|
||
|
|
assert_equal "jared", grouped[:review].first[:assignee]
|
||
|
|
assert_equal "gh", runner.calls.first[0]
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_list_raises_command_failed_on_nonzero_exit
|
||
|
|
runner = FakeIssuesRunner.new([["boom", false]])
|
||
|
|
assert_raises(Backlog::Issues::CommandFailed) do
|
||
|
|
Backlog::Issues.new(runner: runner).list(tracker: "forgejo:jared/cc-os")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_repo_list_reads_open_issue_files_and_groups_them
|
||
|
|
Dir.mktmpdir do |dir|
|
||
|
|
issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]), repo_root: dir)
|
||
|
|
issues.create(tracker: "repo:docs/issues", title: "Waiting one", body: "b", priority: "P1")
|
||
|
|
waiting_path = Dir.glob(File.join(dir, "docs/issues/*.md")).first
|
||
|
|
File.write(waiting_path, File.read(waiting_path).sub("labels: P1", "labels: P1,waiting"))
|
||
|
|
|
||
|
|
grouped = issues.list(tracker: "repo:docs/issues")
|
||
|
|
assert_equal ["Waiting one"], grouped[:waiting].map { |i| i[:title] }
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_repo_list_is_empty_when_directory_absent
|
||
|
|
Dir.mktmpdir do |dir|
|
||
|
|
grouped = Backlog::Issues.new(runner: FakeIssuesRunner.new([]), repo_root: dir)
|
||
|
|
.list(tracker: "repo:docs/issues")
|
||
|
|
assert_equal [], grouped[:other]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_unroutable_tracker_raises
|
||
|
|
issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]))
|
||
|
|
assert_raises(ArgumentError) { issues.list(tracker: "planka:board") }
|
||
|
|
assert_raises(ArgumentError) { issues.create(tracker: nil, title: "T", body: "B") }
|
||
|
|
end
|
||
|
|
end
|