Files
inkreach-official-website/apps/api/src/countries/countries.service.spec.ts
T
yeuimu 1e07c62fc8 feat(admin): country drag-sort + always-available member detail sync
- Country 加 sort_order(默认 0 保持 id 序);PATCH /countries/sort 批量保存
  顺序(对齐 /tags/sort 模式);findAll 与公开 /public/countries 均按
  sortOrder 排序
- CountriesView 表格改为可拖拽行列表:拖动松开即全量保存新顺序,失败回滚
- 商品编辑弹窗成员展开面板:同步详情按钮常驻(已同步显示 重新同步详情),
  不再只在未同步态出现
- goods.service.spec 的 FamilyRecomputeService mock 补齐 syncFamilyTags 等
  方法(全量并行时其他套件的扫名归族会把本套件夹具收进族,create/update
  会调用到,mock 缺方法导致偶发 TypeError)
- api 164/164、admin typecheck+22/22+构建全绿
2026-08-28 20:00:56 +08:00

142 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Test } from '@nestjs/testing';
import {
BadRequestException,
ConflictException,
NotFoundException,
} from '@nestjs/common';
import { CountriesService } from './countries.service';
import { PrismaService } from '../prisma/prisma.service';
describe('CountriesService', () => {
let service: CountriesService;
let prisma: PrismaService;
const created: string[] = [];
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [CountriesService, PrismaService],
}).compile();
service = moduleRef.get(CountriesService);
prisma = moduleRef.get(PrismaService);
await prisma.onModuleInit();
});
afterAll(async () => {
if (created.length) {
// Delete goods/origin_goods first so we can remove the countries.
await prisma.good.deleteMany({
where: { country: { countryName: { in: created } } },
});
await prisma.position.deleteMany({
where: { country: { countryName: { in: created } } },
});
await prisma.country.deleteMany({
where: { countryName: { in: created } },
});
}
await prisma.onModuleDestroy();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('creates and reads back a country', async () => {
const name = `Test Country ${Date.now()}`;
created.push(name);
const createdRow = await service.create({ countryName: name });
expect(createdRow.countryName).toBe(name);
const fetched = await service.findOne(createdRow.id);
expect(fetched.countryName).toBe(name);
});
it('rejects duplicate names with ConflictException', async () => {
const name = `Dup Country ${Date.now()}`;
created.push(name);
await service.create({ countryName: name });
await expect(service.create({ countryName: name })).rejects.toBeInstanceOf(
ConflictException,
);
});
it('throws NotFoundException for unknown id', async () => {
await expect(service.findOne(BigInt(99999999))).rejects.toBeInstanceOf(
NotFoundException,
);
});
it('throws BadRequestException when deleting a country referenced by goods', async () => {
const name = `Ref Country ${Date.now()}`;
created.push(name);
const country = await service.create({ countryName: name });
// Need a category + origin good to satisfy the foreign keys before
// we can attach a good that references the country.
const category = await prisma.category.create({
data: { categoryName: `Cat ${Date.now()}` },
});
const originGood = await prisma.originGood.create({
data: { sdsGoodId: `sds-${Date.now()}-${Math.random()}` },
});
await prisma.good.create({
data: {
originGoodId: originGood.id,
countryId: country.id,
categoryId: category.id,
goodName: 'sample',
},
});
await expect(service.remove(country.id)).rejects.toBeInstanceOf(
BadRequestException,
);
// cleanup
await prisma.good.deleteMany({ where: { countryId: country.id } });
await prisma.originGood.delete({ where: { id: originGood.id } });
await prisma.category.delete({ where: { id: category.id } });
});
it('updates fields and deletes when unreferenced', async () => {
const name = `Upd Country ${Date.now()}`;
created.push(name);
const c = await service.create({ countryName: name });
const updated = await service.update(c.id, { countryName: `${name}-v2` });
expect(updated.countryName).toBe(`${name}-v2`);
created[created.indexOf(name)] = `${name}-v2`;
await service.remove(c.id);
const idx = created.indexOf(`${name}-v2`);
if (idx !== -1) created.splice(idx, 1);
});
it('reorder persists sortOrder and findAll honors it', async () => {
const stamp = Date.now();
const a = await service.create({ countryName: `Sort A ${stamp}` });
const b = await service.create({ countryName: `Sort B ${stamp}` });
const c = await service.create({ countryName: `Sort C ${stamp}` });
created.push(a.countryName, b.countryName, c.countryName);
// 新顺序 C, A, Bid 序与 sortOrder 无关时也能稳定验证)
const reordered = await service.reorder({
items: [
{ id: Number(c.id), sortOrder: 0 },
{ id: Number(a.id), sortOrder: 1 },
{ id: Number(b.id), sortOrder: 2 },
],
});
const ours = reordered.filter((r) =>
[a.id, b.id, c.id].some((id) => id.toString() === r.id.toString()),
);
expect(ours.map((r) => r.id.toString())).toEqual([c.id.toString(), a.id.toString(), b.id.toString()]);
const list = await service.findAll();
const pos = (id: bigint) => list.findIndex((r) => r.id.toString() === id.toString());
expect(pos(c.id)).toBeLessThan(pos(a.id));
expect(pos(a.id)).toBeLessThan(pos(b.id));
});
});