153 lines
4.5 KiB
TypeScript
153 lines
4.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { Client, FakeTransport, APIError, buildCleanConnectFlags, register } from "../src/index.js";
|
|
import { createServer } from "node:http";
|
|
|
|
async function connectFake(fake: FakeTransport): Promise<Client> {
|
|
const c = new Client();
|
|
await c.connect("ws://example.test/mqtt", "ep1", { password: "secret" }, { transport: fake });
|
|
return c;
|
|
}
|
|
|
|
describe("nixmsg sdk", () => {
|
|
it("Clean Start every connect", async () => {
|
|
const fake = new FakeTransport();
|
|
const c = await connectFake(fake);
|
|
await fake.simulateReconnect();
|
|
await fake.simulateReconnect();
|
|
const cs = fake.getConnects();
|
|
expect(cs.length).toBeGreaterThanOrEqual(3);
|
|
for (const x of cs) {
|
|
expect(x.cleanStart).toBe(true);
|
|
expect(x.sessionExpiry).toBe(0);
|
|
}
|
|
await c.close();
|
|
});
|
|
|
|
it("session token callback", async () => {
|
|
const fake = new FakeTransport();
|
|
fake.helloToken = "nst_abc";
|
|
const c = new Client();
|
|
let got = "";
|
|
c.onSessionHandler((t) => {
|
|
got = t;
|
|
});
|
|
await c.connect("ws://example.test/mqtt", "ep1", { password: "p" }, { transport: fake });
|
|
// 等待串行回调
|
|
await new Promise((r) => setTimeout(r, 20));
|
|
expect(got).toBe("nst_abc");
|
|
await c.close();
|
|
});
|
|
|
|
it("dedup then re-ack", async () => {
|
|
const fake = new FakeTransport();
|
|
const c = await connectFake(fake);
|
|
let calls = 0;
|
|
c.onMessageHandler(() => {
|
|
calls++;
|
|
});
|
|
|
|
const replyAcks = setInterval(() => {
|
|
for (const fr of fake.findUp("ack")) {
|
|
fake.replyOK(String(fr.rid), { result: "accepted" });
|
|
}
|
|
}, 5);
|
|
|
|
const msg = JSON.stringify({
|
|
v: 1,
|
|
type: "msg",
|
|
id: "m1",
|
|
from: "a",
|
|
to: { kind: "endpoint", id: "ep1" },
|
|
body: { enc: "utf8", data: "hi" },
|
|
send_at_ms: 1,
|
|
});
|
|
fake.injectDown(msg);
|
|
fake.injectDown(msg);
|
|
await new Promise((r) => setTimeout(r, 80));
|
|
fake.injectDown(msg);
|
|
await new Promise((r) => setTimeout(r, 80));
|
|
clearInterval(replyAcks);
|
|
|
|
expect(calls).toBe(1);
|
|
expect(fake.findUp("ack").length).toBeGreaterThanOrEqual(2);
|
|
await c.close();
|
|
});
|
|
|
|
it("body too large locally", async () => {
|
|
const fake = new FakeTransport();
|
|
fake.maxBodyBytes = 16;
|
|
const c = await connectFake(fake);
|
|
await expect(
|
|
c.send(
|
|
{ kind: "endpoint", id: "b" },
|
|
{ enc: "utf8", data: "x".repeat(64) },
|
|
),
|
|
).rejects.toMatchObject({ code: "body_too_large" });
|
|
await c.close();
|
|
});
|
|
|
|
it("resend keeps id and send_at_ms", async () => {
|
|
const fake = new FakeTransport();
|
|
const c = await connectFake(fake);
|
|
const at = new Date(1_700_000_000_000);
|
|
let firstId = "";
|
|
let firstSendAt: unknown;
|
|
let replied = false;
|
|
|
|
const timer = setInterval(() => {
|
|
const sends = fake.findUp("send");
|
|
if (!sends.length) return;
|
|
if (!replied) {
|
|
replied = true;
|
|
firstId = String(sends[0].id);
|
|
firstSendAt = sends[0].send_at_ms;
|
|
fake.replyErr(String(sends[0].rid), "rate_limited", "slow");
|
|
return;
|
|
}
|
|
if (sends.length >= 2) {
|
|
expect(sends[1].id).toBe(firstId);
|
|
expect(sends[1].send_at_ms).toBe(firstSendAt);
|
|
fake.replyOK(String(sends[1].rid), {
|
|
id: firstId,
|
|
send_at_ms: firstSendAt,
|
|
state: "scheduled",
|
|
});
|
|
clearInterval(timer);
|
|
}
|
|
}, 20);
|
|
|
|
const res = await c.send(
|
|
{ kind: "endpoint", id: "b" },
|
|
{ enc: "utf8", data: "hi" },
|
|
{ sendAt: at },
|
|
);
|
|
expect(res.id).toBe(firstId);
|
|
await c.close();
|
|
}, 10000);
|
|
|
|
it("register HTTP from ws url", async () => {
|
|
const srv = createServer((req, res) => {
|
|
expect(req.url).toBe("/api/client/register");
|
|
res.setHeader("content-type", "application/json");
|
|
res.end(JSON.stringify({ ok: true, data: { id: "e_1", login_password: "gen" } }));
|
|
});
|
|
await new Promise<void>((r) => srv.listen(0, "127.0.0.1", r));
|
|
const addr = srv.address();
|
|
if (!addr || typeof addr === "string") throw new Error("addr");
|
|
const ws = `ws://127.0.0.1:${addr.port}/mqtt`;
|
|
const res = await register(ws, "code", { name: "n" });
|
|
expect(res.id).toBe("e_1");
|
|
expect(res.loginPassword).toBe("gen");
|
|
await new Promise<void>((r) => srv.close(() => r()));
|
|
});
|
|
|
|
it("buildCleanConnectFlags", () => {
|
|
expect(buildCleanConnectFlags()).toEqual({ cleanStart: true, sessionExpiry: 0 });
|
|
});
|
|
|
|
it("APIError shape", () => {
|
|
const e = new APIError("body_too_large", "x");
|
|
expect(e.code).toBe("body_too_large");
|
|
});
|
|
});
|