diff --git a/src/infrastructure/jira/jira-client/jira-client.test.ts b/src/infrastructure/jira/jira-client/jira-client.test.ts index 972794e4..096a7b49 100644 --- a/src/infrastructure/jira/jira-client/jira-client.test.ts +++ b/src/infrastructure/jira/jira-client/jira-client.test.ts @@ -250,4 +250,29 @@ describe('JiraClient', () => { ).rejects.toThrowError(NotFoundOperationError); }); }); + + describe('setAppProperty', () => { + const propertyKey = 'property-key'; + it('should set app property', async () => { + jest.spyOn(axios, 'put').mockResolvedValue({ + status: HttpStatusCode.Ok, + }); + + await jiraClient.setAppProperty( + propertyKey, + 'some value', + connectInstallation, + ); + + const headers = defaultExpectedRequestHeaders() + .headers.setAccept('application/json') + .setContentType('text/plain'); + + expect(axios.put).toHaveBeenCalledWith( + `${connectInstallation.baseUrl}/rest/atlassian-connect/1/addons/${connectInstallation.key}/properties/${propertyKey}`, + 'some value', + { headers }, + ); + }); + }); }); diff --git a/src/infrastructure/jira/jira-client/jira-client.ts b/src/infrastructure/jira/jira-client/jira-client.ts index d1027c9f..04d5d4bd 100644 --- a/src/infrastructure/jira/jira-client/jira-client.ts +++ b/src/infrastructure/jira/jira-client/jira-client.ts @@ -197,6 +197,32 @@ class JiraClient { }); }); + /** + * Sets a connect app property + * + * @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-app-properties/#api-rest-atlassian-connect-1-addons-addonkey-properties-propertykey-put + */ + setAppProperty = async ( + propertyKey: string, + value: unknown, + connectInstallation: ConnectInstallation, + ): Promise => + withAxiosErrorTranslation(async () => { + const url = new URL( + `/rest/atlassian-connect/1/addons/${connectInstallation.key}/properties/${propertyKey}`, + connectInstallation.baseUrl, + ); + + await axios.put(url.toString(), value, { + headers: new AxiosHeaders() + .setAuthorization( + this.buildAuthorizationHeader(url, 'PUT', connectInstallation), + ) + .setAccept('application/json') + .setContentType('text/plain'), + }); + }); + private buildAuthorizationHeader( url: URL, method: Method, diff --git a/src/infrastructure/jira/jira-service.test.ts b/src/infrastructure/jira/jira-service.test.ts index aa15b96d..17278d64 100644 --- a/src/infrastructure/jira/jira-service.test.ts +++ b/src/infrastructure/jira/jira-service.test.ts @@ -13,7 +13,11 @@ import type { AttachedDesignUrlV2IssuePropertyValue, IngestedDesignUrlIssuePropertyValue, } from './jira-service'; -import { jiraService, propertyKeys } from './jira-service'; +import { + ConfigurationState, + issuePropertyKeys, + jiraService, +} from './jira-service'; import { NotFoundOperationError } from '../../common/errors'; import type { @@ -283,7 +287,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toHaveBeenCalledWith( issueId, - propertyKeys.ATTACHED_DESIGN_URL, + issuePropertyKeys.ATTACHED_DESIGN_URL, design.url, connectInstallation, ); @@ -292,7 +296,7 @@ describe('JiraService', () => { it('should not overwrite the issue property if present', async () => { jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL, + key: issuePropertyKeys.ATTACHED_DESIGN_URL, }), ); jest.spyOn(jiraClient, 'setIssueProperty'); @@ -363,7 +367,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toHaveBeenCalledWith( issueId, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, expectedIssuePropertyValue, connectInstallation, ); @@ -379,7 +383,7 @@ describe('JiraService', () => { ]; jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL_V2, + key: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, value: JSON.stringify(attachedDesignPropertyValues), }), ); @@ -403,7 +407,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toHaveBeenCalledWith( issueId, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, expectedIssuePropertyValue, connectInstallation, ); @@ -412,7 +416,7 @@ describe('JiraService', () => { it('should not update the issue property url array if the design has already been linked', async () => { jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL_V2, + key: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, value: JSON.stringify([ { url: design.url, name: design.displayName }, ]), @@ -449,7 +453,7 @@ describe('JiraService', () => { jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL_V2, + key: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, value: JSON.stringify(value), }), ); @@ -463,7 +467,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toBeCalledWith( issueId, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, expectedIssuePropertyValue, connectInstallation, ); @@ -493,7 +497,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toBeCalledWith( issueId, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, expectedIssuePropertyValue, connectInstallation, ); @@ -549,7 +553,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toHaveBeenCalledWith( issueId, - propertyKeys.INGESTED_DESIGN_URLS, + issuePropertyKeys.INGESTED_DESIGN_URLS, expectedIssuePropertyValue, connectInstallation, ); @@ -560,7 +564,7 @@ describe('JiraService', () => { ['https://www.figma.com/file/UcmoEBi9SyNOX3SNhXqShY/test-file']; jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.INGESTED_DESIGN_URLS, + key: issuePropertyKeys.INGESTED_DESIGN_URLS, value: JSON.stringify(ingestedDesignPropertyValues), }), ); @@ -579,7 +583,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toHaveBeenCalledWith( issueId, - propertyKeys.INGESTED_DESIGN_URLS, + issuePropertyKeys.INGESTED_DESIGN_URLS, expectedIssuePropertyValue, connectInstallation, ); @@ -588,7 +592,7 @@ describe('JiraService', () => { it('should not update the ingested designs issue property if the design already exists', async () => { jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.INGESTED_DESIGN_URLS, + key: issuePropertyKeys.INGESTED_DESIGN_URLS, value: JSON.stringify([design.url]), }), ); @@ -610,7 +614,7 @@ describe('JiraService', () => { jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.INGESTED_DESIGN_URLS, + key: issuePropertyKeys.INGESTED_DESIGN_URLS, value: JSON.stringify(value), }), ); @@ -624,7 +628,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toBeCalledWith( issueId, - propertyKeys.INGESTED_DESIGN_URLS, + issuePropertyKeys.INGESTED_DESIGN_URLS, expectedIssuePropertyValue, connectInstallation, ); @@ -647,7 +651,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toBeCalledWith( issueId, - propertyKeys.INGESTED_DESIGN_URLS, + issuePropertyKeys.INGESTED_DESIGN_URLS, expectedIssuePropertyValue, connectInstallation, ); @@ -706,7 +710,7 @@ describe('JiraService', () => { expect(jiraClient.deleteIssueProperty).toHaveBeenCalledWith( issueId, - propertyKeys.ATTACHED_DESIGN_URL, + issuePropertyKeys.ATTACHED_DESIGN_URL, connectInstallation, ); }); @@ -798,7 +802,7 @@ describe('JiraService', () => { jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL_V2, + key: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, value: JSON.stringify(attachedDesignPropertyValues), }), ); @@ -816,7 +820,7 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).toHaveBeenCalledWith( issueId, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, expectedIssuePropertyValue, connectInstallation, ); @@ -832,7 +836,7 @@ describe('JiraService', () => { jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL_V2, + key: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, value: JSON.stringify(attachedDesignPropertyValues), }), ); @@ -858,7 +862,7 @@ describe('JiraService', () => { async (value) => { jest.spyOn(jiraClient, 'getIssueProperty').mockResolvedValue( generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL_V2, + key: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, value: JSON.stringify(value), }), ); @@ -933,4 +937,23 @@ describe('JiraService', () => { expect(jiraClient.setIssueProperty).not.toHaveBeenCalled(); }); }); + + describe('setConfigurationStateInAppProperties', () => { + it('should set configuration state in app properties', async () => { + const configurationState = ConfigurationState.CONFIGURED; + const connectInstallation = generateConnectInstallation(); + jest.spyOn(jiraClient, 'setAppProperty').mockResolvedValue(undefined); + + await jiraService.setConfigurationStateInAppProperties( + configurationState, + connectInstallation, + ); + + expect(jiraClient.setAppProperty).toHaveBeenCalledWith( + 'is-configured', + configurationState.valueOf(), + connectInstallation, + ); + }); + }); }); diff --git a/src/infrastructure/jira/jira-service.ts b/src/infrastructure/jira/jira-service.ts index a96bddf7..da9f085a 100644 --- a/src/infrastructure/jira/jira-service.ts +++ b/src/infrastructure/jira/jira-service.ts @@ -35,12 +35,21 @@ export type AttachedDesignUrlV2IssuePropertyValue = { export type IngestedDesignUrlIssuePropertyValue = string; -export const propertyKeys = { +export const appPropertyKeys = { + CONFIGURATION_STATE: 'is-configured', +}; + +export const issuePropertyKeys = { ATTACHED_DESIGN_URL: 'attached-design-url', ATTACHED_DESIGN_URL_V2: 'attached-design-url-v2', INGESTED_DESIGN_URLS: 'figma-for-jira:ingested-design-urls', }; +export enum ConfigurationState { + CONFIGURED = 'CONFIGURED', + UNCONFIGURED = 'UNCONFIGURED', +} + class JiraService { submitDesign = async ( params: SubmitDesignParams, @@ -122,14 +131,14 @@ class JiraService { try { await jiraClient.getIssueProperty( issueIdOrKey, - propertyKeys.ATTACHED_DESIGN_URL, + issuePropertyKeys.ATTACHED_DESIGN_URL, connectInstallation, ); } catch (error) { if (error instanceof NotFoundOperationError) { await jiraClient.setIssueProperty( issueIdOrKey, - propertyKeys.ATTACHED_DESIGN_URL, + issuePropertyKeys.ATTACHED_DESIGN_URL, url, connectInstallation, ); @@ -156,7 +165,7 @@ class JiraService { const storedValue = await this.getIssuePropertyJsonValue( issueIdOrKey, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, connectInstallation, ATTACHED_DESIGN_URL_V2_VALUE_SCHEMA, ); @@ -170,7 +179,7 @@ class JiraService { return jiraClient.setIssueProperty( issueIdOrKey, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, this.superStringify(newValue), connectInstallation, ); @@ -184,7 +193,7 @@ class JiraService { const storedValue = await this.getIssuePropertyJsonValue( issueIdOrKey, - propertyKeys.INGESTED_DESIGN_URLS, + issuePropertyKeys.INGESTED_DESIGN_URLS, connectInstallation, INGESTED_DESIGN_URL_VALUE_SCHEMA, ); @@ -196,7 +205,7 @@ class JiraService { return jiraClient.setIssueProperty( issueIdOrKey, - propertyKeys.INGESTED_DESIGN_URLS, + issuePropertyKeys.INGESTED_DESIGN_URLS, newValue, connectInstallation, ); @@ -233,7 +242,7 @@ class JiraService { try { const response = await jiraClient.getIssueProperty( issueIdOrKey, - propertyKeys.ATTACHED_DESIGN_URL, + issuePropertyKeys.ATTACHED_DESIGN_URL, connectInstallation, ); @@ -242,7 +251,7 @@ class JiraService { if (storedUrl === design.url) { await jiraClient.deleteIssueProperty( issueIdOrKey, - propertyKeys.ATTACHED_DESIGN_URL, + issuePropertyKeys.ATTACHED_DESIGN_URL, connectInstallation, ); } @@ -267,7 +276,7 @@ class JiraService { try { response = await jiraClient.getIssueProperty( issueIdOrKey, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, connectInstallation, ); } catch (error) { @@ -302,7 +311,7 @@ class JiraService { ) { await jiraClient.setIssueProperty( issueIdOrKey, - propertyKeys.ATTACHED_DESIGN_URL_V2, + issuePropertyKeys.ATTACHED_DESIGN_URL_V2, this.superStringify(newAttachedDesignUrlIssuePropertyValue), connectInstallation, ); @@ -313,6 +322,17 @@ class JiraService { } }; + setConfigurationStateInAppProperties = async ( + configurationState: ConfigurationState, + connectInstallation: ConnectInstallation, + ): Promise => { + return await jiraClient.setAppProperty( + appPropertyKeys.CONFIGURATION_STATE, + configurationState.valueOf(), + connectInstallation, + ); + }; + private async getIssuePropertyJsonValue( issueIdOrKey: string, propertyKey: string, diff --git a/src/usecases/connect-figma-team-use-case.test.ts b/src/usecases/connect-figma-team-use-case.test.ts index 2c1344eb..c671ca50 100644 --- a/src/usecases/connect-figma-team-use-case.test.ts +++ b/src/usecases/connect-figma-team-use-case.test.ts @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid'; import { type FigmaTeam, FigmaTeamAuthStatus } from '../domain/entities'; import { generateConnectInstallation } from '../domain/entities/testing'; import { figmaService } from '../infrastructure/figma'; +import { ConfigurationState, jiraService } from '../infrastructure/jira'; import { figmaTeamRepository } from '../infrastructure/repositories'; import { connectFigmaTeamUseCase } from '.'; @@ -22,6 +23,9 @@ describe('connectFigmaTeamUseCase', () => { jest .spyOn(figmaTeamRepository, 'upsert') .mockResolvedValue({} as FigmaTeam); + jest + .spyOn(jiraService, 'setConfigurationStateInAppProperties') + .mockResolvedValue(undefined); await connectFigmaTeamUseCase.execute( teamId, @@ -49,6 +53,11 @@ describe('connectFigmaTeamUseCase', () => { authStatus: FigmaTeamAuthStatus.OK, connectInstallationId: connectInstallation.id, }); + + expect(jiraService.setConfigurationStateInAppProperties).toBeCalledWith( + ConfigurationState.CONFIGURED, + connectInstallation, + ); }); it('should throw and not create a FigmaTeam record if creating the webhook fails', async () => { diff --git a/src/usecases/connect-figma-team-use-case.ts b/src/usecases/connect-figma-team-use-case.ts index f2673ae9..d58a6ee0 100644 --- a/src/usecases/connect-figma-team-use-case.ts +++ b/src/usecases/connect-figma-team-use-case.ts @@ -5,6 +5,7 @@ import { FigmaTeamAuthStatus, } from '../domain/entities'; import { figmaService } from '../infrastructure/figma'; +import { ConfigurationState, jiraService } from '../infrastructure/jira'; import { figmaTeamRepository } from '../infrastructure/repositories'; export const connectFigmaTeamUseCase = { @@ -35,5 +36,10 @@ export const connectFigmaTeamUseCase = { authStatus: FigmaTeamAuthStatus.OK, connectInstallationId: connectInstallation.id, }); + + await jiraService.setConfigurationStateInAppProperties( + ConfigurationState.CONFIGURED, + connectInstallation, + ); }, }; diff --git a/src/usecases/disconnect-figma-team-use-case.test.ts b/src/usecases/disconnect-figma-team-use-case.test.ts index cb451147..d8bdc22c 100644 --- a/src/usecases/disconnect-figma-team-use-case.test.ts +++ b/src/usecases/disconnect-figma-team-use-case.test.ts @@ -5,9 +5,49 @@ import { generateFigmaTeam, } from '../domain/entities/testing'; import { figmaService } from '../infrastructure/figma'; +import { ConfigurationState, jiraService } from '../infrastructure/jira'; import { figmaTeamRepository } from '../infrastructure/repositories'; describe('disconnectFigmaTeamUseCase', () => { + it('should delete the webhook and FigmaTeam and set unconfigured app state', async () => { + const connectInstallation = generateConnectInstallation(); + const figmaTeam = generateFigmaTeam({ + connectInstallationId: connectInstallation.id, + }); + jest + .spyOn(figmaTeamRepository, 'getByTeamIdAndConnectInstallationId') + .mockResolvedValue(figmaTeam); + jest.spyOn(figmaService, 'tryDeleteWebhook').mockResolvedValue(); + jest.spyOn(figmaTeamRepository, 'delete').mockResolvedValue(figmaTeam); + jest + .spyOn(figmaTeamRepository, 'findManyByConnectInstallationId') + .mockResolvedValue([]); + jest + .spyOn(jiraService, 'setConfigurationStateInAppProperties') + .mockResolvedValue(undefined); + + await disconnectFigmaTeamUseCase.execute( + figmaTeam.teamId, + connectInstallation, + ); + + expect( + figmaTeamRepository.getByTeamIdAndConnectInstallationId, + ).toBeCalledWith(figmaTeam.teamId, figmaTeam.connectInstallationId); + expect(figmaService.tryDeleteWebhook).toBeCalledWith( + figmaTeam.webhookId, + figmaTeam.adminInfo, + ); + expect(figmaTeamRepository.delete).toBeCalledWith(figmaTeam.id); + expect(figmaTeamRepository.findManyByConnectInstallationId).toBeCalledWith( + connectInstallation.id, + ); + expect(jiraService.setConfigurationStateInAppProperties).toBeCalledWith( + ConfigurationState.UNCONFIGURED, + connectInstallation, + ); + }); + it('should delete the webhook and FigmaTeam', async () => { const connectInstallation = generateConnectInstallation(); const figmaTeam = generateFigmaTeam({ @@ -18,6 +58,13 @@ describe('disconnectFigmaTeamUseCase', () => { .mockResolvedValue(figmaTeam); jest.spyOn(figmaService, 'tryDeleteWebhook').mockResolvedValue(); jest.spyOn(figmaTeamRepository, 'delete').mockResolvedValue(figmaTeam); + jest + .spyOn(figmaTeamRepository, 'findManyByConnectInstallationId') + .mockResolvedValue([ + generateFigmaTeam({ + connectInstallationId: connectInstallation.id, + }), + ]); await disconnectFigmaTeamUseCase.execute( figmaTeam.teamId, @@ -32,5 +79,11 @@ describe('disconnectFigmaTeamUseCase', () => { figmaTeam.adminInfo, ); expect(figmaTeamRepository.delete).toBeCalledWith(figmaTeam.id); + expect(figmaTeamRepository.findManyByConnectInstallationId).toBeCalledWith( + connectInstallation.id, + ); + expect( + jiraService.setConfigurationStateInAppProperties, + ).not.toHaveBeenCalled(); }); }); diff --git a/src/usecases/disconnect-figma-team-use-case.ts b/src/usecases/disconnect-figma-team-use-case.ts index e4f10a97..5664d517 100644 --- a/src/usecases/disconnect-figma-team-use-case.ts +++ b/src/usecases/disconnect-figma-team-use-case.ts @@ -1,5 +1,6 @@ import type { ConnectInstallation } from '../domain/entities'; import { figmaService } from '../infrastructure/figma'; +import { ConfigurationState, jiraService } from '../infrastructure/jira'; import { figmaTeamRepository } from '../infrastructure/repositories'; export const disconnectFigmaTeamUseCase = { @@ -16,5 +17,17 @@ export const disconnectFigmaTeamUseCase = { ); await figmaTeamRepository.delete(figmaTeam.id); + + const configuredTeams = + await figmaTeamRepository.findManyByConnectInstallationId( + connectInstallation.id, + ); + + if (configuredTeams.length === 0) { + await jiraService.setConfigurationStateInAppProperties( + ConfigurationState.UNCONFIGURED, + connectInstallation, + ); + } }, }; diff --git a/src/web/routes/entities/integration.test.ts b/src/web/routes/entities/integration.test.ts index eaa29576..690456f0 100644 --- a/src/web/routes/entities/integration.test.ts +++ b/src/web/routes/entities/integration.test.ts @@ -40,7 +40,7 @@ import { transformNodeToAtlassianDesign, } from '../../../infrastructure/figma/transformers'; import type { AttachedDesignUrlV2IssuePropertyValue } from '../../../infrastructure/jira'; -import { propertyKeys } from '../../../infrastructure/jira'; +import { issuePropertyKeys } from '../../../infrastructure/jira'; import { generateGetIssuePropertyResponse, generateSubmitDesignsRequest, @@ -214,25 +214,25 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, status: HttpStatusCode.NotFound, }); mockJiraSetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, request: JSON.stringify(normalizedFigmaDesignUrl), }); mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, status: HttpStatusCode.NotFound, }); mockJiraSetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, request: JSON.stringify( JSON.stringify([ { @@ -245,13 +245,13 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.INGESTED_DESIGN_URLS, + propertyKey: issuePropertyKeys.INGESTED_DESIGN_URLS, status: HttpStatusCode.NotFound, }); mockJiraSetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.INGESTED_DESIGN_URLS, + propertyKey: issuePropertyKeys.INGESTED_DESIGN_URLS, request: [normalizedFigmaDesignUrl], }); @@ -358,25 +358,25 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, status: HttpStatusCode.NotFound, }); mockJiraSetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, request: JSON.stringify(normalizedFigmaDesignUrl), }); mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, status: HttpStatusCode.NotFound, }); mockJiraSetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, request: JSON.stringify( JSON.stringify([ { @@ -389,13 +389,13 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.INGESTED_DESIGN_URLS, + propertyKey: issuePropertyKeys.INGESTED_DESIGN_URLS, status: HttpStatusCode.NotFound, }); mockJiraSetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.INGESTED_DESIGN_URLS, + propertyKey: issuePropertyKeys.INGESTED_DESIGN_URLS, request: [normalizedFigmaDesignUrl], }); @@ -593,16 +593,16 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, response: generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL, + key: issuePropertyKeys.ATTACHED_DESIGN_URL, value: figmaDesignUrl, }), }); mockJiraDeleteIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, }); const expectedDesignUrlV2Value: AttachedDesignUrlV2IssuePropertyValue = { @@ -617,16 +617,16 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, response: generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL_V2, + key: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, value: JSON.stringify(attachedDesignUrlV2Values), }), }); mockJiraSetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, request: JSON.stringify(JSON.stringify([expectedDesignUrlV2Value])), }); @@ -732,16 +732,16 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, response: generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL, + key: issuePropertyKeys.ATTACHED_DESIGN_URL, value: figmaDesignUrl, }), }); mockJiraDeleteIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, }); const expectedDesignUrlV2Value: AttachedDesignUrlV2IssuePropertyValue = { @@ -756,16 +756,16 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, response: generateGetIssuePropertyResponse({ - key: propertyKeys.ATTACHED_DESIGN_URL_V2, + key: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, value: JSON.stringify(attachedDesignUrlV2Values), }), }); mockJiraSetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, request: JSON.stringify(JSON.stringify([expectedDesignUrlV2Value])), }); @@ -821,13 +821,13 @@ describe('/entities', () => { mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL, status: HttpStatusCode.NotFound, }); mockJiraGetIssuePropertyEndpoint({ baseUrl: connectInstallation.baseUrl, issueId: issue.id, - propertyKey: propertyKeys.ATTACHED_DESIGN_URL_V2, + propertyKey: issuePropertyKeys.ATTACHED_DESIGN_URL_V2, status: HttpStatusCode.NotFound, }); mockFigmaGetDevResourcesEndpoint({ diff --git a/src/web/routes/teams/integration.test.ts b/src/web/routes/teams/integration.test.ts index 314fa162..10439111 100644 --- a/src/web/routes/teams/integration.test.ts +++ b/src/web/routes/teams/integration.test.ts @@ -32,6 +32,7 @@ import { mockFigmaCreateWebhookEndpoint, mockFigmaDeleteWebhookEndpoint, mockFigmaGetTeamProjectsEndpoint, + mockJiraSetAppPropertyEndpoint, } from '../../testing'; const figmaTeamSummaryComparer = (a: FigmaTeamSummary, b: FigmaTeamSummary) => @@ -175,6 +176,12 @@ describe('/teams', () => { teamId, }), }); + mockJiraSetAppPropertyEndpoint({ + baseUrl: connectInstallation.baseUrl, + appKey: connectInstallation.key, + propertyKey: 'is-configured', + request: `CONFIGURED`, + }); await request(app) .post(requestPath) @@ -262,6 +269,12 @@ describe('/teams', () => { accessToken: figmaOAuth2UserCredentials.accessToken, status: HttpStatusCode.Ok, }); + mockJiraSetAppPropertyEndpoint({ + baseUrl: connectInstallation.baseUrl, + appKey: connectInstallation.key, + propertyKey: 'is-configured', + request: `UNCONFIGURED`, + }); await request(app) .delete(requestPath) @@ -296,6 +309,12 @@ describe('/teams', () => { accessToken: figmaOAuth2UserCredentials.accessToken, status: HttpStatusCode.NotFound, }); + mockJiraSetAppPropertyEndpoint({ + baseUrl: connectInstallation.baseUrl, + appKey: connectInstallation.key, + propertyKey: 'is-configured', + request: `UNCONFIGURED`, + }); await request(app) .delete(requestPath) diff --git a/src/web/testing/jira-api-mocks.ts b/src/web/testing/jira-api-mocks.ts index e19eec84..a2523a21 100644 --- a/src/web/testing/jira-api-mocks.ts +++ b/src/web/testing/jira-api-mocks.ts @@ -93,3 +93,24 @@ export const mockJiraDeleteIssuePropertyEndpoint = ({ .delete(`/rest/api/2/issue/${issueId}/properties/${propertyKey}`) .reply(status); }; + +export const mockJiraSetAppPropertyEndpoint = ({ + baseUrl, + appKey, + propertyKey, + request, + status = HttpStatusCode.Ok, +}: { + baseUrl: string; + appKey: string; + propertyKey: string; + request: RequestBodyMatcher; + status?: HttpStatusCode; +}) => { + nock(baseUrl) + .put( + `/rest/atlassian-connect/1/addons/${appKey}/properties/${propertyKey}`, + request, + ) + .reply(status); +};