- Changed the terminology from "masters" to "invited" and "invitedBy" across the codebase for clarity and consistency. - Updated the `addContact` function to reflect the new naming convention. - Refactored API actions and server methods to support the new invited structure. - Adjusted components and hooks to utilize the updated invited data, enhancing user experience and simplifying logic.
1436 lines
39 KiB
JavaScript
1436 lines
39 KiB
JavaScript
import { getClientWithToken } from '../apollo/client';
|
||
import * as GQL from '../types';
|
||
import { CustomersService } from './customers';
|
||
import { ERRORS, OrdersService } from './orders';
|
||
import { ServicesService } from './services';
|
||
import { SlotsService } from './slots';
|
||
import { SubscriptionsService } from './subscriptions';
|
||
import dayjs from 'dayjs';
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||
|
||
vi.mock('../apollo/client');
|
||
vi.mock('./customers');
|
||
vi.mock('./services');
|
||
vi.mock('./slots');
|
||
vi.mock('./subscriptions');
|
||
vi.mock('../config/env', () => {
|
||
return {
|
||
env: {
|
||
LOGIN_GRAPHQL: 'test',
|
||
PASSWORD_GRAPHQL: 'test',
|
||
URL_GRAPHQL: 'test',
|
||
},
|
||
};
|
||
});
|
||
|
||
const mockGetClientWithToken = vi.mocked(getClientWithToken);
|
||
const mockCustomersService = vi.mocked(CustomersService);
|
||
const mockServicesService = vi.mocked(ServicesService);
|
||
const mockSlotsService = vi.mocked(SlotsService);
|
||
const mockSubscriptionsService = vi.mocked(SubscriptionsService);
|
||
|
||
describe('OrdersService', () => {
|
||
/**
|
||
* @type {OrdersService}
|
||
*/
|
||
let ordersService;
|
||
const mockUser = { telegramId: 123_456_789 };
|
||
|
||
const mockCustomer = {
|
||
active: true,
|
||
documentId: 'customer-123',
|
||
firstName: 'John',
|
||
lastName: 'Doe',
|
||
role: GQL.Enum_Customer_Role.Customer,
|
||
telegramId: 123_456_789,
|
||
};
|
||
|
||
const mockMaster = {
|
||
active: true,
|
||
documentId: 'master-123',
|
||
firstName: 'Jane',
|
||
lastName: 'Master',
|
||
role: GQL.Enum_Customer_Role.Master,
|
||
telegramId: 987_654_321,
|
||
};
|
||
|
||
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: mockMaster,
|
||
orders: [],
|
||
state: GQL.Enum_Slot_State.Open,
|
||
};
|
||
|
||
const mockService = {
|
||
active: true,
|
||
documentId: 'service-123',
|
||
duration: '01:00:00', // 1 час
|
||
master: mockMaster,
|
||
name: 'Test Service',
|
||
};
|
||
|
||
const mockOrder = {
|
||
client: mockCustomer,
|
||
datetime_end: now.add(1, 'hour').toISOString(),
|
||
datetime_start: now.toISOString(),
|
||
documentId: 'order-123',
|
||
services: [mockService],
|
||
slot: mockSlot,
|
||
state: GQL.Enum_Order_State.Created,
|
||
};
|
||
|
||
const mockGetCustomerResult = {
|
||
data: {
|
||
customers: [mockCustomer],
|
||
},
|
||
};
|
||
|
||
beforeEach(() => {
|
||
ordersService = new OrdersService(mockUser);
|
||
vi.clearAllMocks();
|
||
|
||
// Глобальный мок для _getUser
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
|
||
// Глобальный мок для checkIsBanned
|
||
vi.spyOn(ordersService, 'checkIsBanned').mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
|
||
// Глобальные моки для сервисов
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: vi.fn().mockResolvedValue({
|
||
service: mockService,
|
||
}),
|
||
}));
|
||
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: vi.fn().mockResolvedValue({
|
||
slot: mockSlot,
|
||
}),
|
||
}));
|
||
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: vi.fn().mockResolvedValue({
|
||
customer: mockCustomer,
|
||
}),
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [mockMaster],
|
||
}),
|
||
}));
|
||
|
||
mockSubscriptionsService.mockImplementation(() => ({
|
||
getSubscription: vi.fn().mockResolvedValue({
|
||
maxOrdersPerMonth: 10,
|
||
remainingOrdersCount: 5,
|
||
subscription: {
|
||
autoRenew: false,
|
||
documentId: 'subscription-123',
|
||
expiresAt: now.add(30, 'day').toISOString(),
|
||
isActive: true,
|
||
},
|
||
}),
|
||
getSubscriptionSettings: vi.fn().mockResolvedValue({
|
||
subscriptionSetting: {
|
||
documentId: 'subscription-setting-123',
|
||
maxOrdersPerMonth: 10,
|
||
referralBonusDays: 3,
|
||
referralRewardDays: 7,
|
||
},
|
||
}),
|
||
}));
|
||
});
|
||
|
||
afterEach(() => {
|
||
vi.restoreAllMocks();
|
||
});
|
||
|
||
describe('createOrder', () => {
|
||
const mockVariables = {
|
||
input: {
|
||
client: 'customer-123',
|
||
datetime_start: now.toISOString(),
|
||
services: ['service-123'],
|
||
slot: 'slot-123',
|
||
},
|
||
};
|
||
|
||
const mockMutationResult = {
|
||
data: {
|
||
createOrder: mockOrder,
|
||
},
|
||
errors: undefined,
|
||
};
|
||
|
||
it('should successfully create order for customer', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetOrdersDocument) {
|
||
return Promise.resolve({ data: { orders: [] } }); // нет пересекающихся заказов
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should successfully create approved order for master', async () => {
|
||
const masterCustomer = {
|
||
...mockCustomer,
|
||
role: GQL.Enum_Customer_Role.Master,
|
||
};
|
||
|
||
// Переопределяем мок для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: masterCustomer,
|
||
});
|
||
|
||
const masterSlot = {
|
||
...mockSlot,
|
||
master: masterCustomer,
|
||
};
|
||
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve({
|
||
data: { customers: [masterCustomer] },
|
||
});
|
||
}
|
||
|
||
if (query === GQL.GetOrdersDocument) {
|
||
return Promise.resolve({ data: { orders: [] } });
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Переопределяем моки для мастера
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: vi.fn().mockResolvedValue({
|
||
slot: masterSlot,
|
||
}),
|
||
}));
|
||
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: vi.fn().mockResolvedValue({
|
||
customer: masterCustomer,
|
||
}),
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [masterCustomer],
|
||
}),
|
||
}));
|
||
|
||
const result = ordersService.createOrder({
|
||
...mockVariables,
|
||
input: {
|
||
...mockVariables.input,
|
||
client: masterCustomer.documentId,
|
||
},
|
||
});
|
||
|
||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should throw error when slot is missing', async () => {
|
||
const variablesWithoutSlot = {
|
||
input: {
|
||
client: 'customer-123',
|
||
datetime_start: now.toISOString(),
|
||
services: ['service-123'],
|
||
},
|
||
};
|
||
|
||
const result = ordersService.createOrder(variablesWithoutSlot);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_SLOT);
|
||
});
|
||
|
||
it('should throw error when services are missing', async () => {
|
||
const variablesWithoutServices = {
|
||
input: {
|
||
client: 'customer-123',
|
||
datetime_start: now.toISOString(),
|
||
slot: 'slot-123',
|
||
},
|
||
};
|
||
|
||
const result = ordersService.createOrder(variablesWithoutServices);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_SERVICES);
|
||
});
|
||
|
||
it('should throw error when datetime_start is missing', async () => {
|
||
const variablesWithoutStart = {
|
||
input: {
|
||
client: 'customer-123',
|
||
services: ['service-123'],
|
||
slot: 'slot-123',
|
||
},
|
||
};
|
||
|
||
const result = ordersService.createOrder(variablesWithoutStart);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_START_TIME);
|
||
});
|
||
|
||
it('should throw error when client is missing', async () => {
|
||
const variablesWithoutClient = {
|
||
input: {
|
||
datetime_start: now.toISOString(),
|
||
services: ['service-123'],
|
||
slot: 'slot-123',
|
||
},
|
||
};
|
||
|
||
const result = ordersService.createOrder(variablesWithoutClient);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_CLIENT);
|
||
});
|
||
|
||
it('should throw error when order time is in the past', async () => {
|
||
const pastTime = now.subtract(1, 'hour');
|
||
const variablesWithPastTime = {
|
||
input: {
|
||
client: 'customer-123',
|
||
datetime_end: pastTime.add(1, 'hour').toISOString(),
|
||
datetime_start: pastTime.toISOString(),
|
||
services: ['service-123'],
|
||
slot: 'slot-123',
|
||
},
|
||
};
|
||
|
||
const result = ordersService.createOrder(variablesWithPastTime);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_ORDER_IN_PAST);
|
||
});
|
||
|
||
it('should throw error when slot is not found', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: null, // слот не найден
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INVALID_MASTER);
|
||
});
|
||
|
||
it('should throw error when order is out of slot time', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: mockSlot,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [mockMaster],
|
||
}),
|
||
}));
|
||
|
||
const variablesWithOutOfSlotTime = {
|
||
input: {
|
||
client: 'customer-123',
|
||
datetime_end: now.add(8, 'hour').toISOString(),
|
||
datetime_start: now.add(7, 'hour').toISOString(), // после окончания слота
|
||
services: ['service-123'],
|
||
slot: 'slot-123',
|
||
},
|
||
};
|
||
|
||
const result = ordersService.createOrder(variablesWithOutOfSlotTime);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_ORDER_OUT_OF_SLOT);
|
||
});
|
||
|
||
it('should throw error when slot is closed', async () => {
|
||
const closedSlot = {
|
||
...mockSlot,
|
||
state: GQL.Enum_Slot_State.Closed,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: closedSlot,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [mockMaster],
|
||
}),
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.SLOT_CLOSED);
|
||
});
|
||
|
||
it('should throw error when client is not found', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: mockSlot,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: null, // клиент не найден
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [mockMaster],
|
||
}),
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NOT_FOUND_CLIENT);
|
||
});
|
||
|
||
it('should throw error when client is inactive', async () => {
|
||
const inactiveCustomer = {
|
||
...mockCustomer,
|
||
active: false,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: mockSlot,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: inactiveCustomer,
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [mockMaster],
|
||
}),
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INACTIVE_CLIENT);
|
||
});
|
||
|
||
it('should throw error when master is inactive', async () => {
|
||
const inactiveMaster = {
|
||
...mockMaster,
|
||
active: false,
|
||
};
|
||
|
||
const slotWithInactiveMaster = {
|
||
...mockSlot,
|
||
master: inactiveMaster,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: slotWithInactiveMaster,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [inactiveMaster],
|
||
}),
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INACTIVE_MASTER);
|
||
});
|
||
|
||
it('should throw error when customer tries to book themselves as master', async () => {
|
||
const activeCustomerAsMaster = {
|
||
...mockCustomer,
|
||
active: true,
|
||
role: GQL.Enum_Customer_Role.Master,
|
||
};
|
||
|
||
const slotWithCustomerAsMaster = {
|
||
...mockSlot,
|
||
master: activeCustomerAsMaster,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: slotWithCustomerAsMaster,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [activeCustomerAsMaster],
|
||
}),
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_MASTER_SELF_BOOK);
|
||
});
|
||
|
||
it('should throw error when customer is not linked to master', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: mockSlot,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [], // клиент не связан с мастером
|
||
}),
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INVALID_MASTER);
|
||
});
|
||
|
||
it('should throw error when time overlaps with other orders', async () => {
|
||
const overlappingOrder = {
|
||
...mockOrder,
|
||
documentId: 'order-456',
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: { orders: [overlappingOrder] },
|
||
}); // есть пересекающиеся заказы
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: mockSlot,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [mockMaster],
|
||
}),
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.OVERLAPPING_TIME);
|
||
});
|
||
|
||
it('should throw error when service duration is invalid', async () => {
|
||
const serviceWithoutDuration = {
|
||
...mockService,
|
||
duration: null,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
const mockGetSlot = vi.fn().mockResolvedValue({
|
||
slot: mockSlot,
|
||
});
|
||
mockSlotsService.mockImplementation(() => ({
|
||
getSlot: mockGetSlot,
|
||
}));
|
||
|
||
const mockGetCustomer = vi.fn().mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
mockCustomersService.mockImplementation(() => ({
|
||
getCustomer: mockGetCustomer,
|
||
getInvitedBy: vi.fn().mockResolvedValue({
|
||
invitedBy: [mockMaster],
|
||
}),
|
||
}));
|
||
|
||
const mockGetService = vi.fn().mockResolvedValue({
|
||
service: serviceWithoutDuration,
|
||
});
|
||
mockServicesService.mockImplementation(() => ({
|
||
getService: mockGetService,
|
||
}));
|
||
|
||
const result = ordersService.createOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INVALID_SERVICE_DURATION);
|
||
});
|
||
|
||
it('should calculate datetime_end based on service duration', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetCustomerDocument) {
|
||
return Promise.resolve(mockGetCustomerResult);
|
||
}
|
||
|
||
if (query === GQL.GetOrdersDocument) {
|
||
return Promise.resolve({ data: { orders: [] } });
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
await ordersService.createOrder(mockVariables);
|
||
|
||
expect(mockMutate).toHaveBeenCalledWith({
|
||
mutation: GQL.CreateOrderDocument,
|
||
variables: {
|
||
...mockVariables,
|
||
input: {
|
||
...mockVariables.input,
|
||
datetime_end: now.add(1, 'hour').toISOString(), // 1 час от начала
|
||
state: GQL.Enum_Order_State.Created,
|
||
},
|
||
},
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('updateOrder', () => {
|
||
const mockVariables = {
|
||
data: {
|
||
datetime_end: now.add(2, 'hour').toISOString(),
|
||
datetime_start: now.add(1, 'hour').toISOString(),
|
||
state: GQL.Enum_Order_State.Approved,
|
||
},
|
||
documentId: 'order-123',
|
||
};
|
||
|
||
const mockMutationResult = {
|
||
data: {
|
||
updateOrder: {
|
||
...mockOrder,
|
||
...mockVariables.data,
|
||
},
|
||
},
|
||
errors: undefined,
|
||
};
|
||
|
||
it('should successfully update order by master', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetOrderDocument) {
|
||
return Promise.resolve({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
if (query === GQL.GetOrdersDocument) {
|
||
return Promise.resolve({ data: { orders: [] } });
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const result = await ordersService.updateOrder(mockVariables);
|
||
|
||
expect(result).toBe(mockMutationResult.data);
|
||
expect(mockMutate).toHaveBeenCalledWith({
|
||
mutation: GQL.UpdateOrderDocument,
|
||
variables: {
|
||
...mockVariables,
|
||
data: {
|
||
...mockVariables.data,
|
||
order_number: undefined,
|
||
},
|
||
},
|
||
});
|
||
});
|
||
|
||
it('should successfully update order by client (cancelling)', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetOrderDocument) {
|
||
return Promise.resolve({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
client: mockCustomer,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для клиента
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
|
||
const clientVariables = {
|
||
...mockVariables,
|
||
data: {
|
||
state: GQL.Enum_Order_State.Cancelling,
|
||
},
|
||
};
|
||
|
||
const result = await ordersService.updateOrder(clientVariables);
|
||
|
||
expect(result).toBe(mockMutationResult.data);
|
||
});
|
||
|
||
it('should throw error when order is not found', async () => {
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: { order: null },
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const result = ordersService.updateOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.MISSING_ORDER);
|
||
});
|
||
|
||
it('should throw error when user has no permission', async () => {
|
||
const unauthorizedUser = {
|
||
...mockCustomer,
|
||
documentId: 'unauthorized-user',
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
client: mockCustomer,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для неавторизованного пользователя
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: unauthorizedUser,
|
||
});
|
||
|
||
const result = ordersService.updateOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||
});
|
||
|
||
it('should throw error when client tries to change more than one field', async () => {
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
client: mockCustomer,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для клиента
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
|
||
const clientVariables = {
|
||
...mockVariables,
|
||
data: {
|
||
datetime_start: now.add(1, 'hour').toISOString(),
|
||
state: GQL.Enum_Order_State.Cancelling,
|
||
},
|
||
};
|
||
|
||
const result = ordersService.updateOrder(clientVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||
});
|
||
|
||
it('should throw error when client tries to change state to non-cancelling', async () => {
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
client: mockCustomer,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для клиента
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
|
||
const clientVariables = {
|
||
...mockVariables,
|
||
data: {
|
||
state: GQL.Enum_Order_State.Approved,
|
||
},
|
||
};
|
||
|
||
const result = ordersService.updateOrder(clientVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||
});
|
||
|
||
it('should throw error when trying to change client', async () => {
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const variablesWithClient = {
|
||
...mockVariables,
|
||
data: {
|
||
...mockVariables.data,
|
||
client: 'new-client-id',
|
||
},
|
||
};
|
||
|
||
const result = ordersService.updateOrder(variablesWithClient);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||
});
|
||
|
||
it('should throw error when trying to change services', async () => {
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const variablesWithServices = {
|
||
...mockVariables,
|
||
data: {
|
||
...mockVariables.data,
|
||
services: ['new-service-id'],
|
||
},
|
||
};
|
||
|
||
const result = ordersService.updateOrder(variablesWithServices);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||
});
|
||
|
||
it('should throw error when trying to change slot', async () => {
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const variablesWithSlot = {
|
||
...mockVariables,
|
||
data: {
|
||
...mockVariables.data,
|
||
slot: 'new-slot-id',
|
||
},
|
||
};
|
||
|
||
const result = ordersService.updateOrder(variablesWithSlot);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||
});
|
||
|
||
it('should throw error when order slot is not found', async () => {
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
client: mockCustomer,
|
||
slot: null,
|
||
},
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для клиента заказа
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockCustomer,
|
||
});
|
||
|
||
const variablesWithSingleField = {
|
||
...mockVariables,
|
||
data: {
|
||
state: GQL.Enum_Order_State.Cancelling,
|
||
},
|
||
};
|
||
|
||
const result = ordersService.updateOrder(variablesWithSingleField);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NOT_FOUND_ORDER_SLOT);
|
||
});
|
||
|
||
it('should throw error when trying to change completed order state', async () => {
|
||
const completedOrder = {
|
||
...mockOrder,
|
||
state: GQL.Enum_Order_State.Completed,
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: completedOrder,
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const result = ordersService.updateOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.NO_CHANGE_STATE_COMPLETED);
|
||
});
|
||
|
||
it('should throw error when time is invalid', async () => {
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetOrderDocument) {
|
||
return Promise.resolve({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const variablesWithInvalidTime = {
|
||
...mockVariables,
|
||
data: {
|
||
datetime_end: now.add(1, 'hour').toISOString(),
|
||
datetime_start: now.add(2, 'hour').toISOString(), // конец раньше начала
|
||
},
|
||
};
|
||
|
||
const result = ordersService.updateOrder(variablesWithInvalidTime);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||
});
|
||
|
||
it('should throw error when time overlaps with other orders', async () => {
|
||
const overlappingOrder = {
|
||
...mockOrder,
|
||
documentId: 'order-456',
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetOrderDocument) {
|
||
return Promise.resolve({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
if (query === GQL.GetOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: { orders: [overlappingOrder] },
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const result = ordersService.updateOrder(mockVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.OVERLAPPING_TIME);
|
||
});
|
||
|
||
it('should successfully complete order and set order number', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetOrderDocument) {
|
||
return Promise.resolve({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
client: mockCustomer,
|
||
datetime_start: now.subtract(1, 'hour').toISOString(), // заказ в прошлом
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
if (query === GQL.GetOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: {
|
||
orders: [
|
||
{
|
||
...mockOrder,
|
||
order_number: 5,
|
||
},
|
||
],
|
||
},
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const completeVariables = {
|
||
...mockVariables,
|
||
data: {
|
||
state: GQL.Enum_Order_State.Completed,
|
||
},
|
||
};
|
||
|
||
const result = await ordersService.updateOrder(completeVariables);
|
||
|
||
expect(result).toBe(mockMutationResult.data);
|
||
expect(mockMutate).toHaveBeenCalledWith({
|
||
mutation: GQL.UpdateOrderDocument,
|
||
variables: {
|
||
...completeVariables,
|
||
data: {
|
||
...completeVariables.data,
|
||
order_number: 6, // 5 + 1
|
||
},
|
||
},
|
||
});
|
||
});
|
||
|
||
it('should successfully complete order and set order number to 1 when no previous completed orders', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||
if (query === GQL.GetOrderDocument) {
|
||
return Promise.resolve({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
client: mockCustomer,
|
||
datetime_start: now.subtract(1, 'hour').toISOString(), // заказ в прошлом
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
if (query === GQL.GetOrdersDocument) {
|
||
return Promise.resolve({
|
||
data: {
|
||
orders: [], // нет других завершенных заказов
|
||
},
|
||
});
|
||
}
|
||
|
||
return Promise.resolve({ data: {} });
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const completeVariables = {
|
||
...mockVariables,
|
||
data: {
|
||
state: GQL.Enum_Order_State.Completed,
|
||
},
|
||
};
|
||
|
||
const result = await ordersService.updateOrder(completeVariables);
|
||
|
||
expect(result).toBe(mockMutationResult.data);
|
||
expect(mockMutate).toHaveBeenCalledWith({
|
||
mutation: GQL.UpdateOrderDocument,
|
||
variables: {
|
||
...completeVariables,
|
||
data: {
|
||
...completeVariables.data,
|
||
order_number: undefined,
|
||
},
|
||
},
|
||
});
|
||
});
|
||
|
||
it('should throw error when trying to complete order before start time', async () => {
|
||
const futureOrder = {
|
||
...mockOrder,
|
||
datetime_start: now.add(1, 'hour').toISOString(),
|
||
};
|
||
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: futureOrder,
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: vi.fn(),
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const completeVariables = {
|
||
...mockVariables,
|
||
data: {
|
||
state: GQL.Enum_Order_State.Completed,
|
||
},
|
||
};
|
||
|
||
const result = ordersService.updateOrder(completeVariables);
|
||
|
||
await expect(result).rejects.toThrow(ERRORS.CANNOT_COMPLETE_BEFORE_START);
|
||
});
|
||
|
||
it('should return early when no datetime changes are provided', async () => {
|
||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||
const mockQuery = vi.fn().mockResolvedValue({
|
||
data: {
|
||
order: {
|
||
...mockOrder,
|
||
slot: { ...mockSlot, master: mockMaster },
|
||
},
|
||
},
|
||
});
|
||
|
||
mockGetClientWithToken.mockResolvedValue({
|
||
mutate: mockMutate,
|
||
query: mockQuery,
|
||
});
|
||
|
||
// Мокаем _getUser для мастера
|
||
vi.spyOn(ordersService, '_getUser').mockResolvedValue({
|
||
customer: mockMaster,
|
||
});
|
||
|
||
const variablesWithoutTime = {
|
||
...mockVariables,
|
||
data: {
|
||
state: GQL.Enum_Order_State.Approved,
|
||
},
|
||
};
|
||
|
||
const result = await ordersService.updateOrder(variablesWithoutTime);
|
||
|
||
expect(result).toBe(mockMutationResult.data);
|
||
});
|
||
});
|
||
});
|