Files
stack/apps/api/src/terminal/terminal-session.service.spec.ts
Jason Woltje 8128eb7fbe
Some checks failed
ci/woodpecker/push/api Pipeline failed
feat(api): add terminal session persistence with Prisma model and CRUD (#517)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-02-26 02:49:32 +00:00

230 lines
7.6 KiB
TypeScript

/**
* TerminalSessionService Tests
*
* Unit tests for database-backed terminal session CRUD:
* create, findByWorkspace, close, and findById.
* PrismaService is mocked to isolate the service logic.
*/
import { describe, it, expect, beforeEach, vi } from "vitest";
import { NotFoundException } from "@nestjs/common";
import { TerminalSessionStatus } from "@prisma/client";
import type { TerminalSession } from "@prisma/client";
import { TerminalSessionService } from "./terminal-session.service";
// ==========================================
// Helpers
// ==========================================
function makeSession(overrides: Partial<TerminalSession> = {}): TerminalSession {
return {
id: "session-uuid-1",
workspaceId: "workspace-uuid-1",
name: "Terminal",
status: TerminalSessionStatus.ACTIVE,
createdAt: new Date("2026-02-25T00:00:00Z"),
closedAt: null,
...overrides,
};
}
// ==========================================
// Mock PrismaService
// ==========================================
function makeMockPrisma() {
return {
terminalSession: {
create: vi.fn(),
findMany: vi.fn(),
findUnique: vi.fn(),
update: vi.fn(),
},
};
}
// ==========================================
// Tests
// ==========================================
describe("TerminalSessionService", () => {
let service: TerminalSessionService;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockPrisma: any;
beforeEach(() => {
vi.clearAllMocks();
mockPrisma = makeMockPrisma();
service = new TerminalSessionService(mockPrisma);
});
// ==========================================
// create
// ==========================================
describe("create", () => {
it("should call prisma.terminalSession.create with workspaceId only when no name provided", async () => {
const session = makeSession();
mockPrisma.terminalSession.create.mockResolvedValueOnce(session);
const result = await service.create("workspace-uuid-1");
expect(mockPrisma.terminalSession.create).toHaveBeenCalledWith({
data: { workspaceId: "workspace-uuid-1" },
});
expect(result).toEqual(session);
});
it("should include name in create data when name is provided", async () => {
const session = makeSession({ name: "My Terminal" });
mockPrisma.terminalSession.create.mockResolvedValueOnce(session);
const result = await service.create("workspace-uuid-1", "My Terminal");
expect(mockPrisma.terminalSession.create).toHaveBeenCalledWith({
data: { workspaceId: "workspace-uuid-1", name: "My Terminal" },
});
expect(result).toEqual(session);
});
it("should return the created session", async () => {
const session = makeSession();
mockPrisma.terminalSession.create.mockResolvedValueOnce(session);
const result = await service.create("workspace-uuid-1");
expect(result.id).toBe("session-uuid-1");
expect(result.status).toBe(TerminalSessionStatus.ACTIVE);
});
});
// ==========================================
// findByWorkspace
// ==========================================
describe("findByWorkspace", () => {
it("should query for ACTIVE sessions in the given workspace, ordered by createdAt desc", async () => {
const sessions = [makeSession(), makeSession({ id: "session-uuid-2" })];
mockPrisma.terminalSession.findMany.mockResolvedValueOnce(sessions);
const result = await service.findByWorkspace("workspace-uuid-1");
expect(mockPrisma.terminalSession.findMany).toHaveBeenCalledWith({
where: {
workspaceId: "workspace-uuid-1",
status: TerminalSessionStatus.ACTIVE,
},
orderBy: { createdAt: "desc" },
});
expect(result).toHaveLength(2);
});
it("should return an empty array when no active sessions exist", async () => {
mockPrisma.terminalSession.findMany.mockResolvedValueOnce([]);
const result = await service.findByWorkspace("workspace-uuid-empty");
expect(result).toEqual([]);
});
it("should not include CLOSED sessions", async () => {
// The where clause enforces ACTIVE status — verify it is present
mockPrisma.terminalSession.findMany.mockResolvedValueOnce([]);
await service.findByWorkspace("workspace-uuid-1");
const callArgs = mockPrisma.terminalSession.findMany.mock.calls[0][0] as {
where: { status: TerminalSessionStatus };
};
expect(callArgs.where.status).toBe(TerminalSessionStatus.ACTIVE);
});
});
// ==========================================
// close
// ==========================================
describe("close", () => {
it("should set status to CLOSED and set closedAt when session exists", async () => {
const existingSession = makeSession();
const closedSession = makeSession({
status: TerminalSessionStatus.CLOSED,
closedAt: new Date("2026-02-25T01:00:00Z"),
});
mockPrisma.terminalSession.findUnique.mockResolvedValueOnce(existingSession);
mockPrisma.terminalSession.update.mockResolvedValueOnce(closedSession);
const result = await service.close("session-uuid-1");
expect(mockPrisma.terminalSession.findUnique).toHaveBeenCalledWith({
where: { id: "session-uuid-1" },
});
expect(mockPrisma.terminalSession.update).toHaveBeenCalledWith({
where: { id: "session-uuid-1" },
data: {
status: TerminalSessionStatus.CLOSED,
closedAt: expect.any(Date),
},
});
expect(result.status).toBe(TerminalSessionStatus.CLOSED);
});
it("should throw NotFoundException when session does not exist", async () => {
mockPrisma.terminalSession.findUnique.mockResolvedValueOnce(null);
await expect(service.close("nonexistent-id")).rejects.toThrow(NotFoundException);
expect(mockPrisma.terminalSession.update).not.toHaveBeenCalled();
});
it("should include a non-null closedAt timestamp on close", async () => {
const existingSession = makeSession();
const closedSession = makeSession({
status: TerminalSessionStatus.CLOSED,
closedAt: new Date(),
});
mockPrisma.terminalSession.findUnique.mockResolvedValueOnce(existingSession);
mockPrisma.terminalSession.update.mockResolvedValueOnce(closedSession);
const result = await service.close("session-uuid-1");
expect(result.closedAt).not.toBeNull();
});
});
// ==========================================
// findById
// ==========================================
describe("findById", () => {
it("should return the session when it exists", async () => {
const session = makeSession();
mockPrisma.terminalSession.findUnique.mockResolvedValueOnce(session);
const result = await service.findById("session-uuid-1");
expect(mockPrisma.terminalSession.findUnique).toHaveBeenCalledWith({
where: { id: "session-uuid-1" },
});
expect(result).toEqual(session);
});
it("should return null when session does not exist", async () => {
mockPrisma.terminalSession.findUnique.mockResolvedValueOnce(null);
const result = await service.findById("no-such-id");
expect(result).toBeNull();
});
it("should find CLOSED sessions as well as ACTIVE ones", async () => {
const closedSession = makeSession({
status: TerminalSessionStatus.CLOSED,
closedAt: new Date(),
});
mockPrisma.terminalSession.findUnique.mockResolvedValueOnce(closedSession);
const result = await service.findById("session-uuid-1");
expect(result?.status).toBe(TerminalSessionStatus.CLOSED);
});
});
});