feat: add inspection object resource workflow
This commit is contained in:
75
server/inspectionObject.test.ts
Normal file
75
server/inspectionObject.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user