diff --git a/package.json b/package.json index 33b8887..07293fa 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "test:ui": "vitest --ui", "test:coverage": "vitest run --coverage", "test:commands": "tsx tests/command-coverage.ts", + "prepare": "npm_config_global=false npm install --ignore-scripts && tsc && chmod +x dist/main.js", "prepublishOnly": "npm run build && npm run test && test -x dist/main.js" }, "engines": { diff --git a/src/utils/graphql-issues-service.ts b/src/utils/graphql-issues-service.ts index 6a5e1fe..61baba8 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-null-comment-user.test.ts b/tests/unit/graphql-issues-service-null-comment-user.test.ts new file mode 100644 index 0000000..d12dd49 --- /dev/null +++ b/tests/unit/graphql-issues-service-null-comment-user.test.ts @@ -0,0 +1,92 @@ +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 null comment.user handling in doTransformIssueData. + * + * When a Linear issue has automated/synced comments (e.g. from GitHub + * integration), the `user` field on the comment can be `null`. + * Previously this caused: Cannot read properties of null (reading 'id'). + */ + +function makeIssueResponse(commentOverrides: Record = {}) { + return { + issue: { + id: "issue-1", + identifier: "ENG-100", + title: "Test Issue", + description: null, + branchName: null, + priority: 0, + estimate: null, + state: { id: "state-1", name: "In Progress" }, + assignee: null, + team: { id: "team-1", key: "ENG", name: "Engineering" }, + project: null, + cycle: null, + projectMilestone: null, + labels: { nodes: [] }, + parent: null, + children: { nodes: [] }, + comments: { + nodes: [ + { + id: "comment-1", + body: "Automated sync comment", + user: null, + createdAt: "2025-01-01T00:00:00Z", + updatedAt: "2025-01-01T00:00:00Z", + ...commentOverrides, + }, + ], + }, + createdAt: "2025-01-01T00:00:00Z", + updatedAt: "2025-01-01T00:00:00Z", + }, + }; +} + +describe("GraphQLIssuesService - Null Comment User", () => { + let mockGraphQLService: { rawRequest: ReturnType }; + let service: GraphQLIssuesService; + + beforeEach(() => { + mockGraphQLService = { + rawRequest: vi.fn(), + }; + service = new GraphQLIssuesService( + mockGraphQLService as unknown as GraphQLService, + ); + }); + + it("should handle comment with null user without crashing", async () => { + mockGraphQLService.rawRequest.mockResolvedValue(makeIssueResponse()); + + const result = await service.getIssueById( + "550e8400-e29b-41d4-a716-446655440000", + ); + + expect(result.comments).toHaveLength(1); + expect(result.comments![0].user).toBeUndefined(); + expect(result.comments![0].body).toBe("Automated sync comment"); + }); + + it("should preserve user when present on comment", async () => { + mockGraphQLService.rawRequest.mockResolvedValue( + makeIssueResponse({ + user: { id: "user-1", name: "Alice" }, + }), + ); + + const result = await service.getIssueById( + "550e8400-e29b-41d4-a716-446655440000", + ); + + expect(result.comments).toHaveLength(1); + expect(result.comments![0].user).toEqual({ + id: "user-1", + name: "Alice", + }); + }); +});