Files
pad/internal/models/item_test.go
T
xarmian 7cda0d7896 feat: rebrand to Perpetual Software + new tagline (IDEA-832) (#273)
Migrates from xarmian/pad to PerpetualSoftware/pad across the entire
repo and updates the product subtitle to "Collaborate with your AI
agents".

Go module rename
- go.mod: github.com/xarmian/pad → github.com/PerpetualSoftware/pad
- All Go imports updated across cmd/pad, internal/{cli,server,store,
  models,collections,items,events,metrics,webhooks} (~130 files)
- Test fixtures with the literal repo slug ("xarmian/pad" in JSON
  shapes, SSH/HTTPS git URL strings, workspace_context fixtures)
  also updated, including the secondary repo entry
  (xarmian/pad-web → PerpetualSoftware/pad-web — pad-web was also
  moved to the org per branch context)

Docs / config
- README badges, install instructions, brew tap, Docker image, source
  build path, sponsor link (sponsor link kept as personal @xarmian)
- Subtitle: "Project management for developers and AI agents." →
  "Collaborate with your AI agents." (README, manifests, web layout
  meta, .goreleaser homebrew description)
- CONTRIBUTING.md, SECURITY.md, skills/INSTALL.md
- .goreleaser.yaml: homebrew_casks owner, GHCR image, release github
  owner, cosign cert-identity regex, comments
- .github/workflows/release.yml: tap/release comments
- deploy/k8s/deployment.yaml: container image
- docs/deployment.md: clone URL
- web/static/{site.webmanifest,manifest.json}: description
- web/src/routes/+layout.svelte: meta description + og:description

Brew tap path is PerpetualSoftware/tap/pad (CamelCase, matches
GitHub user case). GHCR image is ghcr.io/perpetualsoftware/pad
(lowercased per GHCR's URL normalization). CODEOWNERS @xarmian and
FUNDING.yml github: xarmian intentionally retained — those are the
personal maintainer / sponsor account, separate from the org repo.

Verification: go build ./..., go test ./... (all pkgs pass), web
build, and make install all clean (TASK-844, TASK-845).
2026-04-28 12:26:39 -04:00

148 lines
5.9 KiB
Go

package models
import "testing"
func TestExtractItemCodeContextFromGitHubPRFields(t *testing.T) {
context := ExtractItemCodeContext(`{"github_pr":{"number":42,"url":"https://github.com/PerpetualSoftware/pad/pull/42","title":"Add branch metadata","state":"OPEN","branch":"feat/task-123-branch-pr-metadata","repo":"PerpetualSoftware/pad","updated_at":"2026-04-02T14:00:00Z"}}`)
if context == nil {
t.Fatal("expected code context")
}
if context.Provider != "github" {
t.Fatalf("expected provider github, got %q", context.Provider)
}
if context.Branch != "feat/task-123-branch-pr-metadata" {
t.Fatalf("expected branch metadata, got %q", context.Branch)
}
if context.Repo != "PerpetualSoftware/pad" {
t.Fatalf("expected repo PerpetualSoftware/pad, got %q", context.Repo)
}
if context.PullRequest == nil {
t.Fatal("expected pull request metadata")
}
if context.PullRequest.Number != 42 {
t.Fatalf("expected PR #42, got #%d", context.PullRequest.Number)
}
}
func TestExtractItemCodeContextReturnsNilForUnrelatedFields(t *testing.T) {
if got := ExtractItemCodeContext(`{"status":"open"}`); got != nil {
t.Fatal("expected nil code context for unrelated fields")
}
}
func TestExtractItemConventionMetadataFromStructuredFields(t *testing.T) {
metadata := ExtractItemConventionMetadata(`{"status":"active","convention":{"category":"build","trigger":"on-pr-create","surfaces":["backend","docs"],"enforcement":"must","commands":["go test ./...","make install"]}}`)
if metadata == nil {
t.Fatal("expected convention metadata")
}
if metadata.Category != "build" {
t.Fatalf("expected category build, got %q", metadata.Category)
}
if metadata.Trigger != "on-pr-create" {
t.Fatalf("expected trigger on-pr-create, got %q", metadata.Trigger)
}
if metadata.Enforcement != "must" {
t.Fatalf("expected enforcement must, got %q", metadata.Enforcement)
}
if len(metadata.Surfaces) != 2 || metadata.Surfaces[0] != "backend" || metadata.Surfaces[1] != "docs" {
t.Fatalf("expected surfaces backend/docs, got %#v", metadata.Surfaces)
}
if len(metadata.Commands) != 2 || metadata.Commands[0] != "go test ./..." {
t.Fatalf("expected commands to be preserved, got %#v", metadata.Commands)
}
}
func TestExtractItemConventionMetadataFallsBackToLegacyFields(t *testing.T) {
metadata := ExtractItemConventionMetadata(`{"status":"active","category":"quality","trigger":"on-commit","scope":"all","priority":"should"}`)
if metadata == nil {
t.Fatal("expected convention metadata")
}
if metadata.Category != "quality" || metadata.Trigger != "on-commit" || metadata.Enforcement != "should" {
t.Fatalf("unexpected convention metadata %#v", metadata)
}
if len(metadata.Surfaces) != 1 || metadata.Surfaces[0] != "all" {
t.Fatalf("expected legacy scope to map to surfaces, got %#v", metadata.Surfaces)
}
}
func TestExtractItemImplementationNotes(t *testing.T) {
notes := ExtractItemImplementationNotes(`{"status":"open","implementation_notes":[{"id":"note-1","summary":"Used SSE refresh","details":"Reload phase tasks on visibility resume","created_at":"2026-04-02T15:00:00Z","created_by":"agent"}]}`)
if len(notes) != 1 {
t.Fatalf("expected 1 implementation note, got %d", len(notes))
}
if notes[0].Summary != "Used SSE refresh" {
t.Fatalf("expected note summary, got %q", notes[0].Summary)
}
if notes[0].CreatedBy != "agent" {
t.Fatalf("expected created_by agent, got %q", notes[0].CreatedBy)
}
}
func TestExtractItemDecisionLog(t *testing.T) {
log := ExtractItemDecisionLog(`{"status":"open","decision_log":[{"id":"decision-1","decision":"Use explicit setup state","rationale":"needs_setup was too ambiguous","created_at":"2026-04-02T15:05:00Z","created_by":"user"}]}`)
if len(log) != 1 {
t.Fatalf("expected 1 decision entry, got %d", len(log))
}
if log[0].Decision != "Use explicit setup state" {
t.Fatalf("expected decision text, got %q", log[0].Decision)
}
if log[0].Rationale == "" {
t.Fatal("expected rationale to be preserved")
}
}
func TestAppendImplementationNotePreservesExistingFields(t *testing.T) {
fields, err := AppendImplementationNote(`{"status":"open","priority":"high"}`, ItemImplementationNote{
ID: "note-1",
Summary: "Added setup guidance",
Details: "Updated the login screen copy",
})
if err != nil {
t.Fatalf("AppendImplementationNote error: %v", err)
}
if got := ExtractItemImplementationNotes(fields); len(got) != 1 {
t.Fatalf("expected 1 note after append, got %#v", got)
}
if ExtractItemCodeContext(fields) != nil {
t.Fatal("did not expect code context")
}
}
func TestAppendDecisionLogEntryPreservesExistingNotes(t *testing.T) {
fields, err := AppendDecisionLogEntry(`{"implementation_notes":[{"id":"note-1","summary":"Keep docker as external-managed"}]}`, ItemDecisionLogEntry{
ID: "decision-1",
Decision: "Keep docker in external-managed mode",
})
if err != nil {
t.Fatalf("AppendDecisionLogEntry error: %v", err)
}
if got := ExtractItemImplementationNotes(fields); len(got) != 1 {
t.Fatalf("expected implementation notes to be preserved, got %#v", got)
}
if got := ExtractItemDecisionLog(fields); len(got) != 1 {
t.Fatalf("expected 1 decision log entry, got %#v", got)
}
}
func TestApplyItemConventionMetadataPreservesStatusAndWritesAliases(t *testing.T) {
fields, err := ApplyItemConventionMetadata(`{"status":"active"}`, &ItemConventionMetadata{
Category: "pm",
Trigger: "on-task-start",
Surfaces: []string{"all"},
Enforcement: "must",
Commands: []string{"pad item update <ref> --status in-progress"},
})
if err != nil {
t.Fatalf("ApplyItemConventionMetadata error: %v", err)
}
metadata := ExtractItemConventionMetadata(fields)
if metadata == nil || metadata.Category != "pm" || metadata.Trigger != "on-task-start" {
t.Fatalf("expected structured convention metadata, got %#v", metadata)
}
if got := ExtractItemImplementationNotes(fields); got != nil {
t.Fatalf("did not expect implementation notes, got %#v", got)
}
}