cc-os/plugins/os-adr/tests/detector_test.rb

176 lines
4.0 KiB
Ruby

require_relative "test_helper"
class DetectorTest < Minitest::Test
include AdrTestHelpers
NUMBERED = <<~MD
# 0001 — PocketBase backend
## Status
Accepted
## Context
Need a backend.
## Decision
PocketBase.
## Consequences
Single binary.
## Alternatives considered
Rails too heavy.
MD
DATED = <<~MD
# JSX conversion pivot
**Date:** 2024-12-15
**Status:** Accepted
## Context
Conversion stalled.
## Decision
Pivot to JSX.
MD
TOPIC = <<~MD
# bin/lint-fix
Status: Confirmed
Decided: 2026-02-01
## Decision
One lint entry point.
## Why
Consistency across repos.
MD
MONOLITHIC = <<~MD
# Decision log
## ADR-001: Use SQLite
Status: Accepted
### Context
Need storage.
### Decision
SQLite.
## ADR-002: Drop SQLite
Status: Accepted
### Decision
Postgres instead.
MD
PROSE = <<~MD
# Gaps and decisions (LOCKED)
Long narrative preamble.
Decision 26: Single entry point
We route everything through one command.
Decision 27: No config file
Defaults only.
MD
UNRECOGNIZED = "# Meeting notes\n\nWe talked about stuff.\n"
MULTI_STATUS_NO_HEADINGS = <<~MD
# DECISIONS
2026-01-05 Hosting
Status: Confirmed
We host on X.
2026-02-10 Pricing
Status: Deferred
Revisit in Q3.
MD
def fixture_project
Dir.mktmpdir do |root|
decisions = File.join(root, "docs", "decisions")
adr = File.join(root, "docs", "adr")
FileUtils.mkdir_p(decisions)
FileUtils.mkdir_p(adr)
File.write(File.join(adr, "0001-pocketbase-backend.md"), NUMBERED)
File.write(File.join(adr, "README.md"), "index")
File.write(File.join(decisions, "2024-12-15-jsx-pivot.md"), DATED)
File.write(File.join(decisions, "bin-lint-fix.md"), TOPIC)
File.write(File.join(decisions, "decision-log.md"), MONOLITHIC)
File.write(File.join(decisions, "gaps-and-decisions.md"), PROSE)
File.write(File.join(decisions, "notes.md"), UNRECOGNIZED)
File.write(File.join(root, "DECISIONS.md"), MULTI_STATUS_NO_HEADINGS)
yield root, Adr::Detector.new(root: root).units
end
end
def shapes_by_source(units)
units.group_by(&:source_path).transform_values { |us| us.map(&:shape).uniq }
end
def test_classifies_each_surveyed_shape
fixture_project do |_root, units|
shapes = shapes_by_source(units)
assert_equal [:numbered_per_file], shapes["docs/adr/0001-pocketbase-backend.md"]
assert_equal [:dated_single_file], shapes["docs/decisions/2024-12-15-jsx-pivot.md"]
assert_equal [:topic_single_file], shapes["docs/decisions/bin-lint-fix.md"]
assert_equal [:monolithic], shapes["docs/decisions/decision-log.md"]
assert_equal [:prose_embedded], shapes["docs/decisions/gaps-and-decisions.md"]
assert_equal [:unrecognized], shapes["docs/decisions/notes.md"]
end
end
def test_monolithic_units_are_enumerated_per_heading
fixture_project do |_root, units|
mono = units.select { |u| u.source_path == "docs/decisions/decision-log.md" }
assert_equal 2, mono.size
assert_equal ["Use SQLite", "Drop SQLite"], mono.map(&:title)
assert_includes mono.first.body, "SQLite."
refute_includes mono.first.body, "Postgres"
end
end
def test_prose_embedded_units_are_enumerated
fixture_project do |_root, units|
prose = units.select { |u| u.source_path == "docs/decisions/gaps-and-decisions.md" }
assert_equal ["Single entry point", "No config file"], prose.map(&:title)
end
end
def test_multi_decision_file_without_unit_headings_is_unrecognized
fixture_project do |_root, units|
assert_equal [:unrecognized], shapes_by_source(units)["DECISIONS.md"]
end
end
def test_index_and_already_migrated_files_are_excluded
with_project("0001-a.md" => sample_adr) do |root|
File.write(File.join(root, "docs", "adr", "README.md"), "index")
assert_empty Adr::Detector.new(root: root).units
end
end
end