669 lines
19 KiB
Go
669 lines
19 KiB
Go
package forgejo
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var fixedTime = time.Date(2025, 1, 15, 10, 0, 0, 0, time.UTC)
|
|
|
|
func TestCreateIssue_Success(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Verify request method and path
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("expected POST, got %s", r.Method)
|
|
}
|
|
expectedPath := "/api/v1/repos/testowner/testrepo/issues"
|
|
if r.URL.Path != expectedPath {
|
|
t.Errorf("expected path %s, got %s", expectedPath, r.URL.Path)
|
|
}
|
|
|
|
// Verify auth header
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader != "token test-token" {
|
|
t.Errorf("expected Authorization header %q, got %q", "token test-token", authHeader)
|
|
}
|
|
|
|
// Verify content type
|
|
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
|
|
t.Errorf("expected Content-Type application/json, got %s", ct)
|
|
}
|
|
|
|
// Decode request body
|
|
var req CreateIssueRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Fatalf("failed to decode request body: %v", err)
|
|
}
|
|
if req.Title != "Test Issue" {
|
|
t.Errorf("expected title %q, got %q", "Test Issue", req.Title)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(Issue{
|
|
Number: 7,
|
|
Title: "Test Issue",
|
|
State: "open",
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "test-token")
|
|
issue, err := client.CreateIssue("testowner", "testrepo", CreateIssueRequest{
|
|
Title: "Test Issue",
|
|
Body: "This is a test issue body.",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got: %v", err)
|
|
}
|
|
|
|
if issue.Number != 7 {
|
|
t.Errorf("expected issue number 7, got %d", issue.Number)
|
|
}
|
|
if issue.Title != "Test Issue" {
|
|
t.Errorf("expected issue title %q, got %q", "Test Issue", issue.Title)
|
|
}
|
|
if issue.State != "open" {
|
|
t.Errorf("expected issue state %q, got %q", "open", issue.State)
|
|
}
|
|
}
|
|
|
|
func TestCreateIssue_ServerError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("internal server error"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "test-token")
|
|
_, err := client.CreateIssue("testowner", "testrepo", CreateIssueRequest{
|
|
Title: "Test Issue",
|
|
Body: "Body",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for 500 response, got nil")
|
|
}
|
|
}
|
|
|
|
func TestCreateIssue_NotFound(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
w.Write([]byte("not found"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "test-token")
|
|
_, err := client.CreateIssue("testowner", "testrepo", CreateIssueRequest{
|
|
Title: "Test Issue",
|
|
Body: "Body",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for 404 response, got nil")
|
|
}
|
|
}
|
|
|
|
func TestCreateComment_Success(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("expected POST, got %s", r.Method)
|
|
}
|
|
expectedPath := "/api/v1/repos/owner/repo/issues/5/comments"
|
|
if r.URL.Path != expectedPath {
|
|
t.Errorf("expected path %s, got %s", expectedPath, r.URL.Path)
|
|
}
|
|
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader != "token my-api-token" {
|
|
t.Errorf("expected Authorization header %q, got %q", "token my-api-token", authHeader)
|
|
}
|
|
|
|
var req CreateCommentRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Fatalf("failed to decode request body: %v", err)
|
|
}
|
|
if req.Body != "A comment" {
|
|
t.Errorf("expected body %q, got %q", "A comment", req.Body)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(Comment{
|
|
ID: 99,
|
|
Body: "A comment",
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "my-api-token")
|
|
comment, err := client.CreateComment("owner", "repo", 5, CreateCommentRequest{
|
|
Body: "A comment",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got: %v", err)
|
|
}
|
|
|
|
if comment.ID != 99 {
|
|
t.Errorf("expected comment ID 99, got %d", comment.ID)
|
|
}
|
|
if comment.Body != "A comment" {
|
|
t.Errorf("expected comment body %q, got %q", "A comment", comment.Body)
|
|
}
|
|
}
|
|
|
|
func TestCreateComment_ServerError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("error"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "test-token")
|
|
_, err := client.CreateComment("owner", "repo", 1, CreateCommentRequest{
|
|
Body: "A comment",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for 500 response, got nil")
|
|
}
|
|
}
|
|
|
|
// --- StripCommentFooter Tests ---
|
|
|
|
func TestStripCommentFooter_SubmittedByEmail(t *testing.T) {
|
|
body, email := StripCommentFooter("Description\n\n---\n*Submitted by: user@example.com*")
|
|
if body != "Description" {
|
|
t.Errorf("expected body %q, got %q", "Description", body)
|
|
}
|
|
if email != "user@example.com" {
|
|
t.Errorf("expected email %q, got %q", "user@example.com", email)
|
|
}
|
|
}
|
|
|
|
func TestStripCommentFooter_CustomerCommentByEmail(t *testing.T) {
|
|
body, email := StripCommentFooter("Reply\n\n---\n*Customer comment by: alice@corp.com*")
|
|
if body != "Reply" {
|
|
t.Errorf("expected body %q, got %q", "Reply", body)
|
|
}
|
|
if email != "alice@corp.com" {
|
|
t.Errorf("expected email %q, got %q", "alice@corp.com", email)
|
|
}
|
|
}
|
|
|
|
func TestStripCommentFooter_NoFooter(t *testing.T) {
|
|
body, email := StripCommentFooter("Plain comment")
|
|
if body != "Plain comment" {
|
|
t.Errorf("expected body %q, got %q", "Plain comment", body)
|
|
}
|
|
if email != "" {
|
|
t.Errorf("expected empty email, got %q", email)
|
|
}
|
|
}
|
|
|
|
func TestStripCommentFooter_EmptyBody(t *testing.T) {
|
|
body, email := StripCommentFooter("")
|
|
if body != "" {
|
|
t.Errorf("expected empty body, got %q", body)
|
|
}
|
|
if email != "" {
|
|
t.Errorf("expected empty email, got %q", email)
|
|
}
|
|
}
|
|
|
|
func TestStripCommentFooter_FooterWithoutColon(t *testing.T) {
|
|
body, email := StripCommentFooter("Body\n\n---\n*NoColonHere*")
|
|
if body != "Body" {
|
|
t.Errorf("expected body %q, got %q", "Body", body)
|
|
}
|
|
if email != "" {
|
|
t.Errorf("expected empty email, got %q", email)
|
|
}
|
|
}
|
|
|
|
func TestStripCommentFooter_MultipleFooters(t *testing.T) {
|
|
input := "First\n\n---\n*Submitted by: first@example.com*\n\nMiddle\n\n---\n*Submitted by: second@example.com*"
|
|
body, email := StripCommentFooter(input)
|
|
expectedBody := "First\n\n---\n*Submitted by: first@example.com*\n\nMiddle"
|
|
if body != expectedBody {
|
|
t.Errorf("expected body %q, got %q", expectedBody, body)
|
|
}
|
|
if email != "second@example.com" {
|
|
t.Errorf("expected email %q, got %q", "second@example.com", email)
|
|
}
|
|
}
|
|
|
|
func TestStripCommentFooter_SubmittedByNameAndEmail(t *testing.T) {
|
|
body, attribution := StripCommentFooter("Description\n\n---\n*Submitted by: John Doe <user@example.com>*")
|
|
if body != "Description" {
|
|
t.Errorf("expected body %q, got %q", "Description", body)
|
|
}
|
|
if attribution != "John Doe <user@example.com>" {
|
|
t.Errorf("expected attribution %q, got %q", "John Doe <user@example.com>", attribution)
|
|
}
|
|
}
|
|
|
|
func TestStripCommentFooter_CustomerCommentByNameAndEmail(t *testing.T) {
|
|
body, attribution := StripCommentFooter("Reply\n\n---\n*Customer comment by: Alice Smith <alice@corp.com>*")
|
|
if body != "Reply" {
|
|
t.Errorf("expected body %q, got %q", "Reply", body)
|
|
}
|
|
if attribution != "Alice Smith <alice@corp.com>" {
|
|
t.Errorf("expected attribution %q, got %q", "Alice Smith <alice@corp.com>", attribution)
|
|
}
|
|
}
|
|
|
|
func TestStripCommentFooter_MarkdownHorizontalRule(t *testing.T) {
|
|
input := "Text\n\n---\n\nMore text"
|
|
body, email := StripCommentFooter(input)
|
|
if body != input {
|
|
t.Errorf("expected body unchanged %q, got %q", input, body)
|
|
}
|
|
if email != "" {
|
|
t.Errorf("expected empty email, got %q", email)
|
|
}
|
|
}
|
|
|
|
// --- BuildTimelineViews Tests: Comment events ---
|
|
|
|
func TestBuildTimelineViews_TeamComment(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 501,
|
|
Type: "comment",
|
|
Body: "Looks good, merging now.",
|
|
User: APIUser{Login: "alicedev", FullName: "Alice Dev"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
v := views[0]
|
|
if v.Type != "comment" {
|
|
t.Errorf("expected type %q, got %q", "comment", v.Type)
|
|
}
|
|
if !v.IsTeam {
|
|
t.Errorf("expected IsTeam=true")
|
|
}
|
|
if v.CommentID != 501 {
|
|
t.Errorf("expected CommentID 501, got %d", v.CommentID)
|
|
}
|
|
if v.AuthorName != "Alice Dev" {
|
|
t.Errorf("expected AuthorName %q, got %q", "Alice Dev", v.AuthorName)
|
|
}
|
|
if v.Body != "Looks good, merging now." {
|
|
t.Errorf("expected body %q, got %q", "Looks good, merging now.", v.Body)
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_CustomerCommentWithFooter(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 602,
|
|
Type: "comment",
|
|
Body: "Please help\n\n---\n*Submitted by: customer@example.com*",
|
|
User: APIUser{Login: "ticket-bot", FullName: "Ticket Bot"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
v := views[0]
|
|
if v.IsTeam {
|
|
t.Errorf("expected IsTeam=false")
|
|
}
|
|
if v.CommentID != 602 {
|
|
t.Errorf("expected CommentID 602, got %d", v.CommentID)
|
|
}
|
|
if v.AuthorName != "customer@example.com" {
|
|
t.Errorf("expected AuthorName %q, got %q", "customer@example.com", v.AuthorName)
|
|
}
|
|
if v.Body != "Please help" {
|
|
t.Errorf("expected body %q, got %q", "Please help", v.Body)
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_CustomerCommentBotNoFooter(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 603,
|
|
Type: "comment",
|
|
Body: "I need assistance",
|
|
User: APIUser{Login: "ticket-bot", FullName: "Ticket Bot"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
v := views[0]
|
|
if v.IsTeam {
|
|
t.Errorf("expected IsTeam=false")
|
|
}
|
|
if v.AuthorName != "Customer" {
|
|
t.Errorf("expected AuthorName %q, got %q", "Customer", v.AuthorName)
|
|
}
|
|
if v.CommentID != 603 {
|
|
t.Errorf("expected CommentID 603, got %d", v.CommentID)
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_CommentWithAttachments(t *testing.T) {
|
|
attachments := []Attachment{
|
|
{ID: 1, Name: "screenshot.png", Size: 1024},
|
|
{ID: 2, Name: "log.txt", Size: 512},
|
|
}
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 700,
|
|
Type: "comment",
|
|
Body: "See attached",
|
|
User: APIUser{Login: "dev1", FullName: "Developer One"},
|
|
Assets: attachments,
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
v := views[0]
|
|
if v.CommentID != 700 {
|
|
t.Errorf("expected CommentID 700, got %d", v.CommentID)
|
|
}
|
|
if len(v.Attachments) != 2 {
|
|
t.Fatalf("expected 2 attachments, got %d", len(v.Attachments))
|
|
}
|
|
if v.Attachments[0].Name != "screenshot.png" {
|
|
t.Errorf("expected first attachment %q, got %q", "screenshot.png", v.Attachments[0].Name)
|
|
}
|
|
if v.Attachments[1].Name != "log.txt" {
|
|
t.Errorf("expected second attachment %q, got %q", "log.txt", v.Attachments[1].Name)
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_CommentIDAlwaysSet(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 9999999,
|
|
Type: "comment",
|
|
Body: "Large ID test",
|
|
User: APIUser{Login: "dev", FullName: "Dev"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
if views[0].CommentID != 9999999 {
|
|
t.Errorf("expected CommentID 9999999, got %d", views[0].CommentID)
|
|
}
|
|
}
|
|
|
|
// --- BuildTimelineViews Tests: Status change events ---
|
|
|
|
func TestBuildTimelineViews_CloseEvent(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 10,
|
|
Type: "close",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
v := views[0]
|
|
if v.Type != "status_change" {
|
|
t.Errorf("expected type %q, got %q", "status_change", v.Type)
|
|
}
|
|
if v.EventText != "closed this ticket" {
|
|
t.Errorf("expected EventText %q, got %q", "closed this ticket", v.EventText)
|
|
}
|
|
if !v.IsTeam {
|
|
t.Errorf("expected IsTeam=true")
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_ReopenEvent(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 11,
|
|
Type: "reopen",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
if views[0].EventText != "reopened this ticket" {
|
|
t.Errorf("expected EventText %q, got %q", "reopened this ticket", views[0].EventText)
|
|
}
|
|
}
|
|
|
|
// --- BuildTimelineViews Tests: Admin-gated events ---
|
|
|
|
func TestBuildTimelineViews_LabelEvent_AdminVisible(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 20,
|
|
Type: "label",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
Label: &Label{ID: 5, Name: "priority/high"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", true)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
v := views[0]
|
|
if v.Type != "label" {
|
|
t.Errorf("expected type %q, got %q", "label", v.Type)
|
|
}
|
|
if v.EventText != "added label priority/high" {
|
|
t.Errorf("expected EventText %q, got %q", "added label priority/high", v.EventText)
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_LabelEvent_NonAdminHidden(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 21,
|
|
Type: "label",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
Label: &Label{ID: 5, Name: "priority/high"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 0 {
|
|
t.Errorf("expected 0 views for non-admin label event, got %d", len(views))
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_LabelEvent_NilLabel(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 22,
|
|
Type: "label",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
Label: nil,
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", true)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
if views[0].EventText != "added label " {
|
|
t.Errorf("expected EventText %q, got %q", "added label ", views[0].EventText)
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_AssigneeEvent_AdminVisible(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 30,
|
|
Type: "assignees",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
Assignee: &APIUser{Login: "dev2", FullName: "Developer Two"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", true)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
v := views[0]
|
|
if v.Type != "assignment" {
|
|
t.Errorf("expected type %q, got %q", "assignment", v.Type)
|
|
}
|
|
if v.EventText != "assigned Developer Two" {
|
|
t.Errorf("expected EventText %q, got %q", "assigned Developer Two", v.EventText)
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_AssigneeEvent_NonAdminHidden(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 31,
|
|
Type: "assignees",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
Assignee: &APIUser{Login: "dev2", FullName: "Developer Two"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(views) != 0 {
|
|
t.Errorf("expected 0 views for non-admin assignees event, got %d", len(views))
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_AssigneeEvent_NilAssignee(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 32,
|
|
Type: "assignees",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
Assignee: nil,
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", true)
|
|
if len(views) != 1 {
|
|
t.Fatalf("expected 1 view, got %d", len(views))
|
|
}
|
|
if views[0].EventText != "assigned " {
|
|
t.Errorf("expected EventText %q, got %q", "assigned ", views[0].EventText)
|
|
}
|
|
}
|
|
|
|
// --- BuildTimelineViews Tests: Edge cases & integration ---
|
|
|
|
func TestBuildTimelineViews_UnknownEventType(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 40,
|
|
Type: "milestone",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
}
|
|
views := BuildTimelineViews(events, "ticket-bot", true)
|
|
if len(views) != 0 {
|
|
t.Errorf("expected 0 views for unknown event type, got %d", len(views))
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_EmptyEvents(t *testing.T) {
|
|
views := BuildTimelineViews([]TimelineEvent{}, "ticket-bot", false)
|
|
if len(views) != 0 {
|
|
t.Errorf("expected 0 views for empty events, got %d", len(views))
|
|
}
|
|
}
|
|
|
|
func TestBuildTimelineViews_MixedEvents(t *testing.T) {
|
|
events := []TimelineEvent{
|
|
{
|
|
ID: 100,
|
|
Type: "comment",
|
|
Body: "Team comment",
|
|
User: APIUser{Login: "dev1", FullName: "Developer One"},
|
|
CreatedAt: fixedTime,
|
|
},
|
|
{
|
|
ID: 101,
|
|
Type: "label",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
Label: &Label{ID: 5, Name: "bug"},
|
|
CreatedAt: fixedTime.Add(1 * time.Minute),
|
|
},
|
|
{
|
|
ID: 102,
|
|
Type: "comment",
|
|
Body: "Customer reply\n\n---\n*Submitted by: cust@test.com*",
|
|
User: APIUser{Login: "ticket-bot", FullName: "Ticket Bot"},
|
|
CreatedAt: fixedTime.Add(2 * time.Minute),
|
|
},
|
|
{
|
|
ID: 103,
|
|
Type: "close",
|
|
User: APIUser{Login: "dev1", FullName: "Developer One"},
|
|
CreatedAt: fixedTime.Add(3 * time.Minute),
|
|
},
|
|
{
|
|
ID: 104,
|
|
Type: "assignees",
|
|
User: APIUser{Login: "admin", FullName: "Admin User"},
|
|
Assignee: &APIUser{Login: "dev1", FullName: "Developer One"},
|
|
CreatedAt: fixedTime.Add(4 * time.Minute),
|
|
},
|
|
}
|
|
|
|
// Non-admin: label and assignees should be filtered
|
|
nonAdminViews := BuildTimelineViews(events, "ticket-bot", false)
|
|
if len(nonAdminViews) != 3 {
|
|
t.Fatalf("non-admin: expected 3 views, got %d", len(nonAdminViews))
|
|
}
|
|
if nonAdminViews[0].Type != "comment" || nonAdminViews[0].CommentID != 100 {
|
|
t.Errorf("non-admin view[0]: expected comment with ID 100, got type=%q id=%d", nonAdminViews[0].Type, nonAdminViews[0].CommentID)
|
|
}
|
|
if nonAdminViews[1].Type != "comment" || nonAdminViews[1].CommentID != 102 {
|
|
t.Errorf("non-admin view[1]: expected comment with ID 102, got type=%q id=%d", nonAdminViews[1].Type, nonAdminViews[1].CommentID)
|
|
}
|
|
if nonAdminViews[2].Type != "status_change" {
|
|
t.Errorf("non-admin view[2]: expected status_change, got %q", nonAdminViews[2].Type)
|
|
}
|
|
|
|
// Admin: all 5 events should produce views
|
|
adminViews := BuildTimelineViews(events, "ticket-bot", true)
|
|
if len(adminViews) != 5 {
|
|
t.Fatalf("admin: expected 5 views, got %d", len(adminViews))
|
|
}
|
|
// Verify order preserved
|
|
expectedTypes := []string{"comment", "label", "comment", "status_change", "assignment"}
|
|
for i, et := range expectedTypes {
|
|
if adminViews[i].Type != et {
|
|
t.Errorf("admin view[%d]: expected type %q, got %q", i, et, adminViews[i].Type)
|
|
}
|
|
}
|
|
// Verify comment IDs
|
|
if adminViews[0].CommentID != 100 {
|
|
t.Errorf("admin view[0]: expected CommentID 100, got %d", adminViews[0].CommentID)
|
|
}
|
|
if adminViews[2].CommentID != 102 {
|
|
t.Errorf("admin view[2]: expected CommentID 102, got %d", adminViews[2].CommentID)
|
|
}
|
|
}
|