From 7c19ad728294e08b9a31eff15a81f590e13ce24f Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Thu, 10 Oct 2024 19:47:11 -0700 Subject: [PATCH] GitOps Account - added unsubscribe account --- requests/Switcher API.postman_collection.json | 39 ++++ src/api-docs/paths/path-gitops.js | 18 ++ src/api-docs/schemas/gitops.js | 13 ++ src/external/gitops.js | 12 + src/routers/gitops.js | 12 + src/services/gitops/index.js | 4 + tests/gitops-account.test.js | 208 ++++++++++++++---- 7 files changed, 258 insertions(+), 48 deletions(-) diff --git a/requests/Switcher API.postman_collection.json b/requests/Switcher API.postman_collection.json index 8760449..e38730f 100644 --- a/requests/Switcher API.postman_collection.json +++ b/requests/Switcher API.postman_collection.json @@ -5679,6 +5679,45 @@ }, "response": [] }, + { + "name": "GitOps Account - Unsubscribe", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{authToken}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"environment\": \"default\",\r\n\t\"domain\": {\r\n\t\t\"id\": \"{{gitops_domain_id}}\"\r\n\t}\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{url}}/gitops/v1/account/unsubscribe", + "host": [ + "{{url}}" + ], + "path": [ + "gitops", + "v1", + "account", + "unsubscribe" + ] + } + }, + "response": [] + }, { "name": "GitOps Account - Update", "request": { diff --git a/src/api-docs/paths/path-gitops.js b/src/api-docs/paths/path-gitops.js index 3ddb57f..48c5a41 100644 --- a/src/api-docs/paths/path-gitops.js +++ b/src/api-docs/paths/path-gitops.js @@ -73,6 +73,24 @@ export default { } } }, + '/gitops/v1/account/unsubscribe': { + post: { + tags: ['Switcher GitOps'], + description: 'Unsubscribe an account from receiving gitops changes', + requestBody: { + content: commonSchemaContent('GitOpsAccountUnsubscribeRequest') + }, + responses: { + 200: { + description: 'Account unsubscribed successfully' + }, + 500: { + description: 'Something went wrong while unsubscribing the account', + content: commonSchemaContent('ErrorResponse') + } + } + } + }, '/gitops/v1/account': { put: { tags: ['Switcher GitOps'], diff --git a/src/api-docs/schemas/gitops.js b/src/api-docs/schemas/gitops.js index 5df6801..a3fcf46 100644 --- a/src/api-docs/schemas/gitops.js +++ b/src/api-docs/schemas/gitops.js @@ -194,6 +194,19 @@ export default { } } }, + GitOpsAccountUnsubscribeRequest: { + type: 'object', + properties: { + environment: { + type: 'string', + description: 'The environment name' + }, + domain: { + type: 'object', + properties: domain + } + } + }, GitOpsAccountForceSyncRequest: { type: 'object', properties: { diff --git a/src/external/gitops.js b/src/external/gitops.js index 5592667..92c6feb 100644 --- a/src/external/gitops.js +++ b/src/external/gitops.js @@ -40,6 +40,18 @@ export async function updateAccount(account) { return response.data; } +export async function deleteAccount(domainId, environment) { + const url = `${process.env.SWITCHER_GITOPS_URL}/account/${domainId}/${environment}`; + const response = await axios.delete(url, { + httpsAgent: agent(url), + headers: headers(domainId) + }); + + if (response.status !== 204) { + throw new GitOpsError(`Failed to delete account [${response.status}] ${JSON.stringify(response.data)}`); + } +} + function generateToken(subject) { const options = { expiresIn: '1m' diff --git a/src/routers/gitops.js b/src/routers/gitops.js index fc70098..3c49319 100644 --- a/src/routers/gitops.js +++ b/src/routers/gitops.js @@ -62,6 +62,18 @@ router.post('/gitops/v1/account/subscribe', auth, accountValidators, validate, } }); +router.post('/gitops/v1/account/unsubscribe', auth, [ + body('environment').isString(), + body('domain.id').isMongoId().withMessage('Invalid domain ID'), +], validate, featureFlag, async (req, res) => { + try { + await Service.unsubscribeAccount(req.body); + res.status(200).send(); + } catch (e) { + responseExceptionSilent(res, e, 500, 'Account unsubscription failed'); + } +}); + router.put('/gitops/v1/account', auth, [ body('environment').isString(), body('domain.id').isMongoId().withMessage('Invalid domain ID'), diff --git a/src/services/gitops/index.js b/src/services/gitops/index.js index 6bfa0cc..19c0e54 100644 --- a/src/services/gitops/index.js +++ b/src/services/gitops/index.js @@ -55,4 +55,8 @@ export async function forceSyncAccount(account) { id: account.domain.id } }); +} + +export async function unsubscribeAccount(account) { + return GitOpsFacade.deleteAccount(account.domain.id, account.environment); } \ No newline at end of file diff --git a/tests/gitops-account.test.js b/tests/gitops-account.test.js index 9b16e38..30dcdb7 100644 --- a/tests/gitops-account.test.js +++ b/tests/gitops-account.test.js @@ -11,53 +11,6 @@ import { adminMasterAccountToken } from './fixtures/db_client'; -const VALID_SUBSCRIPTION_REQUEST = { - repository: 'https://github.com/switcherapi/switcher-gitops-fixture', - token: '{{github_pat}}', - branch: 'main', - environment: EnvType.DEFAULT, - domain: { - id: String(domainId), - name: 'Test Domain' - }, - settings: { - active: true, - window: '30s', - forceprune: true - } -}; - -const VALID_UPDATE_REQUEST = { - repository: 'https://github.com/switcherapi/switcher-gitops-fixture', - branch: 'main', - environment: EnvType.DEFAULT, - domain: { - id: String(domainId), - name: 'Test Domain' - }, - settings: { - active: true, - window: '30s', - forceprune: true - } -}; - -const VALID_TOKEN_UPDATE_REQUEST = { - environment: EnvType.DEFAULT, - token: '123456', - domain: { - id: String(domainId) - } -}; - -const VALID_FORCE_SYNC_REQUEST = { - environment: EnvType.DEFAULT, - domain: { - id: String(domainId) - } -}; - - afterAll(async () => { await new Promise(resolve => setTimeout(resolve, 1000)); await mongoose.disconnect(); @@ -78,7 +31,21 @@ describe('GitOps Account - Feature Toggle', () => { const req = await request(app) .post('/gitops/v1/account/subscribe') .set('Authorization', `Bearer ${adminMasterAccountToken}`) - .send(VALID_SUBSCRIPTION_REQUEST) + .send({ + repository: 'https://github.com/switcherapi/switcher-gitops-fixture', + token: '{{github_pat}}', + branch: 'main', + environment: EnvType.DEFAULT, + domain: { + id: String(domainId), + name: 'Test Domain' + }, + settings: { + active: true, + window: '30s', + forceprune: true + } + }) .expect(400); expect(req.body.error).toBe('GitOps Integration is not available.'); @@ -88,6 +55,22 @@ describe('GitOps Account - Feature Toggle', () => { describe('GitOps Account - Subscribe', () => { beforeAll(setupDatabase); + const VALID_SUBSCRIPTION_REQUEST = { + repository: 'https://github.com/switcherapi/switcher-gitops-fixture', + token: '{{github_pat}}', + branch: 'main', + environment: EnvType.DEFAULT, + domain: { + id: String(domainId), + name: 'Test Domain' + }, + settings: { + active: true, + window: '30s', + forceprune: true + } + }; + test('GITOPS_ACCOUNT_SUITE - Should subscribe account', async () => { // given const expectedResponse = JSON.parse(JSON.stringify(VALID_SUBSCRIPTION_REQUEST)); @@ -208,6 +191,21 @@ describe('GitOps Account - Subscribe', () => { describe('GitOps Account - Update', () => { beforeAll(setupDatabase); + const VALID_UPDATE_REQUEST = { + repository: 'https://github.com/switcherapi/switcher-gitops-fixture', + branch: 'main', + environment: EnvType.DEFAULT, + domain: { + id: String(domainId), + name: 'Test Domain' + }, + settings: { + active: true, + window: '30s', + forceprune: true + } + }; + test('GITOPS_ACCOUNT_SUITE - Should update account', async () => { // given const expectedResponse = JSON.parse(JSON.stringify(VALID_UPDATE_REQUEST)); @@ -290,6 +288,29 @@ describe('GitOps Account - Update', () => { describe('GitOps Account - Update Token', () => { beforeAll(setupDatabase); + const VALID_UPDATE_REQUEST = { + repository: 'https://github.com/switcherapi/switcher-gitops-fixture', + branch: 'main', + environment: EnvType.DEFAULT, + domain: { + id: String(domainId), + name: 'Test Domain' + }, + settings: { + active: true, + window: '30s', + forceprune: true + } + }; + + const VALID_TOKEN_UPDATE_REQUEST = { + environment: EnvType.DEFAULT, + token: '123456', + domain: { + id: String(domainId) + } + }; + test('GITOPS_ACCOUNT_SUITE - Should update account token', async () => { // given const expectedResponse = JSON.parse(JSON.stringify(VALID_UPDATE_REQUEST)); @@ -371,6 +392,13 @@ describe('GitOps Account - Update Token', () => { describe('GitOps Account - Force sync', () => { beforeAll(setupDatabase); + const VALID_FORCE_SYNC_REQUEST = { + environment: EnvType.DEFAULT, + domain: { + id: String(domainId) + } + }; + test('GITOPS_ACCOUNT_SUITE - Should force sync account', async () => { // given const expectedResponse = JSON.parse(JSON.stringify(VALID_FORCE_SYNC_REQUEST)); @@ -448,3 +476,87 @@ describe('GitOps Account - Force sync', () => { expect(req.body.errors[0].msg).toBe('Invalid domain ID'); }); }); + +describe('GitOps Account - Unsubscribe', () => { + beforeAll(setupDatabase); + + const VALID_DELETE_REQUEST = { + environment: EnvType.DEFAULT, + domain: { + id: String(domainId) + } + }; + + test('GITOPS_ACCOUNT_SUITE - Should subscribe account', async () => { + // given + const postStub = sinon.stub(axios, 'delete').resolves({ + status: 204, + data: null + }); + + // test + await request(app) + .post(`/gitops/v1/account/unsubscribe`) + .set('Authorization', `Bearer ${adminMasterAccountToken}`) + .send(VALID_DELETE_REQUEST) + .expect(200); + + postStub.restore(); + }); + + test('GITOPS_ACCOUNT_SUITE - Should return error - error deleting account', async () => { + // given + const postStub = sinon.stub(axios, 'delete').resolves({ + status: 500, + data: { + error: 'Error deleting account' + } + }); + + // test + const req = await request(app) + .post('/gitops/v1/account/unsubscribe') + .set('Authorization', `Bearer ${adminMasterAccountToken}`) + .send(VALID_DELETE_REQUEST) + .expect(500); + + // assert + expect(req.body.error).toBe('Account unsubscription failed'); + postStub.restore(); + }); + + test('GITOPS_ACCOUNT_SUITE - Should return error - unauthorized', async () => { + // given + const postStub = sinon.stub(axios, 'delete').resolves({ + status: 401, + data: { + error: 'Invalid token' + } + }); + + // test + const req = await request(app) + .post('/gitops/v1/account/unsubscribe') + .set('Authorization', `Bearer ${adminMasterAccountToken}`) + .send(VALID_DELETE_REQUEST) + .expect(500); + + // assert + expect(req.body.error).toBe('Account unsubscription failed'); + postStub.restore(); + }); + + test('GITOPS_ACCOUNT_SUITE - Should return error - missing domain.id', async () => { + const payload = JSON.parse(JSON.stringify(VALID_DELETE_REQUEST)); + delete payload.domain.id; + + const req = await request(app) + .post('/gitops/v1/account/unsubscribe') + .set('Authorization', `Bearer ${adminMasterAccountToken}`) + .send(payload) + .expect(422); + + expect(req.body.errors[0].msg).toBe('Invalid domain ID'); + }); + +}); \ No newline at end of file