import { describe, it, expect, vi, beforeEach } from 'vitest'; // Use vi.hoisted so mocks are available in vi.mock factory (which is hoisted) const { mockLean, mockExec, mockFindOne, mockFindById, mockFindOneAndUpdate, mockSave } = vi.hoisted(() => { const mockExec = vi.fn(); const mockLean = vi.fn(() => ({ exec: mockExec })); return { mockExec, mockLean, mockFindOne: vi.fn(() => ({ lean: mockLean })), mockFindById: vi.fn(() => ({ lean: mockLean })), mockFindOneAndUpdate: vi.fn(() => ({ exec: mockExec })), mockSave: vi.fn(), }; }); vi.mock('../../schemas/user.schema.js', () => { class MockUserModel { _data: Record; constructor(data: Record) { this._data = data; Object.assign(this, data); } save() { mockSave(); return Promise.resolve(this); } toObject() { return { _id: 'new-id', ...this._data }; } static findOne = mockFindOne; static findById = mockFindById; static findOneAndUpdate = mockFindOneAndUpdate; } return { UserModel: MockUserModel }; }); import { UsersRepository } from './users.repository.js'; describe('UsersRepository', () => { let repo: UsersRepository; beforeEach(() => { vi.clearAllMocks(); repo = new UsersRepository(); }); describe('findByKeycloakId', () => { it('calls findOne with keycloakId and returns lean result', async () => { const user = { _id: 'u1', keycloakId: 'kc-1' }; mockExec.mockResolvedValue(user); const result = await repo.findByKeycloakId('kc-1'); expect(mockFindOne).toHaveBeenCalledWith({ keycloakId: 'kc-1' }, null, { session: undefined, }); expect(mockLean).toHaveBeenCalled(); expect(mockExec).toHaveBeenCalled(); expect(result).toEqual(user); }); }); describe('findById', () => { it('calls findById and returns lean result', async () => { const user = { _id: 'u1' }; mockExec.mockResolvedValue(user); const result = await repo.findById('u1'); expect(mockFindById).toHaveBeenCalledWith('u1'); expect(result).toEqual(user); }); }); describe('create', () => { it('creates a new user and returns plain object', async () => { mockSave.mockResolvedValue({}); const data = { keycloakId: 'kc-1', email: 'test@example.com', displayName: 'Test', householdIds: [], }; const result = await repo.create(data as never); expect(mockSave).toHaveBeenCalled(); expect(result).toMatchObject({ keycloakId: 'kc-1' }); }); }); describe('update', () => { it('calls findOneAndUpdate with $set', async () => { const updated = { _id: 'u1', displayName: 'Updated' }; mockExec.mockResolvedValue(updated); const result = await repo.update('kc-1', { displayName: 'Updated' } as never); expect(mockFindOneAndUpdate).toHaveBeenCalledWith( { keycloakId: 'kc-1' }, { $set: { displayName: 'Updated' } }, { new: true, lean: true, session: undefined }, ); expect(result).toEqual(updated); }); }); describe('upsertFromToken', () => { it('upserts user with $set and $setOnInsert', async () => { const upserted = { _id: 'u1', keycloakId: 'kc-1' }; mockExec.mockResolvedValue(upserted); const result = await repo.upsertFromToken('kc-1', 'a@b.com', 'Name'); expect(mockFindOneAndUpdate).toHaveBeenCalledWith( { keycloakId: 'kc-1' }, { $set: { email: 'a@b.com', displayName: 'Name' }, $setOnInsert: { keycloakId: 'kc-1', householdIds: [], defaultHouseholdId: null }, }, { upsert: true, new: true, lean: true }, ); expect(result).toEqual(upserted); }); }); });