bagman
Version:
js/ts client for Bagman
181 lines (142 loc) • 6 kB
text/typescript
import { vi, it, expect, describe, beforeEach, afterEach } from "vitest";
import { Bagman, GlobalEvent } from "../src/bagman";
import { Channel } from "../src/channel";
import { SecurityContext } from "../src/security-context";
import { io } from "socket.io-client";
vi.mock("../src/security-context", () => {
const SecurityContext = vi.fn()
SecurityContext.prototype.isAuthorized = vi.fn();
SecurityContext.prototype.token = vi.fn();
SecurityContext.prototype.authorize = vi.fn();
return { SecurityContext }
});
vi.mock("socket.io-client", () => {
const mockSocket = {
connected: false,
connect: vi.fn(),
disconnect: vi.fn(),
emitWithAck: vi.fn(),
on: vi.fn(),
io: {
on: vi.fn(),
opts: {},
engine: {
transport: {
on: vi.fn(),
pollXhr: {
xhr: {
getResponseHeader: vi.fn()
}
}
}
}
}
};
return {
io: vi.fn(() => mockSocket)
};
});
describe("Bagman", () => {
let socket: any;
beforeEach(() => {
socket = io();
});
afterEach(() => {
vi.clearAllMocks();
socket.io.opts = {};
})
it("subscribes to a channel", async () => {
vi.mocked(SecurityContext.prototype.isAuthorized).mockReturnValue(true);
const channel = "test-channel";
const ack = { status: "ok" };
socket.emitWithAck.mockResolvedValue(ack);
const bagman = new Bagman({ url: "http://test-url.com", apiKey: "dummy-token" });
const result = bagman.subscribe(channel);
await expect(result).resolves.toEqual(new Channel(socket, channel));
expect(socket.emitWithAck).toHaveBeenCalledWith("client:subscribe", { channel, withPresence: false });
});
it("subscribes to a channel with presence functionality", async () => {
vi.mocked(SecurityContext.prototype.isAuthorized).mockReturnValue(true);
const channel = "test-channel";
const ack = { status: "ok" };
socket.emitWithAck.mockResolvedValue(ack);
const bagman = new Bagman({ url: "http://test-url.com", apiKey: "dummy-token" });
const result = bagman.subscribe(channel, { withPresence: true });
await expect(result).resolves.toEqual(new Channel(socket, channel));
expect(socket.emitWithAck).toHaveBeenCalledWith("client:subscribe", { channel, withPresence: true });
});
it("rejects when subscription fails", async () => {
const channel = "test-channel";
const ack = { status: "error", message: "subscription failed" };
socket.emitWithAck.mockResolvedValue(ack);
const bagman = new Bagman({ url: "http://test-url.com", apiKey: "dummy-token" });
const result = bagman.subscribe(channel);
await expect(result).rejects.toThrowError("subscription failed");
expect(socket.emitWithAck).toHaveBeenCalledWith("client:subscribe", { channel, withPresence: false });
});
it("defaults to http://localhost:8080 when no url is provided", () => {
new Bagman({ apiKey: "dummy-token" });
expect(vi.mocked(io)).toHaveBeenCalledWith("http://localhost:8080", {
transports: ["websocket"],
autoConnect: false,
})
})
it.each<GlobalEvent>(["connect", "connect_error", "disconnecting", "disconnect"])("listens to global event: %s", (event) => {
const bagman = new Bagman({ apiKey: "dummy-token" });
const cb = vi.fn();
bagman.listen(event, cb);
expect(socket.on).toHaveBeenCalledWith(event, cb);
});
it("throw errors when listening to non-exist global event", () => {
const bagman = new Bagman({ apiKey: "dummy-token" });
// @ts-expect-error
expect(() => bagman.listen("not-permitted-event", () => { })).toThrowError();
});
it("should set api key in query if api key is passed in", async () => {
vi.mocked(SecurityContext.prototype.isAuthorized).mockReturnValue(true);
vi.mocked(SecurityContext.prototype.token).mockReturnValue("dummy-token");
new Bagman({ apiKey: "dummy-token" });
expect(socket.connect).toBeCalled();
expect(socket.auth).toEqual({
'apiKey': 'dummy-token'
});
})
it("should set socket query with apiKey if authorization success", async () => {
vi.mocked(SecurityContext.prototype.isAuthorized).mockReturnValueOnce(false);
vi.mocked(SecurityContext.prototype.authorize).mockResolvedValue();
vi.mocked(SecurityContext.prototype.isAuthorized).mockReturnValueOnce(true);
vi.mocked(SecurityContext.prototype.token).mockReturnValue("dummy-token");
const bagman = new Bagman({
authorizer: {
host: 'localhost',
path: '/'
}
});
await bagman.authorize();
expect(socket.connect).toBeCalled();
expect(socket.auth).toEqual({
'apiKey': 'dummy-token'
});
})
it("should only disconnect socket if it's connected when invoke `close()`", () => {
socket.connected = true;
const bagman = new Bagman({
authorizer: {
host: 'localhost',
path: '/'
}
});
bagman.close();
expect(socket.disconnect).toBeCalled();
});
it("should throw error when subscribing without being authoirzed", async () => {
vi.mocked(SecurityContext.prototype.isAuthorized).mockReturnValue(false);
const bagman = new Bagman({
authorizer: {
host: 'localhost',
path: '/'
}
});
await expect(bagman.subscribe("test-channel")).rejects.toThrowError("Unauthorized. Please authorize client and proceed again.");
})
});