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

chore: expand lib test coverage (hasFlags, hasFeatures, developerTestAccounts) #1348

Merged
merged 2 commits into from
Jan 22, 2025
Merged
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
221 changes: 221 additions & 0 deletions lib/__tests__/developerTestAccounts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
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',
},
});

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();
});
});
});
39 changes: 39 additions & 0 deletions lib/__tests__/hasFeature.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
23 changes: 23 additions & 0 deletions lib/__tests__/hasFlag.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
4 changes: 3 additions & 1 deletion lib/developerTestAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading