From fb0cbbd2ef4a609ab1523f95dd033c1d06234dfe Mon Sep 17 00:00:00 2001 From: Felix Lisczyk <5102728+FelixLisczyk@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:41:32 +0200 Subject: [PATCH 1/2] Add dueDate, estimate, and delegate to list/search issue queries SearchIssuesEnhanced (powering `issues list` and `linear search`) omitted dueDate, estimate, and delegate from its GraphQL selection set, so those fields were always null/omitted even though GetIssue's identical selection returns them fine. Fixed the same gap in ListAssignedIssues and ListAllIssues, which have the identical pattern but aren't yet wired into the CLI, and extended IssueWithDetails/convertIssueDetails so ListAllIssues's fix survives its conversion to core.Issue. Added HTTP-mock regression tests for all three query methods, sharing a new CapturingTransport test helper, to catch this class of bug (a field silently missing from a query) going forward. --- internal/service/issue.go | 3 + internal/service/issue_convert_test.go | 80 ++++++++ pkg/linear/core/types.go | 3 + pkg/linear/issues/client.go | 29 ++- pkg/linear/issues/client_test.go | 258 +++++++++++++++++++++++++ pkg/linear/testutil/mock_transport.go | 57 ++++++ 6 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 internal/service/issue_convert_test.go diff --git a/internal/service/issue.go b/internal/service/issue.go index 2a1537d..689f277 100644 --- a/internal/service/issue.go +++ b/internal/service/issue.go @@ -406,7 +406,10 @@ func convertIssueDetails(details []core.IssueWithDetails) []core.Issue { Name string `json:"name"` }{ID: d.State.ID, Name: d.State.Name}, Priority: &priority, + Estimate: d.Estimate, + DueDate: d.DueDate, Assignee: d.Assignee, + Delegate: d.Delegate, CreatedAt: d.CreatedAt, UpdatedAt: d.UpdatedAt, } diff --git a/internal/service/issue_convert_test.go b/internal/service/issue_convert_test.go new file mode 100644 index 0000000..0561c30 --- /dev/null +++ b/internal/service/issue_convert_test.go @@ -0,0 +1,80 @@ +package service + +import ( + "testing" + + "github.com/joa23/linear-cli/pkg/linear/core" +) + +// TestConvertIssueDetails_CarriesDueDateEstimateDelegate is a regression test +// for TL-563: ListAllIssues's IssueWithDetails carried DueDate/Estimate/Delegate, +// but convertIssueDetails (used by ListAssignedWithPagination) dropped them +// when converting to core.Issue, which would have silently undone the fix one +// layer up. The fixture below starts with all three fields already populated +// (not both-nil, which would pass trivially) to catch a half-applied fix. +func TestConvertIssueDetails_CarriesDueDateEstimateDelegate(t *testing.T) { + estimate := 3.5 + dueDate := "2026-09-01" + details := []core.IssueWithDetails{ + { + ID: "issue-1", + Identifier: "TL-1", + Title: "Issue with due date, estimate, and delegate", + Priority: 2, + Estimate: &estimate, + DueDate: &dueDate, + Delegate: &core.User{ID: "delegate-1", Name: "Bot", Email: "bot@example.com"}, + CreatedAt: "2026-01-01T00:00:00Z", + UpdatedAt: "2026-01-02T00:00:00Z", + }, + } + + issues := convertIssueDetails(details) + + if len(issues) != 1 { + t.Fatalf("expected 1 issue, got %d", len(issues)) + } + got := issues[0] + + if got.Estimate == nil || *got.Estimate != estimate { + t.Errorf("Estimate = %v, want %v", got.Estimate, estimate) + } + if got.DueDate == nil || *got.DueDate != dueDate { + t.Errorf("DueDate = %v, want %v", got.DueDate, dueDate) + } + if got.Delegate == nil || got.Delegate.ID != "delegate-1" { + t.Errorf("Delegate = %v, want ID \"delegate-1\"", got.Delegate) + } +} + +// TestConvertIssueDetails_OmitsUnsetDueDateEstimateDelegate confirms issues +// without these fields set continue to convert to nil, not false positives. +func TestConvertIssueDetails_OmitsUnsetDueDateEstimateDelegate(t *testing.T) { + details := []core.IssueWithDetails{ + { + ID: "issue-2", + Identifier: "TL-2", + Title: "Issue without due date, estimate, or delegate", + Priority: 1, + CreatedAt: "2026-01-01T00:00:00Z", + UpdatedAt: "2026-01-02T00:00:00Z", + }, + } + + issues := convertIssueDetails(details) + + if len(issues) != 1 { + t.Fatalf("expected 1 issue, got %d", len(issues)) + } + got := issues[0] + + if got.Estimate != nil { + t.Errorf("Estimate = %v, want nil", got.Estimate) + } + if got.DueDate != nil { + t.Errorf("DueDate = %v, want nil", got.DueDate) + } + if got.Delegate != nil { + t.Errorf("Delegate = %v, want nil", got.Delegate) + } +} diff --git a/pkg/linear/core/types.go b/pkg/linear/core/types.go index 37b6f7f..3f3b99d 100644 --- a/pkg/linear/core/types.go +++ b/pkg/linear/core/types.go @@ -686,10 +686,13 @@ type IssueWithDetails struct { Title string `json:"title"` Description string `json:"description"` Priority int `json:"priority"` + Estimate *float64 `json:"estimate,omitempty"` + DueDate *string `json:"dueDate,omitempty"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` State WorkflowState `json:"state"` Assignee *User `json:"assignee,omitempty"` + Delegate *User `json:"delegate,omitempty"` Labels []Label `json:"labels"` Project *Project `json:"project,omitempty"` Team Team `json:"team"` diff --git a/pkg/linear/issues/client.go b/pkg/linear/issues/client.go index 1631f88..449dcfc 100644 --- a/pkg/linear/issues/client.go +++ b/pkg/linear/issues/client.go @@ -804,6 +804,13 @@ func (ic *Client) ListAssignedIssues(limit int) ([]core.Issue, error) { name email } + delegate { + id + name + email + } + estimate + dueDate createdAt updatedAt url @@ -831,7 +838,7 @@ func (ic *Client) ListAssignedIssues(limit int) ([]core.Issue, error) { } } ` - + // Filter for issues assigned to the current user // Why: The "me" identifier is Linear's way of referring to the // authenticated user without needing to know their specific ID. @@ -907,6 +914,11 @@ func (ic *Client) SearchIssuesEnhanced(filters *core.IssueSearchFilters) (*core. name email } + delegate { + id + name + email + } labels { nodes { id @@ -919,6 +931,8 @@ func (ic *Client) SearchIssuesEnhanced(filters *core.IssueSearchFilters) (*core. } } priority + estimate + dueDate createdAt updatedAt url @@ -1887,6 +1901,8 @@ func (ic *Client) ListAllIssues(filter *core.IssueFilter) (*core.ListAllIssuesRe title description priority + estimate + dueDate createdAt updatedAt state { @@ -1912,6 +1928,11 @@ func (ic *Client) ListAllIssues(filter *core.IssueFilter) (*core.ListAllIssuesRe createdAt isMe } + delegate { + id + name + email + } labels { nodes { id @@ -1972,10 +1993,13 @@ func (ic *Client) ListAllIssues(filter *core.IssueFilter) (*core.ListAllIssuesRe Title string `json:"title"` Description string `json:"description"` Priority int `json:"priority"` + Estimate *float64 `json:"estimate"` + DueDate *string `json:"dueDate"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` State core.WorkflowState `json:"state"` Assignee *core.User `json:"assignee"` + Delegate *core.User `json:"delegate"` Labels struct { Nodes []core.Label `json:"nodes"` } `json:"labels"` @@ -2010,10 +2034,13 @@ func (ic *Client) ListAllIssues(filter *core.IssueFilter) (*core.ListAllIssuesRe Title: node.Title, Description: node.Description, Priority: node.Priority, + Estimate: node.Estimate, + DueDate: node.DueDate, CreatedAt: node.CreatedAt, UpdatedAt: node.UpdatedAt, State: node.State, Assignee: node.Assignee, + Delegate: node.Delegate, Labels: node.Labels.Nodes, Project: node.Project, Team: node.Team, diff --git a/pkg/linear/issues/client_test.go b/pkg/linear/issues/client_test.go index 14f63d0..715d27b 100644 --- a/pkg/linear/issues/client_test.go +++ b/pkg/linear/issues/client_test.go @@ -2,9 +2,12 @@ package issues import ( "encoding/json" + "net/http" + "strings" "testing" "github.com/joa23/linear-cli/pkg/linear/core" + "github.com/joa23/linear-cli/pkg/linear/testutil" ) func TestBuildUpdateInput_DelegateID(t *testing.T) { @@ -354,3 +357,258 @@ func TestLabelParent_Deserialization(t *testing.T) { t.Errorf("Labels.Nodes[1].Parent = %+v, want nil", second.Parent) } } + +// newCapturingTestClient wires an issues.Client to a testutil.CapturingTransport, +// so tests can inspect the outgoing GraphQL request while feeding back a canned +// response body. +func newCapturingTestClient(responseBody interface{}) (*Client, *testutil.CapturingTransport) { + transport := testutil.NewCapturingTransport(http.StatusOK, responseBody) + base := core.NewBaseClient("test-token") + base.SetHTTPClient(&http.Client{Transport: transport}) + return NewClient(base), transport +} + +// assertQueryRequestsFields fails the test if any of the given field names is +// not present as a token in the outgoing GraphQL selection set. This is a +// lightweight substring check (not a GraphQL parser), used as regression +// protection against a field silently going missing from a query's selection +// set — the exact bug class TL-563 fixed for SearchIssuesEnhanced, +// ListAssignedIssues, and ListAllIssues. +func assertQueryRequestsFields(t *testing.T, query string, fields []string) { + t.Helper() + for _, field := range fields { + if !strings.Contains(query, field) { + t.Errorf("query does not request field %q:\n%s", field, query) + } + } +} + +// requiredIssueFields is the set of fields TL-563 added to SearchIssuesEnhanced, +// ListAssignedIssues, and ListAllIssues, matching what GetIssue already requested. +var requiredIssueFields = []string{"dueDate", "estimate", "delegate"} + +// TestSearchIssuesEnhanced_RequestsAndDecodesDueDateEstimateDelegate is a +// regression test for TL-563: SearchIssuesEnhanced's query silently omitted +// dueDate, estimate, and delegate, so "issues list"/"search" never returned +// them even though GetIssue's identical selection worked fine. +func TestSearchIssuesEnhanced_RequestsAndDecodesDueDateEstimateDelegate(t *testing.T) { + responseBody := testutil.NewGraphQLDataResponse(testutil.IssuesData{ + Issues: testutil.IssuesNodes{ + Nodes: []interface{}{ + // Populated: has a due date, estimate, and delegate. + map[string]interface{}{ + "id": "issue-1", + "identifier": "TL-1", + "title": "Issue with due date, estimate, and delegate", + "state": map[string]interface{}{"id": "state-1", "name": "Todo"}, + "priority": 2, + "estimate": 3.5, + "dueDate": "2026-09-01", + "delegate": map[string]interface{}{"id": "delegate-1", "name": "Bot", "email": "bot@example.com"}, + "createdAt": "2026-01-01T00:00:00Z", + "updatedAt": "2026-01-02T00:00:00Z", + "url": "https://linear.app/test/issue/TL-1", + }, + // Unpopulated: none of the three fields set. + map[string]interface{}{ + "id": "issue-2", + "identifier": "TL-2", + "title": "Issue without due date, estimate, or delegate", + "state": map[string]interface{}{"id": "state-1", "name": "Todo"}, + "createdAt": "2026-01-01T00:00:00Z", + "updatedAt": "2026-01-02T00:00:00Z", + "url": "https://linear.app/test/issue/TL-2", + }, + }, + PageInfo: &testutil.PageInfo{HasNextPage: false}, + }, + }) + + client, transport := newCapturingTestClient(responseBody) + + result, err := client.SearchIssuesEnhanced(&core.IssueSearchFilters{TeamID: "team-1"}) + if err != nil { + t.Fatalf("SearchIssuesEnhanced returned error: %v", err) + } + + query, err := transport.CapturedQuery() + if err != nil { + t.Fatalf("failed to extract captured query: %v", err) + } + assertQueryRequestsFields(t, query, requiredIssueFields) + + if len(result.Issues) != 2 { + t.Fatalf("expected 2 issues, got %d", len(result.Issues)) + } + + populated := result.Issues[0] + if populated.Estimate == nil || *populated.Estimate != 3.5 { + t.Errorf("Issues[0].Estimate = %v, want 3.5", populated.Estimate) + } + if populated.DueDate == nil || *populated.DueDate != "2026-09-01" { + t.Errorf("Issues[0].DueDate = %v, want \"2026-09-01\"", populated.DueDate) + } + if populated.Delegate == nil || populated.Delegate.ID != "delegate-1" { + t.Errorf("Issues[0].Delegate = %v, want ID \"delegate-1\"", populated.Delegate) + } + + unpopulated := result.Issues[1] + if unpopulated.Estimate != nil { + t.Errorf("Issues[1].Estimate = %v, want nil", unpopulated.Estimate) + } + if unpopulated.DueDate != nil { + t.Errorf("Issues[1].DueDate = %v, want nil", unpopulated.DueDate) + } + if unpopulated.Delegate != nil { + t.Errorf("Issues[1].Delegate = %v, want nil", unpopulated.Delegate) + } +} + +// TestListAssignedIssues_RequestsAndDecodesDueDateEstimateDelegate is a +// regression test for TL-563: ListAssignedIssues's query, like +// SearchIssuesEnhanced's, silently omitted dueDate, estimate, and delegate. +func TestListAssignedIssues_RequestsAndDecodesDueDateEstimateDelegate(t *testing.T) { + responseBody := testutil.NewGraphQLDataResponse(testutil.IssuesData{ + Issues: testutil.IssuesNodes{ + Nodes: []interface{}{ + map[string]interface{}{ + "id": "issue-1", + "identifier": "TL-1", + "title": "Issue with due date, estimate, and delegate", + "state": map[string]interface{}{"id": "state-1", "name": "Todo"}, + "estimate": 2.0, + "dueDate": "2026-09-01", + "delegate": map[string]interface{}{"id": "delegate-1", "name": "Bot", "email": "bot@example.com"}, + "createdAt": "2026-01-01T00:00:00Z", + "updatedAt": "2026-01-02T00:00:00Z", + "url": "https://linear.app/test/issue/TL-1", + }, + map[string]interface{}{ + "id": "issue-2", + "identifier": "TL-2", + "title": "Issue without due date, estimate, or delegate", + "state": map[string]interface{}{"id": "state-1", "name": "Todo"}, + "createdAt": "2026-01-01T00:00:00Z", + "updatedAt": "2026-01-02T00:00:00Z", + "url": "https://linear.app/test/issue/TL-2", + }, + }, + }, + }) + + client, transport := newCapturingTestClient(responseBody) + + issues, err := client.ListAssignedIssues(10) + if err != nil { + t.Fatalf("ListAssignedIssues returned error: %v", err) + } + + query, err := transport.CapturedQuery() + if err != nil { + t.Fatalf("failed to extract captured query: %v", err) + } + assertQueryRequestsFields(t, query, requiredIssueFields) + + if len(issues) != 2 { + t.Fatalf("expected 2 issues, got %d", len(issues)) + } + + populated := issues[0] + if populated.Estimate == nil || *populated.Estimate != 2.0 { + t.Errorf("issues[0].Estimate = %v, want 2.0", populated.Estimate) + } + if populated.DueDate == nil || *populated.DueDate != "2026-09-01" { + t.Errorf("issues[0].DueDate = %v, want \"2026-09-01\"", populated.DueDate) + } + if populated.Delegate == nil || populated.Delegate.ID != "delegate-1" { + t.Errorf("issues[0].Delegate = %v, want ID \"delegate-1\"", populated.Delegate) + } + + unpopulated := issues[1] + if unpopulated.Estimate != nil { + t.Errorf("issues[1].Estimate = %v, want nil", unpopulated.Estimate) + } + if unpopulated.DueDate != nil { + t.Errorf("issues[1].DueDate = %v, want nil", unpopulated.DueDate) + } + if unpopulated.Delegate != nil { + t.Errorf("issues[1].Delegate = %v, want nil", unpopulated.Delegate) + } +} + +// TestListAllIssues_RequestsAndDecodesDueDateEstimateDelegate is a regression +// test for TL-563: ListAllIssues's query, like SearchIssuesEnhanced's and +// ListAssignedIssues's, silently omitted dueDate, estimate, and delegate. +// Unlike those two, ListAllIssues decodes into a local anonymous struct and +// maps into core.IssueWithDetails, so this also exercises that mapping. +func TestListAllIssues_RequestsAndDecodesDueDateEstimateDelegate(t *testing.T) { + responseBody := testutil.NewGraphQLDataResponse(testutil.IssuesData{ + Issues: testutil.IssuesNodes{ + Nodes: []interface{}{ + map[string]interface{}{ + "id": "issue-1", + "identifier": "TL-1", + "title": "Issue with due date, estimate, and delegate", + "priority": 2, + "estimate": 1.5, + "dueDate": "2026-09-01", + "createdAt": "2026-01-01T00:00:00Z", + "updatedAt": "2026-01-02T00:00:00Z", + "state": map[string]interface{}{"id": "state-1", "name": "Todo", "type": "unstarted"}, + "delegate": map[string]interface{}{"id": "delegate-1", "name": "Bot", "email": "bot@example.com"}, + "team": map[string]interface{}{"id": "team-1", "name": "Team", "key": "TL"}, + }, + map[string]interface{}{ + "id": "issue-2", + "identifier": "TL-2", + "title": "Issue without due date, estimate, or delegate", + "priority": 1, + "createdAt": "2026-01-01T00:00:00Z", + "updatedAt": "2026-01-02T00:00:00Z", + "state": map[string]interface{}{"id": "state-1", "name": "Todo", "type": "unstarted"}, + "team": map[string]interface{}{"id": "team-1", "name": "Team", "key": "TL"}, + }, + }, + PageInfo: &testutil.PageInfo{HasNextPage: false}, + }, + }) + + client, transport := newCapturingTestClient(responseBody) + + result, err := client.ListAllIssues(&core.IssueFilter{First: 10}) + if err != nil { + t.Fatalf("ListAllIssues returned error: %v", err) + } + + query, err := transport.CapturedQuery() + if err != nil { + t.Fatalf("failed to extract captured query: %v", err) + } + assertQueryRequestsFields(t, query, requiredIssueFields) + + if len(result.Issues) != 2 { + t.Fatalf("expected 2 issues, got %d", len(result.Issues)) + } + + populated := result.Issues[0] + if populated.Estimate == nil || *populated.Estimate != 1.5 { + t.Errorf("Issues[0].Estimate = %v, want 1.5", populated.Estimate) + } + if populated.DueDate == nil || *populated.DueDate != "2026-09-01" { + t.Errorf("Issues[0].DueDate = %v, want \"2026-09-01\"", populated.DueDate) + } + if populated.Delegate == nil || populated.Delegate.ID != "delegate-1" { + t.Errorf("Issues[0].Delegate = %v, want ID \"delegate-1\"", populated.Delegate) + } + + unpopulated := result.Issues[1] + if unpopulated.Estimate != nil { + t.Errorf("Issues[1].Estimate = %v, want nil", unpopulated.Estimate) + } + if unpopulated.DueDate != nil { + t.Errorf("Issues[1].DueDate = %v, want nil", unpopulated.DueDate) + } + if unpopulated.Delegate != nil { + t.Errorf("Issues[1].Delegate = %v, want nil", unpopulated.Delegate) + } +} diff --git a/pkg/linear/testutil/mock_transport.go b/pkg/linear/testutil/mock_transport.go index f1af976..e162539 100644 --- a/pkg/linear/testutil/mock_transport.go +++ b/pkg/linear/testutil/mock_transport.go @@ -43,6 +43,63 @@ func NewSuccessTransport(body interface{}) *MockTransport { return NewMockTransport(http.StatusOK, body) } +// CapturingTransport implements http.RoundTripper for testing HTTP clients. +// It records the outgoing request body (so tests can assert on the GraphQL +// query text that was actually sent) while returning a canned response. +type CapturingTransport struct { + Response *http.Response + + // CapturedBody holds the raw bytes of the last request body seen by + // RoundTrip. Populated after the request under test has been made. + CapturedBody []byte +} + +// RoundTrip implements the http.RoundTripper interface. +func (c *CapturingTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if req.Body != nil { + body, err := io.ReadAll(req.Body) + if err != nil { + return nil, err + } + req.Body.Close() + c.CapturedBody = body + } + return c.Response, nil +} + +// NewCapturingTransport creates a capturing transport with a JSON response body. +func NewCapturingTransport(statusCode int, body interface{}) *CapturingTransport { + var bodyBytes []byte + switch v := body.(type) { + case string: + bodyBytes = []byte(v) + case []byte: + bodyBytes = v + default: + bodyBytes, _ = json.Marshal(body) + } + + return &CapturingTransport{ + Response: &http.Response{ + StatusCode: statusCode, + Body: io.NopCloser(bytes.NewBuffer(bodyBytes)), + Header: make(http.Header), + }, + } +} + +// CapturedQuery extracts the "query" field from the captured GraphQL request +// body, so tests can assert on the selection set that was actually sent. +func (c *CapturingTransport) CapturedQuery() (string, error) { + var payload struct { + Query string `json:"query"` + } + if err := json.Unmarshal(c.CapturedBody, &payload); err != nil { + return "", err + } + return payload.Query, nil +} + // GraphQLResponse is a helper for building GraphQL response bodies. type GraphQLResponse struct { Data interface{} `json:"data,omitempty"` From c24cba0c7ba305df400c08284cf6baa43acb773d Mon Sep 17 00:00:00 2001 From: Felix Lisczyk <5102728+FelixLisczyk@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:55:14 +0200 Subject: [PATCH 2/2] Polish query field order and fixture symmetry for TL-563 Addresses minor cosmetic discrepancies flagged during plan verification of the dueDate/estimate/delegate query fix: reorder the new fields in SearchIssuesEnhanced and ListAllIssues to mirror GetIssue's exact selection shape, add omitempty consistently to ListAllIssues's pointer decode fields, and add empty labels fixtures to the unpopulated test nodes for symmetry with their sibling tests. No behavioral change. --- pkg/linear/issues/client.go | 22 +++++++++++----------- pkg/linear/issues/client_test.go | 2 ++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/linear/issues/client.go b/pkg/linear/issues/client.go index 449dcfc..72abe3f 100644 --- a/pkg/linear/issues/client.go +++ b/pkg/linear/issues/client.go @@ -919,6 +919,9 @@ func (ic *Client) SearchIssuesEnhanced(filters *core.IssueSearchFilters) (*core. name email } + priority + estimate + dueDate labels { nodes { id @@ -930,9 +933,6 @@ func (ic *Client) SearchIssuesEnhanced(filters *core.IssueSearchFilters) (*core. } } } - priority - estimate - dueDate createdAt updatedAt url @@ -1900,9 +1900,6 @@ func (ic *Client) ListAllIssues(filter *core.IssueFilter) (*core.ListAllIssuesRe identifier title description - priority - estimate - dueDate createdAt updatedAt state { @@ -1933,6 +1930,9 @@ func (ic *Client) ListAllIssues(filter *core.IssueFilter) (*core.ListAllIssuesRe name email } + priority + estimate + dueDate labels { nodes { id @@ -1993,17 +1993,17 @@ func (ic *Client) ListAllIssues(filter *core.IssueFilter) (*core.ListAllIssuesRe Title string `json:"title"` Description string `json:"description"` Priority int `json:"priority"` - Estimate *float64 `json:"estimate"` - DueDate *string `json:"dueDate"` + Estimate *float64 `json:"estimate,omitempty"` + DueDate *string `json:"dueDate,omitempty"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` State core.WorkflowState `json:"state"` - Assignee *core.User `json:"assignee"` - Delegate *core.User `json:"delegate"` + Assignee *core.User `json:"assignee,omitempty"` + Delegate *core.User `json:"delegate,omitempty"` Labels struct { Nodes []core.Label `json:"nodes"` } `json:"labels"` - Project *core.Project `json:"project"` + Project *core.Project `json:"project,omitempty"` Team core.Team `json:"team"` } `json:"nodes"` PageInfo struct { diff --git a/pkg/linear/issues/client_test.go b/pkg/linear/issues/client_test.go index 715d27b..9d4f979 100644 --- a/pkg/linear/issues/client_test.go +++ b/pkg/linear/issues/client_test.go @@ -415,6 +415,7 @@ func TestSearchIssuesEnhanced_RequestsAndDecodesDueDateEstimateDelegate(t *testi "identifier": "TL-2", "title": "Issue without due date, estimate, or delegate", "state": map[string]interface{}{"id": "state-1", "name": "Todo"}, + "labels": map[string]interface{}{"nodes": []interface{}{}}, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-02T00:00:00Z", "url": "https://linear.app/test/issue/TL-2", @@ -566,6 +567,7 @@ func TestListAllIssues_RequestsAndDecodesDueDateEstimateDelegate(t *testing.T) { "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-02T00:00:00Z", "state": map[string]interface{}{"id": "state-1", "name": "Todo", "type": "unstarted"}, + "labels": map[string]interface{}{"nodes": []interface{}{}}, "team": map[string]interface{}{"id": "team-1", "name": "Team", "key": "TL"}, }, },