Skip to content

Commit 4a58a19

Browse files
committed
test(server): Add unit tests for server.js error handling paths
Add mocked unit tests covering the previously untested error-handling functions in lib/server.js: the HTTP server error event handler, buildServer error event forwarding, and the close() rejection handler when buildServer.destroy() fails.
1 parent 5e5d4fb commit 4a58a19

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import test from "ava";
2+
import sinon from "sinon";
3+
import esmock from "esmock";
4+
import {EventEmitter} from "node:events";
5+
6+
function createMockGraph(mockBuildServer) {
7+
const mockProject = {
8+
getName: sinon.stub().returns("test.project"),
9+
getSourceReader: sinon.stub().returns({})
10+
};
11+
return {
12+
getRoot: sinon.stub().returns(mockProject),
13+
traverseBreadthFirst: sinon.stub().resolves(),
14+
getProject: sinon.stub().returns(null),
15+
serve: sinon.stub().resolves(mockBuildServer)
16+
};
17+
}
18+
19+
function createMockBuildServer() {
20+
const buildServer = new EventEmitter();
21+
buildServer.getRootReader = sinon.stub().returns({});
22+
buildServer.getDependenciesReader = sinon.stub().returns({});
23+
buildServer.getReader = sinon.stub().returns({});
24+
buildServer.destroy = sinon.stub().resolves();
25+
return buildServer;
26+
}
27+
28+
function createMockServer() {
29+
const mockServer = new EventEmitter();
30+
mockServer.close = sinon.stub().callsFake((cb) => cb());
31+
return mockServer;
32+
}
33+
34+
function createMocks(mockServer) {
35+
const mockApp = {
36+
use: sinon.stub(),
37+
listen: sinon.stub().callsFake((options, cb) => {
38+
process.nextTick(cb);
39+
return mockServer;
40+
})
41+
};
42+
43+
return {
44+
"express": sinon.stub().returns(mockApp),
45+
"portscanner": {
46+
findAPortNotInUse: sinon.stub().callsFake((port, portMax, host, cb) => {
47+
cb(null, port);
48+
})
49+
},
50+
"../../../lib/middleware/MiddlewareManager.js": {
51+
default: class MockMiddlewareManager {
52+
applyMiddleware() {}
53+
}
54+
},
55+
"@ui5/fs/resourceFactory": {
56+
createReaderCollection: sinon.stub().returns({})
57+
},
58+
"@ui5/fs/ReaderCollectionPrioritized": {
59+
default: class MockReaderCollectionPrioritized {}
60+
}
61+
};
62+
}
63+
64+
test("server.on('error') rejects the serve promise", async (t) => {
65+
const mockServer = createMockServer();
66+
const mockBuildServer = createMockBuildServer();
67+
const testError = new Error("server error");
68+
69+
const mockApp = {
70+
use: sinon.stub(),
71+
listen: sinon.stub().callsFake((options, cb) => {
72+
// Emit error before the listen callback fires so reject() is called
73+
process.nextTick(() => {
74+
mockServer.emit("error", testError);
75+
});
76+
return mockServer;
77+
})
78+
};
79+
80+
const mocks = {
81+
"express": sinon.stub().returns(mockApp),
82+
"portscanner": {
83+
findAPortNotInUse: sinon.stub().callsFake((port, portMax, host, cb) => {
84+
cb(null, port);
85+
})
86+
},
87+
"../../../lib/middleware/MiddlewareManager.js": {
88+
default: class MockMiddlewareManager {
89+
applyMiddleware() {}
90+
}
91+
},
92+
"@ui5/fs/resourceFactory": {
93+
createReaderCollection: sinon.stub().returns({})
94+
},
95+
"@ui5/fs/ReaderCollectionPrioritized": {
96+
default: class MockReaderCollectionPrioritized {}
97+
}
98+
};
99+
100+
const {serve} = await esmock("../../../lib/server.js", mocks);
101+
const graph = createMockGraph(mockBuildServer);
102+
const error = await t.throwsAsync(serve(graph, {port: 3000}));
103+
t.is(error, testError);
104+
});
105+
106+
107+
test("buildServer 'error' event is forwarded to error callback", async (t) => {
108+
const mockServer = createMockServer();
109+
const mockBuildServer = createMockBuildServer();
110+
const mocks = createMocks(mockServer);
111+
const testError = new Error("build error");
112+
113+
const {serve} = await esmock("../../../lib/server.js", mocks);
114+
const graph = createMockGraph(mockBuildServer);
115+
116+
const errorReceived = new Promise((resolve) => {
117+
serve(graph, {port: 3000}, resolve).then(() => {
118+
mockBuildServer.emit("error", testError);
119+
});
120+
});
121+
122+
const err = await errorReceived;
123+
t.is(err, testError);
124+
});
125+
126+
test("close() still calls server.close when buildServer.destroy() rejects", async (t) => {
127+
const mockServer = createMockServer();
128+
const mockBuildServer = createMockBuildServer();
129+
const mocks = createMocks(mockServer);
130+
131+
mockBuildServer.destroy = sinon.stub().rejects(new Error("destroy failed"));
132+
133+
const {serve} = await esmock("../../../lib/server.js", mocks);
134+
const graph = createMockGraph(mockBuildServer);
135+
const result = await serve(graph, {port: 3000});
136+
137+
await new Promise((resolve) => {
138+
result.close(resolve);
139+
});
140+
t.true(mockServer.close.calledOnce, "server.close was called despite destroy rejection");
141+
});

0 commit comments

Comments
 (0)