812 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 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('../config/env', () => {
return {
env: {
BOT_TOKEN: 'test',
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);
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();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('createOrder', () => {
const mockVariables = {
input: {
client: 'customer-123',
datetime_end: now.add(1, 'hour').toISOString(),
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,
});
// Мокаем ServicesService.getService
const mockGetService = vi.fn().mockResolvedValue({
service: mockService,
});
mockServicesService.mockImplementation(() => ({
getService: mockGetService,
}));
// Мокаем SlotsService.getSlot
const mockGetSlot = vi.fn().mockResolvedValue({
slot: mockSlot,
});
mockSlotsService.mockImplementation(() => ({
getSlot: mockGetSlot,
}));
// Мокаем CustomersService.getCustomer
const mockGetCustomer = vi.fn().mockResolvedValue({
customer: mockCustomer,
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
}),
}));
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,
};
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,
});
const mockGetService = vi.fn().mockResolvedValue({
service: mockService,
});
mockServicesService.mockImplementation(() => ({
getService: mockGetService,
}));
const mockGetSlot = vi.fn().mockResolvedValue({
slot: masterSlot,
});
mockSlotsService.mockImplementation(() => ({
getSlot: mockGetSlot,
}));
const mockGetCustomer = vi.fn().mockResolvedValue({
customer: masterCustomer,
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [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 datetime_end is missing', async () => {
const variablesWithoutEnd = {
input: {
client: 'customer-123',
datetime_start: now.toISOString(),
services: ['service-123'],
slot: 'slot-123',
},
};
const result = ordersService.createOrder(variablesWithoutEnd);
await expect(result).rejects.toThrow(ERRORS.MISSING_END_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 order time is invalid', async () => {
const variablesWithInvalidTime = {
input: {
client: 'customer-123',
datetime_end: now.toISOString(), // равно datetime_start
datetime_start: now.toISOString(),
services: ['service-123'],
slot: 'slot-123',
},
};
const result = ordersService.createOrder(variablesWithInvalidTime);
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
});
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.MISSING_SLOT);
});
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,
getMasters: vi.fn().mockResolvedValue({
masters: [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,
getMasters: vi.fn().mockResolvedValue({
masters: [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,
getMasters: vi.fn().mockResolvedValue({
masters: [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,
getMasters: vi.fn().mockResolvedValue({
masters: [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,
getMasters: vi.fn().mockResolvedValue({
masters: [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,
getMasters: vi.fn().mockResolvedValue({
masters: [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,
getMasters: vi.fn().mockResolvedValue({
masters: [], // клиент не связан с мастером
}),
}));
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,
getMasters: vi.fn().mockResolvedValue({
masters: [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,
getMasters: vi.fn().mockResolvedValue({
masters: [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,
});
const mockGetService = vi.fn().mockResolvedValue({
service: mockService,
});
mockServicesService.mockImplementation(() => ({
getService: mockGetService,
}));
const mockGetSlot = vi.fn().mockResolvedValue({
slot: mockSlot,
});
mockSlotsService.mockImplementation(() => ({
getSlot: mockGetSlot,
}));
const mockGetCustomer = vi.fn().mockResolvedValue({
customer: mockCustomer,
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
}),
}));
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,
},
},
});
});
});
});