From c15bd0530541fb1ab09287fc6ecb11d248c28068 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 19 Mar 2026 12:09:01 +0100 Subject: [PATCH] test(calendar,contacts,todo): add controller unit tests for all 3 apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Calendar: 3 controller specs (calendar: 6, event: 7, event-tag: 6) → 151 total - Contacts: 2 controller specs (contact: 9, tag: 8) → 72 total - Todo: 2 controller specs (task: 14, project: 6) → 127 total Uses direct instantiation pattern to avoid NestJS DI complexity in unit tests Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/calendar/calendar.controller.spec.ts | 91 +++++++++++ .../event-tag/event-tag.controller.spec.ts | 87 +++++++++++ .../src/event/event.controller.spec.ts | 86 +++++++++++ .../__tests__/contact.controller.spec.ts | 132 ++++++++++++++++ .../src/tag/__tests__/tag.controller.spec.ts | 90 +++++++++++ .../__tests__/project.controller.spec.ts | 73 +++++++++ .../task/__tests__/task.controller.spec.ts | 143 ++++++++++++++++++ 7 files changed, 702 insertions(+) create mode 100644 apps/calendar/apps/backend/src/calendar/calendar.controller.spec.ts create mode 100644 apps/calendar/apps/backend/src/event-tag/event-tag.controller.spec.ts create mode 100644 apps/calendar/apps/backend/src/event/event.controller.spec.ts create mode 100644 apps/contacts/apps/backend/src/contact/__tests__/contact.controller.spec.ts create mode 100644 apps/contacts/apps/backend/src/tag/__tests__/tag.controller.spec.ts create mode 100644 apps/todo/apps/backend/src/project/__tests__/project.controller.spec.ts create mode 100644 apps/todo/apps/backend/src/task/__tests__/task.controller.spec.ts diff --git a/apps/calendar/apps/backend/src/calendar/calendar.controller.spec.ts b/apps/calendar/apps/backend/src/calendar/calendar.controller.spec.ts new file mode 100644 index 000000000..d533e096f --- /dev/null +++ b/apps/calendar/apps/backend/src/calendar/calendar.controller.spec.ts @@ -0,0 +1,91 @@ +import { CalendarController } from './calendar.controller'; +import { createMockCalendar, TEST_USER_ID } from '../__tests__/utils/mock-factories'; + +const mockUser = { userId: TEST_USER_ID, email: 'test@example.com' }; + +describe('CalendarController', () => { + let controller: CalendarController; + let service: any; + + beforeEach(() => { + service = { + findAll: jest.fn(), + findByIdOrThrow: jest.fn(), + create: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + getOrCreateDefaultCalendar: jest.fn(), + }; + controller = new CalendarController(service); + }); + + afterEach(() => jest.clearAllMocks()); + + describe('findAll', () => { + it('should return user calendars', async () => { + const calendars = [createMockCalendar(), createMockCalendar()]; + service.findAll.mockResolvedValue(calendars); + + const result = await controller.findAll(mockUser as any); + + expect(result).toEqual({ calendars }); + expect(service.findAll).toHaveBeenCalledWith(TEST_USER_ID); + }); + + it('should lazy-create default calendar when none exist', async () => { + const defaultCal = createMockCalendar({ isDefault: true }); + service.findAll.mockResolvedValue([]); + service.getOrCreateDefaultCalendar.mockResolvedValue(defaultCal); + + const result = await controller.findAll(mockUser as any); + + expect(result).toEqual({ calendars: [defaultCal] }); + expect(service.getOrCreateDefaultCalendar).toHaveBeenCalledWith(TEST_USER_ID); + }); + }); + + describe('findOne', () => { + it('should return calendar by id', async () => { + const calendar = createMockCalendar(); + service.findByIdOrThrow.mockResolvedValue(calendar); + + const result = await controller.findOne(mockUser as any, calendar.id); + + expect(result).toEqual({ calendar }); + }); + }); + + describe('create', () => { + it('should create and return calendar', async () => { + const calendar = createMockCalendar({ name: 'New Cal' }); + service.create.mockResolvedValue(calendar); + + const result = await controller.create(mockUser as any, { name: 'New Cal' } as any); + + expect(result).toEqual({ calendar }); + }); + }); + + describe('update', () => { + it('should update and return calendar', async () => { + const calendar = createMockCalendar({ name: 'Updated' }); + service.update.mockResolvedValue(calendar); + + const result = await controller.update(mockUser as any, calendar.id, { + name: 'Updated', + } as any); + + expect(result).toEqual({ calendar }); + }); + }); + + describe('delete', () => { + it('should delete and return success', async () => { + service.delete.mockResolvedValue(undefined); + + const result = await controller.delete(mockUser as any, 'cal-id'); + + expect(result).toEqual({ success: true }); + }); + }); +}); diff --git a/apps/calendar/apps/backend/src/event-tag/event-tag.controller.spec.ts b/apps/calendar/apps/backend/src/event-tag/event-tag.controller.spec.ts new file mode 100644 index 000000000..138253332 --- /dev/null +++ b/apps/calendar/apps/backend/src/event-tag/event-tag.controller.spec.ts @@ -0,0 +1,87 @@ +import { NotFoundException } from '@nestjs/common'; +import { EventTagController } from './event-tag.controller'; +import { TEST_USER_ID } from '../__tests__/utils/mock-factories'; +import { v4 as uuidv4 } from 'uuid'; + +const mockUser = { userId: TEST_USER_ID, email: 'test@example.com' }; + +function createMockTag(overrides: Record = {}) { + return { id: uuidv4(), userId: TEST_USER_ID, name: 'Test', color: '#3B82F6', ...overrides }; +} + +describe('EventTagController', () => { + let controller: EventTagController; + let service: any; + + beforeEach(() => { + service = { + findByUserId: jest.fn(), + findById: jest.fn(), + create: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + }; + controller = new EventTagController(service); + }); + + afterEach(() => jest.clearAllMocks()); + + describe('findAll', () => { + it('should return all tags', async () => { + const tags = [createMockTag(), createMockTag()]; + service.findByUserId.mockResolvedValue(tags); + const result = await controller.findAll(mockUser as any); + expect(result).toEqual({ tags }); + }); + }); + + describe('findOne', () => { + it('should return tag by id', async () => { + const tag = createMockTag(); + service.findById.mockResolvedValue(tag); + const result = await controller.findOne(mockUser as any, tag.id); + expect(result).toEqual({ tag }); + }); + + it('should throw NotFoundException when not found', async () => { + service.findById.mockResolvedValue(null); + await expect(controller.findOne(mockUser as any, 'bad-id')).rejects.toThrow( + NotFoundException + ); + }); + }); + + describe('create', () => { + it('should create tag with userId', async () => { + const tag = createMockTag({ name: 'Work' }); + service.create.mockResolvedValue(tag); + const result = await controller.create( + mockUser as any, + { name: 'Work', color: '#3B82F6' } as any + ); + expect(result).toEqual({ tag }); + expect(service.create).toHaveBeenCalledWith({ + name: 'Work', + color: '#3B82F6', + userId: TEST_USER_ID, + }); + }); + }); + + describe('update', () => { + it('should update tag', async () => { + const tag = createMockTag({ name: 'Updated' }); + service.update.mockResolvedValue(tag); + const result = await controller.update(mockUser as any, tag.id, { name: 'Updated' } as any); + expect(result).toEqual({ tag }); + }); + }); + + describe('delete', () => { + it('should delete tag', async () => { + service.delete.mockResolvedValue(undefined); + const result = await controller.delete(mockUser as any, 'tag-id'); + expect(result).toEqual({ success: true }); + }); + }); +}); diff --git a/apps/calendar/apps/backend/src/event/event.controller.spec.ts b/apps/calendar/apps/backend/src/event/event.controller.spec.ts new file mode 100644 index 000000000..ef68ab518 --- /dev/null +++ b/apps/calendar/apps/backend/src/event/event.controller.spec.ts @@ -0,0 +1,86 @@ +import { EventController } from './event.controller'; +import { createMockEvent, TEST_USER_ID } from '../__tests__/utils/mock-factories'; + +const mockUser = { userId: TEST_USER_ID, email: 'test@example.com' }; + +describe('EventController', () => { + let controller: EventController; + let service: any; + + beforeEach(() => { + service = { + getEventsWithCalendar: jest.fn(), + findByIdOrThrow: jest.fn(), + findByCalendar: jest.fn(), + create: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + }; + controller = new EventController(service); + }); + + afterEach(() => jest.clearAllMocks()); + + describe('queryEvents', () => { + it('should return events with pagination', async () => { + const events = [createMockEvent(), createMockEvent()]; + service.getEventsWithCalendar.mockResolvedValue(events); + const query = { limit: 50, offset: 0 } as any; + const result = await controller.queryEvents(mockUser as any, query); + expect(result.events).toEqual(events); + expect(result.pagination).toEqual({ limit: 50, offset: 0, count: 2 }); + }); + + it('should default offset to 0', async () => { + service.getEventsWithCalendar.mockResolvedValue([]); + const result = await controller.queryEvents(mockUser as any, { limit: 50 } as any); + expect(result.pagination.offset).toBe(0); + }); + }); + + describe('findOne', () => { + it('should return event by id', async () => { + const event = createMockEvent(); + service.findByIdOrThrow.mockResolvedValue(event); + const result = await controller.findOne(mockUser as any, event.id); + expect(result).toEqual({ event }); + }); + }); + + describe('findByCalendar', () => { + it('should return events for calendar', async () => { + const events = [createMockEvent()]; + service.findByCalendar.mockResolvedValue(events); + const result = await controller.findByCalendar(mockUser as any, 'cal-id', {} as any); + expect(result).toEqual({ events }); + }); + }); + + describe('create', () => { + it('should create event', async () => { + const event = createMockEvent({ title: 'Meeting' }); + service.create.mockResolvedValue(event); + const result = await controller.create(mockUser as any, { title: 'Meeting' } as any); + expect(result).toEqual({ event }); + }); + }); + + describe('update', () => { + it('should update event', async () => { + const event = createMockEvent({ title: 'Updated' }); + service.update.mockResolvedValue(event); + const result = await controller.update(mockUser as any, event.id, { + title: 'Updated', + } as any); + expect(result).toEqual({ event }); + }); + }); + + describe('delete', () => { + it('should delete and return success', async () => { + service.delete.mockResolvedValue(undefined); + const result = await controller.delete(mockUser as any, 'event-id'); + expect(result).toEqual({ success: true }); + }); + }); +}); diff --git a/apps/contacts/apps/backend/src/contact/__tests__/contact.controller.spec.ts b/apps/contacts/apps/backend/src/contact/__tests__/contact.controller.spec.ts new file mode 100644 index 000000000..829e40819 --- /dev/null +++ b/apps/contacts/apps/backend/src/contact/__tests__/contact.controller.spec.ts @@ -0,0 +1,132 @@ +import { ContactController } from '../contact.controller'; + +const TEST_USER_ID = 'test-user-123'; +const mockUser = { userId: TEST_USER_ID, email: 'test@example.com' }; + +function createMockContact(overrides: Record = {}) { + return { + id: 'contact-1', + userId: TEST_USER_ID, + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + email: 'max@example.com', + isFavorite: false, + isArchived: false, + createdAt: new Date(), + updatedAt: new Date(), + ...overrides, + }; +} + +describe('ContactController', () => { + let controller: ContactController; + let service: any; + + beforeEach(() => { + service = { + findByUserId: jest.fn(), + count: jest.fn(), + findWithBirthdays: jest.fn(), + findById: jest.fn(), + create: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + toggleFavorite: jest.fn(), + toggleArchive: jest.fn(), + }; + controller = new ContactController(service); + }); + + afterEach(() => jest.clearAllMocks()); + + describe('findAll', () => { + it('should return contacts with total', async () => { + const contacts = [createMockContact()]; + service.findByUserId.mockResolvedValue(contacts); + service.count.mockResolvedValue(1); + const result = await controller.findAll(mockUser as any, {} as any); + expect(result).toEqual({ contacts, total: 1 }); + }); + }); + + describe('getBirthdays', () => { + it('should return contacts with birthdays', async () => { + const contacts = [createMockContact({ birthday: '1990-01-15' })]; + service.findWithBirthdays.mockResolvedValue(contacts); + const result = await controller.getBirthdays(mockUser as any); + expect(result).toEqual({ contacts }); + }); + }); + + describe('findOne', () => { + it('should return contact', async () => { + const contact = createMockContact(); + service.findById.mockResolvedValue(contact); + const result = await controller.findOne(mockUser as any, 'contact-1'); + expect(result).toEqual({ contact }); + }); + + it('should return null when not found', async () => { + service.findById.mockResolvedValue(null); + const result = await controller.findOne(mockUser as any, 'bad-id'); + expect(result).toEqual({ contact: null }); + }); + }); + + describe('create', () => { + it('should generate displayName from first+last name', async () => { + const contact = createMockContact(); + service.create.mockResolvedValue(contact); + await controller.create(mockUser as any, { firstName: 'Max', lastName: 'Mustermann' } as any); + expect(service.create).toHaveBeenCalledWith( + expect.objectContaining({ displayName: 'Max Mustermann', userId: TEST_USER_ID }) + ); + }); + + it('should use provided displayName', async () => { + service.create.mockResolvedValue(createMockContact()); + await controller.create(mockUser as any, { displayName: 'Custom' } as any); + expect(service.create).toHaveBeenCalledWith( + expect.objectContaining({ displayName: 'Custom' }) + ); + }); + }); + + describe('update', () => { + it('should update contact', async () => { + const contact = createMockContact({ firstName: 'Updated' }); + service.update.mockResolvedValue(contact); + const result = await controller.update(mockUser as any, 'contact-1', { + firstName: 'Updated', + } as any); + expect(result).toEqual({ contact }); + }); + }); + + describe('delete', () => { + it('should delete and return success', async () => { + service.delete.mockResolvedValue(undefined); + const result = await controller.delete(mockUser as any, 'contact-1'); + expect(result).toEqual({ success: true }); + }); + }); + + describe('toggleFavorite', () => { + it('should toggle favorite', async () => { + const contact = createMockContact({ isFavorite: true }); + service.toggleFavorite.mockResolvedValue(contact); + const result = await controller.toggleFavorite(mockUser as any, 'contact-1'); + expect(result).toEqual({ contact }); + }); + }); + + describe('toggleArchive', () => { + it('should toggle archive', async () => { + const contact = createMockContact({ isArchived: true }); + service.toggleArchive.mockResolvedValue(contact); + const result = await controller.toggleArchive(mockUser as any, 'contact-1'); + expect(result).toEqual({ contact }); + }); + }); +}); diff --git a/apps/contacts/apps/backend/src/tag/__tests__/tag.controller.spec.ts b/apps/contacts/apps/backend/src/tag/__tests__/tag.controller.spec.ts new file mode 100644 index 000000000..d40add19e --- /dev/null +++ b/apps/contacts/apps/backend/src/tag/__tests__/tag.controller.spec.ts @@ -0,0 +1,90 @@ +import { NotFoundException } from '@nestjs/common'; +import { TagController } from '../tag.controller'; + +const TEST_USER_ID = 'test-user-123'; +const mockUser = { userId: TEST_USER_ID, email: 'test@example.com' }; + +function createMockTag(overrides: Record = {}) { + return { id: 'tag-1', userId: TEST_USER_ID, name: 'Work', color: '#3B82F6', ...overrides }; +} + +describe('TagController', () => { + let controller: TagController; + let tagService: any; + let contactService: any; + + beforeEach(() => { + tagService = { + findByUserId: jest.fn(), + findById: jest.fn(), + create: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + addTagToContact: jest.fn(), + removeTagFromContact: jest.fn(), + getTagsForContact: jest.fn(), + }; + contactService = { findById: jest.fn() }; + controller = new TagController(tagService, contactService); + }); + + afterEach(() => jest.clearAllMocks()); + + describe('findAll', () => { + it('should return tags', async () => { + const tags = [createMockTag()]; + tagService.findByUserId.mockResolvedValue(tags); + const result = await controller.findAll(mockUser as any); + expect(result).toEqual({ tags }); + }); + }); + + describe('create', () => { + it('should create tag with userId', async () => { + const tag = createMockTag(); + tagService.create.mockResolvedValue(tag); + await controller.create(mockUser as any, { name: 'Work' } as any); + expect(tagService.create).toHaveBeenCalledWith({ name: 'Work', userId: TEST_USER_ID }); + }); + }); + + describe('delete', () => { + it('should delete tag', async () => { + tagService.delete.mockResolvedValue(undefined); + const result = await controller.delete(mockUser as any, 'tag-1'); + expect(result).toEqual({ success: true }); + }); + }); + + describe('addToContact', () => { + it('should add tag to contact', async () => { + tagService.findById.mockResolvedValue(createMockTag()); + contactService.findById.mockResolvedValue({ id: 'c1' }); + const result = await controller.addToContact(mockUser as any, 'tag-1', 'c1'); + expect(result).toEqual({ success: true }); + }); + + it('should throw when tag not found', async () => { + tagService.findById.mockResolvedValue(null); + await expect(controller.addToContact(mockUser as any, 'bad', 'c1')).rejects.toThrow( + NotFoundException + ); + }); + + it('should throw when contact not found', async () => { + tagService.findById.mockResolvedValue(createMockTag()); + contactService.findById.mockResolvedValue(null); + await expect(controller.addToContact(mockUser as any, 't1', 'bad')).rejects.toThrow( + NotFoundException + ); + }); + }); + + describe('getTagsForContact', () => { + it('should return tag IDs', async () => { + tagService.getTagsForContact.mockResolvedValue(['tag-1']); + const result = await controller.getTagsForContact(mockUser as any, 'c1'); + expect(result).toEqual({ tagIds: ['tag-1'] }); + }); + }); +}); diff --git a/apps/todo/apps/backend/src/project/__tests__/project.controller.spec.ts b/apps/todo/apps/backend/src/project/__tests__/project.controller.spec.ts new file mode 100644 index 000000000..cb6788bc0 --- /dev/null +++ b/apps/todo/apps/backend/src/project/__tests__/project.controller.spec.ts @@ -0,0 +1,73 @@ +import { ProjectController } from '../project.controller'; + +const TEST_USER_ID = 'test-user-123'; +const mockUser = { userId: TEST_USER_ID, email: 'test@example.com' }; + +function createMockProject(overrides: Record = {}) { + return { id: 'proj-1', userId: TEST_USER_ID, name: 'Test', color: '#3B82F6', ...overrides }; +} + +describe('ProjectController', () => { + let controller: ProjectController; + let service: any; + + beforeEach(() => { + service = { + findAll: jest.fn(), + findByIdOrThrow: jest.fn(), + create: jest.fn(), + getOrCreateDefaultProject: jest.fn().mockResolvedValue(undefined), + update: jest.fn(), + delete: jest.fn(), + archive: jest.fn(), + reorder: jest.fn(), + }; + controller = new ProjectController(service); + }); + + afterEach(() => jest.clearAllMocks()); + + describe('findAll', () => { + it('should return projects', async () => { + const projects = [createMockProject()]; + service.findAll.mockResolvedValue(projects); + const result = await controller.findAll(mockUser as any); + expect(result).toEqual({ projects }); + }); + }); + + describe('findOne', () => { + it('should return project', async () => { + const project = createMockProject(); + service.findByIdOrThrow.mockResolvedValue(project); + const result = await controller.findOne(mockUser as any, 'proj-1'); + expect(result).toEqual({ project }); + }); + }); + + describe('create', () => { + it('should create project', async () => { + const project = createMockProject({ name: 'New' }); + service.create.mockResolvedValue(project); + const result = await controller.create(mockUser as any, { name: 'New' } as any); + expect(result).toEqual({ project }); + }); + }); + + describe('delete', () => { + it('should delete', async () => { + service.delete.mockResolvedValue(undefined); + const result = await controller.delete(mockUser as any, 'proj-1'); + expect(result).toEqual({ success: true }); + }); + }); + + describe('archive', () => { + it('should archive project', async () => { + const project = createMockProject({ isArchived: true }); + service.archive.mockResolvedValue(project); + const result = await controller.archive(mockUser as any, 'proj-1'); + expect(result).toEqual({ project }); + }); + }); +}); diff --git a/apps/todo/apps/backend/src/task/__tests__/task.controller.spec.ts b/apps/todo/apps/backend/src/task/__tests__/task.controller.spec.ts new file mode 100644 index 000000000..37b6eb6d6 --- /dev/null +++ b/apps/todo/apps/backend/src/task/__tests__/task.controller.spec.ts @@ -0,0 +1,143 @@ +import { TaskController } from '../task.controller'; + +const TEST_USER_ID = 'test-user-123'; +const mockUser = { userId: TEST_USER_ID, email: 'test@example.com' }; + +function createMockTask(overrides: Record = {}) { + return { id: 'task-1', userId: TEST_USER_ID, title: 'Test', isCompleted: false, ...overrides }; +} + +describe('TaskController', () => { + let controller: TaskController; + let service: any; + + beforeEach(() => { + service = { + findAll: jest.fn(), + getInboxTasks: jest.fn(), + getTodayTasks: jest.fn(), + getUpcomingTasks: jest.fn(), + getCompletedTasks: jest.fn(), + findByContact: jest.fn(), + findByIdOrThrow: jest.fn(), + create: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + complete: jest.fn(), + uncomplete: jest.fn(), + move: jest.fn(), + updateTaskLabels: jest.fn(), + }; + controller = new TaskController(service); + }); + + afterEach(() => jest.clearAllMocks()); + + describe('findAll', () => { + it('should return tasks', async () => { + const tasks = [createMockTask()]; + service.findAll.mockResolvedValue(tasks); + const result = await controller.findAll(mockUser as any, {} as any); + expect(result).toEqual({ tasks }); + }); + }); + + describe('getInbox', () => { + it('should return inbox tasks', async () => { + service.getInboxTasks.mockResolvedValue([createMockTask()]); + const result = await controller.getInbox(mockUser as any); + expect(result.tasks).toHaveLength(1); + }); + }); + + describe('getToday', () => { + it('should return today tasks', async () => { + service.getTodayTasks.mockResolvedValue([]); + const result = await controller.getToday(mockUser as any); + expect(result).toEqual({ tasks: [] }); + }); + }); + + describe('getUpcoming', () => { + it('should default to 7 days', async () => { + service.getUpcomingTasks.mockResolvedValue([]); + await controller.getUpcoming(mockUser as any, undefined); + expect(service.getUpcomingTasks).toHaveBeenCalledWith(TEST_USER_ID, 7); + }); + + it('should use custom days', async () => { + service.getUpcomingTasks.mockResolvedValue([]); + await controller.getUpcoming(mockUser as any, 14); + expect(service.getUpcomingTasks).toHaveBeenCalledWith(TEST_USER_ID, 14); + }); + }); + + describe('getCompleted', () => { + it('should default pagination', async () => { + service.getCompletedTasks.mockResolvedValue({ tasks: [], total: 0 }); + await controller.getCompleted(mockUser as any, undefined, undefined); + expect(service.getCompletedTasks).toHaveBeenCalledWith(TEST_USER_ID, 50, 0); + }); + }); + + describe('findOne', () => { + it('should return task', async () => { + const task = createMockTask(); + service.findByIdOrThrow.mockResolvedValue(task); + const result = await controller.findOne(mockUser as any, 'task-1'); + expect(result).toEqual({ task }); + }); + }); + + describe('create', () => { + it('should create task', async () => { + const task = createMockTask({ title: 'New' }); + service.create.mockResolvedValue(task); + const result = await controller.create(mockUser as any, { title: 'New' } as any); + expect(result).toEqual({ task }); + }); + }); + + describe('delete', () => { + it('should delete and return success', async () => { + service.delete.mockResolvedValue(undefined); + const result = await controller.delete(mockUser as any, 'task-1'); + expect(result).toEqual({ success: true }); + }); + }); + + describe('complete', () => { + it('should complete task', async () => { + const task = createMockTask({ isCompleted: true }); + service.complete.mockResolvedValue(task); + const result = await controller.complete(mockUser as any, 'task-1'); + expect(result).toEqual({ task }); + }); + }); + + describe('uncomplete', () => { + it('should uncomplete task', async () => { + const task = createMockTask({ isCompleted: false }); + service.uncomplete.mockResolvedValue(task); + const result = await controller.uncomplete(mockUser as any, 'task-1'); + expect(result).toEqual({ task }); + }); + }); + + describe('move', () => { + it('should move task to project', async () => { + const task = createMockTask({ projectId: 'proj-1' }); + service.move.mockResolvedValue(task); + const result = await controller.move(mockUser as any, 'task-1', 'proj-1'); + expect(result).toEqual({ task }); + }); + }); + + describe('getByContact', () => { + it('should return tasks for contact', async () => { + service.findByContact.mockResolvedValue([createMockTask()]); + const result = await controller.getByContact(mockUser as any, 'contact-1', 'false'); + expect(result.tasks).toHaveLength(1); + }); + }); +});