Skip to content

Commit 5ea9928

Browse files
Merge pull request #2345 from NguyenTranHoangSym/SDA-4415
SDA-4415: Add IV Zeus log to logs bundle
2 parents 0384d6c + 2fe830a commit 5ea9928

7 files changed

Lines changed: 421 additions & 7 deletions

File tree

spec/helpers/files-helper.spec.ts

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { FileHelper } from '../../src/app/helpers/file/files-helper';
2+
import * as fs from 'fs';
3+
import { IFile } from '../../src/app/interfaces/file-helper.interface';
4+
import { LogCategory } from '../../src/app/interfaces/reports-handlers.interface';
5+
import { isLinux, isMac } from '../../src/common/env';
6+
7+
describe('files-helper', () => {
8+
let fileHelper: FileHelper;
9+
let folderPath = 'C:\\abc';
10+
let fsMock;
11+
let logs = [
12+
'C9Zeus.2529612.log',
13+
'C9Zeus.1.log',
14+
'C9Trader.25296.log',
15+
'C9Trader.25.log',
16+
'HEHE.txt',
17+
'HAH.txt',
18+
];
19+
beforeEach(() => {
20+
fileHelper = new FileHelper();
21+
fileHelper.createFiles();
22+
fsMock = jest.spyOn(fs, 'readdirSync').mockImplementation(() => logs);
23+
});
24+
25+
afterEach(() => {
26+
fsMock.mockRestore();
27+
});
28+
29+
it('should clean file names', () => {
30+
const validatedFileName = fileHelper.validateFilename(
31+
'console.log("hello world").txt',
32+
);
33+
34+
expect(validatedFileName).toBe('console.log__hello_world__.txt');
35+
});
36+
37+
it('should clean file names', () => {
38+
const validatedPath = fileHelper.validateFilePath(
39+
'SELEC*FROM ABC/hello-world/haha',
40+
);
41+
42+
expect(validatedPath).toBe(false);
43+
});
44+
45+
it('should clean file path', () => {
46+
const validatedPath = fileHelper.validateFilePath(
47+
'SELEC*FROM ABC/hello-world/haha',
48+
);
49+
50+
expect(validatedPath).toBe(false);
51+
});
52+
53+
it('should return enough logs in folder - old modified', () => {
54+
const stats = {
55+
dev: 2114,
56+
ino: 48064969,
57+
mode: 33188,
58+
nlink: 1,
59+
uid: 85,
60+
gid: 100,
61+
rdev: 0,
62+
size: 527,
63+
blksize: 4096,
64+
blocks: 8,
65+
atimeMs: 1318289051000.1,
66+
mtimeMs: 1318289051000.1,
67+
ctimeMs: 1318289051000.1,
68+
birthtimeMs: 1318289051000.1,
69+
atime: new Date('Mon, 10 Oct 2011 23:24:11 GMT'),
70+
mtime: new Date('Mon, 10 Oct 2011 23:24:11 GMT'),
71+
ctime: new Date('Mon, 10 Oct 2011 23:24:11 GMT'),
72+
birthtime: new Date('Mon, 10 Oct 2011 23:24:11 GMT'),
73+
isFile: () => true,
74+
};
75+
const now = new Date();
76+
77+
jest.spyOn(fs, 'statSync').mockImplementation((path) => {
78+
switch (path) {
79+
case `${folderPath}\\${logs[0]}`: {
80+
const newStats = { ...stats };
81+
newStats.mtime = new Date(now.getTime() - 360 * 60 * 1000);
82+
return newStats;
83+
}
84+
case `${folderPath}\\${logs[2]}`: {
85+
const newStats = { ...stats };
86+
newStats.mtime = new Date(now.getTime() - 380 * 60 * 1000);
87+
return stats;
88+
}
89+
default: {
90+
return stats;
91+
}
92+
}
93+
});
94+
95+
const files = fileHelper.getLatestModifiedFiles(folderPath);
96+
const firstModifiedFile: IFile = {
97+
fileName: logs[0],
98+
platform: '',
99+
modifiedTimestamp: new Date(now.getTime() - 360 * 60 * 1000).getTime(),
100+
};
101+
102+
if (isLinux || isMac) {
103+
expect(files.get(LogCategory.RECENT)).toEqual(undefined);
104+
} else {
105+
expect(files.get(LogCategory.RECENT)).toEqual(firstModifiedFile);
106+
}
107+
});
108+
109+
it('should return enough logs in folder - mixed modified but get latest ones', () => {
110+
const stats = {
111+
dev: 2114,
112+
ino: 48064969,
113+
mode: 33188,
114+
nlink: 1,
115+
uid: 85,
116+
gid: 100,
117+
rdev: 0,
118+
size: 527,
119+
blksize: 4096,
120+
blocks: 8,
121+
atimeMs: 1318289051000.1,
122+
mtimeMs: 1318289051000.1,
123+
ctimeMs: 1318289051000.1,
124+
birthtimeMs: 1318289051000.1,
125+
atime: new Date('Mon, 10 Oct 2011 23:24:11 GMT'),
126+
mtime: new Date('Mon, 10 Oct 2011 23:24:11 GMT'),
127+
ctime: new Date('Mon, 10 Oct 2011 23:24:11 GMT'),
128+
birthtime: new Date('Mon, 10 Oct 2011 23:24:11 GMT'),
129+
isFile: () => true,
130+
};
131+
const now = new Date();
132+
133+
jest.spyOn(fs, 'statSync').mockImplementation((path) => {
134+
switch (path) {
135+
case `${folderPath}\\${logs[0]}`: {
136+
const newStats = { ...stats };
137+
newStats.mtime = new Date(now.getTime() - 15 * 60 * 1000);
138+
return newStats;
139+
}
140+
case `${folderPath}\\${logs[2]}`: {
141+
const newStats = { ...stats };
142+
newStats.mtime = new Date(now.getTime() - 15 * 60 * 1000);
143+
return newStats;
144+
}
145+
case `${folderPath}\\${logs[3]}`: {
146+
const newStats = { ...stats };
147+
newStats.mtime = new Date(now.getTime() - 380 * 60 * 1000);
148+
return newStats;
149+
}
150+
default: {
151+
return stats;
152+
}
153+
}
154+
});
155+
156+
const files = fileHelper.getLatestModifiedFiles(folderPath);
157+
const result = new Map<string, IFile>();
158+
const firstModifiedFile: IFile = {
159+
fileName: logs[0],
160+
platform: '',
161+
modifiedTimestamp: new Date(now.getTime() - 15 * 60 * 1000).getTime(),
162+
};
163+
const secondModifiedFile: IFile = {
164+
fileName: logs[2],
165+
platform: '',
166+
modifiedTimestamp: new Date(now.getTime() - 15 * 60 * 1000).getTime(),
167+
};
168+
169+
result.set(LogCategory.LATEST + '_0', firstModifiedFile);
170+
result.set(LogCategory.LATEST + '_2', secondModifiedFile);
171+
172+
if (isLinux || isMac) {
173+
expect(files.get(LogCategory.LATEST + '_0')).toEqual(undefined);
174+
expect(files.get(LogCategory.LATEST + '_2')).toEqual(undefined);
175+
} else {
176+
expect(files.get(LogCategory.LATEST + '_0')).toEqual(firstModifiedFile);
177+
expect(files.get(LogCategory.LATEST + '_2')).toEqual(secondModifiedFile);
178+
}
179+
});
180+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { IFile, IFileOptions } from '../../interfaces/file-helper.interface';
2+
import { Files } from './files';
3+
4+
export type GetLatestModifiedFiles = (
5+
folderPath: string,
6+
options?: IFileOptions,
7+
) => Map<string, IFile>;
8+
9+
export abstract class FileHelperBase {
10+
public abstract getLatestModifiedFiles: GetLatestModifiedFiles;
11+
12+
public createFiles = () => {
13+
return new Files();
14+
};
15+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import * as fs from 'fs';
2+
import path = require('path');
3+
import { isWindowsOS } from '../../../common/env';
4+
import { logger } from '../../../common/logger';
5+
import { IFile, IFiles } from '../../interfaces/file-helper.interface';
6+
import {
7+
LogCategory,
8+
LogUtilities,
9+
Platform,
10+
} from '../../interfaces/reports-handlers.interface';
11+
import { Files } from './files';
12+
import { FileHelperBase, GetLatestModifiedFiles } from './files-base-helper';
13+
14+
export class FileHelper extends FileHelperBase {
15+
private files: IFiles;
16+
constructor() {
17+
super();
18+
this.files = this.createFiles();
19+
}
20+
21+
public get = () => {
22+
return this.files;
23+
};
24+
25+
public set = (files: Files) => {
26+
return (this.files = files);
27+
};
28+
29+
public validateFilename = (filename: string): string => {
30+
return filename?.replace(/[^a-zA-Z0-9/_\.-]/g, '_');
31+
};
32+
33+
public validateFilePath = (path: string) => {
34+
const unixPathRegex = /^\/([^\/]+\/)*([^\/]+)$/;
35+
const windowsPathRegex = /^[a-zA-Z]:\\([^\\]+\\)*([^\\]+)$/;
36+
37+
if (isWindowsOS) {
38+
return windowsPathRegex.test(path);
39+
}
40+
41+
return unixPathRegex.test(path);
42+
};
43+
44+
public unsetFiles = () => {
45+
this.files = this.createFiles();
46+
};
47+
48+
public getLatestModifiedFiles: GetLatestModifiedFiles = (
49+
folderPath: string,
50+
options = {
51+
includeOnly: [''],
52+
type: '',
53+
},
54+
) => {
55+
const logFiles = fs.readdirSync(folderPath);
56+
const now = new Date();
57+
let latestModifiedLogTimestamp = 0;
58+
59+
logFiles?.forEach((file, index) => {
60+
const sanitizedLogname = this.validateFilename(file);
61+
const sanitizedFolderPath = path.normalize(folderPath);
62+
63+
if (!sanitizedLogname || !this.validateFilePath(sanitizedFolderPath)) {
64+
logger.error(
65+
'error',
66+
'file-helper: Log file has a malicious format to be exported',
67+
);
68+
return;
69+
}
70+
const logFilePath = path.join(sanitizedFolderPath, sanitizedLogname);
71+
const logFileStats = fs.statSync(logFilePath);
72+
const currentLogModifiedTimestamp = logFileStats.mtime.getTime();
73+
74+
if (
75+
logFileStats.isFile() &&
76+
options.includeOnly?.some((option) =>
77+
file.toLowerCase().includes(option),
78+
)
79+
) {
80+
const logLastModifiedTimestamp = new Date(logFileStats.mtime);
81+
const timestampDiffInMinutes =
82+
(now.getTime() - logLastModifiedTimestamp.getTime()) /
83+
LogUtilities.ONE_MINUTE;
84+
const modifiedFile: IFile = {
85+
fileName: file,
86+
platform: options.type ?? Platform.IV,
87+
modifiedTimestamp: currentLogModifiedTimestamp,
88+
};
89+
90+
if (timestampDiffInMinutes <= LogUtilities.LOG_TIMESTAMP_THRESHOLD) {
91+
this.files.get().set(`${LogCategory.LATEST}_${index}`, modifiedFile);
92+
latestModifiedLogTimestamp = logFileStats.mtime.getTime();
93+
} else if (currentLogModifiedTimestamp > latestModifiedLogTimestamp) {
94+
this.files.get().set(LogCategory.RECENT, modifiedFile);
95+
latestModifiedLogTimestamp = currentLogModifiedTimestamp;
96+
}
97+
}
98+
});
99+
100+
if (this.files.get().has(`${LogCategory.LATEST}_1`)) {
101+
this.files.get().delete(LogCategory.RECENT);
102+
}
103+
104+
return this.files.get();
105+
};
106+
}
107+
108+
export const fileHelper = new FileHelper();

src/app/helpers/file/files.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { IFile, IFiles } from '../../interfaces/file-helper.interface';
2+
3+
export class Files implements IFiles {
4+
private files = new Map<string, IFile>();
5+
6+
constructor(private _files?: Map<string, IFile>) {
7+
this.set(this._files ?? this.files);
8+
}
9+
10+
public get = () => {
11+
return this.files;
12+
};
13+
14+
public set = (files: Map<string, IFile>) => {
15+
this.files = files;
16+
};
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export interface IFile {
2+
fileName: string;
3+
platform: string;
4+
modifiedTimestamp: number;
5+
}
6+
7+
export interface IFileOptions {
8+
includeOnly?: string[];
9+
type?: string;
10+
}
11+
12+
export interface IFiles {
13+
get: () => Map<string, IFile>;
14+
set: (files: Map<string, IFile>) => void;
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export enum Platform {
2+
IV = 'IV',
3+
}
4+
5+
export enum LogCategory {
6+
LATEST = 'LATEST',
7+
RECENT = 'RECENT',
8+
C9_TRADER = 'c9trader',
9+
C9_ZUES = 'c9zeus',
10+
}
11+
12+
export enum LogUtilities {
13+
ONE_MINUTE = 60000,
14+
// Timestamp threshold, in minutes
15+
LOG_TIMESTAMP_THRESHOLD = 30,
16+
}
17+
18+
export type RetrieveIVLogs = (logsPath: string) => void;
19+
export type RemoveIVLogs = (logsPath: string) => void;

0 commit comments

Comments
 (0)