356 lines
11 KiB
TypeScript
356 lines
11 KiB
TypeScript
// tests/socket-service.test.ts
|
|
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
|
|
// All Rights Reserved
|
|
|
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import { Types } from "mongoose";
|
|
import { DroneStatus } from "@gadget/api";
|
|
import {
|
|
createMockSocket,
|
|
createMockUser,
|
|
createMockDroneRegistration,
|
|
createMockChatSession,
|
|
createMockProject,
|
|
} from "./fixtures";
|
|
import { nanoid } from "nanoid";
|
|
|
|
// Mock the entire socket service module
|
|
vi.mock("../src/services/socket", async () => {
|
|
const chatSessionIndex = new Map();
|
|
return {
|
|
default: {
|
|
codeSessions: new Map(),
|
|
droneSessions: new Map(),
|
|
chatSessionIndex: chatSessionIndex,
|
|
droneRegistrationIndex: new Map(),
|
|
codeSessionUserIndex: new Map(),
|
|
getDroneSession: vi.fn(),
|
|
getCodeSession: vi.fn(),
|
|
getCodeSessionByChatSessionId: vi.fn(),
|
|
registerChatSession: vi.fn((chatSessionId: string, codeSession: any) => {
|
|
chatSessionIndex.set(chatSessionId, codeSession);
|
|
}),
|
|
unregisterChatSession: vi.fn((chatSessionId: string) => {
|
|
chatSessionIndex.delete(chatSessionId);
|
|
}),
|
|
},
|
|
};
|
|
});
|
|
|
|
describe("SocketService Session Indexing", () => {
|
|
let SocketService: any;
|
|
let mockUser: any;
|
|
let mockDrone: any;
|
|
let mockChatSession: any;
|
|
let mockProject: any;
|
|
|
|
beforeEach(async () => {
|
|
vi.clearAllMocks();
|
|
|
|
const mod = await import("../src/services/socket");
|
|
SocketService = mod.default;
|
|
|
|
// Clear all session maps
|
|
SocketService.codeSessions.clear();
|
|
SocketService.droneSessions.clear();
|
|
SocketService.chatSessionIndex.clear();
|
|
SocketService.droneRegistrationIndex.clear();
|
|
SocketService.codeSessionUserIndex.clear();
|
|
|
|
mockUser = createMockUser();
|
|
mockDrone = createMockDroneRegistration(mockUser);
|
|
mockChatSession = createMockChatSession(mockUser);
|
|
mockProject = createMockProject(mockUser);
|
|
});
|
|
|
|
describe("Drone Session Indexing", () => {
|
|
it("should store drone session by socket.id and registration._id", () => {
|
|
const mockSocket = createMockSocket("drone-socket-123");
|
|
const mockDroneSession = {
|
|
socket: mockSocket,
|
|
registration: mockDrone,
|
|
type: "drone",
|
|
};
|
|
|
|
// Store in both indexes
|
|
SocketService.droneSessions.set(mockSocket.id, mockDroneSession);
|
|
SocketService.droneRegistrationIndex.set(mockDrone._id, mockDroneSession);
|
|
|
|
// Verify both lookups work
|
|
expect(SocketService.droneSessions.get(mockSocket.id)).toBe(
|
|
mockDroneSession,
|
|
);
|
|
expect(SocketService.droneRegistrationIndex.get(mockDrone._id)).toBe(
|
|
mockDroneSession,
|
|
);
|
|
});
|
|
|
|
it("should find drone session by registration._id", () => {
|
|
const mockSocket = createMockSocket("drone-socket-456");
|
|
const mockDroneSession = {
|
|
socket: mockSocket,
|
|
registration: mockDrone,
|
|
type: "drone",
|
|
};
|
|
|
|
// Store in both indexes
|
|
SocketService.droneSessions.set(mockSocket.id, mockDroneSession);
|
|
SocketService.droneRegistrationIndex.set(mockDrone._id, mockDroneSession);
|
|
|
|
// Mock getDroneSession to use the registration index
|
|
SocketService.getDroneSession.mockImplementation((registration: any) => {
|
|
const session = SocketService.droneRegistrationIndex.get(
|
|
registration._id,
|
|
);
|
|
if (!session) {
|
|
const error = new Error("drone session not found");
|
|
(error as any).statusCode = 404;
|
|
throw error;
|
|
}
|
|
return session;
|
|
});
|
|
|
|
const found = SocketService.getDroneSession(mockDrone);
|
|
expect(found).toBe(mockDroneSession);
|
|
});
|
|
|
|
it("should throw 404 when drone session not found", () => {
|
|
const nonExistentDrone = createMockDroneRegistration(mockUser);
|
|
|
|
SocketService.getDroneSession.mockImplementation(() => {
|
|
const error = new Error("drone session not found");
|
|
(error as any).statusCode = 404;
|
|
throw error;
|
|
});
|
|
|
|
expect(() => SocketService.getDroneSession(nonExistentDrone)).toThrow(
|
|
"drone session not found",
|
|
);
|
|
expect(() =>
|
|
SocketService.getDroneSession(nonExistentDrone),
|
|
).toThrowError(
|
|
expect.objectContaining({
|
|
statusCode: 404,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should remove drone session from all indexes on disconnect", () => {
|
|
const mockSocket = createMockSocket("drone-socket-789");
|
|
const mockDroneSession = {
|
|
socket: mockSocket,
|
|
registration: mockDrone,
|
|
type: "drone",
|
|
};
|
|
|
|
// Store in indexes
|
|
SocketService.droneSessions.set(mockSocket.id, mockDroneSession);
|
|
SocketService.droneRegistrationIndex.set(mockDrone._id, mockDroneSession);
|
|
|
|
// Simulate disconnect
|
|
SocketService.droneSessions.delete(mockSocket.id);
|
|
SocketService.droneRegistrationIndex.delete(mockDrone._id);
|
|
|
|
// Verify removal from all indexes
|
|
expect(SocketService.droneSessions.get(mockSocket.id)).toBeUndefined();
|
|
expect(
|
|
SocketService.droneRegistrationIndex.get(mockDrone._id),
|
|
).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("Code Session Indexing", () => {
|
|
it("should store code session by socket.id and user._id", () => {
|
|
const mockSocket = createMockSocket("code-socket-123");
|
|
const mockCodeSession = {
|
|
socket: mockSocket,
|
|
user: mockUser,
|
|
type: "code",
|
|
};
|
|
|
|
// Store in both indexes
|
|
SocketService.codeSessions.set(mockSocket.id, mockCodeSession);
|
|
SocketService.codeSessionUserIndex.set(mockUser._id, mockCodeSession);
|
|
|
|
// Verify both lookups work
|
|
expect(SocketService.codeSessions.get(mockSocket.id)).toBe(
|
|
mockCodeSession,
|
|
);
|
|
expect(SocketService.codeSessionUserIndex.get(mockUser._id)).toBe(
|
|
mockCodeSession,
|
|
);
|
|
});
|
|
|
|
it("should find code session by user._id", () => {
|
|
const mockSocket = createMockSocket("code-socket-456");
|
|
const mockCodeSession = {
|
|
socket: mockSocket,
|
|
user: mockUser,
|
|
type: "code",
|
|
};
|
|
|
|
// Store in both indexes
|
|
SocketService.codeSessions.set(mockSocket.id, mockCodeSession);
|
|
SocketService.codeSessionUserIndex.set(mockUser._id, mockCodeSession);
|
|
|
|
// Mock getCodeSession to use the user index
|
|
SocketService.getCodeSession.mockImplementation((user: any) => {
|
|
const session = SocketService.codeSessionUserIndex.get(user._id);
|
|
if (!session) {
|
|
const error = new Error("code session not found");
|
|
(error as any).statusCode = 404;
|
|
throw error;
|
|
}
|
|
return session;
|
|
});
|
|
|
|
const found = SocketService.getCodeSession(mockUser);
|
|
expect(found).toBe(mockCodeSession);
|
|
});
|
|
|
|
it("should throw 404 when code session not found", () => {
|
|
const nonExistentUser = createMockUser();
|
|
|
|
SocketService.getCodeSession.mockImplementation(() => {
|
|
const error = new Error("code session not found");
|
|
(error as any).statusCode = 404;
|
|
throw error;
|
|
});
|
|
|
|
expect(() => SocketService.getCodeSession(nonExistentUser)).toThrow(
|
|
"code session not found",
|
|
);
|
|
expect(() => SocketService.getCodeSession(nonExistentUser)).toThrowError(
|
|
expect.objectContaining({
|
|
statusCode: 404,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should remove code session from all indexes on disconnect", () => {
|
|
const mockSocket = createMockSocket("code-socket-789");
|
|
const mockCodeSession = {
|
|
socket: mockSocket,
|
|
user: mockUser,
|
|
type: "code",
|
|
};
|
|
|
|
// Store in indexes
|
|
SocketService.codeSessions.set(mockSocket.id, mockCodeSession);
|
|
SocketService.codeSessionUserIndex.set(mockUser._id, mockCodeSession);
|
|
|
|
// Simulate disconnect
|
|
SocketService.codeSessions.delete(mockSocket.id);
|
|
SocketService.codeSessionUserIndex.delete(mockUser._id);
|
|
|
|
// Verify removal from all indexes
|
|
expect(SocketService.codeSessions.get(mockSocket.id)).toBeUndefined();
|
|
expect(
|
|
SocketService.codeSessionUserIndex.get(mockUser._id),
|
|
).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("Chat Session Index", () => {
|
|
it("should register and retrieve code session by chatSessionId", () => {
|
|
const mockSocket = createMockSocket("code-socket-chat");
|
|
const mockCodeSession = {
|
|
socket: mockSocket,
|
|
user: mockUser,
|
|
type: "code",
|
|
};
|
|
|
|
const chatSessionId = mockChatSession._id;
|
|
|
|
// Mock the retrieval to return our session after registration
|
|
SocketService.registerChatSession(chatSessionId, mockCodeSession);
|
|
SocketService.getCodeSessionByChatSessionId.mockReturnValue(
|
|
mockCodeSession,
|
|
);
|
|
|
|
const found = SocketService.getCodeSessionByChatSessionId(
|
|
mockChatSession._id,
|
|
);
|
|
expect(found).toBe(mockCodeSession);
|
|
expect(SocketService.registerChatSession).toHaveBeenCalledWith(
|
|
chatSessionId,
|
|
mockCodeSession,
|
|
);
|
|
expect(SocketService.getCodeSessionByChatSessionId).toHaveBeenCalledWith(
|
|
mockChatSession._id,
|
|
);
|
|
});
|
|
|
|
it("should handle chatSessionId as string", () => {
|
|
const mockSocket = createMockSocket("code-socket-chat2");
|
|
const mockCodeSession = {
|
|
socket: mockSocket,
|
|
user: mockUser,
|
|
type: "code",
|
|
};
|
|
|
|
const chatSessionId = mockChatSession._id;
|
|
SocketService.registerChatSession(chatSessionId, mockCodeSession);
|
|
SocketService.getCodeSessionByChatSessionId.mockReturnValue(
|
|
mockCodeSession,
|
|
);
|
|
|
|
// Test with string
|
|
const found1 = SocketService.getCodeSessionByChatSessionId(chatSessionId);
|
|
expect(found1).toBe(mockCodeSession);
|
|
|
|
// Test with same ID again
|
|
const found2 = SocketService.getCodeSessionByChatSessionId(
|
|
mockChatSession._id,
|
|
);
|
|
expect(found2).toBe(mockCodeSession);
|
|
});
|
|
|
|
it("should throw 404 when chat session not found", () => {
|
|
const nonExistentChatSessionId = nanoid();
|
|
|
|
SocketService.getCodeSessionByChatSessionId.mockImplementation(() => {
|
|
const error = new Error("code session not found for chat session");
|
|
(error as any).statusCode = 404;
|
|
throw error;
|
|
});
|
|
|
|
expect(() =>
|
|
SocketService.getCodeSessionByChatSessionId(nonExistentChatSessionId),
|
|
).toThrow("code session not found for chat session");
|
|
expect(() =>
|
|
SocketService.getCodeSessionByChatSessionId(nonExistentChatSessionId),
|
|
).toThrowError(
|
|
expect.objectContaining({
|
|
statusCode: 404,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should unregister chat session", () => {
|
|
const mockSocket = createMockSocket("code-socket-chat3");
|
|
const mockCodeSession = {
|
|
socket: mockSocket,
|
|
user: mockUser,
|
|
type: "code",
|
|
};
|
|
|
|
const chatSessionId = mockChatSession._id;
|
|
SocketService.registerChatSession(chatSessionId, mockCodeSession);
|
|
SocketService.getCodeSessionByChatSessionId.mockReturnValue(
|
|
mockCodeSession,
|
|
);
|
|
|
|
// Verify it's registered
|
|
const found = SocketService.getCodeSessionByChatSessionId(chatSessionId);
|
|
expect(found).toBe(mockCodeSession);
|
|
|
|
// Unregister
|
|
SocketService.unregisterChatSession(chatSessionId);
|
|
expect(SocketService.unregisterChatSession).toHaveBeenCalledWith(
|
|
chatSessionId,
|
|
);
|
|
});
|
|
});
|
|
});
|