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
cmd/entire/cli
api
- Mtrail_types.go+65/-26
Mtrail_types_test.go+23
trail
Mtrail.go+51
Mtrail_test.go+45
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()))
}
}