require_relative "test_helper" require "tmpdir" require "fileutils" require "json" class InspectorTest < Minitest::Test def test_detects_docs_issues_dir Dir.mktmpdir do |dir| FileUtils.mkdir_p(File.join(dir, "docs/issues")) assert_includes Backlog::Inspector.issue_files(dir), "docs/issues" end end def test_detects_issues_md_file Dir.mktmpdir do |dir| File.write(File.join(dir, "ISSUES.md"), "# issues\n") assert_includes Backlog::Inspector.issue_files(dir), "ISSUES.md" end end def test_returns_empty_when_nothing_present Dir.mktmpdir do |dir| assert_empty Backlog::Inspector.issue_files(dir) end end end class ClassifyIssueShapeTest < Minitest::Test def issue(number, title: "issue #{number}", labels: [], body: "") { number: number, title: title, labels: labels, body: body } end def test_nil_when_metadata_unavailable assert_nil Backlog::Inspector.classify_issue_shape(nil) end def test_nil_when_fewer_than_three_issues_even_with_chain_labels issues = [issue(1, labels: ["blocked"]), issue(2, labels: ["blocked"])] assert_nil Backlog::Inspector.classify_issue_shape(issues) end def test_flat_adhoc_when_no_issue_shows_chain_evidence issues = [issue(1), issue(2), issue(3, body: "fixed in #1 last week")] assert_equal "flat-adhoc", Backlog::Inspector.classify_issue_shape(issues) end def test_sequential_chain_from_labels issues = [ issue(1, labels: ["Blocked-By"]), issue(2, labels: ["depends-on"]), issue(3, labels: ["enhancement"]) ] assert_equal "sequential-chain", Backlog::Inspector.classify_issue_shape(issues) end def test_negated_label_token_does_not_count issues = [issue(1, labels: ["not-blocked"]), issue(2), issue(3)] assert_equal "flat-adhoc", Backlog::Inspector.classify_issue_shape(issues) end def test_cross_reference_near_dependency_language_counts issues = [ issue(1, body: "This is blocked by #2 until the schema lands."), issue(2, body: "Must land after #3."), issue(3, body: "Standalone groundwork.") ] assert_equal "sequential-chain", Backlog::Inspector.classify_issue_shape(issues) end def test_plain_cross_reference_without_dependency_language_does_not_count issues = [ issue(1, body: "See also #2 for background."), issue(2, body: "Related: #3."), issue(3) ] assert_equal "flat-adhoc", Backlog::Inspector.classify_issue_shape(issues) end def test_reference_to_number_outside_fetched_set_does_not_count issues = [ issue(1, body: "blocked by #99 upstream"), issue(2), issue(3) ] assert_equal "flat-adhoc", Backlog::Inspector.classify_issue_shape(issues) end def test_dependency_language_too_far_from_reference_does_not_count padding = "x" * 60 issues = [ issue(1, body: "blocked #{padding} see #2"), issue(2), issue(3) ] assert_equal "flat-adhoc", Backlog::Inspector.classify_issue_shape(issues) end def test_mixed_when_some_but_not_most_show_evidence issues = [issue(1, labels: ["blocked"]), issue(2), issue(3)] assert_equal "mixed", Backlog::Inspector.classify_issue_shape(issues) end def test_self_reference_does_not_count issues = [ issue(1, body: "depends on #1 (this issue)"), issue(2), issue(3) ] assert_equal "flat-adhoc", Backlog::Inspector.classify_issue_shape(issues) end end class InspectCliContractTest < Minitest::Test BIN = File.expand_path("../bin/os-backlog", __dir__) # `inspect` must always expose the issue_shape key, even when every # network-ish check degrades (tmpdir: no git remote, no config). Value is # null here because no metadata is fetchable — that's the fail-soft path. def test_inspect_json_always_contains_issue_shape_key Dir.mktmpdir do |dir| out = Dir.chdir(dir) { `ruby #{BIN} inspect 2>/dev/null` } findings = JSON.parse(out) assert findings.key?("issue_shape"), "inspect JSON missing issue_shape key" assert_nil findings["issue_shape"] end end end