Skip to content

Commit 63ae7a2

Browse files
chore: Tests for sandboxSync (#1373)
1 parent a85806d commit 63ae7a2

File tree

5 files changed

+250
-44
lines changed

5 files changed

+250
-44
lines changed

lib/__tests__/developerTestAccounts.test.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { getAccountId, getConfigAccounts } from '@hubspot/local-dev-lib/config';
22
import { logger } from '@hubspot/local-dev-lib/logger';
33
import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts';
4-
import { HubSpotHttpError } from '@hubspot/local-dev-lib/models/HubSpotHttpError';
54
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
65
import { fetchDeveloperTestAccounts } from '@hubspot/local-dev-lib/api/developerTestAccounts';
6+
import { makeHubSpotHttpError } from '../testUtils';
77
import * as errorHandlers from '../errorHandlers';
88
import {
99
getHasDevTestAccounts,
@@ -47,15 +47,6 @@ const accounts: CLIAccount[] = [
4747
},
4848
];
4949

50-
const makeHubSpotHttpError = (
51-
message: string,
52-
response: object
53-
): HubSpotHttpError => {
54-
return new HubSpotHttpError(message, {
55-
cause: { isAxiosError: true, response },
56-
});
57-
};
58-
5950
describe('lib/developerTestAccounts', () => {
6051
describe('getHasDevTestAccounts()', () => {
6152
it('should return true if there are developer test accounts associated with the account', () => {

lib/__tests__/sandboxSync.test.ts

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import { logger } from '@hubspot/local-dev-lib/logger';
2+
import { initiateSync } from '@hubspot/local-dev-lib/api/sandboxSync';
3+
import { getAccountId } from '@hubspot/local-dev-lib/config';
4+
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
5+
import { Environment } from '@hubspot/local-dev-lib/types/Config';
6+
import { makeHubSpotHttpError } from '../testUtils';
7+
import { getAvailableSyncTypes } from '../sandboxes';
8+
import { syncSandbox } from '../sandboxSync';
9+
import SpinniesManager from '../ui/SpinniesManager';
10+
11+
jest.mock('@hubspot/local-dev-lib/logger');
12+
jest.mock('@hubspot/local-dev-lib/api/sandboxSync');
13+
jest.mock('@hubspot/local-dev-lib/config');
14+
jest.mock('../sandboxes');
15+
jest.mock('../ui/SpinniesManager');
16+
17+
const mockedLogger = logger as jest.Mocked<typeof logger>;
18+
const mockedInitiateSync = initiateSync as jest.Mock;
19+
const mockedGetAccountId = getAccountId as jest.Mock;
20+
const mockedGetAvailableSyncTypes = getAvailableSyncTypes as jest.Mock;
21+
const mockedSpinniesInit = SpinniesManager.init as jest.Mock;
22+
const mockedSpinniesAdd = SpinniesManager.add as jest.Mock;
23+
const mockedSpinniesSucceed = SpinniesManager.succeed as jest.Mock;
24+
const mockedSpinniesFail = SpinniesManager.fail as jest.Mock;
25+
26+
describe('lib/sandboxSync', () => {
27+
const mockEnv = 'qa' as Environment;
28+
const mockParentAccount = {
29+
name: 'Parent Account',
30+
portalId: 123,
31+
accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX,
32+
env: mockEnv,
33+
};
34+
const mockChildAccount = {
35+
name: 'Child Account',
36+
portalId: 456,
37+
accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
38+
env: mockEnv,
39+
};
40+
const mockSyncTasks = [{ type: 'mock-sync-type' }];
41+
42+
beforeEach(() => {
43+
mockedGetAccountId
44+
.mockReturnValueOnce(mockChildAccount.portalId)
45+
.mockReturnValueOnce(mockParentAccount.portalId);
46+
mockedGetAvailableSyncTypes.mockResolvedValue(mockSyncTasks);
47+
});
48+
49+
describe('syncSandbox()', () => {
50+
it('successfully syncs a sandbox with provided sync tasks', async () => {
51+
mockedInitiateSync.mockResolvedValue({ status: 'SUCCESS' });
52+
53+
await syncSandbox(
54+
mockChildAccount,
55+
mockParentAccount,
56+
mockEnv,
57+
mockSyncTasks
58+
);
59+
60+
expect(mockedSpinniesInit).toHaveBeenCalled();
61+
expect(mockedSpinniesAdd).toHaveBeenCalled();
62+
expect(mockedInitiateSync).toHaveBeenCalledWith(
63+
mockParentAccount.portalId,
64+
mockChildAccount.portalId,
65+
mockSyncTasks,
66+
mockChildAccount.portalId
67+
);
68+
expect(mockedSpinniesSucceed).toHaveBeenCalled();
69+
});
70+
71+
it('fetches sync types when no tasks are provided', async () => {
72+
mockedInitiateSync.mockResolvedValue({ status: 'SUCCESS' });
73+
74+
await syncSandbox(mockChildAccount, mockParentAccount, mockEnv, []);
75+
76+
expect(mockedGetAvailableSyncTypes).toHaveBeenCalledWith(
77+
mockParentAccount,
78+
mockChildAccount
79+
);
80+
81+
expect(mockedGetAvailableSyncTypes).toHaveBeenCalledWith(
82+
mockParentAccount,
83+
mockChildAccount
84+
);
85+
expect(mockedInitiateSync).toHaveBeenCalled();
86+
});
87+
88+
it('throws error when account IDs are missing', async () => {
89+
mockedGetAccountId.mockReset();
90+
mockedGetAccountId.mockReturnValue(null);
91+
92+
const errorRegex = new RegExp(
93+
`Couldn't sync ${mockChildAccount.portalId} because your account has been removed from`
94+
);
95+
96+
await expect(
97+
syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)
98+
).rejects.toThrow(errorRegex);
99+
});
100+
101+
it('handles sync in progress error', async () => {
102+
const error = makeHubSpotHttpError('', {
103+
status: 429,
104+
data: {
105+
category: 'RATE_LIMITS',
106+
subCategory: 'sandboxes-sync-api.SYNC_IN_PROGRESS',
107+
},
108+
});
109+
mockedInitiateSync.mockRejectedValue(error);
110+
111+
await expect(
112+
syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)
113+
).rejects.toEqual(error);
114+
115+
expect(mockedSpinniesFail).toHaveBeenCalled();
116+
expect(mockedLogger.error).toHaveBeenCalledWith(
117+
expect.stringMatching(
118+
/Couldn't run the sync because there's another sync in progress/
119+
)
120+
);
121+
});
122+
123+
it('handles invalid user error', async () => {
124+
const error = makeHubSpotHttpError('', {
125+
status: 403,
126+
data: {
127+
category: 'BANNED',
128+
subCategory: 'sandboxes-sync-api.SYNC_NOT_ALLOWED_INVALID_USER',
129+
},
130+
});
131+
mockedInitiateSync.mockRejectedValue(error);
132+
133+
await expect(
134+
syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)
135+
).rejects.toEqual(error);
136+
137+
expect(mockedSpinniesFail).toHaveBeenCalled();
138+
expect(mockedLogger.error).toHaveBeenCalledWith(
139+
expect.stringMatching(/because your account has been removed from/)
140+
);
141+
});
142+
143+
it('handles not super admin error', async () => {
144+
const error = makeHubSpotHttpError('', {
145+
status: 403,
146+
data: {
147+
category: 'BANNED',
148+
subCategory: 'sandboxes-sync-api.SYNC_NOT_ALLOWED_INVALID_USERID',
149+
},
150+
});
151+
mockedInitiateSync.mockRejectedValue(error);
152+
153+
await expect(
154+
syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)
155+
).rejects.toEqual(error);
156+
157+
expect(mockedSpinniesFail).toHaveBeenCalled();
158+
expect(mockedLogger.error).toHaveBeenCalledWith(
159+
expect.stringMatching(
160+
/Couldn't run the sync because you are not a super admin in/
161+
)
162+
);
163+
});
164+
165+
it('handles sandbox not found error', async () => {
166+
const error = makeHubSpotHttpError('', {
167+
status: 404,
168+
data: {
169+
category: 'OBJECT_NOT_FOUND',
170+
subCategory: 'SandboxErrors.SANDBOX_NOT_FOUND',
171+
},
172+
});
173+
mockedInitiateSync.mockRejectedValue(error);
174+
175+
await expect(
176+
syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)
177+
).rejects.toEqual(error);
178+
179+
expect(mockedSpinniesFail).toHaveBeenCalled();
180+
expect(mockedLogger.error).toHaveBeenCalledWith(
181+
expect.stringMatching(/may have been deleted through the UI/)
182+
);
183+
});
184+
185+
it('displays slim info message when specified', async () => {
186+
mockedInitiateSync.mockResolvedValue({ status: 'SUCCESS' });
187+
188+
await syncSandbox(
189+
mockChildAccount,
190+
mockParentAccount,
191+
mockEnv,
192+
mockSyncTasks,
193+
true
194+
);
195+
196+
expect(mockedLogger.info).not.toHaveBeenCalled();
197+
expect(mockedSpinniesSucceed).toHaveBeenCalledWith(
198+
'sandboxSync',
199+
expect.objectContaining({
200+
text: expect.stringMatching(
201+
/Initiated sync of object definitions from production to /
202+
),
203+
})
204+
);
205+
});
206+
});
207+
});

lib/__tests__/sandboxes.test.ts

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { logger } from '@hubspot/local-dev-lib/logger';
2-
import { HubSpotHttpError } from '@hubspot/local-dev-lib/models/HubSpotHttpError';
32
import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs';
43
import { fetchTypes } from '@hubspot/local-dev-lib/api/sandboxSync';
54
import { getAccountId, getConfigAccounts } from '@hubspot/local-dev-lib/config';
65
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
76
import { Environment } from '@hubspot/local-dev-lib/types/Config';
7+
import { makeHubSpotHttpError } from '../testUtils';
88
import {
99
getSandboxTypeAsString,
1010
getHasSandboxesByType,
@@ -171,16 +171,11 @@ describe('lib/sandboxes', () => {
171171
const mockAccountId = 123;
172172

173173
it('handles missing scope error', () => {
174-
const error = new HubSpotHttpError('missing scopes error', {
175-
cause: {
176-
isAxiosError: true,
177-
response: {
178-
status: 403,
179-
data: {
180-
message: 'Missing scopes error',
181-
category: 'MISSING_SCOPES',
182-
},
183-
},
174+
const error = makeHubSpotHttpError('missing scopes error', {
175+
status: 403,
176+
data: {
177+
message: 'Missing scopes error',
178+
category: 'MISSING_SCOPES',
184179
},
185180
});
186181

@@ -198,16 +193,11 @@ describe('lib/sandboxes', () => {
198193
});
199194

200195
it('handles user access not allowed error', () => {
201-
const error = new HubSpotHttpError('user access not allowed error', {
202-
cause: {
203-
isAxiosError: true,
204-
response: {
205-
status: 403,
206-
data: {
207-
category: 'BANNED',
208-
subCategory: 'SandboxErrors.USER_ACCESS_NOT_ALLOWED',
209-
},
210-
},
196+
const error = makeHubSpotHttpError('user access not allowed error', {
197+
status: 403,
198+
data: {
199+
category: 'BANNED',
200+
subCategory: 'SandboxErrors.USER_ACCESS_NOT_ALLOWED',
211201
},
212202
});
213203

@@ -222,17 +212,11 @@ describe('lib/sandboxes', () => {
222212
});
223213

224214
it('handles 403 gating error', () => {
225-
const error = new HubSpotHttpError('403 gating error', {
226-
cause: {
227-
isAxiosError: true,
228-
response: {
229-
status: 403,
230-
data: {
231-
category: 'BANNED',
232-
subCategory:
233-
'SandboxErrors.DEVELOPMENT_SANDBOX_ACCESS_NOT_ALLOWED',
234-
},
235-
},
215+
const error = makeHubSpotHttpError('403 gating error', {
216+
status: 403,
217+
data: {
218+
category: 'BANNED',
219+
subCategory: 'SandboxErrors.DEVELOPMENT_SANDBOX_ACCESS_NOT_ALLOWED',
236220
},
237221
});
238222

lib/sandboxSync.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@ export async function syncSandbox(
3939
if (!accountId || !parentAccountId) {
4040
throw new Error(
4141
i18n(`${i18nKey}.failure.invalidUser`, {
42-
accountName: uiAccountDescription(accountId),
43-
parentAccountName: uiAccountDescription(parentAccountId),
42+
accountName: accountId
43+
? uiAccountDescription(accountId)
44+
: id!.toString(),
45+
parentAccountName: parentAccountId
46+
? uiAccountDescription(parentAccountId)
47+
: parentId!.toString(),
4448
})
4549
);
4650
}

lib/testUtils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { HubSpotHttpError } from '@hubspot/local-dev-lib/models/HubSpotHttpError';
2+
3+
type MockResponse = {
4+
status: number;
5+
data: {
6+
message?: string;
7+
errorType?: string;
8+
category?: string;
9+
subCategory?: string;
10+
};
11+
};
12+
13+
export const makeHubSpotHttpError = (
14+
message: string,
15+
response: MockResponse
16+
): HubSpotHttpError => {
17+
return new HubSpotHttpError(message, {
18+
cause: { isAxiosError: true, response },
19+
});
20+
};

0 commit comments

Comments
 (0)