Skip to content

Commit 102f8af

Browse files
committed
chore: test
Signed-off-by: dhmlau <dhmlau@ca.ibm.com>
1 parent d75801c commit 102f8af

15 files changed

Lines changed: 2597 additions & 146 deletions

File tree

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright IBM Corp. and LoopBack contributors 2026. All Rights Reserved.
2+
// Node module: @loopback/example-metrics-prometheus
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {
7+
createStubInstance,
8+
expect,
9+
sinon,
10+
StubbedInstanceWithSinonAccessor,
11+
} from '@loopback/testlab';
12+
import {GreetingController} from '../../../controllers';
13+
import {GreetingService} from '../../../services';
14+
15+
describe('GreetingController (unit)', () => {
16+
let controller: GreetingController;
17+
let greetingService: StubbedInstanceWithSinonAccessor<GreetingService>;
18+
19+
beforeEach(() => {
20+
greetingService = createStubInstance(GreetingService);
21+
controller = new GreetingController(greetingService);
22+
});
23+
24+
describe('greet', () => {
25+
it('calls greeting service with name', async () => {
26+
const greeting = '[2026-02-11T12:00:00.000Z: 50] Hello, World';
27+
greetingService.stubs.greet.resolves(greeting);
28+
29+
const result = await controller.greet('World');
30+
31+
expect(result).to.deepEqual([greeting]);
32+
sinon.assert.calledWith(greetingService.stubs.greet, 'World');
33+
});
34+
35+
it('returns single greeting by default', async () => {
36+
const greeting = '[2026-02-11T12:00:00.000Z: 25] Hello, Alice';
37+
greetingService.stubs.greet.resolves(greeting);
38+
39+
const result = await controller.greet('Alice');
40+
41+
expect(result).to.have.length(1);
42+
expect(result[0]).to.equal(greeting);
43+
});
44+
45+
it('returns multiple greetings when count is specified', async () => {
46+
const greeting1 = '[2026-02-11T12:00:00.000Z: 10] Hello, Bob';
47+
const greeting2 = '[2026-02-11T12:00:00.100Z: 20] Hello, Bob';
48+
const greeting3 = '[2026-02-11T12:00:00.200Z: 30] Hello, Bob';
49+
50+
greetingService.stubs.greet
51+
.onFirstCall()
52+
.resolves(greeting1)
53+
.onSecondCall()
54+
.resolves(greeting2)
55+
.onThirdCall()
56+
.resolves(greeting3);
57+
58+
const result = await controller.greet('Bob', 3);
59+
60+
expect(result).to.have.length(3);
61+
expect(result).to.deepEqual([greeting1, greeting2, greeting3]);
62+
sinon.assert.calledThrice(greetingService.stubs.greet);
63+
});
64+
65+
it('handles count parameter of 1', async () => {
66+
const greeting = '[2026-02-11T12:00:00.000Z: 15] Hello, Charlie';
67+
greetingService.stubs.greet.resolves(greeting);
68+
69+
const result = await controller.greet('Charlie', 1);
70+
71+
expect(result).to.have.length(1);
72+
sinon.assert.calledOnce(greetingService.stubs.greet);
73+
});
74+
75+
it('handles count parameter of 0', async () => {
76+
const result = await controller.greet('Dave', 0);
77+
78+
expect(result).to.have.length(0);
79+
sinon.assert.notCalled(greetingService.stubs.greet);
80+
});
81+
82+
it('handles large count values', async () => {
83+
const greeting = '[2026-02-11T12:00:00.000Z: 40] Hello, Eve';
84+
greetingService.stubs.greet.resolves(greeting);
85+
86+
const result = await controller.greet('Eve', 10);
87+
88+
expect(result).to.have.length(10);
89+
sinon.assert.callCount(greetingService.stubs.greet, 10);
90+
});
91+
92+
it('calls service concurrently for multiple greetings', async () => {
93+
const greeting = '[2026-02-11T12:00:00.000Z: 35] Hello, Frank';
94+
greetingService.stubs.greet.resolves(greeting);
95+
96+
await controller.greet('Frank', 5);
97+
98+
// All calls should be made concurrently (Promise.all)
99+
sinon.assert.callCount(greetingService.stubs.greet, 5);
100+
greetingService.stubs.greet.getCalls().forEach(call => {
101+
sinon.assert.calledWith(call, 'Frank');
102+
});
103+
});
104+
105+
it('handles different names', async () => {
106+
greetingService.stubs.greet
107+
.withArgs('Alice')
108+
.resolves('[2026-02-11T12:00:00.000Z: 10] Hello, Alice');
109+
greetingService.stubs.greet
110+
.withArgs('Bob')
111+
.resolves('[2026-02-11T12:00:00.000Z: 20] Hello, Bob');
112+
113+
const result1 = await controller.greet('Alice');
114+
const result2 = await controller.greet('Bob');
115+
116+
expect(result1[0]).to.match(/Hello, Alice/);
117+
expect(result2[0]).to.match(/Hello, Bob/);
118+
});
119+
120+
it('handles empty name', async () => {
121+
const greeting = '[2026-02-11T12:00:00.000Z: 5] Hello, ';
122+
greetingService.stubs.greet.resolves(greeting);
123+
124+
const result = await controller.greet('');
125+
126+
expect(result).to.have.length(1);
127+
sinon.assert.calledWith(greetingService.stubs.greet, '');
128+
});
129+
130+
it('handles special characters in name', async () => {
131+
const greeting = '[2026-02-11T12:00:00.000Z: 45] Hello, Test@123';
132+
greetingService.stubs.greet.resolves(greeting);
133+
134+
const result = await controller.greet('Test@123');
135+
136+
expect(result[0]).to.equal(greeting);
137+
sinon.assert.calledWith(greetingService.stubs.greet, 'Test@123');
138+
});
139+
});
140+
141+
describe('service injection', () => {
142+
it('injects greeting service', () => {
143+
expect(controller).to.have.property('greetingService');
144+
});
145+
146+
it('uses service with interceptors', () => {
147+
// The controller is configured to use asProxyWithInterceptors
148+
// This ensures metrics interceptor can track the service calls
149+
expect(controller).to.be.instanceOf(GreetingController);
150+
});
151+
});
152+
153+
describe('error handling', () => {
154+
it('propagates service errors', async () => {
155+
const error = new Error('Service error');
156+
greetingService.stubs.greet.rejects(error);
157+
158+
await expect(controller.greet('ErrorTest')).to.be.rejectedWith(
159+
'Service error',
160+
);
161+
});
162+
163+
it('handles rejection in one of multiple calls', async () => {
164+
const error = new Error('One call failed');
165+
greetingService.stubs.greet
166+
.onFirstCall()
167+
.resolves('[2026-02-11T12:00:00.000Z: 10] Hello, Test')
168+
.onSecondCall()
169+
.rejects(error);
170+
171+
await expect(controller.greet('Test', 2)).to.be.rejectedWith(
172+
'One call failed',
173+
);
174+
});
175+
});
176+
});
177+
178+
// Made with Bob
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright IBM Corp. and LoopBack contributors 2026. All Rights Reserved.
2+
// Node module: @loopback/example-metrics-prometheus
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {expect} from '@loopback/testlab';
7+
import {GreetingService} from '../../../services';
8+
9+
describe('GreetingService (unit)', () => {
10+
let service: GreetingService;
11+
12+
beforeEach(() => {
13+
service = new GreetingService();
14+
});
15+
16+
describe('greet', () => {
17+
it('returns a greeting message', async () => {
18+
const result = await service.greet('World');
19+
20+
expect(result).to.be.a.String();
21+
expect(result).to.match(/Hello, World/);
22+
});
23+
24+
it('includes timestamp in greeting', async () => {
25+
const result = await service.greet('Alice');
26+
27+
expect(result).to.match(
28+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z: \d+\]/,
29+
);
30+
});
31+
32+
it('includes delay information', async () => {
33+
const result = await service.greet('Bob');
34+
35+
// Extract delay from message format: [timestamp: delay] Hello, name
36+
const delayMatch = result.match(/: (\d+)\]/);
37+
expect(delayMatch).to.not.be.null();
38+
39+
const delay = parseInt(delayMatch![1], 10);
40+
expect(delay).to.be.greaterThanOrEqual(0);
41+
expect(delay).to.be.lessThan(100);
42+
});
43+
44+
it('greets different names', async () => {
45+
const result1 = await service.greet('Alice');
46+
const result2 = await service.greet('Bob');
47+
const result3 = await service.greet('Charlie');
48+
49+
expect(result1).to.match(/Hello, Alice/);
50+
expect(result2).to.match(/Hello, Bob/);
51+
expect(result3).to.match(/Hello, Charlie/);
52+
});
53+
54+
it('handles empty name', async () => {
55+
const result = await service.greet('');
56+
57+
expect(result).to.match(/Hello, $/);
58+
});
59+
60+
it('handles special characters in name', async () => {
61+
const result = await service.greet('Alice & Bob');
62+
63+
expect(result).to.match(/Hello, Alice & Bob/);
64+
});
65+
66+
it('handles unicode characters in name', async () => {
67+
const result = await service.greet('世界');
68+
69+
expect(result).to.match(/Hello, /);
70+
});
71+
72+
it('introduces random delay', async () => {
73+
const delays: number[] = [];
74+
75+
// Call greet multiple times to collect delays
76+
for (let i = 0; i < 10; i++) {
77+
const result = await service.greet('Test');
78+
const delayMatch = result.match(/: (\d+)\]/);
79+
if (delayMatch) {
80+
delays.push(parseInt(delayMatch[1], 10));
81+
}
82+
}
83+
84+
// Check that we got different delays (not all the same)
85+
const uniqueDelays = new Set(delays);
86+
expect(uniqueDelays.size).to.be.greaterThan(1);
87+
});
88+
89+
it('completes within reasonable time', async () => {
90+
const startTime = Date.now();
91+
await service.greet('Performance Test');
92+
const endTime = Date.now();
93+
94+
const duration = endTime - startTime;
95+
// Should complete within 150ms (max delay is 100ms + overhead)
96+
expect(duration).to.be.lessThan(150);
97+
});
98+
});
99+
100+
describe('service injection', () => {
101+
it('is injectable', () => {
102+
expect(service).to.be.instanceOf(GreetingService);
103+
});
104+
105+
it('has greet method', () => {
106+
expect(service.greet).to.be.a.Function();
107+
});
108+
});
109+
110+
describe('concurrent greetings', () => {
111+
it('handles multiple concurrent greetings', async () => {
112+
const promises = [
113+
service.greet('User1'),
114+
service.greet('User2'),
115+
service.greet('User3'),
116+
];
117+
118+
const results = await Promise.all(promises);
119+
120+
expect(results).to.have.length(3);
121+
expect(results[0]).to.match(/Hello, User1/);
122+
expect(results[1]).to.match(/Hello, User2/);
123+
expect(results[2]).to.match(/Hello, User3/);
124+
});
125+
});
126+
});
127+
128+
// Made with Bob

0 commit comments

Comments
 (0)