Skip to content

Commit 37f8b31

Browse files
committed
refactor: 타입 개선
1 parent 538c57e commit 37f8b31

File tree

2 files changed

+113
-15
lines changed

2 files changed

+113
-15
lines changed

src/controllers/__test__/webhook.controller.test.ts

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'reflect-metadata';
22
import { Request, Response } from 'express';
33
import { WebhookController } from '@/controllers/webhook.controller';
44
import { sendSlackMessage } from '@/modules/slack/slack.notifier';
5-
import { SentryWebhookData } from '@/types';
65

76
// Mock dependencies
87
jest.mock('@/modules/slack/slack.notifier');
@@ -44,15 +43,16 @@ describe('WebhookController', () => {
4443
});
4544

4645
describe('handleSentryWebhook', () => {
47-
const mockSentryData: SentryWebhookData = {
46+
// 실제 동작에 필요한 필수 값만 사용하도록 타입 미적용
47+
const mockSentryData = {
4848
action: 'created',
4949
data: {
5050
issue: {
5151
id: 'test-issue-123',
5252
title: '테스트 오류입니다',
5353
culprit: 'TestFile.js:10',
5454
status: 'unresolved',
55-
count: 5,
55+
count: "5",
5656
userCount: 3,
5757
firstSeen: '2024-01-01T12:00:00.000Z',
5858
permalink: 'https://velog-dashboardv2.sentry.io/issues/test-issue-123/',
@@ -179,6 +179,61 @@ describe('WebhookController', () => {
179179
);
180180
});
181181

182+
it('assigned 액션에 대해 올바른 메시지를 생성해야 한다', async () => {
183+
const assignedData = {
184+
...mockSentryData,
185+
action: 'assigned' as const,
186+
data: {
187+
...mockSentryData.data,
188+
issue: {
189+
...mockSentryData.data.issue,
190+
status: 'unresolved' as const
191+
}
192+
}
193+
};
194+
mockRequest.body = assignedData;
195+
mockSendSlackMessage.mockResolvedValue();
196+
197+
await webhookController.handleSentryWebhook(
198+
mockRequest as Request,
199+
mockResponse as Response,
200+
nextFunction
201+
);
202+
203+
expect(mockSendSlackMessage).toHaveBeenCalledWith(
204+
expect.stringContaining('🚨 *오류가 할당되었습니다*')
205+
);
206+
});
207+
208+
it('archived 액션에 대해 올바른 메시지를 생성해야 한다', async () => {
209+
const archivedData = {
210+
...mockSentryData,
211+
action: 'archived' as const,
212+
data: {
213+
...mockSentryData.data,
214+
issue: {
215+
...mockSentryData.data.issue,
216+
status: 'archived' as const
217+
}
218+
}
219+
};
220+
mockRequest.body = archivedData;
221+
mockSendSlackMessage.mockResolvedValue();
222+
223+
await webhookController.handleSentryWebhook(
224+
mockRequest as Request,
225+
mockResponse as Response,
226+
nextFunction
227+
);
228+
229+
expect(mockSendSlackMessage).toHaveBeenCalledWith(
230+
expect.stringContaining('🚨 *오류가 아카이브되었습니다*')
231+
);
232+
expect(mockSendSlackMessage).toHaveBeenCalledWith(
233+
expect.stringContaining('📦 *제목:*')
234+
);
235+
});
236+
182237
it('알 수 없는 액션에 대해 기본 메시지를 생성해야 한다', async () => {
183238
const unknownActionData = {
184239
...mockSentryData,
@@ -281,15 +336,16 @@ describe('WebhookController', () => {
281336

282337
describe('formatSentryMessage (private method integration test)', () => {
283338
it('완전한 Sentry 데이터로 올바른 형식의 메시지를 생성해야 한다', async () => {
284-
const completeData: SentryWebhookData = {
339+
// 실제 동작에 필요한 필수 값만 사용하도록 타입 미적용
340+
const completeData = {
285341
action: 'created',
286342
data: {
287343
issue: {
288344
id: 'issue-456',
289345
title: 'TypeError: Cannot read property of undefined',
290346
culprit: 'components/UserProfile.tsx:25',
291347
status: 'unresolved',
292-
count: 12,
348+
count: "12",
293349
userCount: 8,
294350
firstSeen: '2024-01-15T14:30:00.000Z',
295351
permalink: 'https://velog-dashboardv2.sentry.io/issues/issue-456/',

src/types/models/Sentry.type.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export type SentryIssueStatus = 'resolved' | 'unresolved' | 'ignored';
2-
3-
export type SentryAction = 'created' | 'resolved' | 'unresolved' | 'ignored';
2+
export type SentryIssueSubStatus = "archived_until_escalating" | "archived_until_condition_met" | "archived_forever" | "escalating" | "ongoing" | "regressed" | "new"
43

54
export interface SentryProject {
65
id: string;
@@ -9,23 +8,66 @@ export interface SentryProject {
98
platform?: string;
109
}
1110

11+
export interface SentryMetadata {
12+
filename: string;
13+
type: string;
14+
value: string;
15+
}
16+
17+
export interface SentryIssueStatusDetails {
18+
inRelease: string;
19+
inNextRelease: boolean;
20+
inCommit: string;
21+
ignoreCount: string;
22+
ignoreWindow: string;
23+
}
24+
1225
export interface SentryIssue {
26+
url?: string;
27+
web_url?: string;
28+
project_url?: string;
1329
id: string;
30+
shareId?: string | null;
31+
shortId: string;
1432
title: string;
15-
culprit?: string;
16-
status?: SentryIssueStatus;
17-
count: number;
33+
culprit: string;
34+
permalink?: string | null;
35+
logger?: string | null;
36+
level: string;
37+
status: SentryIssueStatus;
38+
statusDetails?: SentryIssueStatusDetails;
39+
substatus: SentryIssueSubStatus;
40+
isPublic: boolean;
41+
platform: string;
42+
project: SentryProject;
43+
type: string;
44+
metadata?: SentryMetadata;
45+
numComments: number;
46+
assignedTo?: string | null;
47+
isBookmarked: boolean;
48+
isSubscribed: boolean;
49+
subscriptionDetails?: string | null;
50+
hasSeen: boolean;
51+
annotations: [];
52+
issueType: string;
53+
issueCategory: string;
54+
priority: string;
55+
priorityLockedAt?: string | null;
56+
seerFixabilityScore?: string | null;
57+
seerAutofixLastTriggered?: string | null;
58+
isUnhandled: boolean;
59+
count: string;
1860
userCount: number;
1961
firstSeen: string;
20-
lastSeen?: string;
21-
project?: SentryProject;
22-
permalink?: string;
62+
lastSeen: string;
2363
}
2464

65+
// 무조건 오류 생성(created) 메세지만 받도록 해 두었기 때문에, 무조건 해당 타입의 형태로 넘어옵니다
66+
// 참고: https://docs.sentry.io/organization/integrations/integration-platform/webhooks/issues/
2567
export interface SentryWebhookData {
26-
action: SentryAction;
68+
action: 'created';
69+
installation: { uuid: string };
2770
data: {
2871
issue: SentryIssue;
29-
project?: SentryProject;
3072
};
3173
}

0 commit comments

Comments
 (0)