Path: blob/master/src/packages/backend/conat/test/service.test.ts
1450 views
/*12DEVELOPMENT:34pnpm test ./service.test.ts56*/78import { callConatService, createConatService } from "@cocalc/conat/service";9import {10createServiceClient,11createServiceHandler,12} from "@cocalc/conat/service/typed";13import { before, after } from "@cocalc/backend/conat/test/setup";14import { wait } from "@cocalc/backend/conat/test/util";15import { is_date as isDate } from "@cocalc/util/misc";16import { delay } from "awaiting";17import { initConatServer } from "@cocalc/backend/conat/test/setup";18import getPort from "@cocalc/backend/get-port";19import { once } from "@cocalc/util/async-utils";2021beforeAll(before);2223describe("create a service and test it out", () => {24let s;25it("creates a service", async () => {26s = createConatService({27service: "echo",28handler: (mesg) => mesg.repeat(2),29});30await once(s, "running");31expect(await callConatService({ service: "echo", mesg: "hello" })).toBe(32"hellohello",33);34});3536it("closes the services and observes it doesn't work anymore", async () => {37s.close();38await expect(async () => {39await callConatService({ service: "echo", mesg: "hi", timeout: 250 });40}).rejects.toThrowError("time");41});42});4344describe("verify that you can create a service AFTER calling it and things to still work fine", () => {45let result = "";46it("call a service that does not exist yet", () => {47(async () => {48result = await callConatService({ service: "echo3", mesg: "hello " });49})();50});5152it("create the echo3 service and observe that it answer the request we made before the service was created", async () => {53const s = createConatService({54service: "echo3",55handler: (mesg) => mesg.repeat(3),56});57await wait({ until: () => result });58expect(result).toBe("hello hello hello ");5960s.close();61});62});6364describe("create and test a more complicated service", () => {65let client, service;66it("defines the service", async () => {67interface Api {68add: (a: number, b: number) => Promise<number>;69concat: (a: Buffer, b: Buffer) => Promise<Buffer>;70now: () => Promise<Date>;71big: (n: number) => Promise<string>;72len: (s: string) => Promise<number>;73}7475const name = "my-service";76service = await createServiceHandler<Api>({77service: name,78subject: name,79description: "My Service",80impl: {81// put any functions here that take/return MsgPack'able values82add: async (a, b) => a + b,83concat: async (a, b) => Buffer.concat([a, b]),84now: async () => {85await delay(5);86return new Date();87},88big: async (n: number) => "x".repeat(n),89len: async (s: string) => s.length,90},91});9293client = createServiceClient<Api>({94service: name,95subject: name,96});97});9899it("tests the service", async () => {100// these calls are all type checked using typescript101expect(await client.add(2, 3)).toBe(5);102103const a = Buffer.from("hello");104const b = Buffer.from(" conat");105expect(await client.concat(a, b)).toEqual(Buffer.concat([a, b]));106107const d = await client.now();108expect(isDate(d)).toBe(true);109expect(Math.abs(d.valueOf() - Date.now())).toBeLessThan(100);110111const n = 10 * 1e6;112expect((await client.big(n)).length).toBe(n);113114expect(await client.len("x".repeat(n))).toBe(n);115});116117it("cleans up", () => {118service.close();119});120});121122describe("create a service with specified client, stop and start the server, and see service still works", () => {123let server;124let client;125let client2;126let port;127it("create a conat server and client", async () => {128port = await getPort();129server = await initConatServer({ port });130client = server.client({ reconnectionDelay: 50 });131client2 = server.client({ reconnectionDelay: 50 });132});133134let service;135it("create service using specific client and call it using both clients", async () => {136//You usually do NOT want a non-ephemeral service...137service = createConatService({138client,139service: "double",140handler: (mesg) => mesg.repeat(2),141});142await once(service, "running");143144expect(145await callConatService({ client, service: "double", mesg: "hello" }),146).toBe("hellohello");147148expect(149await callConatService({150client: client2,151service: "double",152mesg: "hello",153}),154).toBe("hellohello");155});156157it("disconnect client and check service still works on reconnect", async () => {158// cause a disconnect -- client will connect again in 50ms soon159// and then handle the request below:160client.conn.io.engine.close();161await delay(100);162expect(163await callConatService({164client: client2,165service: "double",166mesg: "hello",167}),168).toBe("hellohello");169});170171it("disconnect client2 and check service still works on reconnect", async () => {172// cause a disconnect -- client will connect again in 50ms soon173// and handle the request below:174client2.conn.io.engine.close();175await delay(100);176expect(177await callConatService({178client: client2,179service: "double",180mesg: "hello",181}),182).toBe("hellohello");183});184185it("disconnect both clients and check service still works on reconnect", async () => {186// cause a disconnect -- client will connect again in 50ms soon187// and handle the request below:188client.conn.io.engine.close();189client2.conn.io.engine.close();190let x;191await wait({192until: async () => {193x = await callConatService({194client: client2,195service: "double",196mesg: "hello",197timeout: 500,198});199return true;200},201});202expect(x).toBe("hellohello");203});204205it("kills the server, then makes another server serving on the same port", async () => {206await server.close();207await delay(250);208server = await initConatServer({ port });209// Killing the server is not at all a normal thing to expect, and causes loss of210// its state. The clients have to sync realize subscriptions are missing. This211// takes a fraction of a second and the call below won't immediately work.212await wait({213until: async () => {214try {215await callConatService({216client: client2,217service: "double",218mesg: "hello",219noRetry: true,220timeout: 250,221});222return true;223} catch (err) {224return false;225}226},227});228expect(229await callConatService({230client: client2,231service: "double",232mesg: "hello",233noRetry: true,234}),235).toBe("hellohello");236});237238it("cleans up", () => {239service.close();240client.close();241client2.close();242server.close();243});244});245246describe("create a slow service and check that the timeout parameter works", () => {247let s;248it("creates a slow service", async () => {249s = createConatService({250service: "slow",251handler: async (d) => {252await delay(d);253return { delay: d };254},255});256await once(s, "running");257});258259it("confirms it works", async () => {260const t0 = Date.now();261const r = await callConatService({262service: s.name,263mesg: 50,264});265expect(r).toEqual({ delay: 50 });266expect(Date.now() - t0).toBeGreaterThan(45);267expect(Date.now() - t0).toBeLessThan(500);268});269270it("confirms it throws a timeout error", async () => {271await expect(async () => {272await callConatService({273service: s.name,274mesg: 5000,275timeout: 75,276});277}).rejects.toThrowError("imeout");278});279280it("clean up", async () => {281s.close();282});283});284285afterAll(after);286287288