From b6a411ee962757cd720628441aabd2520b8c34cf Mon Sep 17 00:00:00 2001 From: Branden Rodgers Date: Tue, 21 Jan 2025 17:32:04 -0500 Subject: [PATCH 1/2] chore: expand lib test coverage 1 --- lib/__tests__/developerTestAccounts.test.ts | 224 ++++++++++++++++++++ lib/__tests__/hasFeature.test.ts | 39 ++++ lib/__tests__/hasFlag.test.ts | 23 ++ lib/developerTestAccounts.ts | 4 +- 4 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 lib/__tests__/developerTestAccounts.test.ts create mode 100644 lib/__tests__/hasFeature.test.ts create mode 100644 lib/__tests__/hasFlag.test.ts diff --git a/lib/__tests__/developerTestAccounts.test.ts b/lib/__tests__/developerTestAccounts.test.ts new file mode 100644 index 000000000..ed9e89d54 --- /dev/null +++ b/lib/__tests__/developerTestAccounts.test.ts @@ -0,0 +1,224 @@ +import { getAccountId, getConfigAccounts } from '@hubspot/local-dev-lib/config'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; +import { HubSpotHttpError } from '@hubspot/local-dev-lib/models/HubSpotHttpError'; +import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config'; +import { fetchDeveloperTestAccounts } from '@hubspot/local-dev-lib/api/developerTestAccounts'; +import * as errorHandlers from '../errorHandlers'; +import { + getHasDevTestAccounts, + handleDeveloperTestAccountCreateError, + validateDevTestAccountUsageLimits, +} from '../developerTestAccounts'; + +jest.mock('@hubspot/local-dev-lib/config'); +jest.mock('@hubspot/local-dev-lib/logger'); +jest.mock('@hubspot/local-dev-lib/api/developerTestAccounts'); +jest.mock('../errorHandlers'); + +const mockedGetAccountId = getAccountId as jest.Mock; +const mockedGetConfigAccounts = getConfigAccounts as jest.Mock; +const mockedFetchDeveloperTestAccounts = + fetchDeveloperTestAccounts as jest.Mock; + +const APP_DEVELOPER_ACCOUNT_1: CLIAccount = { + name: 'app-developer-1', + accountId: 123, + accountType: HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER, + env: 'prod', +}; + +const APP_DEVELOPER_ACCOUNT_2: CLIAccount = { + name: 'app-developer-2', + accountId: 456, + accountType: HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER, + env: 'prod', +}; + +const accounts: CLIAccount[] = [ + APP_DEVELOPER_ACCOUNT_1, + APP_DEVELOPER_ACCOUNT_2, + { + name: 'test-account', + accountId: 789, + parentAccountId: APP_DEVELOPER_ACCOUNT_1.accountId, + accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST, + env: 'prod', + }, +]; + +const makeHubSpotHttpError = ( + message: string, + response: object +): HubSpotHttpError => { + return new HubSpotHttpError(message, { + cause: { isAxiosError: true, response }, + }); +}; + +describe('lib/developerTestAccounts', () => { + describe('getHasDevTestAccounts()', () => { + it('should return true if there are developer test accounts associated with the account', () => { + mockedGetAccountId.mockReturnValueOnce(APP_DEVELOPER_ACCOUNT_1.accountId); + mockedGetConfigAccounts.mockReturnValueOnce(accounts); + + const result = getHasDevTestAccounts(APP_DEVELOPER_ACCOUNT_1); + expect(result).toBe(true); + }); + + it('should return false if there are no developer test accounts associated with the account', () => { + mockedGetAccountId.mockReturnValueOnce(APP_DEVELOPER_ACCOUNT_2.accountId); + mockedGetConfigAccounts.mockReturnValueOnce(accounts); + + const result = getHasDevTestAccounts(APP_DEVELOPER_ACCOUNT_2); + expect(result).toBe(false); + }); + + it('should return false if there are no accounts configured', () => { + mockedGetAccountId.mockReturnValueOnce(APP_DEVELOPER_ACCOUNT_1.accountId); + mockedGetConfigAccounts.mockReturnValueOnce(undefined); + + const result = getHasDevTestAccounts(APP_DEVELOPER_ACCOUNT_1); + expect(result).toBe(false); + }); + }); + + describe('validateDevTestAccountUsageLimits()', () => { + afterEach(() => { + mockedGetAccountId.mockRestore(); + mockedFetchDeveloperTestAccounts.mockRestore(); + }); + + it('should return null if the account id is not found', async () => { + mockedGetAccountId.mockReturnValueOnce(undefined); + + const result = await validateDevTestAccountUsageLimits( + APP_DEVELOPER_ACCOUNT_1 + ); + expect(result).toBe(null); + }); + + it('should return null if there is no developer test account data', async () => { + mockedGetAccountId.mockReturnValueOnce(APP_DEVELOPER_ACCOUNT_1.accountId); + mockedFetchDeveloperTestAccounts.mockResolvedValueOnce({ + data: null, + }); + + const result = await validateDevTestAccountUsageLimits( + APP_DEVELOPER_ACCOUNT_1 + ); + expect(result).toBe(null); + }); + + it('should return the test account data if the account has not reached the limit', async () => { + mockedGetAccountId.mockReturnValueOnce(APP_DEVELOPER_ACCOUNT_1.accountId); + const testAccountData = { + maxTestPortals: 10, + results: [], + }; + mockedFetchDeveloperTestAccounts.mockResolvedValueOnce({ + data: testAccountData, + }); + + const result = await validateDevTestAccountUsageLimits( + APP_DEVELOPER_ACCOUNT_1 + ); + expect(result).toEqual(expect.objectContaining(testAccountData)); + }); + + it('should throw an error if the account has reached the limit', () => { + mockedGetAccountId.mockReturnValueOnce(APP_DEVELOPER_ACCOUNT_1.accountId); + mockedFetchDeveloperTestAccounts.mockResolvedValueOnce({ + data: { + maxTestPortals: 0, + results: [{}], + }, + }); + + expect( + validateDevTestAccountUsageLimits(APP_DEVELOPER_ACCOUNT_1) + ).rejects.toThrow(); + }); + }); + + describe('handleDeveloperTestAccountCreateError()', () => { + let loggerErrorSpy: jest.SpyInstance; + let logErrorSpy: jest.SpyInstance; + + beforeEach(() => { + loggerErrorSpy = jest.spyOn(logger, 'error'); + logErrorSpy = jest.spyOn(errorHandlers, 'logError'); + }); + + afterEach(() => { + loggerErrorSpy.mockRestore(); + logErrorSpy.mockRestore(); + }); + + it('should log and throw an error if the account is missing the required scopes', () => { + const missingScopesError = makeHubSpotHttpError('Missing scopes error', { + status: 403, + data: { + message: 'Missing scopes error', + category: 'MISSING_SCOPES', + }, + }); + console.log(missingScopesError); + console.log(missingScopesError.status); + console.log(missingScopesError.data); + + expect(() => + handleDeveloperTestAccountCreateError( + missingScopesError, + APP_DEVELOPER_ACCOUNT_1.accountId, + 'prod', + 10 + ) + ).toThrow('Missing scopes error'); + expect(loggerErrorSpy).toHaveBeenCalled(); + }); + + it('should log and throw an error if the account is missing the required scopes', () => { + const portalLimitReachedError = makeHubSpotHttpError( + 'Portal limit reached error', + { + status: 400, + data: { + message: 'Portal limit reached error', + errorType: 'TEST_PORTAL_LIMIT_REACHED', + }, + } + ); + + expect(() => + handleDeveloperTestAccountCreateError( + portalLimitReachedError, + APP_DEVELOPER_ACCOUNT_1.accountId, + 'prod', + 10 + ) + ).toThrow('Portal limit reached error'); + expect(loggerErrorSpy).toHaveBeenCalled(); + }); + + it('should log a generic error message for an unknown error type', () => { + const someUnknownError = makeHubSpotHttpError('Some unknown error', { + status: 400, + data: { + message: 'Some unknown error', + category: 'SOME_UNKNOWN_ERROR', + }, + }); + + expect(() => + handleDeveloperTestAccountCreateError( + someUnknownError, + APP_DEVELOPER_ACCOUNT_1.accountId, + 'prod', + 10 + ) + ).toThrow('Some unknown error'); + expect(logErrorSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/lib/__tests__/hasFeature.test.ts b/lib/__tests__/hasFeature.test.ts new file mode 100644 index 000000000..7d2858bb9 --- /dev/null +++ b/lib/__tests__/hasFeature.test.ts @@ -0,0 +1,39 @@ +import { fetchEnabledFeatures } from '@hubspot/local-dev-lib/api/localDevAuth'; +import { hasFeature } from '../hasFeature'; + +jest.mock('@hubspot/local-dev-lib/api/localDevAuth'); + +const mockedFetchEnabledFeatures = fetchEnabledFeatures as jest.Mock; + +describe('lib/hasFeature', () => { + describe('hasFeature()', () => { + const accountId = 123; + + beforeEach(() => { + mockedFetchEnabledFeatures.mockResolvedValueOnce({ + data: { + enabledFeatures: { + 'feature-1': true, + 'feature-2': false, + 'feature-3': true, + }, + }, + }); + }); + + it('should return true if the feature is enabled', async () => { + const result = await hasFeature(accountId, 'feature-1'); + expect(result).toBe(true); + }); + + it('should return false if the feature is not enabled', async () => { + const result = await hasFeature(accountId, 'feature-2'); + expect(result).toBe(false); + }); + + it('should return false if the feature is not present', async () => { + const result = await hasFeature(accountId, 'feature-4'); + expect(result).toBe(false); + }); + }); +}); diff --git a/lib/__tests__/hasFlag.test.ts b/lib/__tests__/hasFlag.test.ts new file mode 100644 index 000000000..6ce3db63d --- /dev/null +++ b/lib/__tests__/hasFlag.test.ts @@ -0,0 +1,23 @@ +import { hasFlag } from '../hasFlag'; + +const argvWithFlag = ['hs', 'command', '--test']; +const argvWithoutFlag = ['hs', 'command']; + +describe('lib/hasFlag', () => { + describe('hasFlag()', () => { + it('should return true if the flag is present', () => { + const flag = hasFlag('test', argvWithFlag); + expect(flag).toBe(true); + }); + + it('should return false if argv is an empty array', () => { + const flag = hasFlag('test', []); + expect(flag).toBe(false); + }); + + it('should return false if the flag is not present', () => { + const flag = hasFlag('test', argvWithoutFlag); + expect(flag).toBe(false); + }); + }); +}); diff --git a/lib/developerTestAccounts.ts b/lib/developerTestAccounts.ts index 3dcdcbbdc..7f1aab5e3 100644 --- a/lib/developerTestAccounts.ts +++ b/lib/developerTestAccounts.ts @@ -16,7 +16,9 @@ import { logError } from './errorHandlers/index'; import { FetchDeveloperTestAccountsResponse } from '@hubspot/local-dev-lib/types/developerTestAccounts'; import { Environment } from '@hubspot/local-dev-lib/types/Config'; -function getHasDevTestAccounts(appDeveloperAccountConfig: CLIAccount): boolean { +export function getHasDevTestAccounts( + appDeveloperAccountConfig: CLIAccount +): boolean { const id = getAccountIdentifier(appDeveloperAccountConfig); const parentPortalId = getAccountId(id); const accountsList = getConfigAccounts(); From 30ab1b9a75af0a6d8abdb086d11a6990efd1673a Mon Sep 17 00:00:00 2001 From: Branden Rodgers Date: Wed, 22 Jan 2025 11:18:27 -0500 Subject: [PATCH 2/2] remove logs --- lib/__tests__/developerTestAccounts.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/__tests__/developerTestAccounts.test.ts b/lib/__tests__/developerTestAccounts.test.ts index ed9e89d54..033d2f48e 100644 --- a/lib/__tests__/developerTestAccounts.test.ts +++ b/lib/__tests__/developerTestAccounts.test.ts @@ -163,9 +163,6 @@ describe('lib/developerTestAccounts', () => { category: 'MISSING_SCOPES', }, }); - console.log(missingScopesError); - console.log(missingScopesError.status); - console.log(missingScopesError.data); expect(() => handleDeveloperTestAccountCreateError(