Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FABB-73: Add error handling for Jira getIssue 404 #194

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/infrastructure/jira/jira-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import {
import type { AttachedDesignUrlV2IssuePropertyValue } from './jira-service';
import {
ConfigurationState,
IssueNotFoundJiraServiceError,
issuePropertyKeys,
JiraService,
jiraService,
JiraService,
SubmitDesignJiraServiceError,
} from './jira-service';

Expand Down Expand Up @@ -247,6 +248,18 @@ describe('JiraService', () => {
connectInstallation,
);
});

it('should throw an error if issue is not found', async () => {
const connectInstallation = generateConnectInstallation();
const jiraIssue = generateJiraIssue();
jest
.spyOn(jiraClient, 'getIssue')
.mockRejectedValue(new NotFoundHttpClientError());

await expect(() =>
jiraService.getIssue(jiraIssue.key, connectInstallation),
).rejects.toThrow(IssueNotFoundJiraServiceError);
});
});

describe('setAttachedDesignUrlInIssuePropertiesIfMissing', () => {
Expand Down
11 changes: 10 additions & 1 deletion src/infrastructure/jira/jira-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ export class JiraService {
issueIdOrKey: string,
connectInstallation: ConnectInstallation,
): Promise<JiraIssue> => {
return await jiraClient.getIssue(issueIdOrKey, connectInstallation);
try {
return await jiraClient.getIssue(issueIdOrKey, connectInstallation);
} catch (err) {
if (err instanceof NotFoundHttpClientError) {
throw new IssueNotFoundJiraServiceError('Issue not found');
}
throw err;
}
};

saveDesignUrlInIssueProperties = async (
Expand Down Expand Up @@ -427,6 +434,8 @@ export class JiraService {

export const jiraService = new JiraService();

export class IssueNotFoundJiraServiceError extends CauseAwareError {}

export class SubmitDesignJiraServiceError extends CauseAwareError {
designId?: string;
rejectionErrors?: { readonly message: string }[];
Expand Down
26 changes: 25 additions & 1 deletion src/usecases/associate-design-use-case.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { associateDesignUseCase } from './associate-design-use-case';
import {
FigmaDesignNotFoundUseCaseResultError,
InvalidInputUseCaseResultError,
JiraIssueNotFoundUseCaseResultError,
} from './errors';
import { generateAssociateDesignUseCaseParams } from './testing';

Expand All @@ -19,7 +20,10 @@ import {
generateJiraIssue,
} from '../domain/entities/testing';
import { figmaService } from '../infrastructure/figma';
import { jiraService } from '../infrastructure/jira';
import {
IssueNotFoundJiraServiceError,
jiraService,
} from '../infrastructure/jira';
import { associatedFigmaDesignRepository } from '../infrastructure/repositories';

describe('associateDesignUseCase', () => {
Expand Down Expand Up @@ -133,6 +137,26 @@ describe('associateDesignUseCase', () => {
).rejects.toBeInstanceOf(InvalidInputUseCaseResultError);
});

it('should throw JiraIssueNotFoundUseCaseResultError when the issue is not found', async () => {
const connectInstallation = generateConnectInstallation();
const issue = generateJiraIssue();
const fileKey = generateFigmaFileKey();
const params: AssociateDesignUseCaseParams =
generateAssociateDesignUseCaseParams({
designUrl: generateFigmaDesignUrl({ fileKey }),
issueId: issue.id,
connectInstallation,
});
jest.spyOn(figmaService, 'getDesignOrParent').mockResolvedValue(null);
jest
.spyOn(jiraService, 'getIssue')
.mockRejectedValue(new IssueNotFoundJiraServiceError());

await expect(() =>
associateDesignUseCase.execute(params),
).rejects.toBeInstanceOf(JiraIssueNotFoundUseCaseResultError);
});

it('should not save associated Figma design when design submission fails', async () => {
const connectInstallation = generateConnectInstallation();
const issue = generateJiraIssue();
Expand Down
9 changes: 8 additions & 1 deletion src/usecases/associate-design-use-case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
FigmaDesignNotFoundUseCaseResultError,
ForbiddenByFigmaUseCaseResultError,
InvalidInputUseCaseResultError,
JiraIssueNotFoundUseCaseResultError,
} from './errors';
import type { AtlassianEntity } from './types';

Expand All @@ -15,7 +16,10 @@ import {
figmaService,
UnauthorizedFigmaServiceError,
} from '../infrastructure/figma';
import { jiraService } from '../infrastructure/jira';
import {
IssueNotFoundJiraServiceError,
jiraService,
} from '../infrastructure/jira';
import { associatedFigmaDesignRepository } from '../infrastructure/repositories';

export type AssociateDesignUseCaseParams = {
Expand Down Expand Up @@ -99,6 +103,9 @@ export const associateDesignUseCase = {
if (e instanceof UnauthorizedFigmaServiceError) {
throw new ForbiddenByFigmaUseCaseResultError(e);
}
if (e instanceof IssueNotFoundJiraServiceError) {
throw new JiraIssueNotFoundUseCaseResultError(e);
}

throw e;
}
Expand Down
47 changes: 45 additions & 2 deletions src/usecases/backfill-design-use-case.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { BackfillDesignUseCaseParams } from './backfill-design-use-case';
import { backfillDesignUseCase } from './backfill-design-use-case';
import { InvalidInputUseCaseResultError } from './errors';
import {
InvalidInputUseCaseResultError,
JiraIssueNotFoundUseCaseResultError,
} from './errors';
import { generateBackfillDesignUseCaseParams } from './testing';

import type { AssociatedFigmaDesign } from '../domain/entities';
Expand All @@ -17,7 +20,10 @@ import {
} from '../domain/entities/testing';
import { figmaService } from '../infrastructure/figma';
import { figmaBackfillService } from '../infrastructure/figma/figma-backfill-service';
import { jiraService } from '../infrastructure/jira';
import {
IssueNotFoundJiraServiceError,
jiraService,
} from '../infrastructure/jira';
import { associatedFigmaDesignRepository } from '../infrastructure/repositories';

describe('backfillDesignUseCase', () => {
Expand Down Expand Up @@ -215,4 +221,41 @@ describe('backfillDesignUseCase', () => {
);
expect(associatedFigmaDesignRepository.upsert).not.toHaveBeenCalled();
});

it('should throw JiraIssueNotFoundUseCaseResultError when the issue is not found', async () => {
const connectInstallation = generateConnectInstallation();
const issue = generateJiraIssue();
const fileKey = generateFigmaFileKey();
const designId = new FigmaDesignIdentifier(fileKey);
const atlassianDesign = generateAtlassianDesign({
id: designId.toAtlassianDesignId(),
});

const params: BackfillDesignUseCaseParams =
generateBackfillDesignUseCaseParams({
designUrl: generateFigmaDesignUrl({ fileKey }),
issueId: issue.id,
connectInstallation,
});

jest
.spyOn(figmaService, 'getDesignOrParent')
.mockResolvedValue(atlassianDesign);
jest.spyOn(jiraService, 'getIssue').mockResolvedValue(issue);
jest
.spyOn(jiraService, 'submitDesign')
.mockRejectedValue(new IssueNotFoundJiraServiceError());
jest
.spyOn(jiraService, 'saveDesignUrlInIssueProperties')
.mockResolvedValue();
jest
.spyOn(figmaService, 'tryCreateDevResourceForJiraIssue')
.mockResolvedValue();
jest.spyOn(associatedFigmaDesignRepository, 'upsert');

await expect(() => backfillDesignUseCase.execute(params)).rejects.toThrow(
JiraIssueNotFoundUseCaseResultError,
);
expect(associatedFigmaDesignRepository.upsert).not.toHaveBeenCalled();
});
});
9 changes: 8 additions & 1 deletion src/usecases/backfill-design-use-case.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ForbiddenByFigmaUseCaseResultError,
InvalidInputUseCaseResultError,
JiraIssueNotFoundUseCaseResultError,
} from './errors';
import type { AtlassianEntity } from './types';

Expand All @@ -15,7 +16,10 @@ import {
UnauthorizedFigmaServiceError,
} from '../infrastructure/figma';
import { figmaBackfillService } from '../infrastructure/figma/figma-backfill-service';
import { jiraService } from '../infrastructure/jira';
import {
IssueNotFoundJiraServiceError,
jiraService,
} from '../infrastructure/jira';
import { associatedFigmaDesignRepository } from '../infrastructure/repositories';

export type BackfillDesignUseCaseParams = {
Expand Down Expand Up @@ -105,6 +109,9 @@ export const backfillDesignUseCase = {
if (e instanceof UnauthorizedFigmaServiceError) {
throw new ForbiddenByFigmaUseCaseResultError(e);
}
if (e instanceof IssueNotFoundJiraServiceError) {
throw new JiraIssueNotFoundUseCaseResultError(e);
}

throw e;
}
Expand Down
6 changes: 6 additions & 0 deletions src/usecases/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export class ForbiddenByFigmaUseCaseResultError extends UseCaseResultError {
}
}

export class JiraIssueNotFoundUseCaseResultError extends UseCaseResultError {
constructor(cause?: Error) {
super('Issue is not found.', cause);
}
}

export class FigmaDesignNotFoundUseCaseResultError extends UseCaseResultError {
constructor(cause?: Error) {
super('Design is not found.', cause);
Expand Down
6 changes: 5 additions & 1 deletion src/web/middleware/error-handler-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
FigmaDesignNotFoundUseCaseResultError,
ForbiddenByFigmaUseCaseResultError,
InvalidInputUseCaseResultError,
JiraIssueNotFoundUseCaseResultError,
PaidFigmaPlanRequiredUseCaseResultError,
UseCaseResultError,
} from '../../usecases';
Expand Down Expand Up @@ -49,7 +50,10 @@ export const errorHandlerMiddleware = (
return next();
}

if (err instanceof FigmaDesignNotFoundUseCaseResultError) {
if (
err instanceof FigmaDesignNotFoundUseCaseResultError ||
err instanceof JiraIssueNotFoundUseCaseResultError
) {
res.status(HttpStatusCode.NotFound).send({ message: err.message });
return next();
}
Expand Down