Skip to content

Commit cf2b6ff

Browse files
committed
refactor and add test cases
1 parent b1635ec commit cf2b6ff

File tree

10 files changed

+204
-74
lines changed

10 files changed

+204
-74
lines changed

packages/core/src/lib/cache-layer/cacheLayerLoader.ts

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import * as moment from 'moment';
44
import { inject, injectable, interfaces } from 'inversify';
55
import { TYPES } from '@vulcan-sql/core/types';
66
import {
7-
IActivityLogger,
87
CacheLayerInfo,
98
ICacheLayerOptions,
109
cacheProfileName,

packages/core/src/lib/cache-layer/cacheLayerRefresher.ts

+43-56
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { uniq } from 'lodash';
33
import { ToadScheduler, SimpleIntervalJob, AsyncTask } from 'toad-scheduler';
44
import { inject, injectable, multiInject } from 'inversify';
55
import { TYPES } from '@vulcan-sql/core/types';
6-
import { APISchema, IActivityLogger } from '@vulcan-sql/core/models';
6+
import {
7+
APISchema,
8+
CacheLayerInfo,
9+
IActivityLogger,
10+
} from '@vulcan-sql/core/models';
711
import { ConfigurationError } from '../utils/errors';
812
import { ICacheLayerLoader } from './cacheLayerLoader';
913
import { getLogger } from '../utils';
@@ -49,15 +53,13 @@ export class CacheLayerRefresher implements ICacheLayerRefresher {
4953
// check if the index name is duplicated more than one API schemas
5054
this.checkDuplicateIndex(schemas);
5155
// traverse each cache table of each schema
52-
const activityLogger = this.getActivityLogger();
5356
await Promise.all(
5457
schemas.map(async (schema) => {
5558
// skip the schema by return if not set the cache
5659
if (!schema.cache) return;
57-
const { urlPath } = schema;
5860
return await Promise.all(
5961
schema.cache.map(async (cache) => {
60-
const { cacheTableName, profile, refreshTime, sql } = cache;
62+
const { cacheTableName, profile, refreshTime } = cache;
6163
// replace the '/' tp '_' to avoid the file path issue for templateSource
6264
const templateName = schema.templateSource.replace('/', '_');
6365
// If refresh time is set, use the scheduler to schedule the load task for refresh
@@ -68,59 +70,14 @@ export class CacheLayerRefresher implements ICacheLayerRefresher {
6870
const refreshJob = new SimpleIntervalJob(
6971
{ milliseconds, runImmediately },
7072
new AsyncTask(workerId, async () => {
71-
// load data the to cache storage
72-
let refreshResult = RefreshResult.SUCCESS;
73-
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss');
74-
try {
75-
// get the current time in format of UTC
76-
await this.cacheLoader.load(templateName, cache);
77-
} catch (error: any) {
78-
refreshResult = RefreshResult.FAILED;
79-
this.logger.debug(`Failed to refresh cache: ${error}`);
80-
} finally {
81-
// send activity log
82-
const content = {
83-
logTime: now,
84-
urlPath,
85-
sql,
86-
refreshResult,
87-
};
88-
if (activityLogger)
89-
activityLogger.log(content).catch((err: any) => {
90-
this.logger.debug(
91-
`Failed to log activity after refreshing cache: ${err}`
92-
);
93-
});
94-
}
73+
await this.sendActivityLogAfterLoad(schema, cache);
9574
}),
9675
{ preventOverrun: true, id: workerId }
9776
);
9877
// add the job to schedule cache refresh task
9978
this.scheduler.addIntervalJob(refreshJob);
10079
} else {
101-
let refreshResult = RefreshResult.SUCCESS;
102-
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss');
103-
try {
104-
// get the current time in format of UTC
105-
await this.cacheLoader.load(templateName, cache);
106-
} catch (error: any) {
107-
refreshResult = RefreshResult.FAILED;
108-
this.logger.debug(`Failed to refresh cache: ${error}`);
109-
} finally {
110-
// send activity log
111-
const content = {
112-
logTime: now,
113-
urlPath,
114-
sql,
115-
refreshResult,
116-
};
117-
if (activityLogger)
118-
activityLogger.log(content).catch((err: any) => {
119-
this.logger.debug(
120-
`Failed to log activity after refreshing cache: ${err}`
121-
);
122-
});
123-
}
80+
await this.sendActivityLogAfterLoad(schema, cache);
12481
}
12582
})
12683
);
@@ -135,12 +92,42 @@ export class CacheLayerRefresher implements ICacheLayerRefresher {
13592
this.scheduler.stop();
13693
}
13794

138-
private getActivityLogger(): IActivityLogger | undefined {
139-
const activityLogger = this.activityLoggers.find((logger) =>
140-
logger.isEnabled()
141-
);
95+
private async sendActivityLogAfterLoad(
96+
schema: APISchema,
97+
cache: CacheLayerInfo
98+
) {
99+
const { urlPath } = schema;
100+
const { sql } = cache;
101+
// if fn is not a function, return
102+
let refreshResult = RefreshResult.SUCCESS;
103+
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss');
104+
const templateName = schema.templateSource.replace('/', '_');
105+
try {
106+
// get the current time in format of UTC
107+
await this.cacheLoader.load(templateName, cache);
108+
} catch (error: any) {
109+
refreshResult = RefreshResult.FAILED;
110+
this.logger.debug(`Failed to refresh cache: ${error}`);
111+
} finally {
112+
// send activity log
113+
const content = {
114+
logTime: now,
115+
urlPath,
116+
sql,
117+
refreshResult,
118+
};
119+
const activityLoggers = this.getActivityLoggers();
120+
for (const activityLogger of activityLoggers)
121+
activityLogger.log(content).catch((err: any) => {
122+
this.logger.debug(
123+
`Failed to log activity after refreshing cache: ${err}`
124+
);
125+
});
126+
}
127+
}
142128

143-
return activityLogger;
129+
private getActivityLoggers(): IActivityLogger[] {
130+
return this.activityLoggers.filter((logger) => logger.isEnabled());
144131
}
145132

146133
private checkDuplicateCacheTableName(schemas: APISchema[]) {

packages/core/src/lib/loggers/httpLogger.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class HttpLogger extends BaseActivityLogger<HttpLoggerConfig> {
3333
try {
3434
// get connection info from option and use axios to send a post requet to the endpoint
3535
await this.sendActivityLog(url, payload, headers);
36+
this.logger.debug(`Activity log sent`);
3637
} catch (err) {
3738
this.logger.debug(
3839
`Failed to send activity log to http logger, url: ${url}`
@@ -43,7 +44,7 @@ export class HttpLogger extends BaseActivityLogger<HttpLoggerConfig> {
4344

4445
protected sendActivityLog = async (
4546
url: string,
46-
payload: JSON,
47+
payload: any,
4748
headers: AxiosRequestHeaders | undefined
4849
): Promise<void> => {
4950
await axios.post(url, payload, {

packages/core/src/models/extensions/logger.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ExtensionBase } from './base';
22
import { TYPES } from '@vulcan-sql/core/types';
33
import { VulcanExtension } from './decorators';
4+
import { isEmpty } from 'lodash';
45

56
export enum ActivityLoggerType {
67
HTTP_LOGGER = 'http-logger',
@@ -20,8 +21,10 @@ export abstract class BaseActivityLogger<ActivityLoggerTypeOption>
2021

2122
public isEnabled(): boolean {
2223
const config = this.getConfig();
23-
if (!config) return false;
24-
if (config.enabled === true) return true;
24+
if (!config || isEmpty(config)) return false;
25+
if (!config.enabled) return false;
26+
if (!config['options']) return false;
27+
if (config['options'][this.getExtensionId()!]) return true;
2528
else return false;
2629
}
2730

packages/core/test/cache-layer/cacheLayerRefresher.spec.ts

+49
Original file line numberDiff line numberDiff line change
@@ -423,4 +423,53 @@ describe('Test cache layer refresher', () => {
423423
},
424424
100 * 1000
425425
);
426+
// should not send activity log when logger is not enabled
427+
it('should not send activity log when logger is not enabled', async () => {
428+
const schemas: Array<APISchema> = [
429+
{
430+
...sinon.stubInterface<APISchema>(),
431+
templateSource: 'template-1',
432+
profiles: [profiles[0].name, profiles[1].name],
433+
cache: [
434+
{
435+
cacheTableName: 'orders',
436+
sql: sinon.default.stub() as any,
437+
profile: profiles[0].name,
438+
},
439+
{
440+
cacheTableName: 'products',
441+
sql: sinon.default.stub() as any,
442+
profile: profiles[1].name,
443+
},
444+
] as Array<CacheLayerInfo>,
445+
},
446+
{
447+
...sinon.stubInterface<APISchema>(),
448+
templateSource: 'template-2',
449+
profiles: [profiles[2].name],
450+
cache: [
451+
{
452+
cacheTableName: 'users',
453+
sql: sinon.default.stub() as any,
454+
profile: profiles[2].name,
455+
},
456+
] as Array<CacheLayerInfo>,
457+
},
458+
];
459+
const mockLogger = new HttpLogger(
460+
{
461+
enabled: false,
462+
},
463+
'http-logger'
464+
);
465+
mockLogger.isEnabled = jest.fn().mockReturnValue(false);
466+
// Act
467+
const loader = new CacheLayerLoader(options, stubFactory as any);
468+
const refresher = new CacheLayerRefresher(loader, [mockLogger]);
469+
await refresher.start(schemas);
470+
471+
// Assert
472+
expect(mockLogger.log).toHaveBeenCalledTimes(0);
473+
refresher.stop();
474+
});
426475
});

packages/core/test/httplogger.spec.ts

+26
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,30 @@ describe('Activity logs', () => {
7474
sinon.stub(httpLogger, 'sendActivityLog').throws();
7575
await expect(httpLogger.log({})).rejects.toThrow();
7676
});
77+
78+
// isEnabled should return false when logger is disabled
79+
it.each([
80+
{}, // empty config
81+
{
82+
enabled: false, // not enabled
83+
},
84+
{
85+
enabled: false, // not enabled but has logger
86+
options: {
87+
'http-logger': { connection: { host: 'localhost', port: 80 } },
88+
},
89+
},
90+
{
91+
enabled: true, // enabled but do not have http-logger config
92+
options: {
93+
'non-http-logger': {},
94+
},
95+
},
96+
])(
97+
'isEnabled should return false when logger is disabled',
98+
async (config) => {
99+
const httpLogger = createMockHttpLogger(config);
100+
expect(httpLogger.isEnabled()).toBe(false);
101+
}
102+
);
77103
});

packages/core/test/utils/url.spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { get } from 'lodash';
21
import { getUrl, ConnectionConfig } from '../../src/lib/utils/url';
32

43
describe('url util functions', () => {

packages/serve/src/lib/middleware/activityLogMiddleware.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,20 @@ export class ActivityLogMiddleware extends BuiltInMiddleware<IActivityLoggerOpti
4242
const body = context.response.body as any;
4343
const error = body?.message;
4444
const user = context.state.user;
45+
const activityLog = {
46+
logTime,
47+
duration,
48+
method: context.request.method,
49+
url: context.request.originalUrl,
50+
ip: context.request.ip,
51+
header: context.request.header,
52+
params: context.params,
53+
query: context.request.query,
54+
status: context.response.status,
55+
error,
56+
user,
57+
};
4558
for (const activityLogger of Object.values(this.activityLoggerMap)) {
46-
const activityLog = {
47-
logTime,
48-
duration,
49-
method: context.request.method,
50-
url: context.request.originalUrl,
51-
ip: context.request.ip,
52-
header: context.request.header,
53-
params: context.params,
54-
query: context.request.query,
55-
status: context.response.status,
56-
error,
57-
user,
58-
};
5959
activityLogger.log(activityLog).catch((e) => {
6060
logger.debug(`Error when logging activity: ${e}`);
6161
});

packages/serve/src/lib/middleware/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from './response-format';
77
export * from './enforceHttpsMiddleware';
88
export * from './docRouterMiddleware';
99
export * from './errorHandlerMIddleware';
10+
export * from './activityLogMiddleware';
1011

1112
import { CorsMiddleware } from './corsMiddleware';
1213
import {

packages/serve/test/middlewares/built-in-middlewares/activityLogMiddleware.spec.ts

+65
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,69 @@ describe('Test activity log middlewares', () => {
174174
expect(actual[0].error).toEqual(expected.error);
175175
expect(actual[0].user).toEqual(expected.user);
176176
});
177+
// should not log when logger is disabled
178+
it('should not log when logger is disabled', async () => {
179+
// Arrange
180+
const ctx: KoaContext = {
181+
...sinon.stubInterface<KoaContext>(),
182+
183+
params: {
184+
uuid: faker.datatype.uuid(),
185+
},
186+
state: {
187+
user: {
188+
name: faker.name.firstName(),
189+
attr: {
190+
email: faker.internet.email(),
191+
id: faker.datatype.uuid(),
192+
},
193+
},
194+
},
195+
request: {
196+
...sinon.stubInterface<Request>(),
197+
ip: faker.internet.ip(),
198+
method: faker.internet.httpMethod(),
199+
originalUrl: faker.internet.url(),
200+
header: {
201+
...sinon.stubInterface<IncomingHttpHeaders>(),
202+
'X-Agent': 'test-normal-client',
203+
},
204+
query: {
205+
...sinon.stubInterface<ParsedUrlQuery>(),
206+
sortby: 'name',
207+
},
208+
},
209+
response: {
210+
...sinon.stubInterface<Response>(),
211+
status: 200,
212+
length: faker.datatype.number({ min: 100, max: 100000 }),
213+
body: {
214+
result: 'OK',
215+
},
216+
},
217+
};
218+
219+
const expected = {
220+
method: ctx.request.method,
221+
url: ctx.request.originalUrl,
222+
status: ctx.response.status,
223+
headers: ctx.request.headers,
224+
error: undefined,
225+
ip: ctx.request.ip,
226+
params: ctx.params,
227+
user: ctx.state.user,
228+
};
229+
// Act
230+
const middleware = new ActivityLogMiddleware(
231+
{ ...extensionConfig, enabled: false },
232+
'',
233+
[mockLogger]
234+
);
235+
await middleware.activate();
236+
await middleware.handle(ctx, async () => Promise.resolve());
237+
238+
// Assert
239+
const logMock = mockLogger.log as jest.Mock;
240+
expect(logMock).not.toHaveBeenCalled();
241+
});
177242
});

0 commit comments

Comments
 (0)