Skip to content

Commit 3bedde4

Browse files
idanlufgregra81
andauthored
fix/idanluf/apps-CLI-error-handeling-fixes (#138)
* Refactor scheduler commands-so error will caught in base command Added API error messages for invalid tokens & Added new test for it Added file size validation for uploads. * fix comments: package.json: bump the version api-service.test: fix imports order push-service: add max_depth to recursion api-service: create new function of isAuthError * add error to user in checkFileSizesInDirectory function --------- Co-authored-by: Greg Rashkevitch <gregr@monday.com>
1 parent f109ede commit 3bedde4

9 files changed

Lines changed: 241 additions & 143 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@mondaycom/apps-cli",
3-
"version": "4.9.0",
3+
"version": "4.9.1",
44
"description": "A cli tool to manage apps (and monday-code projects) in monday.com",
55
"author": "monday.com Apps Team",
66
"type": "module",

src/commands/scheduler/create.ts

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,54 +27,49 @@ export default class SchedulerCreate extends AuthenticatedCommand {
2727
let { appId, name, description, schedule, targetUrl, maxRetries, minBackoffDuration, timeout } = flags;
2828
const { region } = flags;
2929
const parsedRegion = getRegionFromString(region);
30-
try {
31-
if (!appId) appId = await DynamicChoicesService.chooseApp();
32-
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
33-
if (!name) name = await PromptService.promptInput(SchedulerMessages.name, true);
34-
if (!schedule) schedule = await PromptService.promptInput(SchedulerMessages.schedule, true);
35-
validateCronExpression(schedule);
36-
if (!targetUrl) targetUrl = await PromptService.promptInput(SchedulerMessages.targetUrl, true);
37-
targetUrl = addPrefixIfNotExists(targetUrl, '/');
38-
validateTargetUrl(targetUrl);
39-
if (!description) description = await PromptService.promptInput(SchedulerMessages.description, false, true);
40-
if (!maxRetries) maxRetries = await PromptService.promptInputNumber(SchedulerMessages.maxRetries, false, true);
41-
if (!minBackoffDuration)
42-
minBackoffDuration = await PromptService.promptInputNumber(SchedulerMessages.minBackoffDuration, false, true);
43-
if (!timeout) timeout = await PromptService.promptInputNumber(SchedulerMessages.timeout, false, true);
4430

45-
logger.debug(`Creating scheduler job for appId: ${appId}`, this.DEBUG_TAG);
46-
this.preparePrintCommand(this, {
47-
appId,
31+
if (!appId) appId = await DynamicChoicesService.chooseApp();
32+
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
33+
if (!name) name = await PromptService.promptInput(SchedulerMessages.name, true);
34+
if (!schedule) schedule = await PromptService.promptInput(SchedulerMessages.schedule, true);
35+
validateCronExpression(schedule);
36+
if (!targetUrl) targetUrl = await PromptService.promptInput(SchedulerMessages.targetUrl, true);
37+
targetUrl = addPrefixIfNotExists(targetUrl, '/');
38+
validateTargetUrl(targetUrl);
39+
if (!description) description = await PromptService.promptInput(SchedulerMessages.description, false, true);
40+
if (!maxRetries) maxRetries = await PromptService.promptInputNumber(SchedulerMessages.maxRetries, false, true);
41+
if (!minBackoffDuration)
42+
minBackoffDuration = await PromptService.promptInputNumber(SchedulerMessages.minBackoffDuration, false, true);
43+
if (!timeout) timeout = await PromptService.promptInputNumber(SchedulerMessages.timeout, false, true);
44+
45+
logger.debug(`Creating scheduler job for appId: ${appId}`, this.DEBUG_TAG);
46+
this.preparePrintCommand(this, {
47+
appId,
48+
name,
49+
description,
50+
schedule,
51+
targetUrl,
52+
maxRetries,
53+
minBackoffDuration,
54+
timeout,
55+
region: selectedRegion,
56+
});
57+
58+
const job = await SchedulerService.createJob(
59+
appId,
60+
{
4861
name,
49-
description,
5062
schedule,
5163
targetUrl,
52-
maxRetries,
53-
minBackoffDuration,
54-
timeout,
55-
region: selectedRegion,
56-
});
57-
58-
const job = await SchedulerService.createJob(
59-
appId,
60-
{
61-
name,
62-
description,
63-
schedule,
64-
targetUrl,
65-
...(description ? { description } : {}),
66-
...(isDefined(maxRetries) || isDefined(minBackoffDuration)
67-
? { retryConfig: { maxRetries, minBackoffDuration } }
68-
: {}),
69-
...(isDefined(timeout) ? { timeout } : {}),
70-
},
71-
selectedRegion,
72-
);
64+
...(description ? { description } : {}),
65+
...(isDefined(maxRetries) || isDefined(minBackoffDuration)
66+
? { retryConfig: { maxRetries, minBackoffDuration } }
67+
: {}),
68+
...(isDefined(timeout) ? { timeout } : {}),
69+
},
70+
selectedRegion,
71+
);
7372

74-
printJobs([job]);
75-
} catch (error: any) {
76-
logger.debug(error, this.DEBUG_TAG);
77-
process.exit(1);
78-
}
73+
printJobs([job]);
7974
}
8075
}

src/commands/scheduler/delete.ts

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,18 @@ export default class SchedulerDelete extends AuthenticatedCommand {
1919
const { region } = flags;
2020
const parsedRegion = getRegionFromString(region);
2121

22-
try {
23-
if (!appId) appId = await DynamicChoicesService.chooseApp();
24-
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
25-
if (!name) name = await DynamicChoicesService.chooseSchedulerJob(appId, selectedRegion);
22+
if (!appId) appId = await DynamicChoicesService.chooseApp();
23+
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
24+
if (!name) name = await DynamicChoicesService.chooseSchedulerJob(appId, selectedRegion);
2625

27-
logger.debug(`Deleting scheduler job ${name} for appId: ${appId}`, this.DEBUG_TAG);
28-
this.preparePrintCommand(this, {
29-
appId,
30-
name,
31-
region: selectedRegion,
32-
});
26+
logger.debug(`Deleting scheduler job ${name} for appId: ${appId}`, this.DEBUG_TAG);
27+
this.preparePrintCommand(this, {
28+
appId,
29+
name,
30+
region: selectedRegion,
31+
});
3332

34-
await SchedulerService.deleteJob(appId, name, selectedRegion);
35-
logger.info(`Successfully deleted job: ${name}`);
36-
} catch (error: any) {
37-
console.log(error);
38-
logger.debug(error, this.DEBUG_TAG);
39-
process.exit(1);
40-
}
33+
await SchedulerService.deleteJob(appId, name, selectedRegion);
34+
logger.info(`Successfully deleted job: ${name}`);
4135
}
4236
}

src/commands/scheduler/list.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,14 @@ export default class SchedulerList extends AuthenticatedCommand {
2121
let { appId } = flags;
2222
const { region } = flags;
2323
const parsedRegion = getRegionFromString(region);
24-
try {
25-
appId = appId ? Number(appId) : await DynamicChoicesService.chooseApp();
26-
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
2724

28-
logger.debug(`Listing scheduler jobs for appId: ${appId}`, this.DEBUG_TAG);
29-
this.preparePrintCommand(this, { appId, region: selectedRegion });
25+
appId = appId ? Number(appId) : await DynamicChoicesService.chooseApp();
26+
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
3027

31-
const jobs = await SchedulerService.listJobs(appId, selectedRegion);
32-
printJobs(jobs);
33-
} catch (error: any) {
34-
logger.debug(error, this.DEBUG_TAG);
35-
process.exit(1);
36-
}
28+
logger.debug(`Listing scheduler jobs for appId: ${appId}`, this.DEBUG_TAG);
29+
this.preparePrintCommand(this, { appId, region: selectedRegion });
30+
31+
const jobs = await SchedulerService.listJobs(appId, selectedRegion);
32+
printJobs(jobs);
3733
}
3834
}

src/commands/scheduler/run.ts

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,18 @@ export default class SchedulerRun extends AuthenticatedCommand {
1919
const { region } = flags;
2020
const parsedRegion = getRegionFromString(region);
2121

22-
try {
23-
if (!appId) appId = await DynamicChoicesService.chooseApp();
24-
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
25-
if (!name) name = await DynamicChoicesService.chooseSchedulerJob(appId, selectedRegion);
22+
if (!appId) appId = await DynamicChoicesService.chooseApp();
23+
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
24+
if (!name) name = await DynamicChoicesService.chooseSchedulerJob(appId, selectedRegion);
2625

27-
logger.debug(`Running scheduler job ${name} for appId: ${appId}`, this.DEBUG_TAG);
28-
this.preparePrintCommand(this, {
29-
appId,
30-
name,
31-
region: selectedRegion,
32-
});
26+
logger.debug(`Running scheduler job ${name} for appId: ${appId}`, this.DEBUG_TAG);
27+
this.preparePrintCommand(this, {
28+
appId,
29+
name,
30+
region: selectedRegion,
31+
});
3332

34-
await SchedulerService.runJob(appId, name, selectedRegion);
35-
logger.info(`Successfully triggered job: ${name}`);
36-
} catch (error: any) {
37-
console.log(error);
38-
logger.debug(error, this.DEBUG_TAG);
39-
process.exit(1);
40-
}
33+
await SchedulerService.runJob(appId, name, selectedRegion);
34+
logger.info(`Successfully triggered job: ${name}`);
4135
}
4236
}

src/commands/scheduler/update.ts

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -28,65 +28,61 @@ export default class SchedulerUpdate extends AuthenticatedCommand {
2828
let { appId, name, description, schedule, targetUrl, maxRetries, minBackoffDuration, timeout } = flags;
2929
const { region } = flags;
3030
const parsedRegion = getRegionFromString(region);
31-
try {
32-
if (!appId) appId = await DynamicChoicesService.chooseApp();
33-
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
34-
if (!name) name = await DynamicChoicesService.chooseSchedulerJob(appId, selectedRegion);
3531

36-
// Get the current job details
37-
const jobs = await SchedulerService.listJobs(appId, selectedRegion);
38-
const currentJob = jobs.find(job => job.name === name);
39-
if (!currentJob) {
40-
throw new Error(`Job ${name} not found`);
41-
}
32+
if (!appId) appId = await DynamicChoicesService.chooseApp();
33+
const selectedRegion = await chooseRegionIfNeeded(parsedRegion, { appId });
34+
if (!name) name = await DynamicChoicesService.chooseSchedulerJob(appId, selectedRegion);
4235

43-
logger.info(`All parameters are optional, press enter to skip`);
44-
// Only prompt for fields that weren't provided in flags
45-
if (!description) description = await PromptService.promptInput(SchedulerMessages.description, false, true);
46-
if (!schedule) schedule = await PromptService.promptInput(SchedulerMessages.schedule, false, true);
47-
if (schedule) validateCronExpression(schedule);
48-
if (!targetUrl) targetUrl = await PromptService.promptInput(SchedulerMessages.targetUrl, false, true);
49-
targetUrl = addPrefixIfNotExists(targetUrl, '/');
50-
if (targetUrl) validateTargetUrl(targetUrl);
51-
if (!maxRetries) maxRetries = await PromptService.promptInputNumber(SchedulerMessages.maxRetries, false, true);
52-
if (!minBackoffDuration)
53-
minBackoffDuration = await PromptService.promptInputNumber(SchedulerMessages.minBackoffDuration, false, true);
54-
if (!timeout) timeout = await PromptService.promptInputNumber(SchedulerMessages.timeout, false, true);
36+
// Get the current job details
37+
const jobs = await SchedulerService.listJobs(appId, selectedRegion);
38+
const currentJob = jobs.find(job => job.name === name);
39+
if (!currentJob) {
40+
throw new Error(`Job ${name} not found`);
41+
}
42+
43+
logger.info(`All parameters are optional, press enter to skip`);
44+
// Only prompt for fields that weren't provided in flags
45+
if (!description) description = await PromptService.promptInput(SchedulerMessages.description, false, true);
46+
if (!schedule) schedule = await PromptService.promptInput(SchedulerMessages.schedule, false, true);
47+
if (schedule) validateCronExpression(schedule);
48+
if (!targetUrl) targetUrl = await PromptService.promptInput(SchedulerMessages.targetUrl, false, true);
49+
targetUrl = addPrefixIfNotExists(targetUrl, '/');
50+
if (targetUrl) validateTargetUrl(targetUrl);
51+
if (!maxRetries) maxRetries = await PromptService.promptInputNumber(SchedulerMessages.maxRetries, false, true);
52+
if (!minBackoffDuration)
53+
minBackoffDuration = await PromptService.promptInputNumber(SchedulerMessages.minBackoffDuration, false, true);
54+
if (!timeout) timeout = await PromptService.promptInputNumber(SchedulerMessages.timeout, false, true);
5555

56-
logger.debug(`Updating scheduler job ${name} for appId: ${appId}`, this.DEBUG_TAG);
57-
this.preparePrintCommand(this, {
58-
appId,
59-
name,
60-
description,
61-
schedule,
62-
targetUrl,
63-
maxRetries,
64-
minBackoffDuration,
65-
timeout,
66-
region: selectedRegion,
67-
});
56+
logger.debug(`Updating scheduler job ${name} for appId: ${appId}`, this.DEBUG_TAG);
57+
this.preparePrintCommand(this, {
58+
appId,
59+
name,
60+
description,
61+
schedule,
62+
targetUrl,
63+
maxRetries,
64+
minBackoffDuration,
65+
timeout,
66+
region: selectedRegion,
67+
});
6868

69-
// Create update payload with only the fields that were provided
70-
const updatePayload: UpdateJobRequest = {};
71-
if (isDefinedAndNotEmpty(description)) updatePayload.description = description;
72-
if (isDefinedAndNotEmpty(schedule)) updatePayload.schedule = schedule;
73-
if (isDefinedAndNotEmpty(targetUrl)) updatePayload.targetUrl = targetUrl;
74-
if (isDefined(maxRetries) || isDefined(minBackoffDuration)) {
75-
updatePayload.retryConfig = { maxRetries, minBackoffDuration };
76-
}
69+
// Create update payload with only the fields that were provided
70+
const updatePayload: UpdateJobRequest = {};
71+
if (isDefinedAndNotEmpty(description)) updatePayload.description = description;
72+
if (isDefinedAndNotEmpty(schedule)) updatePayload.schedule = schedule;
73+
if (isDefinedAndNotEmpty(targetUrl)) updatePayload.targetUrl = targetUrl;
74+
if (isDefined(maxRetries) || isDefined(minBackoffDuration)) {
75+
updatePayload.retryConfig = { maxRetries, minBackoffDuration };
76+
}
7777

78-
if (isDefined(timeout)) updatePayload.timeout = timeout;
78+
if (isDefined(timeout)) updatePayload.timeout = timeout;
7979

80-
if (isDefinedAndNotEmpty(updatePayload)) {
81-
const job = await SchedulerService.updateJob(appId, name, updatePayload, selectedRegion);
82-
printJobs([job]);
83-
logger.info(`Successfully updated job: ${name}`);
84-
} else {
85-
logger.info(`No changes to update for job: ${name}`);
86-
}
87-
} catch (error: any) {
88-
logger.debug(error, this.DEBUG_TAG);
89-
process.exit(1);
80+
if (isDefinedAndNotEmpty(updatePayload)) {
81+
const job = await SchedulerService.updateJob(appId, name, updatePayload, selectedRegion);
82+
printJobs([job]);
83+
logger.info(`Successfully updated job: ${name}`);
84+
} else {
85+
logger.info(`No changes to update for job: ${name}`);
9086
}
9187
}
9288
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// eslint-disable-next-line node/no-extraneous-import,n/no-extraneous-import
2+
import { describe, expect, it, jest } from '@jest/globals';
3+
import axios, { AxiosError, AxiosResponse } from 'axios';
4+
5+
import { execute } from 'services/api-service';
6+
import { HttpError } from 'types/errors';
7+
import { HttpMethodTypes } from 'types/services/api-service';
8+
9+
jest.mock('axios');
10+
const mockedAxios = axios as jest.Mocked<typeof axios>;
11+
12+
jest.mock('services/config-service', () => ({
13+
ConfigService: {
14+
getConfigDataByKey: jest.fn().mockReturnValue('fake-access-token'),
15+
},
16+
}));
17+
18+
jest.mock('services/env-service', () => ({
19+
getAppsDomain: jest.fn().mockReturnValue('https://api.monday.com'),
20+
}));
21+
22+
describe('ApiService', () => {
23+
it('should throw HttpError with helpful token message for invalid token', async () => {
24+
const mockResponse = {
25+
status: 406,
26+
data: {},
27+
statusText: 'Not Acceptable',
28+
headers: {},
29+
} as AxiosResponse;
30+
31+
const axiosError = new AxiosError('Request failed');
32+
axiosError.response = mockResponse;
33+
34+
mockedAxios.request.mockRejectedValue(axiosError);
35+
36+
const error = (await execute({
37+
url: '/test',
38+
method: HttpMethodTypes.GET,
39+
}).catch((error_: Error) => error_)) as HttpError;
40+
41+
expect(error).toBeInstanceOf(HttpError);
42+
expect(error.message).toEqual(
43+
'Invalid or expired access token.\n\n' +
44+
'To fix this, run:\n' +
45+
' mapps init -t YOUR_ACCESS_TOKEN\n\n' +
46+
'Or run: mapps init\n' +
47+
'(and you will be prompted for your token)\n\n',
48+
);
49+
});
50+
});

0 commit comments

Comments
 (0)