Files
massive-cloud/server/inspectionObject.test.ts

76 lines
3.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { appRouter } from "./routers";
import * as db from "./db";
import type { TrpcContext } from "./_core/context";
vi.mock("./db", () => {
class InspectionObjectReferencedError extends Error {
constructor(public readonly references: { taskReferenceCount: number; pointcloudReferenceCount: number; cadReferenceCount: number }) {
const parts = [
references.taskReferenceCount > 0 ? `任务 ${references.taskReferenceCount} 个` : null,
references.pointcloudReferenceCount > 0 ? `点云 ${references.pointcloudReferenceCount} 个` : null,
references.cadReferenceCount > 0 ? `CAD ${references.cadReferenceCount} 个` : null,
].filter(Boolean);
super(`检测对象仍被以下数据引用:${parts.join("、")}。请先处理关联数据后再删除。`);
}
}
return {
deleteInspectionObject: vi.fn(async () => undefined),
InspectionObjectReferencedError,
};
});
type AuthenticatedUser = NonNullable<TrpcContext["user"]>;
function createContext(role: AuthenticatedUser["role"]): TrpcContext {
return {
user: {
id: 1, username: "test-user", passwordHash: null, email: "test@example.com", name: "Test User",
role, isActive: 1, mustChangePassword: 0, sessionVersion: 0,
createdAt: new Date(), updatedAt: new Date(), lastLoginAt: new Date(),
},
req: { protocol: "https", headers: {} } as TrpcContext["req"],
res: { clearCookie: vi.fn() } as unknown as TrpcContext["res"],
};
}
describe("inspection object delete router", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(db.deleteInspectionObject).mockResolvedValue(undefined);
});
it("allows an administrator to delete an empty object", async () => {
await expect(appRouter.createCaller(createContext("admin")).inspectionObject.delete({ id: 7 }))
.resolves.toEqual({ success: true });
expect(db.deleteInspectionObject).toHaveBeenCalledWith(7);
});
it.each([
[{ taskReferenceCount: 2, pointcloudReferenceCount: 0, cadReferenceCount: 0 }, "任务 2 个"],
[{ taskReferenceCount: 0, pointcloudReferenceCount: 3, cadReferenceCount: 0 }, "点云 3 个"],
[{ taskReferenceCount: 0, pointcloudReferenceCount: 0, cadReferenceCount: 4 }, "CAD 4 个"],
])("rejects a single reference type with its count", async (references, message) => {
vi.mocked(db.deleteInspectionObject).mockRejectedValueOnce(new db.InspectionObjectReferencedError(references));
await expect(appRouter.createCaller(createContext("admin")).inspectionObject.delete({ id: 7 }))
.rejects.toMatchObject({ code: "PRECONDITION_FAILED", message: expect.stringContaining(message) });
});
it("reports all reference types when a concurrent reference is observed by the transaction", async () => {
vi.mocked(db.deleteInspectionObject).mockRejectedValueOnce(new db.InspectionObjectReferencedError({
taskReferenceCount: 1, pointcloudReferenceCount: 2, cadReferenceCount: 3,
}));
await expect(appRouter.createCaller(createContext("admin")).inspectionObject.delete({ id: 7 }))
.rejects.toMatchObject({
code: "PRECONDITION_FAILED",
message: "检测对象仍被以下数据引用:任务 1 个、点云 2 个、CAD 3 个。请先处理关联数据后再删除。",
});
});
it("forbids a non-administrator from deleting", async () => {
await expect(appRouter.createCaller(createContext("user")).inspectionObject.delete({ id: 7 }))
.rejects.toMatchObject({ code: "FORBIDDEN" });
expect(db.deleteInspectionObject).not.toHaveBeenCalled();
});
});