@hubspot/cli 7.10.0 → 7.10.1-experimental.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/bin/cli.js +5 -4
  2. package/commands/__tests__/getStarted.test.js +10 -0
  3. package/commands/__tests__/project.test.js +3 -0
  4. package/commands/account/__tests__/rename.test.js +10 -3
  5. package/commands/account/auth.js +10 -14
  6. package/commands/account/clean.js +11 -19
  7. package/commands/account/createOverride.js +15 -11
  8. package/commands/account/info.js +8 -5
  9. package/commands/account/list.js +15 -19
  10. package/commands/account/remove.js +23 -22
  11. package/commands/account/removeOverride.js +6 -6
  12. package/commands/account/rename.js +2 -2
  13. package/commands/account/use.js +19 -8
  14. package/commands/app/__tests__/migrate.test.js +8 -4
  15. package/commands/app/migrate.js +2 -2
  16. package/commands/auth.js +18 -14
  17. package/commands/config/migrate.js +5 -5
  18. package/commands/customObject/createSchema.js +2 -3
  19. package/commands/customObject/updateSchema.js +2 -3
  20. package/commands/getStarted.js +2 -3
  21. package/commands/hubdb/__tests__/list.test.js +1 -0
  22. package/commands/hubdb/list.js +2 -2
  23. package/commands/init.js +36 -32
  24. package/commands/project/__tests__/deploy.test.js +10 -5
  25. package/commands/project/__tests__/devUnifiedFlow.test.js +6 -4
  26. package/commands/project/__tests__/lint.test.js +709 -0
  27. package/commands/project/__tests__/logs.test.js +4 -0
  28. package/commands/project/__tests__/validate.test.js +286 -28
  29. package/commands/project/cloneApp.js +2 -2
  30. package/commands/project/deploy.js +16 -8
  31. package/commands/project/dev/deprecatedFlow.js +4 -5
  32. package/commands/project/dev/index.js +19 -7
  33. package/commands/project/dev/unifiedFlow.js +4 -5
  34. package/commands/project/lint.d.ts +6 -0
  35. package/commands/project/lint.js +178 -0
  36. package/commands/project/logs.js +2 -3
  37. package/commands/project/migrate.js +4 -13
  38. package/commands/project/profile/add.js +6 -7
  39. package/commands/project/profile/delete.js +2 -2
  40. package/commands/project/upload.js +10 -4
  41. package/commands/project/validate.js +73 -13
  42. package/commands/project.js +2 -0
  43. package/commands/sandbox/__tests__/create.test.js +14 -5
  44. package/commands/sandbox/create.js +4 -5
  45. package/commands/sandbox/delete.js +23 -20
  46. package/commands/testAccount/create.js +2 -2
  47. package/commands/testAccount/delete.js +9 -8
  48. package/lang/en.d.ts +48 -11
  49. package/lang/en.js +58 -15
  50. package/lib/__tests__/buildAccount.test.js +22 -30
  51. package/lib/__tests__/commonOpts.test.js +9 -13
  52. package/lib/__tests__/developerTestAccounts.test.js +29 -17
  53. package/lib/__tests__/importData.test.js +20 -10
  54. package/lib/__tests__/oauth.test.js +19 -8
  55. package/lib/__tests__/projectProfiles.test.js +273 -32
  56. package/lib/__tests__/sandboxSync.test.js +33 -11
  57. package/lib/__tests__/sandboxes.test.js +30 -19
  58. package/lib/__tests__/usageTracking.test.js +10 -10
  59. package/lib/__tests__/validation.test.js +32 -32
  60. package/lib/accountTypes.d.ts +9 -9
  61. package/lib/accountTypes.js +2 -4
  62. package/lib/app/__tests__/migrate.test.js +15 -0
  63. package/lib/app/__tests__/migrate_legacy.test.js +9 -0
  64. package/lib/app/migrate_legacy.d.ts +2 -2
  65. package/lib/buildAccount.d.ts +4 -4
  66. package/lib/buildAccount.js +7 -14
  67. package/lib/commonOpts.js +3 -3
  68. package/lib/configMigrate.d.ts +2 -2
  69. package/lib/configMigrate.js +42 -18
  70. package/lib/configOptions.js +3 -2
  71. package/lib/developerTestAccounts.d.ts +3 -3
  72. package/lib/developerTestAccounts.js +4 -7
  73. package/lib/doctor/DiagnosticInfoBuilder.d.ts +1 -1
  74. package/lib/doctor/DiagnosticInfoBuilder.js +9 -6
  75. package/lib/doctor/Doctor.js +4 -3
  76. package/lib/doctor/__tests__/Diagnosis.test.js +4 -3
  77. package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +17 -9
  78. package/lib/doctor/__tests__/Doctor.test.js +14 -0
  79. package/lib/importData.js +8 -7
  80. package/lib/links.js +5 -5
  81. package/lib/middleware/{__test__ → __tests__}/commandTargetingUtils.test.js +3 -3
  82. package/lib/middleware/{__test__ → __tests__}/configMiddleware.test.js +23 -22
  83. package/lib/middleware/{__test__ → __tests__}/gitMiddleware.test.js +9 -7
  84. package/lib/middleware/autoUpdateMiddleware.js +34 -23
  85. package/lib/middleware/commandTargetingUtils.js +3 -2
  86. package/lib/middleware/configMiddleware.d.ts +6 -1
  87. package/lib/middleware/configMiddleware.js +36 -15
  88. package/lib/middleware/fireAlarmMiddleware.js +4 -15
  89. package/lib/middleware/gitMiddleware.js +8 -4
  90. package/lib/oauth.d.ts +2 -2
  91. package/lib/oauth.js +8 -10
  92. package/lib/projectProfiles.d.ts +4 -3
  93. package/lib/projectProfiles.js +78 -32
  94. package/lib/projects/__tests__/AppDevModeInterface.test.js +17 -6
  95. package/lib/projects/__tests__/DevServerManager.test.js +1 -0
  96. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  97. package/lib/projects/__tests__/deploy.test.js +1 -0
  98. package/lib/projects/__tests__/uieLinting.test.js +640 -0
  99. package/lib/projects/create/__tests__/v2.test.js +11 -0
  100. package/lib/projects/localDev/AppDevModeInterface.js +2 -2
  101. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
  102. package/lib/projects/localDev/LocalDevLogger.js +4 -4
  103. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
  104. package/lib/projects/localDev/helpers/account.d.ts +10 -10
  105. package/lib/projects/localDev/helpers/account.js +6 -11
  106. package/lib/projects/uieLinting.d.ts +33 -0
  107. package/lib/projects/uieLinting.js +222 -0
  108. package/lib/projects/urls.js +5 -6
  109. package/lib/prompts/__tests__/downloadProjectPrompt.test.js +7 -5
  110. package/lib/prompts/accountNamePrompt.js +3 -3
  111. package/lib/prompts/accountsPrompt.d.ts +1 -1
  112. package/lib/prompts/accountsPrompt.js +6 -7
  113. package/lib/prompts/confirmImportDataPrompt.js +2 -2
  114. package/lib/prompts/downloadProjectPrompt.d.ts +1 -0
  115. package/lib/prompts/downloadProjectPrompt.js +5 -2
  116. package/lib/prompts/importDataTestAccountSelectPrompt.js +4 -5
  117. package/lib/prompts/personalAccessKeyPrompt.js +2 -2
  118. package/lib/prompts/projectDevTargetAccountPrompt.d.ts +3 -3
  119. package/lib/prompts/projectDevTargetAccountPrompt.js +5 -7
  120. package/lib/prompts/sandboxesPrompt.js +7 -8
  121. package/lib/prompts/setAsDefaultAccountPrompt.js +7 -6
  122. package/lib/sandboxSync.d.ts +2 -2
  123. package/lib/sandboxSync.js +3 -9
  124. package/lib/sandboxes.d.ts +4 -4
  125. package/lib/sandboxes.js +6 -11
  126. package/lib/serverlessLogs.js +2 -2
  127. package/lib/theme/__tests__/migrate.test.js +15 -0
  128. package/lib/ui/index.js +6 -3
  129. package/lib/usageTracking.js +15 -8
  130. package/lib/validation.js +13 -11
  131. package/mcp-server/tools/cms/HsCreateFunctionTool.js +4 -2
  132. package/mcp-server/tools/cms/HsCreateModuleTool.js +4 -2
  133. package/mcp-server/tools/cms/HsCreateTemplateTool.js +4 -2
  134. package/mcp-server/tools/cms/HsFunctionLogsTool.js +4 -2
  135. package/mcp-server/tools/cms/HsListFunctionsTool.js +3 -1
  136. package/mcp-server/tools/cms/HsListTool.js +3 -1
  137. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -0
  138. package/mcp-server/tools/index.js +4 -0
  139. package/mcp-server/tools/project/AddFeatureToProjectTool.js +4 -2
  140. package/mcp-server/tools/project/CreateProjectTool.js +4 -2
  141. package/mcp-server/tools/project/CreateTestAccountTool.js +17 -7
  142. package/mcp-server/tools/project/DeployProjectTool.js +3 -1
  143. package/mcp-server/tools/project/DocFetchTool.js +6 -4
  144. package/mcp-server/tools/project/DocsSearchTool.d.ts +1 -1
  145. package/mcp-server/tools/project/DocsSearchTool.js +10 -8
  146. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +1 -1
  147. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +9 -7
  148. package/mcp-server/tools/project/GetApplicationInfoTool.js +8 -6
  149. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +26 -0
  150. package/mcp-server/tools/project/GetBuildLogsTool.js +125 -0
  151. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +26 -0
  152. package/mcp-server/tools/project/GetBuildStatusTool.js +166 -0
  153. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +1 -1
  154. package/mcp-server/tools/project/GetConfigValuesTool.js +9 -7
  155. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +1 -1
  156. package/mcp-server/tools/project/GuidedWalkthroughTool.js +5 -3
  157. package/mcp-server/tools/project/UploadProjectTools.js +3 -1
  158. package/mcp-server/tools/project/ValidateProjectTool.js +4 -2
  159. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +12 -2
  160. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +5 -1
  161. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +23 -11
  162. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +7 -5
  163. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +7 -5
  164. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.d.ts +1 -0
  165. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.js +305 -0
  166. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.d.ts +1 -0
  167. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.js +240 -0
  168. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -6
  169. package/mcp-server/utils/__tests__/content.test.js +21 -20
  170. package/mcp-server/utils/__tests__/feedbackTracking.test.js +34 -28
  171. package/mcp-server/utils/config.d.ts +1 -0
  172. package/mcp-server/utils/config.js +10 -0
  173. package/mcp-server/utils/content.d.ts +1 -1
  174. package/mcp-server/utils/content.js +2 -2
  175. package/mcp-server/utils/feedbackTracking.d.ts +1 -1
  176. package/mcp-server/utils/feedbackTracking.js +3 -3
  177. package/mcp-server/utils/toolUsageTracking.js +4 -3
  178. package/package.json +9 -9
  179. package/ui/components/BoxWithTitle.d.ts +2 -1
  180. package/ui/components/BoxWithTitle.js +2 -2
  181. package/ui/components/StatusMessageBoxes.d.ts +5 -4
  182. package/ui/components/StatusMessageBoxes.js +8 -8
  183. package/lib/middleware/__test__/notificationsMiddleware.test.js +0 -8
  184. package/lib/middleware/notificationsMiddleware.d.ts +0 -1
  185. package/lib/middleware/notificationsMiddleware.js +0 -28
  186. package/lib/ui/boxen.d.ts +0 -5
  187. package/lib/ui/boxen.js +0 -26
  188. package/mcp-server/utils/__tests__/cliConfig.test.js +0 -110
  189. package/mcp-server/utils/cliConfig.d.ts +0 -1
  190. package/mcp-server/utils/cliConfig.js +0 -12
  191. /package/{lib/middleware/__test__/commandTargetingUtils.test.d.ts → commands/project/__tests__/lint.test.d.ts} +0 -0
  192. /package/lib/middleware/{__test__/configMiddleware.test.d.ts → __tests__/commandTargetingUtils.test.d.ts} +0 -0
  193. /package/lib/middleware/{__test__/gitMiddleware.test.d.ts → __tests__/configMiddleware.test.d.ts} +0 -0
  194. /package/lib/middleware/{__test__/notificationsMiddleware.test.d.ts → __tests__/gitMiddleware.test.d.ts} +0 -0
  195. /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.d.ts +0 -0
  196. /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.js +0 -0
  197. /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.d.ts +0 -0
  198. /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.js +0 -0
  199. /package/{mcp-server/utils/__tests__/cliConfig.test.d.ts → lib/projects/__tests__/uieLinting.test.d.ts} +0 -0
@@ -1,22 +1,26 @@
1
1
  import path from 'path';
2
- import { loadHsProfileFile, getHsProfileFilename, getAllHsProfiles, } from '@hubspot/project-parsing-lib';
2
+ import { loadHsProfileFile, getHsProfileFilename, getAllHsProfiles, validateProfileVariables, } from '@hubspot/project-parsing-lib';
3
3
  import { lib } from '../../lang/en.js';
4
4
  import { uiBetaTag, uiLine } from '../ui/index.js';
5
5
  import { uiLogger } from '../ui/logger.js';
6
- import { EXIT_CODES } from '../enums/exitCodes.js';
7
- import { logProfileHeader, logProfileFooter, loadProfile, exitIfUsingProfiles, } from '../projectProfiles.js';
6
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
7
+ import { logProfileHeader, logProfileFooter, loadProfile, enforceProfileUsage, loadAndValidateProfile, validateProjectForProfile, } from '../projectProfiles.js';
8
+ import { handleTranslate } from '../projects/upload.js';
9
+ import SpinniesManager from '../ui/SpinniesManager.js';
10
+ import { commands } from '../../lang/en.js';
8
11
  // Mock dependencies
9
12
  vi.mock('@hubspot/project-parsing-lib');
13
+ vi.mock('@hubspot/local-dev-lib/config');
10
14
  vi.mock('../ui');
11
15
  vi.mock('../ui/logger');
12
16
  vi.mock('../../lang/en');
13
- // Mock process.exit
14
- const mockExit = vi.spyOn(process, 'exit').mockImplementation(code => {
15
- throw new Error(`Process.exit called with code ${code}`);
16
- });
17
+ vi.mock('../projects/upload');
18
+ vi.mock('../ui/SpinniesManager');
17
19
  const mockedLoadHsProfileFile = loadHsProfileFile;
18
20
  const mockedGetHsProfileFilename = getHsProfileFilename;
19
21
  const mockedGetAllHsProfiles = getAllHsProfiles;
22
+ const mockedValidateProfileVariables = validateProfileVariables;
23
+ const mockedGetConfigAccountById = getConfigAccountById;
20
24
  const mockedUiBetaTag = uiBetaTag;
21
25
  const mockedUiLine = uiLine;
22
26
  const mockedUiLogger = uiLogger;
@@ -68,62 +72,299 @@ describe('lib/projectProfiles', () => {
68
72
  const mockProfile = {
69
73
  accountId: 123,
70
74
  };
71
- it('should return undefined when project config is missing', () => {
72
- const result = loadProfile(null, mockProjectDir, mockProfileName);
73
- expect(result).toBeUndefined();
74
- expect(mockedUiLogger.error).toHaveBeenCalledWith(lib.projectProfiles.loadProfile.errors.noProjectConfig);
75
+ beforeEach(() => {
76
+ vi.clearAllMocks();
77
+ });
78
+ it('should throw error when project config is missing', () => {
79
+ expect(() => loadProfile(null, mockProjectDir, mockProfileName)).toThrow(lib.projectProfiles.loadProfile.errors.noProjectConfig);
75
80
  });
76
- it('should return undefined when profile is not found', () => {
81
+ it('should throw error when project dir is missing', () => {
82
+ expect(() => loadProfile(mockProjectConfig, null, mockProfileName)).toThrow(lib.projectProfiles.loadProfile.errors.noProjectConfig);
83
+ });
84
+ it('should throw error when profile is not found', () => {
77
85
  mockedLoadHsProfileFile.mockReturnValue(null);
78
86
  const filename = 'test-profile.hsprofile';
79
87
  mockedGetHsProfileFilename.mockReturnValue(filename);
80
- const result = loadProfile(mockProjectConfig, mockProjectDir, mockProfileName);
81
- expect(result).toBeUndefined();
82
- expect(mockedUiLogger.error).toHaveBeenCalledWith(lib.projectProfiles.loadProfile.errors.profileNotFound(filename));
88
+ expect(() => loadProfile(mockProjectConfig, mockProjectDir, mockProfileName)).toThrow(lib.projectProfiles.loadProfile.errors.profileNotFound(filename));
83
89
  });
84
- it('should return undefined when profile has no account ID', () => {
90
+ it('should throw error when profile has no account ID', () => {
85
91
  mockedLoadHsProfileFile.mockReturnValue({});
86
92
  const filename = 'test-profile.hsprofile';
87
93
  mockedGetHsProfileFilename.mockReturnValue(filename);
88
- const result = loadProfile(mockProjectConfig, mockProjectDir, mockProfileName);
89
- expect(result).toBeUndefined();
90
- expect(mockedUiLogger.error).toHaveBeenCalledWith(lib.projectProfiles.loadProfile.errors.missingAccountId(filename));
94
+ expect(() => loadProfile(mockProjectConfig, mockProjectDir, mockProfileName)).toThrow(lib.projectProfiles.loadProfile.errors.missingAccountId(filename));
91
95
  });
92
- it('should return undefined when profile loading fails', () => {
96
+ it('should throw error when profile loading fails', () => {
93
97
  mockedLoadHsProfileFile.mockImplementation(() => {
94
98
  throw new Error('Load failed');
95
99
  });
96
100
  const filename = 'test-profile.hsprofile';
97
101
  mockedGetHsProfileFilename.mockReturnValue(filename);
98
- const result = loadProfile(mockProjectConfig, mockProjectDir, mockProfileName);
99
- expect(result).toBeUndefined();
100
- expect(mockedUiLogger.error).toHaveBeenCalledWith(lib.projectProfiles.loadProfile.errors.failedToLoadProfile(filename));
102
+ expect(() => loadProfile(mockProjectConfig, mockProjectDir, mockProfileName)).toThrow(lib.projectProfiles.loadProfile.errors.failedToLoadProfile(filename));
103
+ });
104
+ it('should throw error when account is not found in config', () => {
105
+ mockedLoadHsProfileFile.mockReturnValue(mockProfile);
106
+ mockedGetConfigAccountById.mockImplementation(() => {
107
+ throw new Error('Account not found');
108
+ });
109
+ const filename = 'test-profile.hsprofile';
110
+ mockedGetHsProfileFilename.mockReturnValue(filename);
111
+ expect(() => loadProfile(mockProjectConfig, mockProjectDir, mockProfileName)).toThrow(lib.projectProfiles.loadProfile.errors.listedAccountNotFound(mockProfile.accountId, filename));
101
112
  });
102
113
  it('should return profile when loading succeeds', () => {
103
114
  mockedLoadHsProfileFile.mockReturnValue(mockProfile);
115
+ mockedGetConfigAccountById.mockReturnValue({
116
+ accountId: mockProfile.accountId,
117
+ });
104
118
  const result = loadProfile(mockProjectConfig, mockProjectDir, mockProfileName);
105
119
  expect(result).toEqual(mockProfile);
106
120
  expect(mockedLoadHsProfileFile).toHaveBeenCalledWith(path.join(mockProjectDir, mockProjectConfig.srcDir), mockProfileName);
121
+ expect(mockedGetConfigAccountById).toHaveBeenCalledWith(mockProfile.accountId);
107
122
  });
108
123
  });
109
- describe('exitIfUsingProfiles()', () => {
124
+ describe('enforceProfileUsage()', () => {
110
125
  const mockProjectConfig = {
111
126
  srcDir: 'src',
112
127
  name: 'test-project',
113
128
  platformVersion: '1.0.0',
114
129
  };
115
130
  const mockProjectDir = '/test/project';
116
- it('should not exit when no profiles exist', async () => {
131
+ beforeEach(() => {
132
+ vi.clearAllMocks();
133
+ });
134
+ it('should not throw when no profiles exist', async () => {
117
135
  mockedGetAllHsProfiles.mockResolvedValue([]);
118
- await exitIfUsingProfiles(mockProjectConfig, mockProjectDir);
119
- expect(mockedUiLogger.error).not.toHaveBeenCalled();
120
- expect(mockExit).not.toHaveBeenCalled();
136
+ await expect(enforceProfileUsage(mockProjectConfig, mockProjectDir)).resolves.toBeUndefined();
121
137
  });
122
- it('should exit with error when profiles exist', async () => {
138
+ it('should throw error when profiles exist', async () => {
123
139
  mockedGetAllHsProfiles.mockResolvedValue(['profile1', 'profile2']);
124
- await expect(exitIfUsingProfiles(mockProjectConfig, mockProjectDir)).rejects.toThrow(`Process.exit called with code ${EXIT_CODES.ERROR}`);
125
- expect(mockedUiLogger.error).toHaveBeenCalledWith(lib.projectProfiles.exitIfUsingProfiles.errors.noProfileSpecified);
126
- expect(mockExit).toHaveBeenCalledWith(EXIT_CODES.ERROR);
140
+ await expect(enforceProfileUsage(mockProjectConfig, mockProjectDir)).rejects.toThrow(lib.projectProfiles.exitIfUsingProfiles.errors.noProfileSpecified);
141
+ });
142
+ it('should not throw when project config is null', async () => {
143
+ await expect(enforceProfileUsage(null, mockProjectDir)).resolves.toBeUndefined();
144
+ });
145
+ it('should not throw when project dir is null', async () => {
146
+ await expect(enforceProfileUsage(mockProjectConfig, null)).resolves.toBeUndefined();
147
+ });
148
+ });
149
+ describe('loadAndValidateProfile()', () => {
150
+ const mockProjectConfig = {
151
+ srcDir: 'src',
152
+ name: 'test-project',
153
+ platformVersion: '1.0.0',
154
+ };
155
+ const mockProjectDir = '/test/project';
156
+ const mockProfileName = 'test-profile';
157
+ const mockProfile = {
158
+ accountId: 123,
159
+ variables: {
160
+ key1: 'value1',
161
+ key2: 'value2',
162
+ },
163
+ };
164
+ beforeEach(() => {
165
+ vi.clearAllMocks();
166
+ });
167
+ it('should enforce profile usage when no profile name provided', async () => {
168
+ mockedGetAllHsProfiles.mockResolvedValue([]);
169
+ const result = await loadAndValidateProfile(mockProjectConfig, mockProjectDir, undefined);
170
+ expect(result).toBeUndefined();
171
+ expect(mockedGetAllHsProfiles).toHaveBeenCalledWith(path.join(mockProjectDir, mockProjectConfig.srcDir));
172
+ });
173
+ it('should throw when profiles exist but no profile name provided', async () => {
174
+ mockedGetAllHsProfiles.mockResolvedValue(['profile1']);
175
+ await expect(loadAndValidateProfile(mockProjectConfig, mockProjectDir, undefined)).rejects.toThrow(lib.projectProfiles.exitIfUsingProfiles.errors.noProfileSpecified);
176
+ });
177
+ it('should load and return account ID when profile is valid', async () => {
178
+ mockedLoadHsProfileFile.mockReturnValue(mockProfile);
179
+ mockedGetConfigAccountById.mockReturnValue({
180
+ accountId: mockProfile.accountId,
181
+ });
182
+ mockedGetHsProfileFilename.mockReturnValue('test-profile.hsprofile');
183
+ mockedValidateProfileVariables.mockReturnValue({ success: true });
184
+ const result = await loadAndValidateProfile(mockProjectConfig, mockProjectDir, mockProfileName);
185
+ expect(result).toBe(mockProfile.accountId);
186
+ expect(mockedLoadHsProfileFile).toHaveBeenCalledWith(path.join(mockProjectDir, mockProjectConfig.srcDir), mockProfileName);
187
+ expect(mockedValidateProfileVariables).toHaveBeenCalledWith(mockProfile.variables, mockProfileName);
188
+ });
189
+ it('should log profile header and footer when not silent', async () => {
190
+ mockedLoadHsProfileFile.mockReturnValue(mockProfile);
191
+ mockedGetConfigAccountById.mockReturnValue({
192
+ accountId: mockProfile.accountId,
193
+ });
194
+ mockedGetHsProfileFilename.mockReturnValue('test-profile.hsprofile');
195
+ mockedValidateProfileVariables.mockReturnValue({ success: true });
196
+ await loadAndValidateProfile(mockProjectConfig, mockProjectDir, mockProfileName, false);
197
+ expect(mockedUiBetaTag).toHaveBeenCalled();
198
+ expect(mockedUiLine).toHaveBeenCalled();
199
+ expect(mockedUiLogger.log).toHaveBeenCalled();
200
+ });
201
+ it('should not log when silent is true', async () => {
202
+ mockedLoadHsProfileFile.mockReturnValue(mockProfile);
203
+ mockedGetConfigAccountById.mockReturnValue({
204
+ accountId: mockProfile.accountId,
205
+ });
206
+ mockedValidateProfileVariables.mockReturnValue({ success: true });
207
+ await loadAndValidateProfile(mockProjectConfig, mockProjectDir, mockProfileName, true);
208
+ expect(mockedUiBetaTag).not.toHaveBeenCalled();
209
+ expect(mockedUiLine).not.toHaveBeenCalled();
210
+ });
211
+ it('should throw error when profile variables are invalid', async () => {
212
+ const invalidProfile = {
213
+ accountId: 123,
214
+ variables: {
215
+ invalid: 'value',
216
+ },
217
+ };
218
+ const validationErrors = ['Variable "invalid" is not allowed'];
219
+ mockedLoadHsProfileFile.mockReturnValue(invalidProfile);
220
+ mockedGetConfigAccountById.mockReturnValue({
221
+ accountId: invalidProfile.accountId,
222
+ });
223
+ mockedGetHsProfileFilename.mockReturnValue('test-profile.hsprofile');
224
+ mockedValidateProfileVariables.mockReturnValue({
225
+ success: false,
226
+ errors: validationErrors,
227
+ });
228
+ await expect(loadAndValidateProfile(mockProjectConfig, mockProjectDir, mockProfileName)).rejects.toThrow(lib.projectProfiles.loadProfile.errors.profileNotValid('test-profile.hsprofile', validationErrors));
229
+ });
230
+ it('should not validate when profile has no variables', async () => {
231
+ const profileWithoutVars = {
232
+ accountId: 123,
233
+ };
234
+ mockedLoadHsProfileFile.mockReturnValue(profileWithoutVars);
235
+ mockedGetConfigAccountById.mockReturnValue({
236
+ accountId: profileWithoutVars.accountId,
237
+ });
238
+ mockedGetHsProfileFilename.mockReturnValue('test-profile.hsprofile');
239
+ const result = await loadAndValidateProfile(mockProjectConfig, mockProjectDir, mockProfileName);
240
+ expect(result).toBe(profileWithoutVars.accountId);
241
+ expect(mockedValidateProfileVariables).not.toHaveBeenCalled();
242
+ });
243
+ });
244
+ describe('validateProjectForProfile()', () => {
245
+ const mockProjectConfig = {
246
+ srcDir: 'src',
247
+ name: 'test-project',
248
+ platformVersion: '2025.2',
249
+ };
250
+ const mockProjectDir = '/test/project';
251
+ const mockProfileName = 'test-profile';
252
+ const mockDerivedAccountId = 123;
253
+ const mockProfileFilename = 'test-profile.hsprofile';
254
+ const mockProfile = {
255
+ accountId: mockDerivedAccountId,
256
+ };
257
+ beforeEach(() => {
258
+ vi.clearAllMocks();
259
+ mockedGetHsProfileFilename.mockReturnValue(mockProfileFilename);
260
+ vi.mocked(SpinniesManager.init);
261
+ vi.mocked(SpinniesManager.add);
262
+ vi.mocked(SpinniesManager.succeed);
263
+ vi.mocked(SpinniesManager.fail);
264
+ // Mock dependencies for loadAndValidateProfile
265
+ mockedGetAllHsProfiles.mockResolvedValue([]);
266
+ mockedLoadHsProfileFile.mockReturnValue(mockProfile);
267
+ mockedGetConfigAccountById.mockReturnValue({
268
+ accountId: mockDerivedAccountId,
269
+ });
270
+ mockedValidateProfileVariables.mockReturnValue({ success: true });
271
+ vi.mocked(handleTranslate).mockResolvedValue(undefined);
272
+ });
273
+ it('should return empty array when validation succeeds', async () => {
274
+ const result = await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId);
275
+ expect(result).toEqual([]);
276
+ expect(SpinniesManager.init).toHaveBeenCalled();
277
+ expect(SpinniesManager.add).toHaveBeenCalledWith(`validatingProfile-${mockProfileName}`, {
278
+ text: commands.project.validate.spinners.validatingProfile(mockProfileFilename),
279
+ indent: 0,
280
+ });
281
+ expect(SpinniesManager.succeed).toHaveBeenCalledWith(`validatingProfile-${mockProfileName}`, {
282
+ text: commands.project.validate.spinners.profileValidationSucceeded(mockProfileFilename),
283
+ });
284
+ });
285
+ it('should call handleTranslate with profile account ID from profile', async () => {
286
+ await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId);
287
+ expect(handleTranslate).toHaveBeenCalledWith(mockProjectDir, mockProjectConfig, mockDerivedAccountId, false, mockProfileName);
288
+ });
289
+ it('should call handleTranslate with different profile account ID when profile has different ID', async () => {
290
+ const profileAccountId = 456;
291
+ const profileWithDifferentId = {
292
+ accountId: profileAccountId,
293
+ };
294
+ mockedLoadHsProfileFile.mockReturnValue(profileWithDifferentId);
295
+ mockedGetConfigAccountById.mockReturnValue({
296
+ accountId: profileAccountId,
297
+ });
298
+ await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId);
299
+ expect(handleTranslate).toHaveBeenCalledWith(mockProjectDir, mockProjectConfig, profileAccountId, false, mockProfileName);
300
+ });
301
+ it('should return error when profile has no accountId', async () => {
302
+ // @ts-expect-error causing an error on purpose
303
+ const profileWithoutId = {};
304
+ mockedLoadHsProfileFile.mockReturnValue(profileWithoutId);
305
+ const result = await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId);
306
+ expect(result.length).toBeGreaterThan(0);
307
+ expect(SpinniesManager.fail).toHaveBeenCalled();
308
+ expect(handleTranslate).not.toHaveBeenCalled();
309
+ });
310
+ it('should indent spinners when indentSpinners is true', async () => {
311
+ await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId, true);
312
+ expect(SpinniesManager.add).toHaveBeenCalledWith(`validatingProfile-${mockProfileName}`, {
313
+ text: commands.project.validate.spinners.validatingProfile(mockProfileFilename),
314
+ indent: 4,
315
+ });
316
+ });
317
+ it('should not indent spinners when indentSpinners is false', async () => {
318
+ await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId, false);
319
+ expect(SpinniesManager.add).toHaveBeenCalledWith(`validatingProfile-${mockProfileName}`, {
320
+ text: commands.project.validate.spinners.validatingProfile(mockProfileFilename),
321
+ indent: 0,
322
+ });
323
+ });
324
+ it('should return error array when profile loading fails', async () => {
325
+ mockedLoadHsProfileFile.mockReturnValue(null);
326
+ const result = await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId);
327
+ expect(result.length).toBeGreaterThan(0);
328
+ expect(SpinniesManager.fail).toHaveBeenCalledWith(`validatingProfile-${mockProfileName}`, {
329
+ text: commands.project.validate.spinners.profileValidationFailed(mockProfileFilename),
330
+ });
331
+ expect(handleTranslate).not.toHaveBeenCalled();
332
+ });
333
+ it('should return error when profile file loading throws', async () => {
334
+ mockedLoadHsProfileFile.mockImplementation(() => {
335
+ throw new Error('Failed to load profile file');
336
+ });
337
+ const result = await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId);
338
+ expect(result.length).toBeGreaterThan(0);
339
+ expect(SpinniesManager.fail).toHaveBeenCalledWith(`validatingProfile-${mockProfileName}`, {
340
+ text: commands.project.validate.spinners.profileValidationFailed(mockProfileFilename),
341
+ });
342
+ expect(handleTranslate).not.toHaveBeenCalled();
343
+ });
344
+ it('should return error array when translation fails', async () => {
345
+ const error = new Error('Translation failed');
346
+ vi.mocked(handleTranslate).mockRejectedValue(error);
347
+ const result = await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId);
348
+ expect(result).toHaveLength(2);
349
+ expect(result[0]).toBe(commands.project.validate.failure(mockProjectConfig.name));
350
+ expect(result[1]).toBe(error);
351
+ expect(SpinniesManager.fail).toHaveBeenCalledWith(`validatingProfile-${mockProfileName}`, {
352
+ text: commands.project.validate.spinners.invalidWithProfile(mockProfileFilename, mockProjectConfig.name),
353
+ });
354
+ });
355
+ it('should return string error when translation fails with non-Error', async () => {
356
+ const error = 'Translation error';
357
+ vi.mocked(handleTranslate).mockRejectedValue(error);
358
+ const result = await validateProjectForProfile(mockProjectConfig, mockProjectDir, mockProfileName, mockDerivedAccountId);
359
+ expect(result).toHaveLength(2);
360
+ expect(result[0]).toBe(commands.project.validate.failure(mockProjectConfig.name));
361
+ expect(result[1]).toBe(error);
362
+ });
363
+ it('should use correct spinner name based on profile name', async () => {
364
+ const customProfileName = 'custom-profile';
365
+ await validateProjectForProfile(mockProjectConfig, mockProjectDir, customProfileName, mockDerivedAccountId);
366
+ expect(SpinniesManager.add).toHaveBeenCalledWith(`validatingProfile-${customProfileName}`, expect.any(Object));
367
+ expect(SpinniesManager.succeed).toHaveBeenCalledWith(`validatingProfile-${customProfileName}`, expect.any(Object));
127
368
  });
128
369
  });
129
370
  });
@@ -1,6 +1,6 @@
1
1
  import { uiLogger } from '../ui/logger.js';
2
2
  import { initiateSync } from '@hubspot/local-dev-lib/api/sandboxSync';
3
- import { getAccountId } from '@hubspot/local-dev-lib/config';
3
+ import { getConfigAccountIfExists, getConfigAccountById, } from '@hubspot/local-dev-lib/config';
4
4
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
5
5
  import { mockHubSpotHttpError } from '../testUtils.js';
6
6
  import { getAvailableSyncTypes } from '../sandboxes.js';
@@ -13,7 +13,8 @@ vi.mock('../sandboxes');
13
13
  vi.mock('../ui/SpinniesManager');
14
14
  const mockedUiLogger = uiLogger;
15
15
  const mockedInitiateSync = initiateSync;
16
- const mockedGetAccountId = getAccountId;
16
+ const mockedGetConfigAccountIfExists = getConfigAccountIfExists;
17
+ const mockedGetConfigAccountById = getConfigAccountById;
17
18
  const mockedGetAvailableSyncTypes = getAvailableSyncTypes;
18
19
  const mockedSpinniesInit = SpinniesManager.init;
19
20
  const mockedSpinniesAdd = SpinniesManager.add;
@@ -23,22 +24,45 @@ describe('lib/sandboxSync', () => {
23
24
  const mockEnv = 'qa';
24
25
  const mockParentAccount = {
25
26
  name: 'Parent Account',
26
- portalId: 123,
27
+ accountId: 123,
27
28
  accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX,
28
29
  env: mockEnv,
30
+ authType: 'personalaccesskey',
29
31
  };
30
32
  const mockChildAccount = {
31
33
  name: 'Child Account',
32
- portalId: 456,
34
+ accountId: 456,
33
35
  accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
34
36
  env: mockEnv,
37
+ authType: 'personalaccesskey',
38
+ };
39
+ const mockChildAccountWithMissingId = {
40
+ name: 'Child Account',
41
+ accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
42
+ env: mockEnv,
43
+ authType: 'personalaccesskey',
35
44
  };
36
45
  const mockSyncTasks = [{ type: 'mock-sync-type' }];
37
46
  beforeEach(() => {
38
- mockedGetAccountId
39
- .mockReturnValueOnce(mockChildAccount.portalId)
40
- .mockReturnValueOnce(mockParentAccount.portalId);
47
+ mockedGetConfigAccountIfExists
48
+ .mockReturnValueOnce(mockChildAccount)
49
+ .mockReturnValueOnce(mockParentAccount);
41
50
  mockedGetAvailableSyncTypes.mockResolvedValue(mockSyncTasks);
51
+ // Mock SpinniesManager methods to prevent spinner errors
52
+ mockedSpinniesInit.mockImplementation(() => { });
53
+ mockedSpinniesAdd.mockImplementation(() => { });
54
+ mockedSpinniesSucceed.mockImplementation(() => { });
55
+ mockedSpinniesFail.mockImplementation(() => { });
56
+ // Mock account config for uiAccountDescription calls
57
+ mockedGetConfigAccountById.mockImplementation(accountId => {
58
+ if (accountId === mockChildAccount.accountId) {
59
+ return mockChildAccount;
60
+ }
61
+ if (accountId === mockParentAccount.accountId) {
62
+ return mockParentAccount;
63
+ }
64
+ return undefined; // Don't throw, just return undefined for unknown accounts
65
+ });
42
66
  });
43
67
  describe('syncSandbox()', () => {
44
68
  it('successfully syncs a sandbox with provided sync tasks', async () => {
@@ -46,7 +70,7 @@ describe('lib/sandboxSync', () => {
46
70
  await syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks);
47
71
  expect(mockedSpinniesInit).toHaveBeenCalled();
48
72
  expect(mockedSpinniesAdd).toHaveBeenCalled();
49
- expect(mockedInitiateSync).toHaveBeenCalledWith(mockParentAccount.portalId, mockChildAccount.portalId, mockSyncTasks, mockChildAccount.portalId);
73
+ expect(mockedInitiateSync).toHaveBeenCalledWith(mockParentAccount.accountId, mockChildAccount.accountId, mockSyncTasks, mockChildAccount.accountId);
50
74
  expect(mockedSpinniesSucceed).toHaveBeenCalled();
51
75
  });
52
76
  it('fetches sync types when no tasks are provided', async () => {
@@ -57,10 +81,8 @@ describe('lib/sandboxSync', () => {
57
81
  expect(mockedInitiateSync).toHaveBeenCalled();
58
82
  });
59
83
  it('throws error when account IDs are missing', async () => {
60
- mockedGetAccountId.mockReset();
61
- mockedGetAccountId.mockReturnValue(null);
62
84
  const errorRegex = new RegExp(`because your account has been removed from`);
63
- await expect(syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toThrow(errorRegex);
85
+ await expect(syncSandbox(mockChildAccountWithMissingId, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toThrow(errorRegex);
64
86
  });
65
87
  it('handles sync in progress error', async () => {
66
88
  const error = mockHubSpotHttpError('', {
@@ -1,19 +1,23 @@
1
1
  import { uiLogger } from '../ui/logger.js';
2
2
  import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs';
3
3
  import { fetchTypes } from '@hubspot/local-dev-lib/api/sandboxSync';
4
- import { getAccountId, getConfigAccounts } from '@hubspot/local-dev-lib/config';
4
+ import { getAllConfigAccounts, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
5
5
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
6
6
  import { mockHubSpotHttpError } from '../testUtils.js';
7
7
  import { getSandboxTypeAsString, getHasSandboxesByType, getAvailableSyncTypes, validateSandboxUsageLimits, handleSandboxCreateError, } from '../sandboxes.js';
8
+ import { isMissingScopeError, isSpecifiedError, } from '@hubspot/local-dev-lib/errors/index';
8
9
  vi.mock('../ui/logger.js');
9
10
  vi.mock('@hubspot/local-dev-lib/api/sandboxHubs');
10
11
  vi.mock('@hubspot/local-dev-lib/api/sandboxSync');
11
12
  vi.mock('@hubspot/local-dev-lib/config');
12
- const mockedGetAccountId = getAccountId;
13
+ vi.mock('@hubspot/local-dev-lib/errors/index');
14
+ const mockedGetConfigAccountIfExists = getConfigAccountIfExists;
13
15
  const mockedGetSandboxUsageLimits = getSandboxUsageLimits;
14
16
  const mockedFetchTypes = fetchTypes;
15
- const mockedGetConfigAccounts = getConfigAccounts;
17
+ const mockedGetAllConfigAccounts = getAllConfigAccounts;
16
18
  const mockedUiLogger = uiLogger;
19
+ const mockedIsMissingScopeError = isMissingScopeError;
20
+ const mockedIsSpecifiedError = isSpecifiedError;
17
21
  describe('lib/sandboxes', () => {
18
22
  describe('getSandboxTypeAsString()', () => {
19
23
  it('returns "development" for development sandbox type', () => {
@@ -29,14 +33,13 @@ describe('lib/sandboxes', () => {
29
33
  describe('getHasSandboxesByType()', () => {
30
34
  const mockParentAccount = {
31
35
  name: 'Parent Account',
32
- portalId: 123,
33
- authType: undefined,
36
+ accountId: 123,
34
37
  accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX,
35
38
  env: 'qa',
36
39
  };
37
40
  it('returns true when sandbox of specified type exists', () => {
38
- mockedGetAccountId.mockReturnValue(mockParentAccount.portalId);
39
- mockedGetConfigAccounts.mockReturnValue([
41
+ mockedGetConfigAccountIfExists.mockReturnValue(mockParentAccount);
42
+ mockedGetAllConfigAccounts.mockReturnValue([
40
43
  mockParentAccount,
41
44
  {
42
45
  ...mockParentAccount,
@@ -47,25 +50,25 @@ describe('lib/sandboxes', () => {
47
50
  expect(getHasSandboxesByType(mockParentAccount, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX)).toBe(true);
48
51
  });
49
52
  it('returns false when no sandbox of specified type exists', () => {
50
- mockedGetConfigAccounts.mockReturnValue([mockParentAccount]);
53
+ mockedGetAllConfigAccounts.mockReturnValue([mockParentAccount]);
51
54
  expect(getHasSandboxesByType(mockParentAccount, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX)).toBe(false);
52
55
  });
53
56
  });
54
57
  describe('getAvailableSyncTypes()', () => {
55
58
  const mockParentAccount = {
56
59
  name: 'Parent Account',
57
- portalId: 123,
60
+ accountId: 123,
58
61
  env: 'qa',
59
62
  };
60
63
  const mockChildAccount = {
61
64
  ...mockParentAccount,
62
- portalId: 456,
65
+ accountId: 456,
63
66
  };
64
67
  it('returns available sync types when fetch is successful', async () => {
65
68
  const mockSyncTypes = [{ name: 'type1' }, { name: 'type2' }];
66
- mockedGetAccountId
67
- .mockReturnValue(mockParentAccount.portalId)
68
- .mockReturnValue(mockChildAccount.portalId);
69
+ mockedGetConfigAccountIfExists
70
+ .mockReturnValue(mockParentAccount.accountId)
71
+ .mockReturnValue(mockChildAccount.accountId);
69
72
  mockedFetchTypes.mockResolvedValue({
70
73
  data: { results: mockSyncTypes },
71
74
  });
@@ -80,12 +83,11 @@ describe('lib/sandboxes', () => {
80
83
  describe('validateSandboxUsageLimits()', () => {
81
84
  const mockAccount = {
82
85
  name: 'Test Account',
83
- portalId: 123,
84
- authType: undefined,
86
+ accountId: 123,
85
87
  env: 'qa',
86
88
  };
87
89
  it('validates successfully when limits are not reached', async () => {
88
- mockedGetAccountId.mockReturnValue(mockAccount.portalId);
90
+ mockedGetConfigAccountIfExists.mockReturnValue(mockAccount.accountId);
89
91
  mockedGetSandboxUsageLimits.mockResolvedValue({
90
92
  data: {
91
93
  usage: { DEVELOPER: { available: 1, limit: 3 } },
@@ -94,8 +96,8 @@ describe('lib/sandboxes', () => {
94
96
  await expect(validateSandboxUsageLimits(mockAccount, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, 'qa')).resolves.not.toThrow();
95
97
  });
96
98
  it('throws error when development sandbox limit is reached', async () => {
97
- mockedGetAccountId.mockReturnValue(mockAccount.portalId);
98
- mockedGetConfigAccounts.mockReturnValue([]);
99
+ mockedGetConfigAccountIfExists.mockReturnValue(mockAccount.accountId);
100
+ mockedGetAllConfigAccounts.mockReturnValue([]);
99
101
  mockedGetSandboxUsageLimits.mockResolvedValue({
100
102
  data: {
101
103
  usage: { DEVELOPER: { available: 0, limit: 1 } },
@@ -116,6 +118,9 @@ describe('lib/sandboxes', () => {
116
118
  category: 'MISSING_SCOPES',
117
119
  },
118
120
  });
121
+ // Mock the error checking function to return true for missing scope error
122
+ mockedIsMissingScopeError.mockReturnValue(true);
123
+ mockedIsSpecifiedError.mockReturnValue(false);
119
124
  expect(() => handleSandboxCreateError(error, mockEnv, mockName, mockAccountId)).toThrow(error);
120
125
  expect(mockedUiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/The personal access key you provided doesn't include sandbox permissions/));
121
126
  expect(mockedUiLogger.info).toHaveBeenCalledWith(expect.stringMatching(/To update CLI permissions for/));
@@ -128,6 +133,9 @@ describe('lib/sandboxes', () => {
128
133
  subCategory: 'SandboxErrors.USER_ACCESS_NOT_ALLOWED',
129
134
  },
130
135
  });
136
+ // Mock the error checking function to return true for this error type
137
+ mockedIsMissingScopeError.mockReturnValue(false);
138
+ mockedIsSpecifiedError.mockReturnValue(true);
131
139
  expect(() => handleSandboxCreateError(error, mockEnv, mockName, mockAccountId)).toThrow(error);
132
140
  expect(mockedUiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/your permission set doesn't allow you to create the sandbox/));
133
141
  });
@@ -139,8 +147,11 @@ describe('lib/sandboxes', () => {
139
147
  subCategory: 'SandboxErrors.DEVELOPMENT_SANDBOX_ACCESS_NOT_ALLOWED',
140
148
  },
141
149
  });
150
+ // Mock the error checking function to return true for this error type
151
+ mockedIsMissingScopeError.mockReturnValue(false);
152
+ mockedIsSpecifiedError.mockReturnValue(true);
142
153
  expect(() => handleSandboxCreateError(error, mockEnv, mockName, mockAccountId)).toThrow(error);
143
- expect(mockedUiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/does not have access to development sandboxes/));
154
+ expect(mockedUiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/Couldn't create.*because your account has been removed from.*or your permission set doesn't allow you to create/));
144
155
  });
145
156
  });
146
157
  });