* fix(jwt): update import path for isTokenExpired function to remove file extension * packages/graphql: add slot tests * fix(slots): update error handling for customer and slot retrieval, enhance time validation in slot updates * fix(slots): update error messages for missing datetime fields and improve validation logic in slot updates * fix(slots): update error messages and validation logic for slot creation and updates, including handling of datetime fields and master status * refactor(slots): rename checkUpdateIsTimeChanging to checkUpdateDatetime for clarity in slot update validation * test(slots): add comprehensive tests for getAvailableTimeSlots method, including edge cases and error handling * fix(api): standardize error messages for customer and slot retrieval, and improve validation logic in slots service * refactor(slots): rename validation methods for clarity and consistency in slot service * OrdersService: add checkBeforeCreate * add orders.test.js * test(orders): add validation test for missing datetime_end in order creation * feat(orders): implement updateOrder functionality with comprehensive validation tests - Added updateOrder method in OrdersService with checks for permissions, order state, and datetime validation. - Implemented tests for various scenarios including successful updates, permission errors, and validation failures. - Enhanced error handling for overlapping time and invalid state changes. - Updated GraphQL operations to support sorting in GetOrders query. * fix(orders): update datetime validation logic and test cases for order creation and completion - Modified order creation tests to set datetime_start to one hour in the past for past orders. - Updated the OrdersService to use isNowOrAfter for validating order completion against the start time. - Enhanced datetime utility function to accept a unit parameter for more flexible comparisons. * fix(calendar): initialize selected date in ScheduleCalendar component if not set - Added useEffect to set the selected date to the current date if it is not already defined. - Imported useEffect alongside useState for managing component lifecycle. * fix(order-form): initialize selected date in DateSelect component if not set - Added useEffect to set the selected date to the current date if it is not already defined. - Renamed setDate to setSelectedDate for clarity in state management. * refactor(orders): streamline order creation logic and enhance test setup - Removed redundant variable assignments in the createOrder method for cleaner code. - Updated test setup in orders.test.js to use global mocks for user and service retrieval, improving test clarity and maintainability. - Added checks for required fields in order creation to ensure data integrity.
1506 lines
43 KiB
JavaScript
1506 lines
43 KiB
JavaScript
import { getClientWithToken } from '../apollo/client';
|
||
import * as GQL from '../types';
|
||
import { ERRORS as BASE_ERRORS } from './base';
|
||
import { ServicesService } from './services';
|
||
import { ERRORS, SlotsService } from './slots';
|
||
import dayjs from 'dayjs';
|
||
import duration from 'dayjs/plugin/duration';
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||
|
||
if (!dayjs.prototype.duration) {
|
||
dayjs.extend(duration);
|
||
}
|
||
|
||
vi.mock('../apollo/client');
|
||
vi.mock('./services');
|
||
vi.mock('../config/env', () => {
|
||
return {
|
||
env: {
|
||
BOT_TOKEN: 'test',
|
||
LOGIN_GRAPHQL: 'test',
|
||
PASSWORD_GRAPHQL: 'test',
|
||
URL_GRAPHQL: 'test',
|
||
},
|
||
};
|
||
});
|
||
|
||
const mockGetClientWithToken = vi.mocked(getClientWithToken);
|
||
const mockServicesService = vi.mocked(ServicesService);
|
||
|
||
describe('SlotsService', () => {
|
||
/**
|
||
* @type {SlotsService}
|
||
*/
|
||
let slotsService;
|
||
const mockUser = { telegramId: 123_456_789 };
|
||
|
||
const mockCustomer = {
|
||
documentId: 'customer-123',
|
||
firstName: 'John',
|
||
lastName: 'Doe',
|
||
telegramId: 123_456_789,
|
||
};
|
||
|
||
const now = dayjs().minute(0).second(0).millisecond(0);
|
||
vi.setSystemTime(now.toDate());
|
||
|
||
const mockSlot = {
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'slot-123',
|
||
master: mockCustomer,
|
||
orders: [],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockGetCustomerResult = {
|
||
data: {
|
||
customers: [mockCustomer],
|
||
},
|
||
};
|
||
|
||
const mockGetSlotResult = {
|
||
data: {
|
||
slot: mockSlot,
|
||
},
|
||
};
|
||
|
||
beforeEach(() => {
|
||
slotsService = new SlotsService(mockUser);
|
||
vi.clearAllMocks();
|
||
});
|
||
|
||
afterEach(() => {
|
||
vi.restoreAllMocks();
|
||
});
|
||
|
||
describe('getAvailableTimeSlots', () => {
|
||
const mockVariables = {
|
||
filters: {
|
||
datetime_start: now.toISOString(),
|
||
},
|
||
};
|
||
|
||
const mockContext = {
|
||
service: {
|
||
documentId: { eq: 'service-123' },
|
||
},
|
||
};
|
||
|
||
const mockService = {
|
||
active: true,
|
||
documentId: 'service-123',
|
||
duration: '01:00:00', // 1 час
|
||
master: mockCustomer,
|
||
name: 'Test Service',
|
||
};
|
||
|
||
const mockGetServiceResult = {
|
||
service: mockService,
|
||
};
|
||
|
||
const mockSlotWithOrders = {
|
||
datetime_end: now.add(2, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'slot-123',
|
||
master: mockCustomer,
|
||
orders: [
|
||
{
|
||
datetime_end: now.add(1, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
state: GQL.Enum_Order_State.Scheduled,
|
||
},
|
||
],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockSlotWithoutOrders = {
|
||
datetime_end: now.add(5, 'hour').toISOString(),
|
||
datetime_start: now.add(3, 'hour').toISOString(),
|
||
documentId: 'slot-124',
|
||
master: mockCustomer,
|
||
orders: [],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockGetSlotsOrdersResult = {
|
||
data: {
|
||
slots: [mockSlotWithOrders, mockSlotWithoutOrders],
|
||
},
|
||
};
|
||
|
||
it('should successfully get available time slots', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve(mockGetSlotsOrdersResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем ServicesService.getService
|
||
const mockGetService = vi.fn().mockResolvedValue(mockGetServiceResult);
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
expect(result).resolves.toEqual({
|
||
slots: [mockSlotWithOrders, mockSlotWithoutOrders],
|
||
times: [
|
||
{
|
||
slotId: 'slot-123',
|
||
time: now.add(1, 'hour').toISOString(), // 11:00
|
||
},
|
||
{
|
||
slotId: 'slot-124',
|
||
time: now.add(3, 'hour').toISOString(), // 13:00
|
||
},
|
||
{
|
||
slotId: 'slot-124',
|
||
time: now.add(3, 'hour').add(15, 'minute').toISOString(), // 13:15
|
||
},
|
||
{
|
||
slotId: 'slot-124',
|
||
time: now.add(3, 'hour').add(30, 'minute').toISOString(), // 13:30
|
||
},
|
||
{
|
||
slotId: 'slot-124',
|
||
time: now.add(3, 'hour').add(45, 'minute').toISOString(), // 13:45
|
||
},
|
||
{
|
||
slotId: 'slot-124',
|
||
time: now.add(4, 'hour').toISOString(), // 14:00
|
||
},
|
||
],
|
||
});
|
||
});
|
||
|
||
it('should throw error when datetime_start is missing', async () => {
|
||
const variablesWithoutStart = {
|
||
filters: {},
|
||
};
|
||
|
||
const result = slotsService.getAvailableTimeSlots(variablesWithoutStart, mockContext);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_DATETIME_START);
|
||
});
|
||
|
||
it('should throw error when service documentId is missing', async () => {
|
||
const contextWithoutService = {
|
||
service: {},
|
||
};
|
||
|
||
const result = slotsService.getAvailableTimeSlots(mockVariables, contextWithoutService);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_SERVICE_ID);
|
||
});
|
||
|
||
it('should throw error when service is not found', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve(mockGetSlotsOrdersResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем ServicesService.getService возвращающий null
|
||
const mockGetService = vi.fn().mockResolvedValue({
|
||
data: { service: null },
|
||
});
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.SERVICE_NOT_FOUND);
|
||
});
|
||
|
||
it('should filter out times that conflict with orders', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve(mockGetSlotsOrdersResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем ServicesService.getService
|
||
const mockGetService = vi.fn().mockResolvedValue(mockGetServiceResult);
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = await slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
// Проверяем, что время конфликтующее с заказом не включено
|
||
const conflictingTime = now.toISOString();
|
||
const hasConflictingTime = result.times.some((time) => time.time === conflictingTime);
|
||
expect(hasConflictingTime).toBe(false);
|
||
});
|
||
|
||
it('should include times from cancelled orders', async () => {
|
||
vi.setSystemTime(now.toDate()); // синхронизируем "текущее" время
|
||
|
||
const slotWithCancelledOrder = {
|
||
...mockSlotWithOrders,
|
||
orders: [
|
||
{
|
||
datetime_end: now.add(1, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
state: GQL.Enum_Order_State.Cancelled,
|
||
},
|
||
],
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [slotWithCancelledOrder] },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetService = vi.fn().mockResolvedValue(mockGetServiceResult);
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = await slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
// Слот: 10:00–12:00, сервис = 1 час, шаг = 15 мин
|
||
// Все возможные начала интервалов: от 10:00 до 11:00 включительно (всего 5)
|
||
const expectedTimes = [
|
||
// now.toISOString(), // 10:00
|
||
now.add(15, 'minute').toISOString(), // 10:15
|
||
now.add(30, 'minute').toISOString(), // 10:30
|
||
now.add(45, 'minute').toISOString(), // 10:45
|
||
now.add(1, 'hour').toISOString(), // 11:00
|
||
];
|
||
|
||
const actualTimes = result.times
|
||
.filter((time) => time.slotId === slotWithCancelledOrder.documentId)
|
||
.map((time) => time.time);
|
||
|
||
expect(actualTimes).toEqual(expectedTimes);
|
||
});
|
||
|
||
it('should filter out times in the past', async () => {
|
||
const pastSlot = {
|
||
datetime_end: now.subtract(1, 'hour').toISOString(),
|
||
datetime_start: now.subtract(2, 'hour').toISOString(),
|
||
documentId: 'slot-past',
|
||
master: mockCustomer,
|
||
orders: [],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [pastSlot] },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем ServicesService.getService
|
||
const mockGetService = vi.fn().mockResolvedValue(mockGetServiceResult);
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = await slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
// Проверяем, что время в прошлом не включено
|
||
expect(result.times).toHaveLength(0);
|
||
});
|
||
|
||
it('should generate times with 15-minute intervals', async () => {
|
||
const longSlot = {
|
||
datetime_end: now.add(3, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'slot-long',
|
||
master: mockCustomer,
|
||
orders: [],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [longSlot] },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем ServicesService.getService
|
||
const mockGetService = vi.fn().mockResolvedValue(mockGetServiceResult);
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = await slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
// Проверяем, что времена генерируются с интервалом 15 минут
|
||
expect(result.times.length).toBeGreaterThan(1);
|
||
|
||
for (let index = 1; index < result.times.length; index++) {
|
||
const currentTime = dayjs(result.times[index].time);
|
||
const previousTime = dayjs(result.times[index - 1].time);
|
||
const diff = currentTime.diff(previousTime, 'minute');
|
||
expect(diff).toBe(15);
|
||
}
|
||
});
|
||
|
||
it('should skip slots without datetime_start or datetime_end', async () => {
|
||
const incompleteSlot = {
|
||
datetime_end: now.add(1, 'hour').toISOString(),
|
||
datetime_start: null, // отсутствует datetime_start
|
||
documentId: 'slot-incomplete',
|
||
master: mockCustomer,
|
||
orders: [],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [incompleteSlot] },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем ServicesService.getService
|
||
const mockGetService = vi.fn().mockResolvedValue(mockGetServiceResult);
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = await slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
// Проверяем, что слот без datetime_start пропущен
|
||
expect(result.times).toHaveLength(0);
|
||
});
|
||
|
||
it('should handle GraphQL errors', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve({
|
||
error: { message: 'GraphQL error' },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
await expect(result).rejects.toThrow('GraphQL error');
|
||
});
|
||
|
||
it('should calculate service duration correctly', async () => {
|
||
const serviceWithDuration = {
|
||
...mockService,
|
||
duration: '00:30:00', // 30 минут
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetSlotsOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [mockSlotWithoutOrders] },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем ServicesService.getService с другой длительностью
|
||
const mockGetService = vi.fn().mockResolvedValue({
|
||
service: serviceWithDuration,
|
||
});
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = await slotsService.getAvailableTimeSlots(mockVariables, mockContext);
|
||
|
||
// Проверяем, что времена генерируются с учетом длительности услуги
|
||
expect(result.times.length).toBeGreaterThan(0);
|
||
|
||
// Последнее время должно быть не позже чем datetime_end минус длительность услуги
|
||
const lastTime = dayjs(result.times[result.times.length - 1].time);
|
||
const slotEnd = dayjs(mockSlotWithoutOrders.datetime_end);
|
||
const serviceDuration = dayjs.duration('00:30:00');
|
||
const maxTime = slotEnd.subtract(serviceDuration);
|
||
|
||
expect(lastTime.valueOf()).toBeLessThanOrEqual(maxTime.valueOf());
|
||
});
|
||
});
|
||
|
||
describe('createSlot', () => {
|
||
const mockVariables = {
|
||
input: {
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
state: GQL.Enum_Slot_State.Open,
|
||
},
|
||
};
|
||
|
||
const mockMutationResult = {
|
||
data: {
|
||
createSlot: mockSlot,
|
||
},
|
||
errors: undefined,
|
||
};
|
||
|
||
const mockMasterCustomer = {
|
||
...mockCustomer,
|
||
active: true,
|
||
role: 'master',
|
||
};
|
||
|
||
const mockGetMasterCustomerResult = {
|
||
data: {
|
||
customers: [mockMasterCustomer],
|
||
},
|
||
};
|
||
|
||
it('should successfully create slot when master is active', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotsDocument) {
|
||
return Promise.resolve({ data: { slots: [] } }); // нет пересекающихся слотов
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot({
|
||
input: {
|
||
...mockVariables.input,
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
},
|
||
});
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should throw error when master is not found', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve({
|
||
data: { customers: [] }, // мастер не найден
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(BASE_ERRORS.CUSTOMER_NOT_FOUND);
|
||
});
|
||
|
||
it('should throw error when master is not active', async () => {
|
||
const inactiveMaster = {
|
||
...mockMasterCustomer,
|
||
active: false,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve({
|
||
data: { customers: [inactiveMaster] },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INACTIVE_MASTER);
|
||
});
|
||
|
||
it('should throw error when master role is not master', async () => {
|
||
const nonMasterCustomer = {
|
||
...mockMasterCustomer,
|
||
role: 'customer',
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve({
|
||
data: { customers: [nonMasterCustomer] },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INACTIVE_MASTER);
|
||
});
|
||
|
||
it('should throw error when datetime_start is missing', async () => {
|
||
const variablesWithoutStart = {
|
||
input: {
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
state: GQL.Enum_Slot_State.Open,
|
||
},
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(variablesWithoutStart);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_DATETIME_START);
|
||
});
|
||
|
||
it('should throw error when datetime_end is missing', async () => {
|
||
const variablesWithoutEnd = {
|
||
input: {
|
||
datetime_start: now.toISOString(),
|
||
state: GQL.Enum_Slot_State.Open,
|
||
},
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(variablesWithoutEnd);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_DATETIME_END);
|
||
});
|
||
|
||
it('should throw error when datetime_end is before datetime_start', async () => {
|
||
const variablesWithInvalidTime = {
|
||
input: {
|
||
datetime_end: now.toISOString(),
|
||
datetime_start: now.add(6, 'hour').toISOString(),
|
||
state: GQL.Enum_Slot_State.Open,
|
||
},
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(variablesWithInvalidTime);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||
});
|
||
|
||
it('should throw error when datetime_end equals datetime_start', async () => {
|
||
const variablesWithEqualTime = {
|
||
input: {
|
||
datetime_end: now.toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
state: GQL.Enum_Slot_State.Open,
|
||
},
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(variablesWithEqualTime);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||
});
|
||
|
||
it('should throw error when slot is created in the past', async () => {
|
||
const yesterday = dayjs().subtract(1, 'day');
|
||
|
||
const variablesWithPastTime = {
|
||
input: {
|
||
datetime_end: yesterday.add(1, 'hour').toISOString(),
|
||
datetime_start: yesterday.toISOString(),
|
||
state: GQL.Enum_Slot_State.Open,
|
||
},
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(variablesWithPastTime);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.PAST_SLOT);
|
||
});
|
||
|
||
it('should throw error when time overlaps with other slots', async () => {
|
||
const overlappingSlot = {
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'slot-456',
|
||
master: mockMasterCustomer,
|
||
orders: [],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotsDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [overlappingSlot] },
|
||
}); // есть пересекающиеся слоты
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.OVERLAPPING_TIME);
|
||
});
|
||
|
||
it('should allow creation when time does not overlap with other slots', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotsDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [] },
|
||
}); // нет пересекающихся слотов
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.createSlot(mockVariables);
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should include master documentId in mutation variables', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetMasterCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotsDocument) {
|
||
return Promise.resolve({ data: { slots: [] } });
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
await slotsService.createSlot(mockVariables);
|
||
|
||
expect(mockMutate).toHaveBeenCalledWith({
|
||
mutation: GQL.CreateSlotDocument,
|
||
variables: {
|
||
...mockVariables,
|
||
input: {
|
||
...mockVariables.input,
|
||
master: mockMasterCustomer.documentId,
|
||
},
|
||
},
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('updateSlot', () => {
|
||
const mockVariables = {
|
||
data: {
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
state: GQL.Enum_Slot_State.Open,
|
||
},
|
||
documentId: 'slot-123',
|
||
};
|
||
|
||
const mockMutationResult = {
|
||
data: {
|
||
updateSlot: mockSlot,
|
||
},
|
||
errors: undefined,
|
||
};
|
||
|
||
it('should successfully update slot when user has permission', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve(mockGetSlotResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotsDocument) {
|
||
return Promise.resolve({ data: { slots: [] } });
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should successfully update slot when time is not changing', async () => {
|
||
const variablesWithoutTime = {
|
||
data: {
|
||
state: GQL.Enum_Slot_State.Closed,
|
||
},
|
||
documentId: 'slot-123',
|
||
};
|
||
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve(mockGetSlotResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(variablesWithoutTime);
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should throw error when user does not have permission', async () => {
|
||
const unrelatedCustomer = {
|
||
...mockCustomer,
|
||
documentId: 'different-customer-123',
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve({
|
||
data: { customers: [unrelatedCustomer] },
|
||
});
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: mockSlot }, // slot принадлежит другому пользователю
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||
});
|
||
|
||
it('should throw error when slot does not exist', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: null }, // slot не найден
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.SLOT_NOT_FOUND);
|
||
});
|
||
|
||
it('should throw error when customer is not found', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve({
|
||
data: { customers: [] }, // пользователь не найден
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(BASE_ERRORS.CUSTOMER_NOT_FOUND);
|
||
});
|
||
|
||
it('should throw error when datetime_start is missing', async () => {
|
||
const variablesWithMissingStart = {
|
||
data: {
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
},
|
||
documentId: 'slot-123',
|
||
};
|
||
|
||
const slotWithoutStart = {
|
||
...mockSlot,
|
||
datetime_start: null,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: slotWithoutStart },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(variablesWithMissingStart);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_DATETIME_START);
|
||
});
|
||
|
||
it('should throw error when datetime_end is missing', async () => {
|
||
const variablesWithMissingEnd = {
|
||
data: {
|
||
datetime_start: now.toISOString(),
|
||
},
|
||
documentId: 'slot-123',
|
||
};
|
||
|
||
const slotWithoutEnd = {
|
||
...mockSlot,
|
||
datetime_end: null,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: slotWithoutEnd },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(variablesWithMissingEnd);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_DATETIME_END);
|
||
});
|
||
|
||
it('should throw error when datetime_end is before datetime_start', async () => {
|
||
const variablesWithInvalidTime = {
|
||
data: {
|
||
datetime_end: now.toISOString(),
|
||
datetime_start: now.add(6, 'hour').toISOString(),
|
||
},
|
||
documentId: 'slot-123',
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve(mockGetSlotResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(variablesWithInvalidTime);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||
});
|
||
|
||
it('should throw error when datetime_end equals datetime_start', async () => {
|
||
const variablesWithEqualTime = {
|
||
data: {
|
||
datetime_end: now.toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
},
|
||
documentId: 'slot-123',
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve(mockGetSlotResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(variablesWithEqualTime);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||
});
|
||
|
||
it('should throw error when slot has scheduled orders', async () => {
|
||
const slotWithScheduledOrders = {
|
||
...mockSlot,
|
||
orders: [
|
||
{
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
state: GQL.Enum_Order_State.Scheduled,
|
||
},
|
||
],
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: slotWithScheduledOrders },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.HAS_ORDERS);
|
||
});
|
||
|
||
it('should throw error when slot has approved orders', async () => {
|
||
const slotWithApprovedOrders = {
|
||
...mockSlot,
|
||
orders: [
|
||
{
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
state: GQL.Enum_Order_State.Approved,
|
||
},
|
||
],
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: slotWithApprovedOrders },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.HAS_ORDERS);
|
||
});
|
||
|
||
it('should throw error when slot has completed orders', async () => {
|
||
const slotWithCompletedOrders = {
|
||
...mockSlot,
|
||
orders: [
|
||
{
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
state: GQL.Enum_Order_State.Completed,
|
||
},
|
||
],
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: slotWithCompletedOrders },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.HAS_ORDERS);
|
||
});
|
||
|
||
it('should throw error when slot has cancelled orders', async () => {
|
||
const slotWithCancelledOrders = {
|
||
...mockSlot,
|
||
orders: [
|
||
{
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
state: GQL.Enum_Order_State.Cancelled,
|
||
},
|
||
],
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: slotWithCancelledOrders },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.HAS_ORDERS);
|
||
});
|
||
|
||
it('should allow update when slot has non-forbidden order states', async () => {
|
||
const slotWithNonForbiddenOrders = {
|
||
...mockSlot,
|
||
orders: [
|
||
{
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
state: GQL.Enum_Order_State.Draft, // не запрещенное состояние
|
||
},
|
||
],
|
||
};
|
||
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: slotWithNonForbiddenOrders },
|
||
});
|
||
}
|
||
|
||
if (query === GQL.GetSlotsDocument) {
|
||
return Promise.resolve({ data: { slots: [] } }); // нет пересекающихся слотов
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should throw error when time overlaps with other slots', async () => {
|
||
const overlappingSlot = {
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'slot-456',
|
||
master: mockCustomer,
|
||
orders: [],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve(mockGetSlotResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotsDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [overlappingSlot] },
|
||
}); // есть пересекающиеся слоты
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.OVERLAPPING_TIME);
|
||
});
|
||
|
||
it('should allow update when time does not overlap with other slots', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve(mockGetSlotResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotsDocument) {
|
||
return Promise.resolve({
|
||
data: { slots: [] },
|
||
}); // нет пересекающихся слотов
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.updateSlot(mockVariables);
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
});
|
||
|
||
describe('deleteSlot', () => {
|
||
const mockVariables = {
|
||
documentId: 'slot-123',
|
||
};
|
||
|
||
const mockMutationResult = {
|
||
data: {
|
||
deleteSlot: {
|
||
documentId: 'slot-123',
|
||
},
|
||
},
|
||
errors: undefined,
|
||
};
|
||
|
||
it('should successfully delete slot when no orders', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve(mockGetSlotResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.deleteSlot(mockVariables);
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should throw error when slot has orders', async () => {
|
||
const slotWithOrders = {
|
||
...mockSlot,
|
||
orders: [
|
||
{
|
||
datetime_end: now.add(6, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
state: GQL.Enum_Order_State.Scheduled,
|
||
},
|
||
],
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: slotWithOrders }, // slot с заказами
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.deleteSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.HAS_ORDERS);
|
||
});
|
||
|
||
it('should throw error when user does not have permission', async () => {
|
||
const unrelatedCustomer = {
|
||
...mockCustomer,
|
||
documentId: 'different-customer-123',
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve({
|
||
data: { customers: [unrelatedCustomer] },
|
||
});
|
||
}
|
||
|
||
if (query === GQL.GetSlotDocument) {
|
||
return Promise.resolve({
|
||
data: { slot: mockSlot }, // slot принадлежит другому пользователю
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = slotsService.deleteSlot(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||
});
|
||
});
|
||
});
|