Skip to content

Commit 63fd5e4

Browse files
authored
Test/unit (#54)
* test(unit): add exceptions * test(unit): add template.util * test(unit): add queue.util * test(unit): add middleware * test(unit): add tests by date.util * refactor(service-core): remove logger from constructor * test(unit): add unit test for the apple service * test(unit): add unit test for the facebook service * test(unit): add unit test for the github service * test(unit): add unit test for the google service * test(unit): add unit test for email service
1 parent 7eaee37 commit 63fd5e4

File tree

58 files changed

+2234
-100
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2234
-100
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"rest-client.rememberCookiesForSubsequentRequests": false,
1111
"editor.formatOnSave": true,
1212
"editor.codeActionsOnSave": {
13-
"source.fixAll.eslint": true
13+
"source.fixAll.eslint": "explicit"
1414
},
1515
"eslint.workingDirectories": ["/src", "/tests"],
1616
"npm.exclude": ["**/dist"],

jest.config.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
"testMatch": ["<rootDir>/tests/**/*.test.ts"],
1616
"coverageDirectory": "coverage",
1717
"moduleNameMapper": {
18+
"__mocks__/(.*)": "<rootDir>/tests/__mocks__/$1",
1819
"@common/(.*)": "<rootDir>/src/common/$1",
19-
"@config": "<rootDir>/src/config",
2020
"@config/(.*)": "<rootDir>/src/config/$1",
21-
"@core": "<rootDir>/src/core",
21+
"@config": "<rootDir>/src/config",
2222
"@core/(.*)": "<rootDir>/src/core/$1",
23-
"@database": "<rootDir>/src/database",
23+
"@core": "<rootDir>/src/core",
2424
"@database/(.*)": "<rootDir>/src/database/$1",
25+
"@database": "<rootDir>/src/database",
2526
"@i18n": "<rootDir>/src/i18n",
2627
"@middleware/(.*)": "<rootDir>/src/middleware/$1",
2728
"@modules/(.*)": "<rootDir>/src/modules/$1",

src/@types/kernel.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type PagePayload = {
2929
page: number;
3030
};
3131

32-
type DateCtx = string | number | Date;
32+
type FlexibleDate = string | number | Date;
3333

3434
type DeepPartial<T> = T extends object
3535
? {

src/common/constants/common.constant.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ export const HASH_ENCODING = 'hex';
1717
export const BIG_INT = Number.MAX_SAFE_INTEGER;
1818

1919
export const AUTH_REFRESH_LINK = '/api/v1/auth/refresh';
20+
21+
export const TIMEZONE_UTC = 'UTC';

src/common/utils/date.util.ts

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
addMonths as fnsAddMonths,
44
endOfDay as fnsEndOfDay,
55
format as fnsFormat,
6-
formatISO as fnsFormatISO,
76
getUnixTime as fnsGetUnixTime,
87
isAfter as fnsIsAfter,
98
isBefore as fnsIsBefore,
@@ -13,98 +12,125 @@ import {
1312
parseISO as fnsParseISO,
1413
startOfDay as fnsStartOfDay,
1514
} from 'date-fns';
15+
import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
1616
import ms from 'ms';
1717

18-
import { DEFAULT_FORMAT_DATE } from '@common/constants';
18+
import { DEFAULT_FORMAT_DATE, TIMEZONE_UTC } from '@common/constants';
1919

20+
//TODO: should be work only with UTC time
2021
export class DateUtil {
21-
static addMillisecondToDate(date?: DateCtx, amount?: number): Date {
22-
return fnsAddMilliseconds(DateUtil.transformDateToISO(date), amount || 0);
23-
}
22+
static addMillisecondToDate(date: FlexibleDate, amount?: number): Date {
23+
const dateISO = this.parseISO(date);
2424

25-
static addMonths(date?: DateCtx, amount?: number): Date {
26-
return fnsAddMonths(DateUtil.transformDateToISO(date), amount || 0);
25+
return fnsAddMilliseconds(dateISO, amount || 0);
2726
}
2827

29-
static endOfDay(date?: DateCtx | null) {
30-
date = DateUtil.transformDateToISO(date || new Date());
28+
static addMonths(date: FlexibleDate, amount?: number): Date {
29+
const dateISO = this.parseISO(date);
3130

32-
return fnsEndOfDay(date);
31+
return fnsAddMonths(dateISO, amount || 0);
3332
}
3433

35-
static formatISO(date?: DateCtx) {
36-
date = (DateUtil.isValid(date) ? date : new Date()) as DateCtx;
34+
static endOfDay(date: FlexibleDate) {
35+
const dateISO = this.parseISO(date);
3736

38-
return fnsFormatISO(DateUtil.parseISO(date));
37+
return fnsEndOfDay(dateISO);
3938
}
4039

4140
static isBetweenDay(
42-
from: DateCtx,
43-
to?: DateCtx | null,
44-
date?: DateCtx | null,
41+
dateFrom: FlexibleDate,
42+
dateTo: FlexibleDate,
43+
date: FlexibleDate,
4544
) {
46-
from = DateUtil.startOfDay(from);
47-
to = DateUtil.endOfDay(to);
48-
date = DateUtil.transformDateToISO(date || new Date());
45+
const dateStartUtc = this.timeZoneToUTC(this.startOfDay(dateFrom));
46+
const dateEndUtc = this.timeZoneToUTC(this.endOfDay(dateTo));
47+
const dateISOUtc = this.parseISO(date);
4948

5049
return (
51-
(fnsIsEqual(from, date) || fnsIsBefore(from, date)) &&
52-
(fnsIsEqual(to, date) || fnsIsAfter(to, date))
50+
(fnsIsEqual(dateStartUtc, dateISOUtc) ||
51+
fnsIsBefore(dateStartUtc, dateISOUtc)) &&
52+
(fnsIsEqual(dateEndUtc, dateISOUtc) || fnsIsAfter(dateEndUtc, dateISOUtc))
5353
);
5454
}
5555

56-
static isSameDay(dateLeft?: DateCtx, dateRight?: DateCtx): boolean {
57-
if (DateUtil.isValid(dateLeft) && DateUtil.isValid(dateRight)) {
58-
return fnsIsSameDay(
59-
DateUtil.parseISO(dateLeft as DateCtx),
60-
DateUtil.parseISO(dateRight as DateCtx),
61-
);
56+
static isSameDay(dateLeft: FlexibleDate, dateRight: FlexibleDate): boolean {
57+
if (!this.isValid(dateLeft) || !this.isValid(dateRight)) {
58+
return false;
6259
}
6360

64-
return false;
61+
return fnsIsSameDay(this.parseISO(dateLeft), this.parseISO(dateRight));
6562
}
6663

67-
static isSameOrBeforeDay(from?: DateCtx | null, to?: DateCtx | null) {
68-
from = DateUtil.startOfDay(from || new Date());
69-
to = DateUtil.startOfDay(to || new Date());
64+
static isSameOrBeforeDay(dateFrom: FlexibleDate, dateTo: FlexibleDate) {
65+
const dateStartFrom = this.startOfDay(dateFrom);
66+
const dateStartTo = this.startOfDay(dateTo);
7067

71-
return fnsIsEqual(from, to) || fnsIsBefore(from, to);
68+
return (
69+
fnsIsEqual(dateStartFrom, dateStartTo) ||
70+
fnsIsBefore(dateStartFrom, dateStartTo)
71+
);
7272
}
7373

74-
static isValid(date?: DateCtx) {
75-
return fnsIsValid(typeof date === 'string' ? Date.parse(date) : date);
76-
}
74+
static parseISO(date: FlexibleDate) {
75+
if (!date || !this.isValid(date)) {
76+
throw new Error('Invalid Date');
77+
}
7778

78-
static parseISO(date: DateCtx) {
7979
return typeof date === 'string' ? fnsParseISO(date) : date;
8080
}
8181

82-
static startOfDay(date?: DateCtx | null) {
83-
date = DateUtil.transformDateToISO(date || new Date());
82+
static parseStringToMs(str: string): number {
83+
if (!str) {
84+
return 0;
85+
}
8486

85-
return fnsStartOfDay(date);
87+
return ms(str) || 0;
8688
}
8789

88-
static toDate(date: DateCtx) {
89-
return DateUtil.transformDateToISO(date);
90+
static startOfDay(date: FlexibleDate) {
91+
const dateISO = this.parseISO(date);
92+
93+
return fnsStartOfDay(dateISO);
9094
}
9195

92-
// FIXME: https://github.com/date-fns/date-fns/issues/2151
93-
static toFormat(date?: DateCtx, format = DEFAULT_FORMAT_DATE) {
94-
return fnsFormat(DateUtil.transformDateToISO(date), format);
96+
static timeZoneToUTC(date: FlexibleDate, tz = TIMEZONE_UTC) {
97+
try {
98+
const dateUtc = zonedTimeToUtc(date, tz);
99+
100+
if (!this.isValid(dateUtc)) {
101+
throw new Error();
102+
}
103+
104+
return dateUtc;
105+
} catch {
106+
throw new Error('Invalid Date');
107+
}
95108
}
96109

97-
static toMs(input: string): number {
98-
return ms(input);
110+
static toFormat(date: FlexibleDate, format = DEFAULT_FORMAT_DATE) {
111+
const dateISO = this.parseISO(date);
112+
113+
return fnsFormat(dateISO, format);
99114
}
100115

101-
static toUnix(date?: DateCtx): number {
102-
return fnsGetUnixTime(DateUtil.transformDateToISO(date));
116+
static toFormatUTC(localDate: FlexibleDate, format = DEFAULT_FORMAT_DATE) {
117+
const dateISO = this.parseISO(localDate);
118+
const date = utcToZonedTime(dateISO, TIMEZONE_UTC);
119+
120+
return fnsFormat(date, format);
103121
}
104122

105-
static transformDateToISO(date?: DateCtx) {
106-
date = (DateUtil.isValid(date) ? date : new Date()) as DateCtx;
123+
static toUnix(date: FlexibleDate): number {
124+
const dateISO = this.parseISO(date);
107125

108-
return DateUtil.parseISO(date);
126+
return fnsGetUnixTime(dateISO);
127+
}
128+
129+
private static isValid(date?: FlexibleDate) {
130+
try {
131+
return fnsIsValid(typeof date === 'string' ? Date.parse(date) : date);
132+
} catch {
133+
throw new Error('Invalid Date');
134+
}
109135
}
110136
}

src/common/utils/queue.util.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { IRedisConfig } from '@config';
55

66
export class QueueUtil {
77
static connect<T>(name: string, redisConfig: IRedisConfig) {
8+
if (!name) throw new Error('name is required');
9+
if (!redisConfig.host) throw new Error('redisConfig.host is required');
10+
if (!redisConfig.port) throw new Error('redisConfig.port is required');
11+
812
return new Bull<T>(name, {
913
redis: this.getRedisOptions(redisConfig),
1014
prefix: redisConfig.queuePrefix,
@@ -20,7 +24,7 @@ export class QueueUtil {
2024
host: redisConfig.host,
2125
port: redisConfig.port,
2226
...(redisConfig.username && { username: redisConfig.username }),
23-
...(redisConfig.username && { password: redisConfig.password }),
27+
...(redisConfig.password && { password: redisConfig.password }),
2428
...(redisConfig.tls && {
2529
tls: {},
2630
connectTimeout: 30000,

src/config/interface/redis.config.interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export interface IRedisConfig {
2-
clusterModeEnabled: boolean;
2+
clusterModeEnabled?: boolean;
33
host: string;
44
password?: string;
55
port: number;

src/core/controller.core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class ControllerCore {
7777
}
7878

7979
private getCookieMaxAge(options: Partial<CookieParam>) {
80-
const maxAge = DateUtil.toMs(options?.expiresIn || '');
80+
const maxAge = DateUtil.parseStringToMs(options?.expiresIn || '');
8181

8282
if (options?.maxAge || options?.rememberMe) {
8383
return { maxAge: options.maxAge ?? maxAge };

src/core/service/provider.service.core.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
1-
import { container as Container } from 'tsyringe';
2-
31
import { LoggerCtx } from '@common/enums';
42
import { InternalServerErrorException } from '@common/exceptions';
5-
import { ILoggerService, LoggerInject } from '@providers/logger';
3+
import type { ILoggerService } from '@providers/logger';
64

75
export class ProviderServiceCore {
8-
protected readonly logger: ILoggerService;
9-
private readonly loggerCtx: LoggerCtx;
10-
11-
constructor(loggerCtx?: LoggerCtx) {
12-
this.loggerCtx = loggerCtx || LoggerCtx.SERVICE;
13-
this.logger = Container.resolve<ILoggerService>(LoggerInject.SERVICE);
6+
protected readonly logger?: ILoggerService;
147

8+
constructor() {
159
this.init();
1610
}
1711

12+
protected get loggerCtx(): LoggerCtx {
13+
return LoggerCtx.SERVICE;
14+
}
15+
1816
protected handleError(err: unknown) {
19-
this.logger.error(this.constructor.name, { err, context: this.loggerCtx });
17+
if (this.logger) {
18+
this.logger.error(this.constructor.name, {
19+
err,
20+
context: this.loggerCtx,
21+
});
22+
}
2023

2124
return new InternalServerErrorException();
2225
}
2326

2427
protected init() {
28+
if (!this.logger) {
29+
return;
30+
}
31+
2532
this.logger.debug(`${this.constructor.name} initialized...`, {
2633
context: this.loggerCtx,
2734
});

src/core/service/service.core.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
1-
import { container as Container } from 'tsyringe';
2-
31
import { LoggerCtx } from '@common/enums';
4-
import { ILoggerService, LoggerInject } from '@providers/logger';
2+
import { ILoggerService } from '@providers/logger';
53

64
export class ServiceCore {
7-
protected readonly logger: ILoggerService;
8-
private readonly loggerCtx: LoggerCtx;
9-
10-
constructor(loggerCtx?: LoggerCtx) {
11-
this.loggerCtx = loggerCtx || LoggerCtx.SERVICE;
12-
this.logger = Container.resolve<ILoggerService>(LoggerInject.SERVICE);
5+
protected readonly logger?: ILoggerService;
136

7+
constructor() {
148
this.init();
159
}
1610

11+
protected get loggerCtx(): LoggerCtx {
12+
return LoggerCtx.SERVICE;
13+
}
14+
1715
protected handleError(err: unknown) {
16+
if (!this.logger) {
17+
return;
18+
}
19+
1820
this.logger.error(err, {
1921
name: this.constructor.name,
2022
context: this.loggerCtx,
2123
});
2224
}
2325

2426
protected init() {
27+
if (!this.logger) {
28+
return;
29+
}
30+
2531
this.logger.debug(`${this.constructor.name} initialized...`, {
2632
context: this.loggerCtx,
2733
});

0 commit comments

Comments
 (0)