Skip to content
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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@
"jsmin": "1.0.1",
"json-interpolate": "^1.0.3",
"lru-cache": "^7.14.1",
"ms": "^2.1.3",
"node-clipboardy": "^1.0.3",
"node-fetch": "2.6.1",
"pino": "^9.5.0",
Expand Down
208 changes: 9 additions & 199 deletions src/commands/api-mesh/__tests__/log-get-bulk.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,186 +295,6 @@ describe('GetBulkLogCommand startTime and endTime validation', () => {
);
});

describe('GetBulkLogCommand with --past and --from flags', () => {
let parseSpy;

let now;
let fromDate;
beforeEach(() => {
now = new Date();
fromDate = new Date(now);
fromDate.setDate(fromDate.getDate() - 29); // Set fromDate to 29 days ago
parseSpy = jest.spyOn(GetBulkLogCommand.prototype, 'parse').mockResolvedValue({
flags: {
past: '20mins',
from: fromDate.toISOString().slice(0, 10) + ':12:00:00',
filename: 'test.csv',
ignoreCache: false,
},
});

initSdk.mockResolvedValue({
imsOrgId: 'orgId',
imsOrgCode: 'orgCode',
projectId: 'projectId',
workspaceId: 'workspaceId',
workspaceName: 'workspaceName',
});
getMeshId.mockResolvedValue('meshId');
getPresignedUrls.mockResolvedValue({
presignedUrls: [{ key: 'log1.csv', url: 'http://example.com/someHash' }],
totalSize: 2048,
});
promptConfirm.mockResolvedValue(true);
global.requestId = 'dummy_request_id';
});

afterEach(() => {
jest.clearAllMocks();
// clear the date objects
now = null;
fromDate = null;
});

test('runs with valid --past and --from flags', async () => {
fs.existsSync.mockReturnValue(true);
fs.statSync.mockReturnValue({ size: 0 });

const mockWriteStream = {
write: jest.fn(),
end: jest.fn(),
on: jest.fn((event, callback) => {
if (event === 'finish') {
callback();
}
}),
};
fs.createWriteStream.mockReturnValue(mockWriteStream);

const command = new GetBulkLogCommand([], {});
await command.run();

expect(initSdk).toHaveBeenCalled();
expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName');
expect(getPresignedUrls).toHaveBeenCalledWith(
'orgCode',
'projectId',
'workspaceId',
'meshId',
expect.any(String),
expect.any(String),
);
expect(fs.createWriteStream).toHaveBeenCalledWith(path.resolve(process.cwd(), 'test.csv'), {
flags: 'a',
});
expect(mockWriteStream.write).toHaveBeenCalled();
expect(mockWriteStream.end).toHaveBeenCalled();
});

test('throws an error with invalid --from date components', async () => {
parseSpy.mockResolvedValueOnce({
flags: {
past: '20mins',
from: fromDate.toISOString().slice(0, 10) + ':25:61:61',
filename: 'test.csv',
ignoreCache: false,
},
});

const command = new GetBulkLogCommand([], {});
await expect(command.run()).rejects.toThrow(
'Invalid date components passed in --from. Correct the date.',
);
});

test('throws an error with invalid --from date format', async () => {
parseSpy.mockResolvedValueOnce({
flags: {
past: '15mins',
from: fromDate.toISOString().slice(0, 10).replace(/-/g, ':') + ':15:00:00',
filename: 'test.csv',
ignoreCache: false,
},
});

const command = new GetBulkLogCommand([], {});
await expect(command.run()).rejects.toThrow(
'Invalid format. Use the format YYYY-MM-DD:HH:MM:SS for --from.',
);
});

test('runs with valid --past flag without --from', async () => {
parseSpy.mockResolvedValueOnce({
flags: {
past: '15mins',
filename: 'test.csv',
ignoreCache: false,
},
});

fs.existsSync.mockReturnValue(true);
fs.statSync.mockReturnValue({ size: 0 });

const command = new GetBulkLogCommand([], {});
await command.run();

expect(initSdk).toHaveBeenCalled();
expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName');
expect(getPresignedUrls).toHaveBeenCalledWith(
'orgCode',
'projectId',
'workspaceId',
'meshId',
expect.any(String),
expect.any(String),
);
});

test('throws an error with edge case for --past duration', async () => {
parseSpy.mockResolvedValueOnce({
flags: {
past: '0s',
from: fromDate.toISOString().slice(0, 10) + ':12:00:00',
filename: 'test.csv',
ignoreCache: false,
},
});

const command = new GetBulkLogCommand([], {});
await expect(command.run()).rejects.toThrow(
'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".',
);
});

test('runs with edge case for --from date', async () => {
parseSpy.mockResolvedValueOnce({
flags: {
past: '15mins',
from: fromDate.toISOString().slice(0, 10) + ':00:00:00',
filename: 'test.csv',
ignoreCache: false,
},
});

fs.existsSync.mockReturnValue(true);
fs.statSync.mockReturnValue({ size: 0 });

const command = new GetBulkLogCommand([], {});
await command.run();

expect(initSdk).toHaveBeenCalled();
expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName');
expect(getPresignedUrls).toHaveBeenCalledWith(
'orgCode',
'projectId',
'workspaceId',
'meshId',
expect.any(String),
expect.any(String),
);
});
});

describe('validateDateTimeRange', () => {
const testCases = [
{
Expand Down Expand Up @@ -524,16 +344,9 @@ describe('validateDateTimeRange', () => {

describe('parsePastDuration', () => {
const validDurations = [
['20m', 20 * 60 * 1000],
['20 m', 20 * 60 * 1000],
['20min', 20 * 60 * 1000],
['20 min', 20 * 60 * 1000],
['20mins', 20 * 60 * 1000],
['20 mins', 20 * 60 * 1000],
['20minute', 20 * 60 * 1000],
['20 minute', 20 * 60 * 1000],
['20minutes', 20 * 60 * 1000],
['20 minutes', 20 * 60 * 1000],
['20', 20 * 60 * 1000],
['30', 30 * 60 * 1000],
['15', 15 * 60 * 1000],
];

test.each(validDurations)(
Expand All @@ -544,14 +357,11 @@ describe('parsePastDuration', () => {
},
);

const invalidDurations = ['20h', '20 hours', '20s', '20 seconds'];
const invalidDurations = ['20h', '20 hours', '20s', '20 seconds', 'minutes', 'NaN', 'abc', ''];

test.each(invalidDurations)(
'throws an error for invalid past duration format "%s"',
invalidPastDuration => {
expect(() => parsePastDuration(invalidPastDuration)).toThrow(
'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".',
);
},
);
test.each(invalidDurations)('throws an error for non-numeric input "%s"', invalidPastDuration => {
expect(() => parsePastDuration(invalidPastDuration)).toThrow(
'Invalid format. The time window must be an integer, for example "20" or "15".',
);
});
});
28 changes: 4 additions & 24 deletions src/commands/api-mesh/log-get-bulk.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ const {
endTimeFlag,
logFilenameFlag,
pastFlag,
fromFlag,
suggestCorrectedDateFormat,
parsePastDuration,
validateDateTimeRange,
validateDateTimeFormat,
localToUTCTime,
} = require('../../utils');

require('dotenv').config();
Expand All @@ -28,7 +26,6 @@ class GetBulkLogCommand extends Command {
endTime: endTimeFlag,
filename: logFilenameFlag,
past: pastFlag,
from: fromFlag,
};

async run() {
Expand Down Expand Up @@ -89,26 +86,9 @@ class GetBulkLogCommand extends Command {
formattedEndTime = flags.endTime.replace(/-|:|Z/g, '').replace('T', 'T');
} else if (flags.past) {
const pastTimeWindow = parsePastDuration(flags.past);
if (flags.from) {
let convertedTime;
const dateTimeRegex = /^\d{4}-\d{2}-\d{2}:\d{2}:\d{2}:\d{2}$/;
if (!dateTimeRegex.test(flags.from)) {
this.error('Invalid format. Use the format YYYY-MM-DD:HH:MM:SS for --from.');
} else {
try {
convertedTime = await localToUTCTime(flags.from.toString());
} catch (error) {
this.error(`Invalid date components passed in --from. Correct the date.`);
}
}
// add the past window to the converted time to get the end time to fetch logs from the past
calculatedStartTime = new Date(convertedTime);
calculatedEndTime = new Date(calculatedStartTime.getTime() + pastTimeWindow);
} else {
// subtract the past window from the current time to get the start time to fetch recent logs from now
calculatedEndTime = new Date();
calculatedStartTime = new Date(calculatedEndTime.getTime() - pastTimeWindow);
}
// Subtract the past window from the current time to get the start time to fetch recent logs from now
calculatedEndTime = new Date();
calculatedStartTime = new Date(calculatedEndTime.getTime() - pastTimeWindow);

// Validate the calculated start and end times range
validateDateTimeRange(calculatedStartTime, calculatedEndTime);
Expand All @@ -120,7 +100,7 @@ class GetBulkLogCommand extends Command {
return;
} else {
this.error(
'Missing required flags. Provide at least one flag --startTime, --endTime, or --past --from or type `mesh log:get-bulk --help` for more information.',
'Missing required flags. Provide a time range with --startTime and --endTime flags, or use the --past flag for more recent logs. Use the `mesh log:get-bulk --help` command for more information.',
);
return;
}
Expand Down
25 changes: 9 additions & 16 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const { readFile } = require('fs/promises');
const { interpolateMesh } = require('./helpers');
const dotenv = require('dotenv');
const YAML = require('yaml');
const ms = require('ms');
const parseEnv = require('envsub/js/envsub-parser');
const os = require('os');
const chalk = require('chalk');
Expand Down Expand Up @@ -94,11 +93,7 @@ const endTimeFlag = Flags.string({
});

const pastFlag = Flags.string({
description: 'Past time window in mins',
});

const fromFlag = Flags.string({
description: `The from time in YYYY-MM-DD:HH:MM:SS format based on your system's time zone. It is used to fetch logs from the past and is the starting time for the past time duration.`,
description: 'Past time window in minutes',
});

const logFilenameFlag = Flags.string({
Expand Down Expand Up @@ -665,23 +660,21 @@ function suggestCorrectedDateFormat(inputDate) {
/**
* Parses a duration string representing a past time window and converts it to milliseconds.
*
* @param {string} pastTimeWindow - The past time duration to parse, e.g., "20 mins", "15 minutes".
* @param {string} pastTimeWindow - The past time duration in minutes, e.g., "20", "15".
* @returns {number} The duration in milliseconds.
*/
function parsePastDuration(pastTimeWindow) {
// Regular expression to match various formats of minute abbreviations
const pastDurationRegex = /^(\d+)\s*(m|mins?|minutes?)$/i;
const match = pastTimeWindow.match(pastDurationRegex);
// Check if pastTimeWindow contains non-numeric characters
const match = pastTimeWindow.match(/^(\d+)$/);

if (!match) {
const durationInMs = Number(pastTimeWindow) * 60 * 1000;

if (isNaN(durationInMs) || !match) {
throw new Error(
'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".',
'Invalid format. The time window must be an integer, for example "20" or "15".',
);
}

// Convert the matched duration to milliseconds
const durationInMs = ms(pastTimeWindow);

return durationInMs;
}

Expand Down Expand Up @@ -770,6 +763,7 @@ function validateDateTimeFormat(time) {
return timeString.replace(/-|:|Z/g, '').replace('T', 'T');
}

// can be used later if we want to take --startTime and --endTime in local time
/**
* Convert a given local time string to UTC time string
* @param {string} timeString - The time string in the format YYYY-MM-DD:HH:MM:SS
Expand Down Expand Up @@ -823,7 +817,6 @@ module.exports = {
endTimeFlag,
logFilenameFlag,
pastFlag,
fromFlag,
suggestCorrectedDateFormat,
parsePastDuration,
validateDateTimeRange,
Expand Down
Loading