Skip to content

Commit 45bb27f

Browse files
committed
feat: add more tests to improve coverage
1 parent 9b0ee25 commit 45bb27f

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

src/decorators/logged.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ export function logged(options?: ILogger | LoggedDecoratorOptions) {
6161

6262
descriptor.value = async function<T>(...args: any[]): Promise<T|void> {
6363
try {
64-
return original && await original.apply(this || target, args);
64+
const ctx = (typeof this !== 'undefined' && this !== null)
65+
? this
66+
: target;
67+
68+
if (original) {
69+
return await original.apply(ctx, args);
70+
}
6571
} catch (err) {
6672
const logger: ILogger = (options && (options as LoggedDecoratorOptions).logger)
6773
? (options as LoggedDecoratorOptions).logger as ILogger
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { expect } from 'chai';
2+
import { logged } from '../../src/decorators/logged';
3+
4+
/*
5+
* Additional coverage for logged.ts to reach 100% branches:
6+
* - Exercise `this || target` left-hand path (this is truthy)
7+
* - Ensure `this.logger` is preferred when available
8+
* - Cover options.logger usage and default rethrow behavior
9+
* - Cover passing an ILogger directly as options
10+
*/
11+
12+
describe('decorators/logged() this logger and options logger branches', () => {
13+
afterEach(() => {
14+
// nothing to cleanup
15+
});
16+
17+
it('should use bound `this` in apply(), prefer this.logger, not rethrow when doNotThrow=true', async () => {
18+
const error = new Error('use-this');
19+
let capturedThis: any;
20+
// original captures `this` then throws
21+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22+
const original: any = function original(this: any) {
23+
capturedThis = this;
24+
throw error;
25+
};
26+
27+
const descriptor: PropertyDescriptor = { value: original } as any;
28+
const target: any = { logger: { error: () => void 0, warn: () => void 0, info: () => void 0, log: () => void 0 } };
29+
30+
// Apply decorator: do not throw and use non-default level to verify dynamic call
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
32+
(logged({ doNotThrow: true, level: 'warn' }) as any)(target, 'x', descriptor);
33+
34+
const thisLoggerCalls: { level: string; error: Error }[] = [];
35+
const self: any = {
36+
logger: {
37+
error: (_e: Error) => thisLoggerCalls.push({ level: 'error', error: _e }),
38+
warn: (_e: Error) => thisLoggerCalls.push({ level: 'warn', error: _e }),
39+
info: (_e: Error) => thisLoggerCalls.push({ level: 'info', error: _e }),
40+
log: (_e: Error) => thisLoggerCalls.push({ level: 'log', error: _e }),
41+
},
42+
};
43+
44+
// Call with `this` bound so (this || target) takes left-hand branch
45+
const res = await (descriptor.value as any).call(self);
46+
expect(res).to.be.undefined; // swallowed due to doNotThrow
47+
expect(capturedThis).to.equal(self);
48+
expect(thisLoggerCalls).to.have.length(1);
49+
expect(thisLoggerCalls[0].level).to.equal('warn');
50+
expect(thisLoggerCalls[0].error).to.equal(error);
51+
});
52+
53+
it('should use options.logger when provided and rethrow by default', async () => {
54+
const error = new Error('use-options-logger');
55+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
56+
const original: any = () => { throw error; };
57+
const descriptor: PropertyDescriptor = { value: original } as any;
58+
const target: any = {};
59+
60+
const calls: Error[] = [];
61+
const logger = { info: (e: Error) => { calls.push(e); }, error: () => {}, warn: () => {}, log: () => {} } as any;
62+
63+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
64+
(logged({ logger, level: 'info' }) as any)(target, 'y', descriptor);
65+
66+
try {
67+
await (descriptor.value as any)();
68+
expect.fail('should have thrown');
69+
} catch (e) {
70+
expect(e).to.equal(error);
71+
expect(calls).to.have.length(1);
72+
expect(calls[0]).to.equal(error);
73+
}
74+
});
75+
76+
it('should treat options itself as ILogger when it has error() method', async () => {
77+
const error = new Error('options-is-logger');
78+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
79+
const original: any = () => { throw error; };
80+
const descriptor: PropertyDescriptor = { value: original } as any;
81+
const target: any = {};
82+
83+
const calls: Error[] = [];
84+
const optionsLogger = { error: (e: Error) => calls.push(e), log: () => {}, warn: () => {}, info: () => {} } as any;
85+
86+
// Pass logger directly instead of options object
87+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
88+
(logged(optionsLogger) as any)(target, 'z', descriptor);
89+
90+
try {
91+
await (descriptor.value as any)();
92+
expect.fail('should have thrown');
93+
} catch (e) {
94+
expect(e).to.equal(error);
95+
expect(calls).to.have.length(1);
96+
expect(calls[0]).to.equal(error);
97+
}
98+
});
99+
100+
it('should use bound `this` in apply() and return value when original resolves', async () => {
101+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
102+
const original: any = function(this: any) { return this.answer; };
103+
const descriptor: PropertyDescriptor = { value: original } as any;
104+
const target: any = {};
105+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
106+
(logged() as any)(target, 'ok', descriptor);
107+
const self: any = { answer: 42 };
108+
const res = await (descriptor.value as any).call(self);
109+
expect(res).to.equal(42);
110+
});
111+
112+
it('should use target as `this` when caller `this` is falsy (right branch of this||target)', async () => {
113+
let gotThis: any;
114+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
115+
const original: any = function(this: any) { gotThis = this; return this.mark; };
116+
const descriptor: PropertyDescriptor = { value: original } as any;
117+
const target: any = { mark: 'TARGET' };
118+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
119+
(logged() as any)(target, 'right', descriptor);
120+
// Explicitly call with undefined `this`
121+
const res = await (descriptor.value as any).call(undefined);
122+
expect(res).to.equal('TARGET');
123+
expect(gotThis).to.equal(target);
124+
});
125+
126+
it('should use target when caller `this` is null (second condition false)', async () => {
127+
let gotThis: any;
128+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
129+
const original: any = function(this: any) { gotThis = this; return this.mark; };
130+
const descriptor: PropertyDescriptor = { value: original } as any;
131+
const target: any = { mark: 'TARGET-NULL' };
132+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
133+
(logged() as any)(target, 'right-null', descriptor);
134+
// Explicitly call with null `this` to pass first check and fail second
135+
const res = await (descriptor.value as any).call(null);
136+
expect(res).to.equal('TARGET-NULL');
137+
expect(gotThis).to.equal(target);
138+
});
139+
});

0 commit comments

Comments
 (0)