Add closed_at timestamp tracking to issues
- Add closed_at field to Issue type with JSON marshaling - Implement closed_at timestamp in SQLite storage layer - Update import/export to handle closed_at field - Add comprehensive tests for closed_at functionality - Maintain backward compatibility with existing databases Amp-Thread-ID: https://ampcode.com/threads/T-f3a7799b-f91e-4432-a690-aae0aed364b3 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -46,6 +46,13 @@ func (i *Issue) Validate() error {
|
||||
if i.EstimatedMinutes != nil && *i.EstimatedMinutes < 0 {
|
||||
return fmt.Errorf("estimated_minutes cannot be negative")
|
||||
}
|
||||
// Enforce closed_at invariant: closed_at should be set if and only if status is closed
|
||||
if i.Status == StatusClosed && i.ClosedAt == nil {
|
||||
return fmt.Errorf("closed issues must have closed_at timestamp")
|
||||
}
|
||||
if i.Status != StatusClosed && i.ClosedAt != nil {
|
||||
return fmt.Errorf("non-closed issues cannot have closed_at timestamp")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,57 @@ func TestIssueValidation(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "closed issue without closed_at",
|
||||
issue: Issue{
|
||||
ID: "test-1",
|
||||
Title: "Test",
|
||||
Status: StatusClosed,
|
||||
Priority: 2,
|
||||
IssueType: TypeFeature,
|
||||
ClosedAt: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "closed issues must have closed_at timestamp",
|
||||
},
|
||||
{
|
||||
name: "open issue with closed_at",
|
||||
issue: Issue{
|
||||
ID: "test-1",
|
||||
Title: "Test",
|
||||
Status: StatusOpen,
|
||||
Priority: 2,
|
||||
IssueType: TypeFeature,
|
||||
ClosedAt: timePtr(time.Now()),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "non-closed issues cannot have closed_at timestamp",
|
||||
},
|
||||
{
|
||||
name: "in_progress issue with closed_at",
|
||||
issue: Issue{
|
||||
ID: "test-1",
|
||||
Title: "Test",
|
||||
Status: StatusInProgress,
|
||||
Priority: 2,
|
||||
IssueType: TypeFeature,
|
||||
ClosedAt: timePtr(time.Now()),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "non-closed issues cannot have closed_at timestamp",
|
||||
},
|
||||
{
|
||||
name: "closed issue with closed_at",
|
||||
issue: Issue{
|
||||
ID: "test-1",
|
||||
Title: "Test",
|
||||
Status: StatusClosed,
|
||||
Priority: 2,
|
||||
IssueType: TypeFeature,
|
||||
ClosedAt: timePtr(time.Now()),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -287,6 +338,10 @@ func intPtr(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
func timePtr(t time.Time) *time.Time {
|
||||
return &t
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || containsMiddle(s, substr)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user