From cba64aedff877f639b2a522606bfe09b82096e56 Mon Sep 17 00:00:00 2001 From: Richard Bolkey Date: Fri, 16 Jan 2026 12:15:44 -0600 Subject: [PATCH] fix: Handle null user in comment transformation System-generated comments (e.g., Jira sync notifications) have user: null in the Linear API response. Apply the same null-checking pattern used for other nullable relationships (assignee, project, cycle). Co-Authored-By: Claude Opus 4.5 --- src/utils/graphql-issues-service.ts | 10 +- src/utils/linear-types.d.ts | 4 +- .../graphql-issues-service-comments.test.ts | 179 ++++++++++++++++++ 3 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 tests/unit/graphql-issues-service-comments.test.ts diff --git a/src/utils/graphql-issues-service.ts b/src/utils/graphql-issues-service.ts index 6a5e1fe..fb9ece3 100644 --- a/src/utils/graphql-issues-service.ts +++ b/src/utils/graphql-issues-service.ts @@ -881,10 +881,12 @@ export class GraphQLIssuesService { id: comment.id, body: comment.body, embeds: extractEmbeds(comment.body), - user: { - id: comment.user.id, - name: comment.user.name, - }, + user: comment.user + ? { + id: comment.user.id, + name: comment.user.name, + } + : undefined, createdAt: comment.createdAt instanceof Date ? comment.createdAt.toISOString() : (comment.createdAt diff --git a/src/utils/linear-types.d.ts b/src/utils/linear-types.d.ts index ec24d51..29c775d 100644 --- a/src/utils/linear-types.d.ts +++ b/src/utils/linear-types.d.ts @@ -60,7 +60,7 @@ export interface LinearIssue { url: string; expiresAt: string; }>; - user: { + user?: { id: string; name: string; }; @@ -153,7 +153,7 @@ export interface CreateCommentArgs { export interface LinearComment { id: string; body: string; - user: { + user?: { id: string; name: string; }; diff --git a/tests/unit/graphql-issues-service-comments.test.ts b/tests/unit/graphql-issues-service-comments.test.ts new file mode 100644 index 0000000..3dcfd33 --- /dev/null +++ b/tests/unit/graphql-issues-service-comments.test.ts @@ -0,0 +1,179 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { GraphQLIssuesService } from "../../src/utils/graphql-issues-service.js"; +import type { GraphQLService } from "../../src/utils/graphql-service.js"; + +/** + * Unit tests for comment transformation in GraphQLIssuesService + * + * These tests verify the fix for null user handling: + * - System-generated comments (e.g., Jira sync) have user: null + * - The transformation should handle null users without crashing + */ + +describe("GraphQLIssuesService - Comment Transformation", () => { + let mockGraphQLService: { + rawRequest: ReturnType; + }; + let service: GraphQLIssuesService; + + beforeEach(() => { + mockGraphQLService = { + rawRequest: vi.fn(), + }; + service = new GraphQLIssuesService( + mockGraphQLService as unknown as GraphQLService, + ); + }); + + describe("getIssueById - comment user handling", () => { + it("should handle comment with null user", async () => { + mockGraphQLService.rawRequest.mockResolvedValue({ + issues: { + nodes: [ + { + id: "issue-1", + identifier: "TEST-1", + title: "Test Issue", + description: null, + branchName: "test-branch", + priority: 0, + estimate: null, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + state: { id: "state-1", name: "Todo" }, + assignee: null, + team: { id: "team-1", key: "TEST", name: "Test Team" }, + project: null, + labels: { nodes: [] }, + cycle: null, + projectMilestone: null, + parent: null, + children: { nodes: [] }, + comments: { + nodes: [ + { + id: "comment-1", + body: "System generated comment", + user: null, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + }, + ], + }, + }, + ], + }, + }); + + const result = await service.getIssueById("TEST-1"); + + expect(result.comments).toHaveLength(1); + expect(result.comments![0].user).toBeUndefined(); + expect(result.comments![0].body).toBe("System generated comment"); + }); + + it("should handle comment with valid user", async () => { + mockGraphQLService.rawRequest.mockResolvedValue({ + issues: { + nodes: [ + { + id: "issue-1", + identifier: "TEST-1", + title: "Test Issue", + description: null, + branchName: "test-branch", + priority: 0, + estimate: null, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + state: { id: "state-1", name: "Todo" }, + assignee: null, + team: { id: "team-1", key: "TEST", name: "Test Team" }, + project: null, + labels: { nodes: [] }, + cycle: null, + projectMilestone: null, + parent: null, + children: { nodes: [] }, + comments: { + nodes: [ + { + id: "comment-1", + body: "User comment", + user: { id: "user-1", name: "Test User" }, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + }, + ], + }, + }, + ], + }, + }); + + const result = await service.getIssueById("TEST-1"); + + expect(result.comments).toHaveLength(1); + expect(result.comments![0].user).toEqual({ + id: "user-1", + name: "Test User", + }); + }); + + it("should handle mix of comments with and without users", async () => { + mockGraphQLService.rawRequest.mockResolvedValue({ + issues: { + nodes: [ + { + id: "issue-1", + identifier: "TEST-1", + title: "Test Issue", + description: null, + branchName: "test-branch", + priority: 0, + estimate: null, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + state: { id: "state-1", name: "Todo" }, + assignee: null, + team: { id: "team-1", key: "TEST", name: "Test Team" }, + project: null, + labels: { nodes: [] }, + cycle: null, + projectMilestone: null, + parent: null, + children: { nodes: [] }, + comments: { + nodes: [ + { + id: "comment-1", + body: "System comment", + user: null, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + }, + { + id: "comment-2", + body: "User comment", + user: { id: "user-1", name: "Test User" }, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + }, + ], + }, + }, + ], + }, + }); + + const result = await service.getIssueById("TEST-1"); + + expect(result.comments).toHaveLength(2); + expect(result.comments![0].user).toBeUndefined(); + expect(result.comments![1].user).toEqual({ + id: "user-1", + name: "Test User", + }); + }); + }); +});