refactor: replace UserDingMapping with StudentDingMapping entity

This commit is contained in:
2026-07-09 16:53:25 +08:00
parent 1fa336331c
commit ef1b46b9f4
18 changed files with 104 additions and 146 deletions

View File

@@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { SyncService, ImportUserDto } from './sync.service';
import { SyncLog, SyncState, UserDingMapping, ClassStudent, ClassTeacher } from '../entities';
import { SyncLog, SyncState, StudentDingMapping, ClassStudent, ClassTeacher } from '../entities';
import { Class as ClassEntity } from '../entities/class.entity';
import { User } from '../entities/user.entity';
import { Student } from '../entities/student.entity';
@@ -36,7 +36,7 @@ describe('SyncService — importDingTalkUsers (transactional)', () => {
let service: SyncService;
let mappingRepo: jest.Mocked<
Pick<Repository<UserDingMapping>, 'findOne' | 'create' | 'save' | 'find'>
Pick<Repository<StudentDingMapping>, 'findOne' | 'create' | 'save' | 'find'>
>;
let userRepo: jest.Mocked<Pick<Repository<User>, 'create' | 'save'>>;
let studentRepo: jest.Mocked<Pick<Repository<Student>, 'create' | 'save'>>;
@@ -109,7 +109,7 @@ describe('SyncService — importDingTalkUsers (transactional)', () => {
SyncService,
{ provide: getRepositoryToken(SyncLog), useValue: { create: jest.fn(), save: jest.fn(), find: jest.fn(), findOne: jest.fn() } },
{ provide: getRepositoryToken(SyncState), useValue: { findOne: jest.fn(), upsert: jest.fn() } },
{ provide: getRepositoryToken(UserDingMapping), useValue: mappingRepo },
{ provide: getRepositoryToken(StudentDingMapping), useValue: mappingRepo },
{ provide: getRepositoryToken(User), useValue: userRepo },
{ provide: getRepositoryToken(Student), useValue: studentRepo },
{ provide: getRepositoryToken(Role), useValue: roleRepo },
@@ -142,7 +142,7 @@ describe('SyncService — importDingTalkUsers (transactional)', () => {
it('imports teacher when roleId is a number (role found)', async () => {
const mockRole = { id: 5, name: 'Teacher' } as Role;
mgr.findOne.mockImplementation(async (entityClass: unknown) => {
if (entityClass === UserDingMapping) return null;
if (entityClass === StudentDingMapping) return null;
if (entityClass === Role) return mockRole;
return null;
});
@@ -170,7 +170,7 @@ describe('SyncService — importDingTalkUsers (transactional)', () => {
});
it('throws when role not found', async () => {
// UserDingMapping lookup → null (not skipped), Role lookup → null → throws
// StudentDingMapping lookup → null (not skipped), Role lookup → null → throws
mgr.findOne.mockResolvedValue(null);
const mockUser = { id: 11 } as User;
@@ -194,8 +194,8 @@ describe('SyncService — importDingTalkUsers (transactional)', () => {
mgr.save.mockResolvedValueOnce(mockStudent);
// mapping create+save also calls create/save
mgr.create.mockReturnValueOnce({} as UserDingMapping);
mgr.save.mockResolvedValueOnce({} as UserDingMapping);
mgr.create.mockReturnValueOnce({} as StudentDingMapping);
mgr.save.mockResolvedValueOnce({} as StudentDingMapping);
mappingRepo.findOne.mockResolvedValue(null);
@@ -225,7 +225,7 @@ describe('SyncService — importDingTalkUsers (transactional)', () => {
});
it('skips user when mapping already exists', async () => {
// mgr.findOne(UserDingMapping, ...) returns truthy → skip inside transaction
// mgr.findOne(StudentDingMapping, ...) returns truthy → skip inside transaction
mgr.findOne.mockResolvedValue({ id: 1 });
const users: ImportUserDto[] = [
@@ -259,10 +259,10 @@ describe('SyncService — importDingTalkUsers (transactional)', () => {
// Single transaction: User 1→skip, User 2→teacher, User 3→student
mgr.findOne
.mockResolvedValueOnce({ id: 99 }) // User 1: UserDingMapping → skip
.mockResolvedValueOnce(null) // User 2: UserDingMapping → not skipped
.mockResolvedValueOnce({ id: 99 }) // User 1: StudentDingMapping → skip
.mockResolvedValueOnce(null) // User 2: StudentDingMapping → not skipped
.mockResolvedValueOnce(mockRole) // User 2: Role lookup
.mockResolvedValueOnce(null); // User 3: UserDingMapping → not skipped
.mockResolvedValueOnce(null); // User 3: StudentDingMapping → not skipped
const users: ImportUserDto[] = [
{ dingUserId: 'skip', name: 'Skip', mobile: '138', roleId: null, dingDeptIds: [] },
@@ -395,7 +395,7 @@ describe('SyncService — importDingTalkUsers (transactional)', () => {
});
mgr.count.mockResolvedValue(0);
// save(ClassEntity, data) returns a class — but the smart mock already handles this
mgr.findOne.mockResolvedValueOnce(null); // first findOne: UserDingMapping for teacher
mgr.findOne.mockResolvedValueOnce(null); // first findOne: StudentDingMapping for teacher
mgr.findOne.mockResolvedValueOnce({ id: 5 }); // Role lookup
mgr.findOne.mockResolvedValueOnce({ name: 'Empty Class' }); // Class lookup in empty check