From ba65b00330a675dfe57fbffc2f1cc0f6a6a93ca9 Mon Sep 17 00:00:00 2001 From: Harshitha D Date: Wed, 11 Sep 2024 13:40:23 +0530 Subject: [PATCH 1/5] added test cases for set rate limit command --- .../test/unit/commands/rate-limit.test.ts | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 packages/contentstack-config/test/unit/commands/rate-limit.test.ts diff --git a/packages/contentstack-config/test/unit/commands/rate-limit.test.ts b/packages/contentstack-config/test/unit/commands/rate-limit.test.ts new file mode 100644 index 0000000000..1565f78f87 --- /dev/null +++ b/packages/contentstack-config/test/unit/commands/rate-limit.test.ts @@ -0,0 +1,72 @@ +import { expect } from 'chai'; +import { cliux } from '@contentstack/cli-utilities'; +import RateLimitSetCommand from '../../../src/commands/config/set/rate-limit'; + +describe('Rate Limit Set Command', () => { + let originalCliuxError: typeof cliux.error; + let errorMessage: any; + + beforeEach(() => { + originalCliuxError = cliux.error; + cliux.error = (message: string) => { + errorMessage = message; + }; + }); + + afterEach(() => { + cliux.error = originalCliuxError; + errorMessage = undefined; + }); + + it('Set rate limit: should handle valid input', async () => { + const args = [ + '--org', + 'test-org-id', + '--utilize', + '70,80', + '--limit-name', + 'getLimit,postLimit' + ]; + await RateLimitSetCommand.run(args); + expect(errorMessage).to.equal('Invalid limit names provided: getLimit, postLimit'); + }); + + it('Set rate limit: should handle invalid utilization percentages', async () => { + const args = [ + '--org', + 'test-org-id', + '--utilize', + '150', + '--limit-name', + 'getLimit' + ]; + await RateLimitSetCommand.run(args); + expect(errorMessage).to.equal('Utilize percentages must be numbers between 0 and 100.'); + }); + + it('Set rate limit: should handle mismatch between utilize percentages and limit names', async () => { + const args = [ + '--org', + 'test-org-id', + '--utilize', + '70', + '--limit-name', + 'getLimit,postLimit' + ]; + await RateLimitSetCommand.run(args); + expect(errorMessage).to.equal('The number of utilization percentages must match the number of limit names provided.'); + }); + + it('Set rate limit: should handle invalid limit names', async () => { + const args = [ + '--org', + 'test-org-id', + '--utilize', + '70,80', + '--limit-name', + 'invalidLimit' + ]; + await RateLimitSetCommand.run(args); + expect(errorMessage).to.equal('The number of utilization percentages must match the number of limit names provided.'); + }); +}); \ No newline at end of file From 5ad476ddb8012ab4b606ce0162765e5eb438e2a5 Mon Sep 17 00:00:00 2001 From: Harshitha D Date: Wed, 11 Sep 2024 19:19:57 +0530 Subject: [PATCH 2/5] added get and remove rate limit command test cases and updated set command --- .../src/commands/config/set/rate-limit.ts | 4 +- .../test/unit/commands/rate-limit.test.ts | 167 ++++++++++++------ 2 files changed, 111 insertions(+), 60 deletions(-) diff --git a/packages/contentstack-config/src/commands/config/set/rate-limit.ts b/packages/contentstack-config/src/commands/config/set/rate-limit.ts index ab6d503b60..90df539c5c 100644 --- a/packages/contentstack-config/src/commands/config/set/rate-limit.ts +++ b/packages/contentstack-config/src/commands/config/set/rate-limit.ts @@ -5,7 +5,7 @@ import { askOrgID } from '../../../utils/interactive'; import { SetRateLimitConfig } from '../../../interfaces'; import { limitNamesConfig } from '../../../utils/common-utilities'; -export default class RateLimitSetCommand extends BaseCommand { +export default class SetRateLimitCommand extends BaseCommand { static description = 'Set rate-limit for CLI'; static flags: FlagInput = { @@ -42,7 +42,7 @@ export default class RateLimitSetCommand extends BaseCommand { - let originalCliuxError: typeof cliux.error; - let errorMessage: any; +let config = configHandler; - beforeEach(() => { - originalCliuxError = cliux.error; - cliux.error = (message: string) => { - errorMessage = message; - }; +describe('Rate Limit Commands', () => { + let originalCliuxError: typeof cliux.error; + let originalCliuxPrint: typeof cliux.print; + let originalCliuxInquire: typeof cliux.inquire; + let errorMessage: any; + let printMessage: any; + + beforeEach(() => { + originalCliuxError = cliux.error; + originalCliuxPrint = cliux.print; + originalCliuxInquire = cliux.inquire; + cliux.error = (message: string) => { + errorMessage = message; + }; + cliux.print = (message: string) => { + printMessage = message; + }; + }); + + afterEach(() => { + cliux.error = originalCliuxError; + cliux.print = originalCliuxPrint; + }); + + describe('Set Rate Limit Command', () => { + it('Set Rate Limit: with all flags, should be successful', async () => { + const stub1 = stub(SetRateLimitCommand.prototype, 'run'); + const args = ['--org', 'test-org-id', '--utilize', '70,80', '--limit-name', 'getLimit,bulkLimit']; + await SetRateLimitCommand.run(args); + expect(stub1.calledOnce).to.be.true; + stub1.restore(); }); - afterEach(() => { - cliux.error = originalCliuxError; - errorMessage = undefined; + it('Set Rate Limit: should handle invalid utilization percentages', async () => { + const args = ['--org', 'test-org-id', '--utilize', '150', '--limit-name', 'getLimit']; + await SetRateLimitCommand.run(args); + expect(errorMessage).to.equal('Utilize percentages must be numbers between 0 and 100.'); }); - it('Set rate limit: should handle valid input', async () => { - const args = [ - '--org', - 'test-org-id', - '--utilize', - '70,80', - '--limit-name', - 'getLimit,postLimit' - ]; - await RateLimitSetCommand.run(args); - expect(errorMessage).to.equal('Invalid limit names provided: getLimit, postLimit'); + it('Set Rate Limit: should handle mismatch between utilize percentages and limit names', async () => { + const args = ['--org', 'test-org-id', '--utilize', '70', '--limit-name', 'getLimit,postLimit']; + await SetRateLimitCommand.run(args); + expect(errorMessage).to.equal( + 'The number of utilization percentages must match the number of limit names provided.', + ); }); - it('Set rate limit: should handle invalid utilization percentages', async () => { - const args = [ - '--org', - 'test-org-id', - '--utilize', - '150', - '--limit-name', - 'getLimit' - ]; - await RateLimitSetCommand.run(args); - expect(errorMessage).to.equal('Utilize percentages must be numbers between 0 and 100.'); + it('Set Rate Limit: should handle invalid number of limit names', async () => { + const args = ['--org', 'test-org-id', '--utilize', '70,80', '--limit-name', 'getLimit']; + await SetRateLimitCommand.run(args); + expect(errorMessage).to.equal( + 'The number of utilization percentages must match the number of limit names provided.', + ); }); - it('Set rate limit: should handle mismatch between utilize percentages and limit names', async () => { - const args = [ - '--org', - 'test-org-id', - '--utilize', - '70', - '--limit-name', - 'getLimit,postLimit' - ]; - await RateLimitSetCommand.run(args); - expect(errorMessage).to.equal('The number of utilization percentages must match the number of limit names provided.'); + it('Set Rate Limit: should prompt for the organization UID', async () => { + const inquireStub = stub(cliux, 'inquire').resolves('test-org-id'); + const orgID = await askOrgID(); + expect(orgID).to.equal('test-org-id'); + inquireStub.restore(); + }); + }); + + describe('Get Rate Limit Command', () => { + const rateLimit = { + 'test-org-id': { + getLimit: { value: 10, utilize: 70 }, + bulkLimit: { value: 1, utilize: 80 }, + }, + }; + + it('Get Rate Limit: should print the rate limit for the given organization', async () => { + config.set('rateLimit', rateLimit); + await GetRateLimitCommand.run(['--org', 'test-org-id']); + expect(printMessage).to.include(' test-org-id 10(70%) 0 1(80%) '); + }); + + it('Get Rate Limit: should throw an error if the organization is not found', async () => { + config.set('rateLimit', {}); + try { + await GetRateLimitCommand.run(['--org', 'non-existent-org']); + } catch (error) { + expect(errorMessage).to.equal('Error: Organization not found'); + } + }); + }); + + describe('Remove Rate Limit Command', () => { + const rateLimit = { + 'test-org-id': { + getLimit: { value: 10, utilize: 70 }, + bulkLimit: { value: 1, utilize: 80 }, + }, + }; + + it('Remove Rate Limit: should remove the rate limit for the given organization', async () => { + config.set('rateLimit', rateLimit); + await RemoveRateLimitCommand.run(['--org', 'test-org-id']); + const updatedRateLimit = config.get('rateLimit'); + expect(updatedRateLimit['test-org-id']).to.be.undefined; + expect(printMessage).to.equal('Rate limit entry for organization UID test-org-id has been removed.'); }); - it('Set rate limit: should handle invalid limit names', async () => { - const args = [ - '--org', - 'test-org-id', - '--utilize', - '70,80', - '--limit-name', - 'invalidLimit' - ]; - await RateLimitSetCommand.run(args); - expect(errorMessage).to.equal('The number of utilization percentages must match the number of limit names provided.'); + it('Remove Rate Limit: should throw an error if the organization is not found', async () => { + config.set('rateLimit', {}); + try { + await RemoveRateLimitCommand.run(['--org', 'non-existent-org']); + } catch (error) { + expect(errorMessage).to.equal('Error: Organization not found'); + } }); -}); \ No newline at end of file + }); +}); From 2a7324892847479a1bd485c530f3990c64da54c6 Mon Sep 17 00:00:00 2001 From: "harshitha.d" Date: Thu, 12 Sep 2024 13:26:39 +0530 Subject: [PATCH 3/5] setup repo --- packages/contentstack/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/contentstack/README.md b/packages/contentstack/README.md index 9f430fa663..aad5340ff0 100644 --- a/packages/contentstack/README.md +++ b/packages/contentstack/README.md @@ -95,6 +95,7 @@ USAGE * [`csdx config:set:base-branch`](#csdx-configsetbase-branch) * [`csdx config:set:ea-header`](#csdx-configsetea-header) * [`csdx config:set:early-access-header`](#csdx-configsetearly-access-header) +* [`csdx config:set:rate-limit`](#csdx-configsetrate-limit) * [`csdx config:set:region [REGION]`](#csdx-configsetregion-region) * [`csdx help [COMMANDS]`](#csdx-help-commands) * [`csdx launch`](#csdx-launch) @@ -3196,6 +3197,33 @@ EXAMPLES _See code: [@contentstack/cli-config](https://github.com/contentstack/cli/blob/main/packages/contentstack-config/src/commands/config/set/early-access-header.ts)_ +## `csdx config:set:rate-limit` + +Set rate-limit for CLI + +``` +USAGE + $ csdx config:set:rate-limit [--org ] [--utilize ] [--limit-name ...] [--default] + +FLAGS + --default Reset to default rate limit + --limit-name=... [Optional] Provide the limit names separated by commas ['limit', 'getLimit', 'bulkLimit'] + --org= Provide the organization UID + --utilize= [default: 50] Provide the utilization percentages for rate limit, separated by commas + +DESCRIPTION + Set rate-limit for CLI + +EXAMPLES + $ csdx config:set:rate-limit --org <> + + $ csdx config:set:rate-limit --org <> --utilize 70,80 --limit-name getLimit,limit + + $ csdx config:set:rate-limit --org <> --default +``` + +_See code: [@contentstack/cli-config](https://github.com/contentstack/cli/blob/main/packages/contentstack-config/src/commands/config/set/rate-limit.ts)_ + ## `csdx config:set:region [REGION]` Set region for CLI From 5506110f59c76c33f37776335cab9e89bc3efcc8 Mon Sep 17 00:00:00 2001 From: "harshitha.d" Date: Thu, 12 Sep 2024 17:18:57 +0530 Subject: [PATCH 4/5] added clius succes message after handling rate limit --- .../contentstack-config/src/commands/config/set/rate-limit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contentstack-config/src/commands/config/set/rate-limit.ts b/packages/contentstack-config/src/commands/config/set/rate-limit.ts index 90df539c5c..68885bf0aa 100644 --- a/packages/contentstack-config/src/commands/config/set/rate-limit.ts +++ b/packages/contentstack-config/src/commands/config/set/rate-limit.ts @@ -79,9 +79,9 @@ export default class SetRateLimitCommand extends BaseCommand Date: Fri, 13 Sep 2024 14:47:36 +0530 Subject: [PATCH 5/5] improved coverage and updated readme --- packages/contentstack-config/README.md | 28 +++++++ .../src/commands/config/set/rate-limit.ts | 3 +- .../src/utils/rate-limit-handler.ts | 1 + .../test/unit/commands/rate-limit.test.ts | 81 ++++++++++++++++--- 4 files changed, 98 insertions(+), 15 deletions(-) diff --git a/packages/contentstack-config/README.md b/packages/contentstack-config/README.md index 6932817d16..00750b929e 100644 --- a/packages/contentstack-config/README.md +++ b/packages/contentstack-config/README.md @@ -41,6 +41,7 @@ USAGE * [`csdx config:set:base-branch`](#csdx-configsetbase-branch) * [`csdx config:set:ea-header`](#csdx-configsetea-header) * [`csdx config:set:early-access-header`](#csdx-configsetearly-access-header) +* [`csdx config:set:rate-limit`](#csdx-configsetrate-limit) * [`csdx config:set:region [REGION]`](#csdx-configsetregion-region) ## `csdx config:get:base-branch` @@ -298,6 +299,33 @@ EXAMPLES _See code: [src/commands/config/set/early-access-header.ts](https://github.com/contentstack/cli/blob/main/packages/contentstack-config/src/commands/config/set/early-access-header.ts)_ +## `csdx config:set:rate-limit` + +Set rate-limit for CLI + +``` +USAGE + $ csdx config:set:rate-limit [--org ] [--utilize ] [--limit-name ] [--default] + +FLAGS + --default Reset to default rate limit + --limit-name=... [Optional] Provide the limit names separated by commas ['limit', 'getLimit', 'bulkLimit'] + --org= Provide the organization UID + --utilize= [default: 50] Provide the utilization percentages for rate limit, separated by commas + +DESCRIPTION + Set rate-limit for CLI + +EXAMPLES + $ csdx config:set:rate-limit --org <> + + $ csdx config:set:rate-limit --org <> --utilize 70,80 --limit-name getLimit,limit + + $ csdx config:set:rate-limit --org <> --default +``` + +_See code: [src/commands/config/set/rate-limit.ts](https://github.com/contentstack/cli/blob/main/packages/contentstack-config/src/commands/config/set/rate-limit.ts)_ + ## `csdx config:set:region [REGION]` Set region for CLI diff --git a/packages/contentstack-config/src/commands/config/set/rate-limit.ts b/packages/contentstack-config/src/commands/config/set/rate-limit.ts index 68885bf0aa..47e7f5d80a 100644 --- a/packages/contentstack-config/src/commands/config/set/rate-limit.ts +++ b/packages/contentstack-config/src/commands/config/set/rate-limit.ts @@ -68,7 +68,7 @@ export default class SetRateLimitCommand extends BaseCommand name.trim()); - if (!limitNamesConfig.includes(invalidLimitNames)) { + if (invalidLimitNames.some((name: string) => !limitNamesConfig.includes(name))) { cliux.error(`Invalid limit names provided: ${invalidLimitNames.join(', ')}`); return; } else { @@ -81,7 +81,6 @@ export default class SetRateLimitCommand extends BaseCommand { let originalCliuxError: typeof cliux.error; let originalCliuxPrint: typeof cliux.print; - let originalCliuxInquire: typeof cliux.inquire; + let originalIsAuthenticated: () => boolean; let errorMessage: any; let printMessage: any; + let authenticated = isAuthenticated; + let rateLimitHandler: RateLimitHandler; + let mockClient: any; beforeEach(() => { originalCliuxError = cliux.error; originalCliuxPrint = cliux.print; - originalCliuxInquire = cliux.inquire; + originalIsAuthenticated = isAuthenticated; + cliux.error = (message: string) => { errorMessage = message; }; cliux.print = (message: string) => { printMessage = message; }; + rateLimitHandler = new RateLimitHandler(); + mockClient = { + organization: stub().returns({ + fetch: stub().resolves({ plan: { features: [{ uid: 'getLimit' }, { uid: 'bulkLimit' }] } }), + }), + }; + rateLimitHandler.setClient(mockClient); + restore(); + + restore(); }); afterEach(() => { cliux.error = originalCliuxError; cliux.print = originalCliuxPrint; + authenticated = originalIsAuthenticated; }); describe('Set Rate Limit Command', () => { it('Set Rate Limit: with all flags, should be successful', async () => { - const stub1 = stub(SetRateLimitCommand.prototype, 'run'); + const stub1 = stub(SetRateLimitCommand.prototype, 'run').resolves(); const args = ['--org', 'test-org-id', '--utilize', '70,80', '--limit-name', 'getLimit,bulkLimit']; await SetRateLimitCommand.run(args); expect(stub1.calledOnce).to.be.true; - stub1.restore(); }); it('Set Rate Limit: should handle invalid utilization percentages', async () => { @@ -69,6 +83,47 @@ describe('Rate Limit Commands', () => { expect(orgID).to.equal('test-org-id'); inquireStub.restore(); }); + + it('Set Rate Limit: should handle API client failure gracefully', async () => { + const handler = new RateLimitHandler(); + handler.setClient({ + organization: () => { + throw new Error('Client Error'); + }, + }); + const config = { org: 'test-org-id', utilize: ['70'], 'limit-name': ['getLimit'] }; + await handler.setRateLimit(config); + expect(errorMessage).to.include('Error: Unable to set the rate limit'); + }); + + it('Set Rate Limit: should handle unauthenticated user', async () => { + const isAuthenticatedStub = stub().returns(false); + authenticated = isAuthenticatedStub; + const args = ['--org', 'test-org-id', '--utilize', '70,80', '--limit-name', 'getLimit,bulkLimit']; + try { + await SetRateLimitCommand.run(args); + } catch (error) { + expect(errorMessage).to.equal('You are not logged in. Please login with command $ csdx auth:login'); + expect(error?.code).to.equal(1); + } + }); + it('should set default rate limit for organization', async () => { + const config = { org: 'test-org-id', default: true }; + await rateLimitHandler.setRateLimit(config); + const rateLimit = configHandler.get('rateLimit'); + expect(rateLimit['test-org-id']).to.deep.equal(defaultRalteLimitConfig); + }); + + it('should set rate limit when only utilization percentages are provided', async () => { + const config = { + org: 'test-org-id', + utilize: ['70'], + 'limit-name': ['getLimit'], + }; + await rateLimitHandler.setRateLimit(config); + const rateLimit = configHandler.get('rateLimit'); + expect(rateLimit['test-org-id']['getLimit'].utilize).to.equal(70); + }); }); describe('Get Rate Limit Command', () => { @@ -80,13 +135,13 @@ describe('Rate Limit Commands', () => { }; it('Get Rate Limit: should print the rate limit for the given organization', async () => { - config.set('rateLimit', rateLimit); + configHandler.set('rateLimit', rateLimit); await GetRateLimitCommand.run(['--org', 'test-org-id']); expect(printMessage).to.include(' test-org-id 10(70%) 0 1(80%) '); }); it('Get Rate Limit: should throw an error if the organization is not found', async () => { - config.set('rateLimit', {}); + configHandler.set('rateLimit', {}); try { await GetRateLimitCommand.run(['--org', 'non-existent-org']); } catch (error) { @@ -104,15 +159,15 @@ describe('Rate Limit Commands', () => { }; it('Remove Rate Limit: should remove the rate limit for the given organization', async () => { - config.set('rateLimit', rateLimit); + configHandler.set('rateLimit', rateLimit); await RemoveRateLimitCommand.run(['--org', 'test-org-id']); - const updatedRateLimit = config.get('rateLimit'); + const updatedRateLimit = configHandler.get('rateLimit'); expect(updatedRateLimit['test-org-id']).to.be.undefined; expect(printMessage).to.equal('Rate limit entry for organization UID test-org-id has been removed.'); }); it('Remove Rate Limit: should throw an error if the organization is not found', async () => { - config.set('rateLimit', {}); + configHandler.set('rateLimit', {}); try { await RemoveRateLimitCommand.run(['--org', 'non-existent-org']); } catch (error) {