1import { describe, it, expect, vi, beforeEach } from 'vitest';
2import { UserService } from './user-service';
3import { UserRepository } from './user-repository';
4
5describe('UserService', () => {
6 let userRepo: UserRepository;
7 let userService: UserService;
8
9 beforeEach(() => {
10 // Fresh mock before each test — no state leakage
11 userRepo = {
12 findById: vi.fn(),
13 save: vi.fn(),
14 delete: vi.fn(),
15 } as unknown as UserRepository;
16 userService = new UserService(userRepo);
17 });
18
19 it('returns user when found', async () => {
20 const mockUser = { id: '1', name: 'Alice', email: 'alice@example.com' };
21 vi.mocked(userRepo.findById).mockResolvedValue(mockUser);
22
23 const result = await userService.getUser('1');
24
25 expect(result).toEqual(mockUser);
26 expect(userRepo.findById).toHaveBeenCalledWith('1');
27 expect(userRepo.findById).toHaveBeenCalledOnce();
28 });
29
30 it('throws when user not found', async () => {
31 vi.mocked(userRepo.findById).mockResolvedValue(null);
32
33 await expect(userService.getUser('999'))
34 .rejects.toThrow('User 999 not found');
35 });
36
37 it('hashes password on create', async () => {
38 const saved = { id: '2', name: 'Bob', email: 'b@x.com', passwordHash: 'hashed' };
39 vi.mocked(userRepo.save).mockResolvedValue(saved);
40
41 await userService.createUser({ name: 'Bob', email: 'b@x.com', password: 'secret' });
42
43 const saved_arg = vi.mocked(userRepo.save).mock.calls[0][0];
44 expect(saved_arg.passwordHash).toBeDefined();
45 expect(saved_arg.passwordHash).not.toBe('secret'); // not stored in plain text
46 });
47});