import { Test, TestingModule } from '@nestjs/testing' import { AuthorizationValidatorService, IReferralRepository } from '@/domain/services/authorization-validator.service' import { AssessmentCalculatorService, TeamStatistics, ITeamStatisticsRepository } from '@/domain/services/assessment-calculator.service' import { IAuthorizationRoleRepository } from '@/domain/repositories/authorization-role.repository' import { IMonthlyAssessmentRepository } from '@/domain/repositories/monthly-assessment.repository' import { AUTHORIZATION_ROLE_REPOSITORY } from '@/infrastructure/persistence/repositories/authorization-role.repository.impl' import { MONTHLY_ASSESSMENT_REPOSITORY } from '@/infrastructure/persistence/repositories/monthly-assessment.repository.impl' import { UserId, RegionCode, Month, AuthorizationId } from '@/domain/value-objects' import { RoleType, AuthorizationStatus } from '@/domain/enums' import { AuthorizationRole } from '@/domain/aggregates' import { LadderTargetRule } from '@/domain/entities' describe('Domain Services Integration Tests', () => { let module: TestingModule let validatorService: AuthorizationValidatorService let calculatorService: AssessmentCalculatorService // Mock repositories const mockAuthorizationRoleRepository: jest.Mocked = { save: jest.fn(), findById: jest.fn(), findByUserIdAndRoleType: jest.fn(), findByUserIdRoleTypeAndRegion: jest.fn(), findByUserId: jest.fn(), findActiveByRoleTypeAndRegion: jest.fn(), findAllActive: jest.fn(), findPendingByUserId: jest.fn(), findByStatus: jest.fn(), delete: jest.fn(), findByAccountSequenceAndRoleType: jest.fn(), findByAccountSequence: jest.fn(), findActiveCommunityByAccountSequences: jest.fn(), } const mockMonthlyAssessmentRepository: jest.Mocked = { save: jest.fn(), saveAll: jest.fn(), findById: jest.fn(), findByAuthorizationAndMonth: jest.fn(), findByUserAndMonth: jest.fn(), findFirstByAuthorization: jest.fn(), findByMonthAndRegion: jest.fn(), findRankingsByMonthAndRegion: jest.fn(), findByAuthorization: jest.fn(), delete: jest.fn(), } // Mock referral repository const mockReferralRepository: jest.Mocked = { findByUserId: jest.fn(), getAllAncestors: jest.fn(), getAllDescendants: jest.fn(), } // Mock team statistics repository const mockTeamStatisticsRepository: jest.Mocked = { findByUserId: jest.fn(), findByAccountSequence: jest.fn(), } beforeAll(async () => { module = await Test.createTestingModule({ providers: [ AuthorizationValidatorService, AssessmentCalculatorService, { provide: AUTHORIZATION_ROLE_REPOSITORY, useValue: mockAuthorizationRoleRepository, }, { provide: MONTHLY_ASSESSMENT_REPOSITORY, useValue: mockMonthlyAssessmentRepository, }, { provide: 'REFERRAL_REPOSITORY', useValue: mockReferralRepository, }, { provide: 'TEAM_STATISTICS_REPOSITORY', useValue: mockTeamStatisticsRepository, }, ], }).compile() validatorService = module.get(AuthorizationValidatorService) calculatorService = module.get(AssessmentCalculatorService) }) afterAll(async () => { await module.close() }) beforeEach(() => { jest.clearAllMocks() }) describe('AuthorizationValidatorService', () => { describe('validateAuthorizationRequest', () => { it('should return success when no conflicts in referral chain', async () => { const userId = UserId.create('user-123', BigInt(123)) const roleType = RoleType.AUTH_PROVINCE_COMPANY const regionCode = RegionCode.create('430000') mockAuthorizationRoleRepository.findByUserIdAndRoleType.mockResolvedValue(null) mockReferralRepository.findByUserId.mockResolvedValue(null) const result = await validatorService.validateAuthorizationRequest( userId, roleType, regionCode, mockReferralRepository, mockAuthorizationRoleRepository, ) expect(result.isValid).toBe(true) }) it('should return failure when user already has province authorization', async () => { const userId = UserId.create('user-123', BigInt(123)) const roleType = RoleType.AUTH_PROVINCE_COMPANY const regionCode = RegionCode.create('430000') const existingAuth = AuthorizationRole.createAuthProvinceCompany({ userId, provinceCode: '440000', provinceName: '广东省', }) mockAuthorizationRoleRepository.findByUserIdAndRoleType.mockResolvedValue(existingAuth) const result = await validatorService.validateAuthorizationRequest( userId, roleType, regionCode, mockReferralRepository, mockAuthorizationRoleRepository, ) expect(result.isValid).toBe(false) expect(result.errorMessage).toContain('只能申请一个省代或市代授权') }) it('should return failure when ancestor has same region authorization', async () => { const userId = UserId.create('user-123', BigInt(123)) const roleType = RoleType.AUTH_PROVINCE_COMPANY const regionCode = RegionCode.create('430000') const ancestorUserId = UserId.create('ancestor-user', BigInt(999)) mockAuthorizationRoleRepository.findByUserIdAndRoleType.mockResolvedValue(null) mockReferralRepository.findByUserId.mockResolvedValue({ parentId: 'ancestor-user' }) mockReferralRepository.getAllAncestors.mockResolvedValue([ancestorUserId]) mockReferralRepository.getAllDescendants.mockResolvedValue([]) const existingAuth = AuthorizationRole.createAuthProvinceCompany({ userId: ancestorUserId, provinceCode: '430000', provinceName: '湖南省', }) mockAuthorizationRoleRepository.findByUserIdRoleTypeAndRegion.mockResolvedValue(existingAuth) const result = await validatorService.validateAuthorizationRequest( userId, roleType, regionCode, mockReferralRepository, mockAuthorizationRoleRepository, ) expect(result.isValid).toBe(false) expect(result.errorMessage).toContain('本团队已有人申请') }) }) }) describe('AssessmentCalculatorService', () => { describe('assessAndRankRegion', () => { it('should calculate assessments and rank by exceed ratio', async () => { const roleType = RoleType.AUTH_PROVINCE_COMPANY const regionCode = RegionCode.create('430000') const assessmentMonth = Month.current() // Mock two authorizations const auth1 = AuthorizationRole.createAuthProvinceCompany({ userId: UserId.create('user-1', BigInt(1)), provinceCode: '430000', provinceName: '湖南省', }) auth1.authorize(UserId.create('admin', BigInt(100))) const auth2 = AuthorizationRole.createAuthProvinceCompany({ userId: UserId.create('user-2', BigInt(2)), provinceCode: '430000', provinceName: '湖南省', }) auth2.authorize(UserId.create('admin', BigInt(100))) mockAuthorizationRoleRepository.findActiveByRoleTypeAndRegion.mockResolvedValue([auth1, auth2]) mockMonthlyAssessmentRepository.findByAuthorizationAndMonth.mockResolvedValue(null) // User 1 has better stats mockTeamStatisticsRepository.findByUserId .mockResolvedValueOnce({ userId: 'user-1', accountSequence: BigInt(1), totalTeamPlantingCount: 200, getProvinceTeamCount: () => 70, getCityTeamCount: () => 0, }) .mockResolvedValueOnce({ userId: 'user-2', accountSequence: BigInt(2), totalTeamPlantingCount: 100, getProvinceTeamCount: () => 35, getCityTeamCount: () => 0, }) const assessments = await calculatorService.assessAndRankRegion( roleType, regionCode, assessmentMonth, mockAuthorizationRoleRepository, mockTeamStatisticsRepository, mockMonthlyAssessmentRepository, ) expect(assessments.length).toBe(2) // User 1 should rank first (higher exceed ratio) expect(assessments[0].userId.value).toBe('user-1') expect(assessments[0].rankingInRegion).toBe(1) expect(assessments[0].isFirstPlace).toBe(true) }) }) }) }) describe('LadderTargetRule Entity Integration Tests', () => { describe('getTarget', () => { it('should return correct target for province month 1', () => { const target = LadderTargetRule.getTarget(RoleType.AUTH_PROVINCE_COMPANY, 1) expect(target.monthlyTarget).toBe(150) expect(target.cumulativeTarget).toBe(150) }) it('should return correct target for province month 5', () => { const target = LadderTargetRule.getTarget(RoleType.AUTH_PROVINCE_COMPANY, 5) expect(target.monthlyTarget).toBe(2400) expect(target.cumulativeTarget).toBe(4650) }) it('should return month 9 target for months beyond 9', () => { const target = LadderTargetRule.getTarget(RoleType.AUTH_PROVINCE_COMPANY, 12) expect(target.monthlyTarget).toBe(11750) expect(target.cumulativeTarget).toBe(50000) }) it('should return correct target for city month 1', () => { const target = LadderTargetRule.getTarget(RoleType.AUTH_CITY_COMPANY, 1) expect(target.monthlyTarget).toBe(30) expect(target.cumulativeTarget).toBe(30) }) it('should return correct target for city month 9', () => { const target = LadderTargetRule.getTarget(RoleType.AUTH_CITY_COMPANY, 9) expect(target.monthlyTarget).toBe(2350) expect(target.cumulativeTarget).toBe(10000) }) it('should return fixed target for community', () => { const target = LadderTargetRule.getTarget(RoleType.COMMUNITY, 1) expect(target.monthlyTarget).toBe(10) expect(target.cumulativeTarget).toBe(10) }) }) describe('getFinalTarget', () => { it('should return 50000 for province', () => { expect(LadderTargetRule.getFinalTarget(RoleType.AUTH_PROVINCE_COMPANY)).toBe(50000) }) it('should return 10000 for city', () => { expect(LadderTargetRule.getFinalTarget(RoleType.AUTH_CITY_COMPANY)).toBe(10000) }) it('should return 10 for community', () => { expect(LadderTargetRule.getFinalTarget(RoleType.COMMUNITY)).toBe(10) }) }) describe('getAllTargets', () => { it('should return 9 targets for province', () => { const targets = LadderTargetRule.getAllTargets(RoleType.AUTH_PROVINCE_COMPANY) expect(targets.length).toBe(9) }) it('should return 9 targets for city', () => { const targets = LadderTargetRule.getAllTargets(RoleType.AUTH_CITY_COMPANY) expect(targets.length).toBe(9) }) it('should return 1 target for community', () => { const targets = LadderTargetRule.getAllTargets(RoleType.COMMUNITY) expect(targets.length).toBe(1) }) }) }) describe('Month Value Object Integration Tests', () => { describe('create and format', () => { it('should create month from string', () => { const month = Month.create('2024-03') expect(month.value).toBe('2024-03') }) it('should create month from date', () => { const date = new Date('2024-06-15') const month = Month.fromDate(date) expect(month.value).toBe('2024-06') }) it('should create current month', () => { const month = Month.current() const now = new Date() const expectedMonth = String(now.getMonth() + 1).padStart(2, '0') const expectedYear = now.getFullYear() expect(month.value).toBe(`${expectedYear}-${expectedMonth}`) }) }) describe('navigation', () => { it('should get next month correctly', () => { const month = Month.create('2024-03') const next = month.next() expect(next.value).toBe('2024-04') }) it('should handle year boundary for next', () => { const month = Month.create('2024-12') const next = month.next() expect(next.value).toBe('2025-01') }) it('should get previous month correctly', () => { const month = Month.create('2024-03') const prev = month.previous() expect(prev.value).toBe('2024-02') }) it('should handle year boundary for previous', () => { const month = Month.create('2024-01') const prev = month.previous() expect(prev.value).toBe('2023-12') }) }) describe('comparison', () => { it('should compare months correctly', () => { const month1 = Month.create('2024-03') const month2 = Month.create('2024-06') expect(month1.isBefore(month2)).toBe(true) expect(month2.isAfter(month1)).toBe(true) expect(month1.equals(month1)).toBe(true) }) it('should compare across years', () => { const month1 = Month.create('2023-12') const month2 = Month.create('2024-01') expect(month1.isBefore(month2)).toBe(true) expect(month2.isAfter(month1)).toBe(true) }) }) describe('extraction', () => { it('should extract year and month', () => { const month = Month.create('2024-03') expect(month.getYear()).toBe(2024) expect(month.getMonth()).toBe(3) }) }) }) describe('RegionCode Value Object Integration Tests', () => { describe('create', () => { it('should create valid region code', () => { const regionCode = RegionCode.create('430000') expect(regionCode.value).toBe('430000') }) it('should create city code', () => { const regionCode = RegionCode.create('430100') expect(regionCode.value).toBe('430100') }) }) describe('equality', () => { it('should compare equal region codes', () => { const code1 = RegionCode.create('430000') const code2 = RegionCode.create('430000') expect(code1.equals(code2)).toBe(true) }) it('should compare different region codes', () => { const code1 = RegionCode.create('430000') const code2 = RegionCode.create('440000') expect(code1.equals(code2)).toBe(false) }) }) })