419 lines
15 KiB
TypeScript
419 lines
15 KiB
TypeScript
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<IAuthorizationRoleRepository> = {
|
|
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<IMonthlyAssessmentRepository> = {
|
|
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<IReferralRepository> = {
|
|
findByUserId: jest.fn(),
|
|
getAllAncestors: jest.fn(),
|
|
getAllDescendants: jest.fn(),
|
|
}
|
|
|
|
// Mock team statistics repository
|
|
const mockTeamStatisticsRepository: jest.Mocked<ITeamStatisticsRepository> = {
|
|
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>(AuthorizationValidatorService)
|
|
calculatorService = module.get<AssessmentCalculatorService>(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)
|
|
})
|
|
})
|
|
})
|