260 lines
7.2 KiB
Go
260 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// testdataDir returns the path to the testdata directory.
|
|
func testdataDir() string {
|
|
return "testdata"
|
|
}
|
|
|
|
// TestParseIssue_BasicFields verifies that ParseIssue correctly reads frontmatter.
|
|
func TestParseIssue_BasicFields(t *testing.T) {
|
|
path := filepath.Join(testdataDir(), "issues", "0099-sample.md")
|
|
iss, err := ParseIssue(path)
|
|
if err != nil {
|
|
t.Fatalf("ParseIssue: %v", err)
|
|
}
|
|
|
|
if iss.ID != "0099" {
|
|
t.Errorf("ID: got %q, want %q", iss.ID, "0099")
|
|
}
|
|
if iss.Title != "datahub app (launcher central para todas las apps)" {
|
|
t.Errorf("Title: got %q", iss.Title)
|
|
}
|
|
if iss.Status != "pendiente" {
|
|
t.Errorf("Status: got %q, want %q", iss.Status, "pendiente")
|
|
}
|
|
if iss.Priority != "alta" {
|
|
t.Errorf("Priority: got %q, want %q", iss.Priority, "alta")
|
|
}
|
|
if iss.Type != "feature" {
|
|
t.Errorf("Type: got %q, want %q", iss.Type, "feature")
|
|
}
|
|
if len(iss.Domain) != 1 || iss.Domain[0] != "apps-infra" {
|
|
t.Errorf("Domain: got %v, want [apps-infra]", iss.Domain)
|
|
}
|
|
if iss.Path != path {
|
|
t.Errorf("Path: got %q, want %q", iss.Path, path)
|
|
}
|
|
if iss.Body == "" {
|
|
t.Error("Body should not be empty")
|
|
}
|
|
}
|
|
|
|
// TestParseIssue_AcceptancePct verifies checkbox counting in ## Acceptance.
|
|
func TestParseIssue_AcceptancePct(t *testing.T) {
|
|
path := filepath.Join(testdataDir(), "issues", "0099-sample.md")
|
|
iss, err := ParseIssue(path)
|
|
if err != nil {
|
|
t.Fatalf("ParseIssue: %v", err)
|
|
}
|
|
// In 0099-sample.md: 2 checked, 2 unchecked = 50%
|
|
if iss.AcceptancePct != 50 {
|
|
t.Errorf("AcceptancePct: got %d, want 50", iss.AcceptancePct)
|
|
}
|
|
}
|
|
|
|
// TestParseIssue_DoDPct verifies checkbox counting in ## Definition of Done.
|
|
func TestParseIssue_DoDPct(t *testing.T) {
|
|
path := filepath.Join(testdataDir(), "issues", "0099-sample.md")
|
|
iss, err := ParseIssue(path)
|
|
if err != nil {
|
|
t.Fatalf("ParseIssue: %v", err)
|
|
}
|
|
// In 0099-sample.md DoD: 1 checked, 3 unchecked = 25%
|
|
if iss.DoDPct != 25 {
|
|
t.Errorf("DoDPct: got %d, want 25", iss.DoDPct)
|
|
}
|
|
}
|
|
|
|
// TestParseIssue_EmptyBody handles a file with no body.
|
|
func TestParseIssue_EmptyBody(t *testing.T) {
|
|
path := filepath.Join(testdataDir(), "issues", "0051-missing-dep.md")
|
|
iss, err := ParseIssue(path)
|
|
if err != nil {
|
|
t.Fatalf("ParseIssue: %v", err)
|
|
}
|
|
if iss.ID != "0051" {
|
|
t.Errorf("ID: got %q, want %q", iss.ID, "0051")
|
|
}
|
|
// No checkboxes — both pcts should be 0
|
|
if iss.AcceptancePct != 0 {
|
|
t.Errorf("AcceptancePct: got %d, want 0", iss.AcceptancePct)
|
|
}
|
|
}
|
|
|
|
// TestDepsResolved_AllCompletado verifies DepsResolved=true when all deps are completado.
|
|
func TestDepsResolved_AllCompletado(t *testing.T) {
|
|
issues := []Issue{
|
|
{ID: "0001", Status: "completado"},
|
|
{ID: "0050", Status: "pendiente", Depends: []string{"0001"}},
|
|
}
|
|
ComputeDepsResolved(issues)
|
|
|
|
if !issues[1].DepsResolved {
|
|
t.Error("DepsResolved should be true when all deps are completado")
|
|
}
|
|
}
|
|
|
|
// TestDepsResolved_DepNotCompletado verifies DepsResolved=false when dep not completado.
|
|
func TestDepsResolved_DepNotCompletado(t *testing.T) {
|
|
issues := []Issue{
|
|
{ID: "0001", Status: "pendiente"}, // not completado
|
|
{ID: "0050", Status: "pendiente", Depends: []string{"0001"}},
|
|
}
|
|
ComputeDepsResolved(issues)
|
|
|
|
if issues[1].DepsResolved {
|
|
t.Error("DepsResolved should be false when dep is not completado")
|
|
}
|
|
}
|
|
|
|
// TestDepsResolved_DepMissing verifies DepsResolved=false when dep is not in list.
|
|
func TestDepsResolved_DepMissing(t *testing.T) {
|
|
issues := []Issue{
|
|
{ID: "0051", Status: "pendiente", Depends: []string{"9999"}},
|
|
}
|
|
ComputeDepsResolved(issues)
|
|
|
|
if issues[0].DepsResolved {
|
|
t.Error("DepsResolved should be false when dep is missing from issue list")
|
|
}
|
|
}
|
|
|
|
// TestDepsResolved_NoDeps verifies DepsResolved=true when there are no deps.
|
|
func TestDepsResolved_NoDeps(t *testing.T) {
|
|
issues := []Issue{
|
|
{ID: "0099", Status: "pendiente", Depends: []string{}},
|
|
}
|
|
ComputeDepsResolved(issues)
|
|
|
|
if !issues[0].DepsResolved {
|
|
t.Error("DepsResolved should be true when there are no deps")
|
|
}
|
|
}
|
|
|
|
// TestLoadAllIssues_SkipsSkippable verifies that README.md and template.md are skipped.
|
|
func TestLoadAllIssues_SkipsSkippable(t *testing.T) {
|
|
issues, err := loadIssuesFromTestdata()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
for _, iss := range issues {
|
|
base := filepath.Base(iss.Path)
|
|
lower := strings.ToLower(base)
|
|
if strings.Contains(lower, "readme") || strings.Contains(lower, "template") {
|
|
t.Errorf("should have skipped %s", base)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestLoadAllIssues_CountsCorrect verifies we load the expected number of fixtures.
|
|
func TestLoadAllIssues_CountsCorrect(t *testing.T) {
|
|
issues, err := loadIssuesFromTestdata()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
// We have 4 fixture issues: 0099, 0001, 0050, 0051
|
|
if len(issues) != 4 {
|
|
t.Errorf("expected 4 issues, got %d", len(issues))
|
|
}
|
|
}
|
|
|
|
// TestParseFlow_BasicFields verifies that ParseFlow reads frontmatter correctly.
|
|
func TestParseFlow_BasicFields(t *testing.T) {
|
|
path := filepath.Join(testdataDir(), "flows", "0001-sample.md")
|
|
fl, err := ParseFlow(path)
|
|
if err != nil {
|
|
t.Fatalf("ParseFlow: %v", err)
|
|
}
|
|
|
|
if fl.ID != "0001" {
|
|
t.Errorf("ID: got %q, want %q", fl.ID, "0001")
|
|
}
|
|
if fl.Name != "hn-top-stories" {
|
|
t.Errorf("Name: got %q, want %q", fl.Name, "hn-top-stories")
|
|
}
|
|
if fl.Status != "pending" {
|
|
t.Errorf("Status: got %q, want %q", fl.Status, "pending")
|
|
}
|
|
if fl.Risk != "low" {
|
|
t.Errorf("Risk: got %q, want %q", fl.Risk, "low")
|
|
}
|
|
if len(fl.Apps) != 2 {
|
|
t.Errorf("Apps: got %d apps, want 2", len(fl.Apps))
|
|
}
|
|
}
|
|
|
|
// TestParseFlow_AcceptancePct verifies checkbox counting in flow Acceptance.
|
|
func TestParseFlow_AcceptancePct(t *testing.T) {
|
|
path := filepath.Join(testdataDir(), "flows", "0001-sample.md")
|
|
fl, err := ParseFlow(path)
|
|
if err != nil {
|
|
t.Fatalf("ParseFlow: %v", err)
|
|
}
|
|
// 1 checked, 2 unchecked = 33%
|
|
if fl.AcceptancePct != 33 {
|
|
t.Errorf("AcceptancePct: got %d, want 33", fl.AcceptancePct)
|
|
}
|
|
}
|
|
|
|
// TestParseFlow_UserFacingPct verifies user-facing checkbox counting.
|
|
func TestParseFlow_UserFacingPct(t *testing.T) {
|
|
path := filepath.Join(testdataDir(), "flows", "0001-sample.md")
|
|
fl, err := ParseFlow(path)
|
|
if err != nil {
|
|
t.Fatalf("ParseFlow: %v", err)
|
|
}
|
|
// 0 checked, 2 unchecked = 0%
|
|
if fl.UserFacingPct != 0 {
|
|
t.Errorf("UserFacingPct: got %d, want 0", fl.UserFacingPct)
|
|
}
|
|
}
|
|
|
|
// TestNormalizeID pads short numeric IDs.
|
|
func TestNormalizeID(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want string
|
|
}{
|
|
{"99", "0099"},
|
|
{"0099", "0099"},
|
|
{"1", "0001"},
|
|
{"0001", "0001"},
|
|
{"0088a", "0088a"}, // non-numeric, no pad
|
|
{"101", "0101"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := normalizeID(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("normalizeID(%q) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// loadIssuesFromTestdata is a helper that loads from testdata/ instead of dev/issues/.
|
|
func loadIssuesFromTestdata() ([]Issue, error) {
|
|
dir := filepath.Join(testdataDir(), "issues")
|
|
entries, err := filepath.Glob(filepath.Join(dir, "*.md"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var issues []Issue
|
|
for _, path := range entries {
|
|
name := filepath.Base(path)
|
|
if isSkippable(name) {
|
|
continue
|
|
}
|
|
iss, err := ParseIssue(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
issues = append(issues, iss)
|
|
}
|
|
return issues, nil
|
|
}
|