Skip to content

Commit

Permalink
Merge pull request #53 from HubSpot/br-unit-tests-4
Browse files Browse the repository at this point in the history
Unit tests for personalAccessKey utils
  • Loading branch information
brandenrodgers authored Oct 30, 2023
2 parents b5c6325 + 0965917 commit e844ae4
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 4 deletions.
4 changes: 3 additions & 1 deletion api/localDevAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ const LOCALDEVAUTH_API_AUTH_PATH = 'localdevauth/v1/auth';

type AccessTokenResponse = {
hubId: number;
userId: number;
oauthAccessToken: string;
expiresAtMillis: number;
enabledFeatures?: { [key: string]: number };
scopeGroups: Array<string>;
encodedOauthRefreshToken: string;
encodedOAuthRefreshToken: string;
};

export async function fetchAccessToken(
Expand Down
253 changes: 253 additions & 0 deletions lib/__tests__/personalAccessKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import moment from 'moment';
import {
getAndLoadConfigIfNeeded as __getAndLoadConfigIfNeeded,
getAccountConfig as __getAccountConfig,
updateAccountConfig as __updateAccountConfig,
} from '../../config';
import { fetchAccessToken as __fetchAccessToken } from '../../api/localDevAuth';
import { fetchSandboxHubData as __fetchSandboxHubData } from '../../api/sandboxHubs';
import { ENVIRONMENTS } from '../../constants/environments';
import {
accessTokenForPersonalAccessKey,
updateConfigWithPersonalAccessKey,
} from '../personalAccessKey';
import { AuthType } from '../../types/Accounts';

jest.mock('../../config');
jest.mock('../logging/logger');
jest.mock('../../api/localDevAuth');
jest.mock('../../api/sandboxHubs');

const updateAccountConfig = __updateAccountConfig as jest.MockedFunction<
typeof __updateAccountConfig
>;
const getAccountConfig = __getAccountConfig as jest.MockedFunction<
typeof __getAccountConfig
>;
const getAndLoadConfigIfNeeded =
__getAndLoadConfigIfNeeded as jest.MockedFunction<
typeof __getAndLoadConfigIfNeeded
>;
const fetchAccessToken = __fetchAccessToken as jest.MockedFunction<
typeof __fetchAccessToken
>;
const fetchSandboxHubData = __fetchSandboxHubData as jest.MockedFunction<
typeof __fetchSandboxHubData
>;

describe('personalAccessKey', () => {
describe('accessTokenForPersonalAccessKey', () => {
it('refreshes access token when access token is missing', async () => {
const accountId = 123;
const account = {
accountId,
authType: 'personalaccesskey' as AuthType,
personalAccessKey: 'let-me-in',
env: ENVIRONMENTS.QA,
};
getAndLoadConfigIfNeeded.mockReturnValue({
accounts: [account],
});
getAccountConfig.mockReturnValue(account);

const freshAccessToken = 'fresh-token';
fetchAccessToken.mockReturnValue(
Promise.resolve({
oauthAccessToken: freshAccessToken,
expiresAtMillis: moment().add(1, 'hours').valueOf(),
encodedOAuthRefreshToken: 'let-me-in',
scopeGroups: ['content'],
hubId: accountId,
userId: 456,
})
);
const accessToken = await accessTokenForPersonalAccessKey(accountId);
expect(accessToken).toEqual(freshAccessToken);
});
it('uses accountId when refreshing token', async () => {
const accountId = 123;
const account = {
accountId,
authType: 'personalaccesskey' as AuthType,
personalAccessKey: 'let-me-in-2',
env: ENVIRONMENTS.PROD,
};
getAndLoadConfigIfNeeded.mockReturnValue({
accounts: [account],
});
getAccountConfig.mockReturnValue(account);

await accessTokenForPersonalAccessKey(accountId);
expect(fetchAccessToken).toHaveBeenCalledWith(
'let-me-in-2',
ENVIRONMENTS.PROD,
accountId
);
});
it('refreshes access token when the existing token is expired', async () => {
const accountId = 123;
const account = {
accountId,
authType: 'personalaccesskey' as AuthType,
personalAccessKey: 'let-me-in-3',
auth: {
tokenInfo: {
expiresAt: moment().subtract(1, 'hours').toISOString(),
accessToken: 'test-token',
},
},
env: ENVIRONMENTS.QA,
};
getAndLoadConfigIfNeeded.mockReturnValue({
accounts: [account],
});
getAccountConfig.mockReturnValue(account);

const freshAccessToken = 'fresh-token';
fetchAccessToken.mockReturnValue(
Promise.resolve({
oauthAccessToken: freshAccessToken,
expiresAtMillis: moment().add(1, 'hours').valueOf(),
encodedOAuthRefreshToken: 'let-me-in-3',
scopeGroups: ['content'],
hubId: accountId,
userId: 456,
})
);
const accessToken = await accessTokenForPersonalAccessKey(accountId);
expect(accessToken).toEqual(freshAccessToken);
});
it('refreshes access tokens multiple times', async () => {
const accountId = 123;
const accessKey = 'let-me-in-4';
const userId = 456;
const mockAccount = (expiresAt: string, accessToken: string) => ({
accountId,
authType: 'personalaccesskey' as AuthType,
personalAccessKey: accessKey,
auth: {
tokenInfo: {
expiresAt,
accessToken,
},
},
env: ENVIRONMENTS.QA,
});
const initialAccountConfig = mockAccount(
moment().subtract(2, 'hours').toISOString(),
'test-token'
);
getAndLoadConfigIfNeeded.mockReturnValueOnce({
accounts: [initialAccountConfig],
});
getAccountConfig.mockReturnValueOnce(initialAccountConfig);

const firstAccessToken = 'fresh-token';
const expiresAtMillis = moment().subtract(1, 'hours').valueOf();

fetchAccessToken.mockReturnValueOnce(
Promise.resolve({
oauthAccessToken: firstAccessToken,
expiresAtMillis,
encodedOAuthRefreshToken: accessKey,
scopeGroups: ['content'],
hubId: accountId,
userId,
})
);
const firstRefreshedAccessToken =
await accessTokenForPersonalAccessKey(accountId);
expect(firstRefreshedAccessToken).toEqual(firstAccessToken);
const updatedAccountConfig = mockAccount(
moment(expiresAtMillis).toISOString(),
firstAccessToken
);
getAndLoadConfigIfNeeded.mockReturnValueOnce({
accounts: [updatedAccountConfig],
});
getAccountConfig.mockReturnValueOnce(updatedAccountConfig);

const secondAccessToken = 'another-fresh-token';
fetchAccessToken.mockReturnValueOnce(
Promise.resolve({
oauthAccessToken: secondAccessToken,
expiresAtMillis,
encodedOAuthRefreshToken: accessKey,
scopeGroups: ['content'],
hubId: accountId,
userId,
})
);

const secondRefreshedAccessToken =
await accessTokenForPersonalAccessKey(accountId);
expect(secondRefreshedAccessToken).toEqual(secondAccessToken);
});
});

describe('updateConfigWithPersonalAccessKey', () => {
beforeEach(() => {
fetchAccessToken.mockClear();
updateAccountConfig.mockClear();

const freshAccessToken = 'fresh-token';
fetchAccessToken.mockReturnValue(
Promise.resolve({
oauthAccessToken: freshAccessToken,
expiresAtMillis: moment().add(1, 'hours').valueOf(),
encodedOAuthRefreshToken: 'let-me-in-5',
scopeGroups: ['content'],
hubId: 123,
userId: 456,
})
);
});

it('updates the config with the new account', async () => {
fetchAccessToken.mockClear();

await updateConfigWithPersonalAccessKey(
'pak_123',
'account-name',
ENVIRONMENTS.QA
);

expect(updateAccountConfig).toHaveBeenCalledWith(
expect.objectContaining({
accountId: 123,
personalAccessKey: 'pak_123',
name: 'account-name',
authType: 'personalaccesskey',
sandboxAccountType: null,
parentAccountId: null,
})
);
});

it('updates the config with the new account for sandbox accounts', async () => {
fetchSandboxHubData.mockReturnValue(
Promise.resolve({
type: 'developer',
parentHubId: 789,
})
);

await updateConfigWithPersonalAccessKey(
'pak_123',
'account-name',
ENVIRONMENTS.QA
);

expect(updateAccountConfig).toHaveBeenCalledWith(
expect.objectContaining({
accountId: 123,
personalAccessKey: 'pak_123',
name: 'account-name',
authType: 'personalaccesskey',
sandboxAccountType: 'developer',
parentAccountId: 789,
})
);
});
});
});
6 changes: 3 additions & 3 deletions lib/personalAccessKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type AccessToken = {
accessToken: string;
expiresAt: string;
scopeGroups: Array<string>;
encodedOauthRefreshToken: string;
encodedOAuthRefreshToken: string;
};

export async function getAccessToken(
Expand Down Expand Up @@ -58,7 +58,7 @@ export async function getAccessToken(
accessToken: response.oauthAccessToken,
expiresAt: moment(response.expiresAtMillis).toISOString(),
scopeGroups: response.scopeGroups,
encodedOauthRefreshToken: response.encodedOauthRefreshToken,
encodedOAuthRefreshToken: response.encodedOAuthRefreshToken,
};
}

Expand Down Expand Up @@ -144,7 +144,7 @@ export async function accessTokenForPersonalAccessKey(
return auth?.tokenInfo?.accessToken;
}

// Adds a account to the config using authType: personalAccessKey
// Adds an account to the config using authType: personalAccessKey
export const updateConfigWithPersonalAccessKey = async (
personalAccessKey: string,
name: string,
Expand Down

0 comments on commit e844ae4

Please sign in to comment.