Skip to content

Commit 79f4cbc

Browse files
committed
Refactoring (env, require, test)
1 parent 321d1aa commit 79f4cbc

35 files changed

+380
-326
lines changed

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Fixed
1111

12-
- tbd
12+
- Add `cds.requires.kinds` for websockets and merge config
13+
- Pass all `cds.env.websocket` config to adapter and redis implementation
14+
- Streamline `cds.env` access in socket and redis implementation
15+
- Refactor unit-tests to be grouped by implementation
1316

1417
## Version 1.0.1 - 2024-06-03
1518

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ cds.test(__dirname + "/..");
624624

625625
cds.env.websocket = {
626626
kind: "socket.io",
627+
impl: null,
627628
};
628629

629630
const authorization = `Basic ${Buffer.from("alice:alice").toString("base64")}`;

package-lock.json

+187-181
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+22-5
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,19 @@
5454
"@eslint/js": "9.4.0",
5555
"@sap/cds": "^7.9.2",
5656
"@sap/cds-dk": "^7.9.2",
57-
"@sap/xssec": "4.0.1",
57+
"@sap/xssec": "4.0.3",
5858
"@socket.io/redis-adapter": "^8.3.0",
5959
"@socket.io/redis-streams-adapter": "^0.2.2",
6060
"@types/express": "^4.17.21",
6161
"eslint": "^9.4.0",
6262
"eslint-config-prettier": "^9.1.0",
63-
"eslint-plugin-jest": "^28.5.0",
64-
"eslint-plugin-n": "17.7.0",
63+
"eslint-plugin-jest": "^28.6.0",
64+
"eslint-plugin-n": "17.8.1",
6565
"express-request-proxy": "^2.2.2",
66-
"globals": "15.3.0",
66+
"globals": "15.4.0",
6767
"jest": "^29.7.0",
6868
"passport": "0.7.0",
69-
"prettier": "^3.3.0",
69+
"prettier": "^3.3.1",
7070
"socket.io-client": "^4.7.5"
7171
},
7272
"license": "Apache-2.0",
@@ -75,6 +75,16 @@
7575
"url": "https://github.com/cap-js-community/websocket.git"
7676
},
7777
"cds": {
78+
"requires": {
79+
"kinds": {
80+
"websocket-ws": {
81+
"impl": "@cap-js-community/websocket/src/socket/ws.js"
82+
},
83+
"websocket-socket.io": {
84+
"impl": "@cap-js-community/websocket/src/socket/socket.io.js"
85+
}
86+
}
87+
},
7888
"schema": {
7989
"cds": {
8090
"websocket": {
@@ -116,6 +126,13 @@
116126
"options": {
117127
"type": "object",
118128
"description": "Websocket adapter implementation options",
129+
"properties": {
130+
"key": {
131+
"type": "string",
132+
"description": "Websocket adapter channel prefix",
133+
"default": "websocket"
134+
}
135+
},
119136
"additionalProperties": true
120137
},
121138
"config": {

src/adapter/redis.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ const cds = require("@sap/cds");
66
const LOG = cds.log("/websocket/redis");
77

88
class RedisAdapter {
9-
constructor(server, options, config) {
9+
constructor(server, config) {
1010
this.server = server;
11-
this.options = options;
1211
this.config = config;
13-
this.prefix = options?.key ?? "websocket";
12+
this.prefix = config?.options?.key ?? "websocket";
1413
}
1514

1615
async setup() {

src/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,11 @@ async function initWebSocketServer(server, path) {
9595
}
9696
try {
9797
cds.env.websocket ??= {};
98+
cds.env.websocket = { ...cds.env.requires?.websocket, ...cds.env.websocket };
9899
cds.env.websocket.kind ??= "ws";
99100
const serverImpl = cds.env.websocket.impl || cds.env.websocket.kind;
100101
const socketModule = SocketServer.require(serverImpl, "socket");
101-
socketServer = new socketModule(server, path);
102+
socketServer = new socketModule(server, path, cds.env.websocket);
102103
await socketServer.setup();
103104
return socketServer;
104105
} catch (err) {

src/redis/index.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@ const createSecondaryClientAndConnect = (options) => {
4343
};
4444

4545
const createClientBase = (options = {}) => {
46-
const adapterActive = cds.env.websocket?.adapter?.active !== false;
46+
const adapterActive = options?.active !== false;
4747
if (!adapterActive) {
4848
LOG?.info("Redis adapter is disabled");
4949
return;
5050
}
51-
const adapterActiveExplicit = !!cds.env.websocket?.adapter?.active;
52-
const adapterLocal = !!cds.env.websocket?.adapter?.local;
51+
const adapterActiveExplicit = !!options?.active;
52+
const adapterLocal = !!options?.local;
5353
if (!(IS_ON_CF || adapterActiveExplicit || adapterLocal)) {
5454
LOG?.info("Redis not available in local environment");
5555
return;
5656
}
5757
let credentials;
5858
try {
59-
credentials = xsenv.serviceCredentials({ label: "redis-cache", ...cds.env.websocket?.adapter?.vcap });
59+
credentials = xsenv.serviceCredentials({ label: "redis-cache", ...options?.vcap });
6060
} catch (err) {
6161
LOG?.info(err.message);
6262
}
@@ -74,11 +74,11 @@ const createClientBase = (options = {}) => {
7474
defaults: {
7575
password: credentials.password,
7676
socket: { tls: credentials.tls },
77-
...options,
77+
...options?.config,
7878
},
7979
});
8080
}
81-
return redis.createClient({ url, ...options });
81+
return redis.createClient({ url, ...options?.config });
8282
} catch (err) {
8383
throw new Error("Error during create client with redis-cache service:" + err);
8484
}

src/socket/base.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ class SocketServer {
1414
* Constructor for websocket server
1515
* @param {Object} server HTTP server from express app
1616
* @param {string} path Protocol path, e.g. '/ws'
17+
* @param {Object} config Websocket server configuration
1718
*/
18-
constructor(server, path) {
19+
constructor(server, path, config) {
1920
this.id = crypto.randomUUID();
2021
this.server = server;
2122
this.path = path;
23+
this.config = config;
2224
this.adapter = null;
2325
this.adapterActive = false;
2426
cds.ws = null;
@@ -299,10 +301,13 @@ class SocketServer {
299301
if (impl.startsWith("./") || impl.startsWith("../")) {
300302
return require(path.join(process.cwd(), impl));
301303
} else if (context) {
302-
return require(path.join("..", context, impl));
303-
} else {
304-
return require(impl);
304+
try {
305+
return require(path.join("..", context, impl));
306+
} catch {
307+
// ignore
308+
}
305309
}
310+
return require(impl);
306311
}
307312
}
308313

src/socket/socket.io.js

+8-15
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ const LOG = cds.log("/websocket/socket.io");
1010
const DEBUG = cds.debug("websocket");
1111

1212
class SocketIOServer extends SocketServer {
13-
constructor(server, path) {
14-
super(server, path);
13+
constructor(server, path, config) {
14+
super(server, path, config);
1515
this.io = new Server(server, {
1616
path,
17-
...cds.env.websocket?.options,
17+
...config?.options,
1818
});
1919
this.io.engine.on("connection_error", (err) => {
2020
delete err.req;
@@ -137,20 +137,13 @@ class SocketIOServer extends SocketServer {
137137

138138
async applyAdapter() {
139139
try {
140-
const adapterImpl = cds.env.websocket?.adapter?.impl;
141-
if (adapterImpl) {
142-
let options = {};
143-
if (cds.env.websocket?.adapter?.options) {
144-
options = { ...options, ...cds.env.websocket?.adapter?.options };
145-
}
146-
let config = {};
147-
if (cds.env.websocket?.adapter?.config) {
148-
config = { ...config, ...cds.env.websocket?.adapter?.config };
149-
}
140+
const config = { ...this.config?.adapter };
141+
if (config.impl) {
150142
let client;
151143
let subClient;
152-
const adapterFactory = SocketServer.require(adapterImpl);
153-
switch (adapterImpl) {
144+
const options = { ...config?.options };
145+
const adapterFactory = SocketServer.require(config.impl);
146+
switch (config.impl) {
154147
case "@socket.io/redis-adapter":
155148
if (await redis.connectionCheck(config)) {
156149
client = await redis.createPrimaryClientAndConnect(config);

src/socket/ws.js

+6-14
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ const LOG = cds.log("/websocket/ws");
1010
const DEBUG = cds.debug("websocket");
1111

1212
class SocketWSServer extends SocketServer {
13-
constructor(server, path) {
14-
super(server, path);
13+
constructor(server, path, config) {
14+
super(server, path, config);
1515
this.wss = new WebSocket.Server({ server });
1616
this.services = {};
1717
cds.ws = this;
@@ -196,18 +196,10 @@ class SocketWSServer extends SocketServer {
196196

197197
async applyAdapter() {
198198
try {
199-
const adapterImpl = cds.env.websocket?.adapter?.impl;
200-
if (adapterImpl) {
201-
let options = {};
202-
if (cds.env.websocket?.adapter?.options) {
203-
options = { ...options, ...cds.env.websocket?.adapter?.options };
204-
}
205-
let config = {};
206-
if (cds.env.websocket?.adapter?.config) {
207-
config = { ...config, ...cds.env.websocket?.adapter?.config };
208-
}
209-
const adapterFactory = SocketServer.require(adapterImpl, "adapter");
210-
this.adapter = new adapterFactory(this, options, config);
199+
const config = { ...this.config?.adapter };
200+
if (config.impl) {
201+
const adapterFactory = SocketServer.require(config.impl, "adapter");
202+
this.adapter = new adapterFactory(this, config);
211203
await this.adapter?.setup?.();
212204
this.adapterActive = !!this.adapter?.client;
213205
}

test/_env/mocks/redis.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,20 @@ module.exports = {
7575
break;
7676
}
7777
},
78-
createClient: jest.fn(() => {
78+
createClient: jest.fn((options) => {
7979
if (createClientError) {
8080
createClientError = false;
8181
throw new Error("create client error");
8282
}
83+
client.options = options;
8384
return client;
8485
}),
85-
createCluster: jest.fn(() => {
86+
createCluster: jest.fn((options) => {
8687
if (createClientError) {
8788
createClientError = false;
8889
throw new Error("create cluster error");
8990
}
91+
client.options = options;
9092
return client;
9193
}),
9294
commandOptions: jest.fn(() => {

test/_env/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
}
4949
},
5050
"requires": {
51+
"websocket": {
52+
"kind": "ws"
53+
},
5154
"auth": {
5255
"[production]": {
5356
"kind": "xsuaa"

test/base.test.js test/base/base.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const cds = require("@sap/cds");
44

5-
const SocketServer = require("../src/socket/base");
5+
const SocketServer = require("../../src/socket/base");
66

77
describe("Base", () => {
88
beforeAll(async () => {});

test/redis.test.js test/redis/redis.test.js

+25-17
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@
22

33
const cds = require("@sap/cds");
44
const xsenv = require("@sap/xsenv");
5-
const redisMock = require("./_env/mocks/redis");
6-
jest.mock("redis", () => require("./_env/mocks/redis"));
7-
const redis = require("../src/redis");
5+
const redisMock = require("../_env/mocks/redis");
6+
jest.mock("redis", () => require("../_env/mocks/redis"));
7+
const redis = require("../../src/redis");
88

9-
cds.test(__dirname + "/_env");
9+
cds.test(__dirname + "/../_env");
1010

1111
jest.spyOn(xsenv, "serviceCredentials").mockReturnValue({ uri: "uri" });
1212

13+
const redisConfig = {
14+
a: 1,
15+
};
16+
const adapterOptions = {
17+
impl: "redis",
18+
local: true,
19+
config: redisConfig,
20+
};
21+
1322
cds.env.websocket = {
1423
kind: "ws",
15-
adapter: {
16-
impl: "redis",
17-
local: true,
18-
},
24+
adapter: adapterOptions,
1925
};
2026

2127
describe("Redis", () => {
@@ -24,14 +30,16 @@ describe("Redis", () => {
2430
afterAll(async () => {});
2531

2632
test("Client", async () => {
27-
const main = await redis.createPrimaryClientAndConnect();
33+
const main = await redis.createPrimaryClientAndConnect(adapterOptions);
2834
expect(main).toBeDefined();
29-
const second = await redis.createSecondaryClientAndConnect();
35+
expect(main.options).toMatchObject(redisConfig);
36+
const second = await redis.createSecondaryClientAndConnect(adapterOptions);
3037
expect(second).toBeDefined();
38+
expect(second.options).toMatchObject(redisConfig);
3139
});
3240

3341
test("Client fail", async () => {
34-
const main = await redis.createPrimaryClientAndConnect();
42+
const main = await redis.createPrimaryClientAndConnect(adapterOptions);
3543
expect(main).toBeDefined();
3644
main.error(new Error("Failed"));
3745
expect(main.on).toHaveBeenNthCalledWith(1, "error", expect.any(Function));
@@ -40,33 +48,33 @@ describe("Redis", () => {
4048
test("Client createClient exception", async () => {
4149
await redis.closeClients();
4250
redisMock.throwError("createClient");
43-
let main = await redis.createPrimaryClientAndConnect();
51+
let main = await redis.createPrimaryClientAndConnect(adapterOptions);
4452
expect(main).toBeUndefined();
4553
redisMock.throwError("createClient");
46-
let secondary = await redis.createSecondaryClientAndConnect();
54+
let secondary = await redis.createSecondaryClientAndConnect(adapterOptions);
4755
expect(secondary).toBeUndefined();
4856
});
4957

5058
test("Client connect exception", async () => {
5159
await redis.closeClients();
5260
redisMock.throwError("connect");
53-
let main = await redis.createPrimaryClientAndConnect();
61+
let main = await redis.createPrimaryClientAndConnect(adapterOptions);
5462
expect(main).toBeUndefined();
5563
redisMock.throwError("connect");
56-
let secondary = await redis.createSecondaryClientAndConnect();
64+
let secondary = await redis.createSecondaryClientAndConnect(adapterOptions);
5765
expect(secondary).toBeUndefined();
5866
});
5967

6068
test("Client error", async () => {
6169
await redis.closeClients();
62-
const main = await redis.createPrimaryClientAndConnect();
70+
const main = await redis.createPrimaryClientAndConnect(adapterOptions);
6371
expect(main).toBeDefined();
6472
main.error(new Error("error"));
6573
});
6674

6775
test("Client reconnect", async () => {
6876
await redis.closeClients();
69-
const main = await redis.createPrimaryClientAndConnect();
77+
const main = await redis.createPrimaryClientAndConnect(adapterOptions);
7078
expect(main).toBeDefined();
7179
main.reconnect();
7280
});

0 commit comments

Comments
 (0)