719 lines
20 KiB
JavaScript
719 lines
20 KiB
JavaScript
import { getClientWithToken } from '../apollo/client';
|
||
import * as GQL from '../types';
|
||
import { ERRORS, SlotsService } from './slots';
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||
|
||
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);
|
||
|
||
describe('SlotsService', () => {
|
||
let slotsService;
|
||
const mockUser = { telegramId: 123_456_789 };
|
||
|
||
const mockCustomer = {
|
||
documentId: 'customer-123',
|
||
firstName: 'John',
|
||
lastName: 'Doe',
|
||
telegramId: 123_456_789,
|
||
};
|
||
|
||
const mockSlot = {
|
||
datetime_end: '2024-01-01T11:00:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
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('updateSlot', () => {
|
||
const mockVariables = {
|
||
data: {
|
||
datetime_end: '2024-01-01T11:00:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
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('Customer not found');
|
||
});
|
||
|
||
it('should throw error when datetime_start is missing', async () => {
|
||
const variablesWithMissingStart = {
|
||
data: {
|
||
datetime_end: '2024-01-01T11:00:00Z',
|
||
},
|
||
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: '2024-01-01T10:00:00Z',
|
||
},
|
||
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: '2024-01-01T10:00:00Z',
|
||
datetime_start: '2024-01-01T11:00:00Z',
|
||
},
|
||
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: '2024-01-01T10:00:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
},
|
||
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: '2024-01-01T10:30:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
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: '2024-01-01T10:30:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
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: '2024-01-01T10:30:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
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: '2024-01-01T10:30:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
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: '2024-01-01T10:30:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
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: '2024-01-01T11:30:00Z',
|
||
datetime_start: '2024-01-01T10:30:00Z',
|
||
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: '2024-01-01T11:00:00Z',
|
||
datetime_start: '2024-01-01T10:00:00Z',
|
||
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);
|
||
});
|
||
});
|
||
});
|