Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/backend/conat/test/service.test.ts
1450 views
1
/*
2
3
DEVELOPMENT:
4
5
pnpm test ./service.test.ts
6
7
*/
8
9
import { callConatService, createConatService } from "@cocalc/conat/service";
10
import {
11
createServiceClient,
12
createServiceHandler,
13
} from "@cocalc/conat/service/typed";
14
import { before, after } from "@cocalc/backend/conat/test/setup";
15
import { wait } from "@cocalc/backend/conat/test/util";
16
import { is_date as isDate } from "@cocalc/util/misc";
17
import { delay } from "awaiting";
18
import { initConatServer } from "@cocalc/backend/conat/test/setup";
19
import getPort from "@cocalc/backend/get-port";
20
import { once } from "@cocalc/util/async-utils";
21
22
beforeAll(before);
23
24
describe("create a service and test it out", () => {
25
let s;
26
it("creates a service", async () => {
27
s = createConatService({
28
service: "echo",
29
handler: (mesg) => mesg.repeat(2),
30
});
31
await once(s, "running");
32
expect(await callConatService({ service: "echo", mesg: "hello" })).toBe(
33
"hellohello",
34
);
35
});
36
37
it("closes the services and observes it doesn't work anymore", async () => {
38
s.close();
39
await expect(async () => {
40
await callConatService({ service: "echo", mesg: "hi", timeout: 250 });
41
}).rejects.toThrowError("time");
42
});
43
});
44
45
describe("verify that you can create a service AFTER calling it and things to still work fine", () => {
46
let result = "";
47
it("call a service that does not exist yet", () => {
48
(async () => {
49
result = await callConatService({ service: "echo3", mesg: "hello " });
50
})();
51
});
52
53
it("create the echo3 service and observe that it answer the request we made before the service was created", async () => {
54
const s = createConatService({
55
service: "echo3",
56
handler: (mesg) => mesg.repeat(3),
57
});
58
await wait({ until: () => result });
59
expect(result).toBe("hello hello hello ");
60
61
s.close();
62
});
63
});
64
65
describe("create and test a more complicated service", () => {
66
let client, service;
67
it("defines the service", async () => {
68
interface Api {
69
add: (a: number, b: number) => Promise<number>;
70
concat: (a: Buffer, b: Buffer) => Promise<Buffer>;
71
now: () => Promise<Date>;
72
big: (n: number) => Promise<string>;
73
len: (s: string) => Promise<number>;
74
}
75
76
const name = "my-service";
77
service = await createServiceHandler<Api>({
78
service: name,
79
subject: name,
80
description: "My Service",
81
impl: {
82
// put any functions here that take/return MsgPack'able values
83
add: async (a, b) => a + b,
84
concat: async (a, b) => Buffer.concat([a, b]),
85
now: async () => {
86
await delay(5);
87
return new Date();
88
},
89
big: async (n: number) => "x".repeat(n),
90
len: async (s: string) => s.length,
91
},
92
});
93
94
client = createServiceClient<Api>({
95
service: name,
96
subject: name,
97
});
98
});
99
100
it("tests the service", async () => {
101
// these calls are all type checked using typescript
102
expect(await client.add(2, 3)).toBe(5);
103
104
const a = Buffer.from("hello");
105
const b = Buffer.from(" conat");
106
expect(await client.concat(a, b)).toEqual(Buffer.concat([a, b]));
107
108
const d = await client.now();
109
expect(isDate(d)).toBe(true);
110
expect(Math.abs(d.valueOf() - Date.now())).toBeLessThan(100);
111
112
const n = 10 * 1e6;
113
expect((await client.big(n)).length).toBe(n);
114
115
expect(await client.len("x".repeat(n))).toBe(n);
116
});
117
118
it("cleans up", () => {
119
service.close();
120
});
121
});
122
123
describe("create a service with specified client, stop and start the server, and see service still works", () => {
124
let server;
125
let client;
126
let client2;
127
let port;
128
it("create a conat server and client", async () => {
129
port = await getPort();
130
server = await initConatServer({ port });
131
client = server.client({ reconnectionDelay: 50 });
132
client2 = server.client({ reconnectionDelay: 50 });
133
});
134
135
let service;
136
it("create service using specific client and call it using both clients", async () => {
137
//You usually do NOT want a non-ephemeral service...
138
service = createConatService({
139
client,
140
service: "double",
141
handler: (mesg) => mesg.repeat(2),
142
});
143
await once(service, "running");
144
145
expect(
146
await callConatService({ client, service: "double", mesg: "hello" }),
147
).toBe("hellohello");
148
149
expect(
150
await callConatService({
151
client: client2,
152
service: "double",
153
mesg: "hello",
154
}),
155
).toBe("hellohello");
156
});
157
158
it("disconnect client and check service still works on reconnect", async () => {
159
// cause a disconnect -- client will connect again in 50ms soon
160
// and then handle the request below:
161
client.conn.io.engine.close();
162
await delay(100);
163
expect(
164
await callConatService({
165
client: client2,
166
service: "double",
167
mesg: "hello",
168
}),
169
).toBe("hellohello");
170
});
171
172
it("disconnect client2 and check service still works on reconnect", async () => {
173
// cause a disconnect -- client will connect again in 50ms soon
174
// and handle the request below:
175
client2.conn.io.engine.close();
176
await delay(100);
177
expect(
178
await callConatService({
179
client: client2,
180
service: "double",
181
mesg: "hello",
182
}),
183
).toBe("hellohello");
184
});
185
186
it("disconnect both clients and check service still works on reconnect", async () => {
187
// cause a disconnect -- client will connect again in 50ms soon
188
// and handle the request below:
189
client.conn.io.engine.close();
190
client2.conn.io.engine.close();
191
let x;
192
await wait({
193
until: async () => {
194
x = await callConatService({
195
client: client2,
196
service: "double",
197
mesg: "hello",
198
timeout: 500,
199
});
200
return true;
201
},
202
});
203
expect(x).toBe("hellohello");
204
});
205
206
it("kills the server, then makes another server serving on the same port", async () => {
207
await server.close();
208
await delay(250);
209
server = await initConatServer({ port });
210
// Killing the server is not at all a normal thing to expect, and causes loss of
211
// its state. The clients have to sync realize subscriptions are missing. This
212
// takes a fraction of a second and the call below won't immediately work.
213
await wait({
214
until: async () => {
215
try {
216
await callConatService({
217
client: client2,
218
service: "double",
219
mesg: "hello",
220
noRetry: true,
221
timeout: 250,
222
});
223
return true;
224
} catch (err) {
225
return false;
226
}
227
},
228
});
229
expect(
230
await callConatService({
231
client: client2,
232
service: "double",
233
mesg: "hello",
234
noRetry: true,
235
}),
236
).toBe("hellohello");
237
});
238
239
it("cleans up", () => {
240
service.close();
241
client.close();
242
client2.close();
243
server.close();
244
});
245
});
246
247
describe("create a slow service and check that the timeout parameter works", () => {
248
let s;
249
it("creates a slow service", async () => {
250
s = createConatService({
251
service: "slow",
252
handler: async (d) => {
253
await delay(d);
254
return { delay: d };
255
},
256
});
257
await once(s, "running");
258
});
259
260
it("confirms it works", async () => {
261
const t0 = Date.now();
262
const r = await callConatService({
263
service: s.name,
264
mesg: 50,
265
});
266
expect(r).toEqual({ delay: 50 });
267
expect(Date.now() - t0).toBeGreaterThan(45);
268
expect(Date.now() - t0).toBeLessThan(500);
269
});
270
271
it("confirms it throws a timeout error", async () => {
272
await expect(async () => {
273
await callConatService({
274
service: s.name,
275
mesg: 5000,
276
timeout: 75,
277
});
278
}).rejects.toThrowError("imeout");
279
});
280
281
it("clean up", async () => {
282
s.close();
283
});
284
});
285
286
afterAll(after);
287
288