feat(trail): model and wire structs for reviewers, type, priority, approvals · Entire

feat(trail): model and wire structs for reviewers, type, priority, approvals

dcdb247→main·computermode·1w ago·4 files·+184 added/-26 removed

Add trail.Type and trail.Priority enums (mirroring the server's VALID_TRAIL_TYPES / VALID_PRIORITIES), carry type/priority/reviewers on trail.Metadata, add requested_reviewers to TrailResource, extend TrailUpdateRequest with the new metadata fields, and add the approval request/response types.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

Sessions

01KX6M7PRVN5CEJSW99JRFXR60View transcript

Changes

4

20 unmodified lines

// TrailResource represents a single trail from the API. type TrailResource struct { ID string json:"id,omitempty" Number int json:"number,omitempty" URL string json:"url,omitempty" Branch string json:"branch" Base string json:"base" Title string json:"title" Body string json:"body" Status string json:"status" Phase string json:"phase,omitempty" Author *trail.Author json:"author" Assignees []string json:"assignees" Labels []string json:"labels" Priority string json:"priority,omitempty" Type string json:"type,omitempty" Reviewers []trail.Reviewer json:"reviewers,omitempty" CreatedAt time.Time json:"created_at" UpdatedAt time.Time json:"updated_at" MergedAt *time.Time json:"merged_at,omitempty" CommentCount int json:"comment_count,omitempty" UnresolvedCount int json:"unresolved_count,omitempty" CheckpointCount int json:"checkpoint_count,omitempty" CommitsAhead int json:"commits_ahead,omitempty" RequestedReviewers []string json:"requested_reviewers,omitempty" BodyDocument *TrailBodyDocument json:"body_document,omitempty" }

// TrailUpdateRequest represents a request to update a trail. type TrailUpdateRequest struct { Status *string json:"status,omitempty" Title *string json:"title,omitempty" Body *string json:"body,omitempty" Labels *[]string json:"labels,omitempty" Assignees *[]string json:"assignees,omitempty" RequestedReviewers *[]string json:"requested_reviewers,omitempty" Type *string json:"type,omitempty" Priority *string json:"priority,omitempty" }

// TrailApproval is a single approval decision on a trail. type TrailApproval struct { ID string json:"id" Author *trail.Author json:"author" Event string json:"event" // "approved" | "changes_requested" Body string json:"body,omitempty" CommitSHA string json:"commit_sha,omitempty" CreatedAt time.Time json:"created_at" }

// TrailApprovalRequest is the body for POST .../:number/approvals. type TrailApprovalRequest struct { Event string json:"event" Body string json:"body,omitempty" }

// TrailApprovalResponse is the response from POST .../:number/approvals. type TrailApprovalResponse struct { OK bool json:"ok" Approval TrailApproval json:"approval" }

// TrailApprovalsResponse is the response from GET .../:number/approvals. type TrailApprovalsResponse struct { Approvals []TrailApproval json:"approvals" }

Mcmd/entire/cli/api/trail_types.go+65/-26
2 unmodified lines

import ( "encoding/json" "testing"

"github.com/entireio/cli/cmd/entire/cli/trail" )

// TestTrailResourceDecodesServerURL covers the wire-compatibility matrix for the // metadata URL. func TestTrailResourceDecodesServerURL(t *testing.T) { // Your test code here }

func TestToMetadataMapsTypePriorityReviewers(t *testing.T) { t.Parallel() login := "octocat" r := &TrailResource{ Type: "bug", Priority: "high", Reviewers: []trail.Reviewer{{Login: "rev1", Status: trail.ReviewerApproved}}, Author: &trail.Author{ID: "1", Login: &login}, } m := r.ToMetadata() if m.Type != trail.TypeBug {

t.Errorf("Type = %q, want bug", m.Type) } if m.Priority != trail.PriorityHigh {

t.Errorf("Priority = %q, want high", m.Priority) } if len(m.Reviewers) != 1 || m.Reviewers[0].Login != "rev1" {

t.Errorf("Reviewers = %#v, want one rev1", m.Reviewers) } }

Mcmd/entire/cli/api/trail_types_test.go+23
72 unmodified lines
2 unmodified lines
// Pointer fields distinguish "not provided" (nil) from "set to value".
// For slices, *[]string is used so nil means "no change" while &[]string{} means "clear".
type TrailDeleteResponse struct {
    OK bool `json:"ok"`
}
Mcmd/entire/cli/trail/trail.go+51
89 unmodified lines
// Type represents the category of a trail. Mirrors VALID_TRAIL_TYPES server-side.
type Type string

const (
    TypeBug     Type = "bug"
    TypeFeature Type = "feature"
    TypeTask    Type = "task"
)

// ValidTypes returns all valid trail types.
func ValidTypes() []Type { return []Type{TypeBug, TypeFeature, TypeTask} }

// IsValid reports whether t is a recognized trail type.
func (t Type) IsValid() bool {
    for _, vt := range ValidTypes() {
        if t == vt {
            return true
        }
    }
    return false
}
// Priority represents a trail's priority. Mirrors VALID_PRIORITIES server-side.
type Priority string

const (
    PriorityUrgent Priority = "urgent"
    PriorityHigh   Priority = "high"
    PriorityMedium Priority = "medium"
    PriorityLow    Priority = "low"
    PriorityNone   Priority = "none"
)

// ValidPriorities returns all valid priorities in descending urgency order.
func ValidPriorities() []Priority {
    return []Priority{PriorityUrgent, PriorityHigh, PriorityMedium, PriorityLow, PriorityNone}
}

// IsValid reports whether p is a recognized priority.
func (p Priority) IsValid() bool {
    for _, vp := range ValidPriorities() {
        if p == vp {
            return true
        }
    }
    return false
}
17 unmodified lines
// Author identifies the user who created a trail.
// On the wire the whole object may be null when the original author can no
// longer be resolved (e.g. the GitHub user no longer exists), and login may
// be omitted as needed. Note that login must be non-empty when serialized.
type Author struct {
    ID    string  `json:"id"`
    Login *string `json:"login"`
}
72 unmodified lines
func TestType_IsValid(t *testing.T) {
    t.Parallel()
    tests := []struct {
        typ   Type
        valid bool
    }{
        {TypeBug, true}, {TypeFeature, true}, {TypeTask, true},
        {"", false}, {"epic", false},
    }
    for _, tt := range tests {
        t.Run(string(tt.typ), func(t *testing.T) {
            t.Parallel()
            if got := tt.typ.IsValid(); got != tt.valid {
                t.Errorf("Type(%q).IsValid() = %v, want %v", tt.typ, got, tt.valid)
            }
        })
    }
    if len(ValidTypes()) != 3 {
        t.Errorf("ValidTypes() len = %d, want 3", len(ValidTypes()))
    }
}

func TestPriority_IsValid(t *testing.T) {
    t.Parallel()
    tests := []struct {
        p     Priority
        valid bool
    }{
        {PriorityUrgent, true}, {PriorityHigh, true}, {PriorityMedium, true},
        {PriorityLow, true}, {PriorityNone, true},
        {"", false}, {"critical", false},
    }
    for _, tt := range tests {
        t.Run(string(tt.p), func(t *testing.T) {
            t.Parallel()
            if got := tt.p.IsValid(); got != tt.valid {
                t.Errorf("Priority(%q).IsValid() = %v, want %v", tt.p, got, tt.valid)
            }
        })
    }
    if len(ValidPriorities()) != 5 {
        t.Errorf("ValidPriorities() len = %d, want 5", len(ValidPriorities()))
    }
}