2026-01-28 13:03:28 +08:00
|
|
|
import { Test, TestingModule } from "@nestjs/testing";
|
|
|
|
|
import { getRepositoryToken } from "@nestjs/typeorm";
|
|
|
|
|
import { Repository, DataSource } from "typeorm";
|
|
|
|
|
import { AssetsService } from "./assets.service";
|
|
|
|
|
import { Asset } from "../../entities/asset.entity";
|
|
|
|
|
import { AssetLog } from "../../entities/asset-log.entity";
|
|
|
|
|
import { Group } from "../../entities/group.entity";
|
|
|
|
|
import { GroupMember } from "../../entities/group-member.entity";
|
|
|
|
|
import {
|
|
|
|
|
AssetType,
|
|
|
|
|
AssetStatus,
|
|
|
|
|
GroupMemberRole,
|
|
|
|
|
AssetLogAction,
|
|
|
|
|
} from "../../common/enums";
|
|
|
|
|
import {
|
|
|
|
|
NotFoundException,
|
|
|
|
|
ForbiddenException,
|
|
|
|
|
BadRequestException,
|
|
|
|
|
} from "@nestjs/common";
|
|
|
|
|
|
|
|
|
|
describe("AssetsService", () => {
|
2026-01-28 10:42:06 +08:00
|
|
|
let service: AssetsService;
|
|
|
|
|
let assetRepository: Repository<Asset>;
|
|
|
|
|
let assetLogRepository: Repository<AssetLog>;
|
|
|
|
|
let groupRepository: Repository<Group>;
|
|
|
|
|
let groupMemberRepository: Repository<GroupMember>;
|
|
|
|
|
|
|
|
|
|
const mockAsset = {
|
2026-01-28 13:03:28 +08:00
|
|
|
id: "asset-1",
|
|
|
|
|
groupId: "group-1",
|
2026-01-28 10:42:06 +08:00
|
|
|
type: AssetType.ACCOUNT,
|
2026-01-28 13:03:28 +08:00
|
|
|
name: "测试账号",
|
|
|
|
|
description: "测试描述",
|
|
|
|
|
accountCredentials: "encrypted-data",
|
2026-01-28 10:42:06 +08:00
|
|
|
quantity: 1,
|
|
|
|
|
status: AssetStatus.AVAILABLE,
|
|
|
|
|
currentBorrowerId: null,
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockGroup = {
|
2026-01-28 13:03:28 +08:00
|
|
|
id: "group-1",
|
|
|
|
|
name: "测试小组",
|
2026-01-28 10:42:06 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockGroupMember = {
|
2026-01-28 13:03:28 +08:00
|
|
|
id: "member-1",
|
|
|
|
|
userId: "user-1",
|
|
|
|
|
groupId: "group-1",
|
2026-01-28 10:42:06 +08:00
|
|
|
role: GroupMemberRole.ADMIN,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockDataSource = {
|
|
|
|
|
createQueryRunner: jest.fn().mockReturnValue({
|
|
|
|
|
connect: jest.fn(),
|
|
|
|
|
startTransaction: jest.fn(),
|
|
|
|
|
commitTransaction: jest.fn(),
|
|
|
|
|
rollbackTransaction: jest.fn(),
|
|
|
|
|
release: jest.fn(),
|
|
|
|
|
manager: {
|
|
|
|
|
findOne: jest.fn(),
|
|
|
|
|
find: jest.fn(),
|
|
|
|
|
create: jest.fn(),
|
|
|
|
|
save: jest.fn(),
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
|
|
|
providers: [
|
|
|
|
|
AssetsService,
|
|
|
|
|
{
|
|
|
|
|
provide: getRepositoryToken(Asset),
|
|
|
|
|
useValue: {
|
|
|
|
|
create: jest.fn(),
|
|
|
|
|
save: jest.fn(),
|
|
|
|
|
find: jest.fn(),
|
|
|
|
|
findOne: jest.fn(),
|
|
|
|
|
remove: jest.fn(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
provide: getRepositoryToken(AssetLog),
|
|
|
|
|
useValue: {
|
|
|
|
|
create: jest.fn(),
|
|
|
|
|
save: jest.fn(),
|
|
|
|
|
find: jest.fn(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
provide: getRepositoryToken(Group),
|
|
|
|
|
useValue: {
|
|
|
|
|
findOne: jest.fn(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
provide: getRepositoryToken(GroupMember),
|
|
|
|
|
useValue: {
|
|
|
|
|
findOne: jest.fn(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
provide: DataSource,
|
|
|
|
|
useValue: mockDataSource,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}).compile();
|
|
|
|
|
|
|
|
|
|
service = module.get<AssetsService>(AssetsService);
|
|
|
|
|
assetRepository = module.get<Repository<Asset>>(getRepositoryToken(Asset));
|
2026-01-28 13:03:28 +08:00
|
|
|
assetLogRepository = module.get<Repository<AssetLog>>(
|
|
|
|
|
getRepositoryToken(AssetLog),
|
|
|
|
|
);
|
2026-01-28 10:42:06 +08:00
|
|
|
groupRepository = module.get<Repository<Group>>(getRepositoryToken(Group));
|
2026-01-28 13:03:28 +08:00
|
|
|
groupMemberRepository = module.get<Repository<GroupMember>>(
|
|
|
|
|
getRepositoryToken(GroupMember),
|
|
|
|
|
);
|
2026-01-28 10:42:06 +08:00
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
it("should be defined", () => {
|
2026-01-28 10:42:06 +08:00
|
|
|
expect(service).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
describe("create", () => {
|
|
|
|
|
it("应该成功创建资产", async () => {
|
2026-01-28 10:42:06 +08:00
|
|
|
const createDto = {
|
2026-01-28 13:03:28 +08:00
|
|
|
groupId: "group-1",
|
2026-01-28 10:42:06 +08:00
|
|
|
type: AssetType.ACCOUNT,
|
2026-01-28 13:03:28 +08:00
|
|
|
name: "测试账号",
|
|
|
|
|
description: "测试描述",
|
2026-01-28 10:42:06 +08:00
|
|
|
};
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
jest
|
|
|
|
|
.spyOn(groupRepository, "findOne")
|
|
|
|
|
.mockResolvedValue(mockGroup as any);
|
|
|
|
|
jest
|
|
|
|
|
.spyOn(groupMemberRepository, "findOne")
|
|
|
|
|
.mockResolvedValue(mockGroupMember as any);
|
|
|
|
|
jest.spyOn(assetRepository, "create").mockReturnValue(mockAsset as any);
|
|
|
|
|
jest.spyOn(assetRepository, "save").mockResolvedValue(mockAsset as any);
|
|
|
|
|
jest
|
|
|
|
|
.spyOn(assetRepository, "findOne")
|
|
|
|
|
.mockResolvedValue(mockAsset as any);
|
2026-01-28 10:42:06 +08:00
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
const result = await service.create("user-1", createDto);
|
2026-01-28 10:42:06 +08:00
|
|
|
|
|
|
|
|
expect(result).toBeDefined();
|
2026-01-28 13:03:28 +08:00
|
|
|
expect(groupRepository.findOne).toHaveBeenCalledWith({
|
|
|
|
|
where: { id: "group-1" },
|
|
|
|
|
});
|
2026-01-28 10:42:06 +08:00
|
|
|
expect(groupMemberRepository.findOne).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
it("小组不存在时应该抛出异常", async () => {
|
2026-01-28 10:42:06 +08:00
|
|
|
const createDto = {
|
2026-01-28 13:03:28 +08:00
|
|
|
groupId: "group-1",
|
2026-01-28 10:42:06 +08:00
|
|
|
type: AssetType.ACCOUNT,
|
2026-01-28 13:03:28 +08:00
|
|
|
name: "测试账号",
|
2026-01-28 10:42:06 +08:00
|
|
|
};
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
jest.spyOn(groupRepository, "findOne").mockResolvedValue(null);
|
2026-01-28 10:42:06 +08:00
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
await expect(service.create("user-1", createDto)).rejects.toThrow(
|
|
|
|
|
NotFoundException,
|
|
|
|
|
);
|
2026-01-28 10:42:06 +08:00
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
it("无权限时应该抛出异常", async () => {
|
2026-01-28 10:42:06 +08:00
|
|
|
const createDto = {
|
2026-01-28 13:03:28 +08:00
|
|
|
groupId: "group-1",
|
2026-01-28 10:42:06 +08:00
|
|
|
type: AssetType.ACCOUNT,
|
2026-01-28 13:03:28 +08:00
|
|
|
name: "测试账号",
|
2026-01-28 10:42:06 +08:00
|
|
|
};
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
jest
|
|
|
|
|
.spyOn(groupRepository, "findOne")
|
|
|
|
|
.mockResolvedValue(mockGroup as any);
|
|
|
|
|
jest.spyOn(groupMemberRepository, "findOne").mockResolvedValue({
|
2026-01-28 10:42:06 +08:00
|
|
|
...mockGroupMember,
|
|
|
|
|
role: GroupMemberRole.MEMBER,
|
|
|
|
|
} as any);
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
await expect(service.create("user-1", createDto)).rejects.toThrow(
|
|
|
|
|
ForbiddenException,
|
|
|
|
|
);
|
2026-01-28 10:42:06 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
describe("findAll", () => {
|
|
|
|
|
it("应该返回资产列表", async () => {
|
|
|
|
|
jest.spyOn(assetRepository, "find").mockResolvedValue([mockAsset] as any);
|
2026-01-28 10:42:06 +08:00
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
const result = await service.findAll("group-1");
|
2026-01-28 10:42:06 +08:00
|
|
|
|
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
|
expect(result[0].accountCredentials).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
describe("borrow", () => {
|
|
|
|
|
it("应该成功借用资产", async () => {
|
|
|
|
|
const borrowDto = { reason: "需要使用" };
|
|
|
|
|
|
|
|
|
|
jest
|
|
|
|
|
.spyOn(assetRepository, "findOne")
|
|
|
|
|
.mockResolvedValue(mockAsset as any);
|
|
|
|
|
jest
|
|
|
|
|
.spyOn(groupMemberRepository, "findOne")
|
|
|
|
|
.mockResolvedValue(mockGroupMember as any);
|
|
|
|
|
jest
|
|
|
|
|
.spyOn(assetRepository, "save")
|
|
|
|
|
.mockResolvedValue({ ...mockAsset, status: AssetStatus.IN_USE } as any);
|
|
|
|
|
jest.spyOn(assetLogRepository, "create").mockReturnValue({} as any);
|
|
|
|
|
jest.spyOn(assetLogRepository, "save").mockResolvedValue({} as any);
|
|
|
|
|
|
|
|
|
|
const result = await service.borrow("user-1", "asset-1", borrowDto);
|
|
|
|
|
|
|
|
|
|
expect(result.message).toBe("借用成功");
|
2026-01-28 10:42:06 +08:00
|
|
|
expect(assetRepository.save).toHaveBeenCalled();
|
|
|
|
|
expect(assetLogRepository.save).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
it("资产不可用时应该抛出异常", async () => {
|
|
|
|
|
const borrowDto = { reason: "需要使用" };
|
2026-01-28 10:42:06 +08:00
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
jest.spyOn(assetRepository, "findOne").mockResolvedValue({
|
2026-01-28 10:42:06 +08:00
|
|
|
...mockAsset,
|
|
|
|
|
status: AssetStatus.IN_USE,
|
|
|
|
|
} as any);
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
await expect(
|
|
|
|
|
service.borrow("user-1", "asset-1", borrowDto),
|
|
|
|
|
).rejects.toThrow(BadRequestException);
|
2026-01-28 10:42:06 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
describe("return", () => {
|
|
|
|
|
it("应该成功归还资产", async () => {
|
|
|
|
|
jest.spyOn(assetRepository, "findOne").mockResolvedValue({
|
2026-01-28 10:42:06 +08:00
|
|
|
...mockAsset,
|
2026-01-28 13:03:28 +08:00
|
|
|
currentBorrowerId: "user-1",
|
2026-01-28 10:42:06 +08:00
|
|
|
} as any);
|
2026-01-28 13:03:28 +08:00
|
|
|
jest.spyOn(assetRepository, "save").mockResolvedValue(mockAsset as any);
|
|
|
|
|
jest.spyOn(assetLogRepository, "create").mockReturnValue({} as any);
|
|
|
|
|
jest.spyOn(assetLogRepository, "save").mockResolvedValue({} as any);
|
2026-01-28 10:42:06 +08:00
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
const result = await service.return("user-1", "asset-1", "已归还");
|
2026-01-28 10:42:06 +08:00
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
expect(result.message).toBe("归还成功");
|
2026-01-28 10:42:06 +08:00
|
|
|
expect(assetRepository.save).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
it("非借用人归还时应该抛出异常", async () => {
|
|
|
|
|
jest.spyOn(assetRepository, "findOne").mockResolvedValue({
|
2026-01-28 10:42:06 +08:00
|
|
|
...mockAsset,
|
2026-01-28 13:03:28 +08:00
|
|
|
currentBorrowerId: "user-2",
|
2026-01-28 10:42:06 +08:00
|
|
|
} as any);
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
await expect(service.return("user-1", "asset-1")).rejects.toThrow(
|
|
|
|
|
ForbiddenException,
|
|
|
|
|
);
|
2026-01-28 10:42:06 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
describe("remove", () => {
|
|
|
|
|
it("应该成功删除资产", async () => {
|
|
|
|
|
jest
|
|
|
|
|
.spyOn(assetRepository, "findOne")
|
|
|
|
|
.mockResolvedValue(mockAsset as any);
|
|
|
|
|
jest
|
|
|
|
|
.spyOn(groupMemberRepository, "findOne")
|
|
|
|
|
.mockResolvedValue(mockGroupMember as any);
|
|
|
|
|
jest.spyOn(assetRepository, "remove").mockResolvedValue(mockAsset as any);
|
2026-01-28 10:42:06 +08:00
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
const result = await service.remove("user-1", "asset-1");
|
2026-01-28 10:42:06 +08:00
|
|
|
|
2026-01-28 13:03:28 +08:00
|
|
|
expect(result.message).toBe("删除成功");
|
2026-01-28 10:42:06 +08:00
|
|
|
expect(assetRepository.remove).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|