import { ConflictException, NotFoundException } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { PrismaService } from '../../prisma/prisma.service'; import { StylistService } from './stylist.service'; describe('StylistService', () => { let service: StylistService; const prisma = { hairstylistUser: { findFirst: jest.fn(), update: jest.fn(), }, hairTemplate: { count: jest.fn(), findMany: jest.fn(), }, }; const stylist = { id: BigInt(7), username: 'Yasuaki', email: 'yasuaki@example.com', simulationInviteCode: 'ABCD1234', displayName: 'YASUAKI OGAWA', slug: 'yasuaki-ogawa', photoUrl: 'https://cdn.example.com/photo.jpg', lead: '短介绍', bio: '详细介绍', bookingUrl: 'https://example.com/booking', experienceYears: 22, rating: '5.0', reviewCount: 124, specialties: '鲍勃', address: '东京', hours: '预约制', password: 'hashed', createTime: new Date('2026-09-17T00:00:00.000Z'), updateTime: new Date('2026-09-17T00:00:00.000Z'), isDeleted: 0, }; beforeEach(async () => { jest.resetAllMocks(); const moduleRef = await Test.createTestingModule({ providers: [StylistService, { provide: PrismaService, useValue: prisma }], }).compile(); service = moduleRef.get(StylistService); }); it('returns the authenticated stylist profile with published template count', async () => { prisma.hairstylistUser.findFirst.mockResolvedValue(stylist); prisma.hairTemplate.count.mockResolvedValue(3); prisma.hairTemplate.findMany.mockResolvedValue([ { id: BigInt(11), templateName: '柔和长发', templateNameI18n: { zh: '柔和长发', ja: '柔らかいロング', en: 'Soft long hair' }, coverUrl: 'https://cdn.example.com/cover.jpg', category: 'LONG', description: '自然长发', }, ]); await expect(service.getMyProfile(BigInt(7))).resolves.toMatchObject({ id: '7', displayName: 'YASUAKI OGAWA', slug: 'yasuaki-ogawa', inviteCode: 'ABCD1234', publishedTemplateCount: 3, experienceYears: 22, }); }); it('rejects reserved public slugs', async () => { prisma.hairstylistUser.findFirst.mockResolvedValue(stylist); await expect( service.updateMyProfile(BigInt(7), { displayName: 'YASUAKI OGAWA', slug: 'manage', }), ).rejects.toThrow('该公开主页路径不可用'); }); it('rejects duplicate public slugs', async () => { prisma.hairstylistUser.findFirst .mockResolvedValueOnce(stylist) .mockResolvedValueOnce({ id: BigInt(8) }); await expect( service.updateMyProfile(BigInt(7), { displayName: 'YASUAKI OGAWA', slug: 'taken-slug', }), ).rejects.toBeInstanceOf(ConflictException); }); it('returns a public profile with published templates only', async () => { prisma.hairstylistUser.findFirst.mockResolvedValue(stylist); prisma.hairTemplate.count.mockResolvedValue(1); prisma.hairTemplate.findMany.mockResolvedValue([ { id: BigInt(11), templateName: '柔和长发', templateNameI18n: { zh: '柔和长发', ja: '柔らかいロング', en: 'Soft long hair' }, coverUrl: 'https://cdn.example.com/cover.jpg', category: 'LONG', description: '自然长发', }, ]); const result = await service.getPublicProfile('yasuaki-ogawa'); expect(result).not.toHaveProperty('email'); expect(result).not.toHaveProperty('username'); expect(result.templates).toEqual([ expect.objectContaining({ id: '11', templateName: '柔和长发', coverUrl: 'https://cdn.example.com/cover.jpg', }), ]); expect(prisma.hairTemplate.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ status: 'PUBLIC', isSystemTemplate: 'NO', templateUrl: { not: '' }, }), }), ); }); it('persists a public slug and invite code when they are missing', async () => { const pending = { ...stylist, slug: null, simulationInviteCode: null }; prisma.hairstylistUser.findFirst .mockResolvedValueOnce(pending) .mockResolvedValueOnce(null) .mockResolvedValueOnce(null); prisma.hairstylistUser.update.mockResolvedValue({ ...pending, slug: 'yasuaki', simulationInviteCode: 'ABCD2345', }); prisma.hairTemplate.count.mockResolvedValue(0); prisma.hairTemplate.findMany.mockResolvedValue([]); await expect(service.getMyProfile(BigInt(7))).resolves.toMatchObject({ slug: 'yasuaki', inviteCode: 'ABCD2345', }); expect(prisma.hairstylistUser.update).toHaveBeenCalledWith({ where: { id: stylist.id }, data: { slug: 'yasuaki', simulationInviteCode: expect.stringMatching(/^[A-HJ-NP-Z2-9]{8}$/), }, }); }); it('throws when the public slug does not exist', async () => { prisma.hairstylistUser.findFirst.mockResolvedValue(null); await expect(service.getPublicProfile('missing')).rejects.toBeInstanceOf(NotFoundException); }); });