fix(slots): update error handling for customer and slot retrieval, enhance time validation in slot updates
This commit is contained in:
parent
5ec324d207
commit
6d86c0d2db
@ -3,8 +3,8 @@ import { getClientWithToken } from '../apollo/client';
|
||||
import * as GQL from '../types';
|
||||
|
||||
const ERRORS = {
|
||||
CUSTOMER_NOT_FOUND: 'Customer not found',
|
||||
MISSING_TELEGRAM_ID: 'Missing telegram id',
|
||||
NOT_FOUND_CUSTOMER: 'Customer not found',
|
||||
};
|
||||
|
||||
type UserProfile = {
|
||||
@ -32,7 +32,7 @@ export class BaseService {
|
||||
|
||||
const customer = result.data.customers.at(0);
|
||||
|
||||
if (!customer) throw new Error(ERRORS.NOT_FOUND_CUSTOMER);
|
||||
if (!customer) throw new Error(ERRORS.CUSTOMER_NOT_FOUND);
|
||||
|
||||
return { customer };
|
||||
}
|
||||
|
||||
@ -78,10 +78,21 @@ describe('SlotsService', () => {
|
||||
|
||||
it('should successfully update slot when user has permission', async () => {
|
||||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||||
const mockQuery = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(mockGetCustomerResult)
|
||||
.mockResolvedValueOnce(mockGetSlotResult);
|
||||
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,
|
||||
@ -93,20 +104,58 @@ describe('SlotsService', () => {
|
||||
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()
|
||||
.mockResolvedValueOnce({
|
||||
data: { customers: [unrelatedCustomer] },
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: { slot: mockSlot }, // slot принадлежит другому пользователю
|
||||
});
|
||||
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(),
|
||||
@ -119,12 +168,19 @@ describe('SlotsService', () => {
|
||||
});
|
||||
|
||||
it('should throw error when slot does not exist', async () => {
|
||||
const mockQuery = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(mockGetCustomerResult)
|
||||
.mockResolvedValueOnce({
|
||||
data: { slot: null }, // slot не найден
|
||||
});
|
||||
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(),
|
||||
@ -133,12 +189,18 @@ describe('SlotsService', () => {
|
||||
|
||||
const result = slotsService.updateSlot(mockVariables);
|
||||
|
||||
await expect(result).rejects.toThrow();
|
||||
await expect(result).rejects.toThrow(ERRORS.SLOT_NOT_FOUND);
|
||||
});
|
||||
|
||||
it('should throw error when customer is not found', async () => {
|
||||
const mockQuery = vi.fn().mockResolvedValue({
|
||||
data: { customers: [] }, // пользователь не найден
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve({
|
||||
data: { customers: [] }, // пользователь не найден
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
@ -150,67 +212,469 @@ describe('SlotsService', () => {
|
||||
|
||||
await expect(result).rejects.toThrow('Customer not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkPermission', () => {
|
||||
const mockVariables = {
|
||||
documentId: 'slot-123',
|
||||
};
|
||||
|
||||
it('should not throw error when user has permission', async () => {
|
||||
const mockQuery = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(mockGetCustomerResult)
|
||||
.mockResolvedValueOnce(mockGetSlotResult);
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.checkPermission(mockVariables);
|
||||
|
||||
await expect(result).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw error when user does not have permission', async () => {
|
||||
const unrelatedCustomer = {
|
||||
...mockCustomer,
|
||||
documentId: 'different-customer-123',
|
||||
it('should throw error when datetime_start is missing', async () => {
|
||||
const variablesWithMissingStart = {
|
||||
data: {
|
||||
datetime_end: '2024-01-01T11:00:00Z',
|
||||
},
|
||||
documentId: 'slot-123',
|
||||
};
|
||||
|
||||
const mockQuery = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: { customers: [unrelatedCustomer] },
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: { slot: mockSlot }, // slot принадлежит другому пользователю
|
||||
});
|
||||
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.checkPermission(mockVariables);
|
||||
const result = slotsService.updateSlot(variablesWithMissingStart);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.NO_PERMISSION);
|
||||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||||
});
|
||||
|
||||
it('should throw error when slot does not exist', async () => {
|
||||
const mockQuery = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(mockGetCustomerResult)
|
||||
.mockResolvedValueOnce({
|
||||
data: { slot: null }, // slot не найден
|
||||
});
|
||||
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.checkPermission(mockVariables);
|
||||
const result = slotsService.updateSlot(variablesWithMissingEnd);
|
||||
|
||||
await expect(result).rejects.toThrow();
|
||||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('should use existing slot times when only datetime_start is provided', async () => {
|
||||
const variablesWithOnlyStart = {
|
||||
data: {
|
||||
datetime_start: '2024-01-01T09:00:00Z',
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
if (query === GQL.GetSlotsDocument) {
|
||||
return Promise.resolve({ data: { slots: [] } });
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: mockMutate,
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.updateSlot(variablesWithOnlyStart);
|
||||
|
||||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||||
});
|
||||
|
||||
it('should use existing slot times when only datetime_end is provided', async () => {
|
||||
const variablesWithOnlyEnd = {
|
||||
data: {
|
||||
datetime_end: '2024-01-01T12:00:00Z',
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
if (query === GQL.GetSlotsDocument) {
|
||||
return Promise.resolve({ data: { slots: [] } });
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: mockMutate,
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.updateSlot(variablesWithOnlyEnd);
|
||||
|
||||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||||
});
|
||||
});
|
||||
|
||||
@ -230,10 +694,17 @@ describe('SlotsService', () => {
|
||||
|
||||
it('should successfully delete slot when no orders', async () => {
|
||||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||||
const mockQuery = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(mockGetCustomerResult)
|
||||
.mockResolvedValue(mockGetSlotResult);
|
||||
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,
|
||||
@ -258,12 +729,19 @@ describe('SlotsService', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const mockQuery = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(mockGetCustomerResult)
|
||||
.mockResolvedValue({
|
||||
data: { slot: slotWithOrders }, // slot с заказами
|
||||
});
|
||||
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(),
|
||||
@ -281,14 +759,21 @@ describe('SlotsService', () => {
|
||||
documentId: 'different-customer-123',
|
||||
};
|
||||
|
||||
const mockQuery = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: { customers: [unrelatedCustomer] },
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: { slot: mockSlot }, // slot принадлежит другому пользователю
|
||||
});
|
||||
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(),
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable canonical/id-match */
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { getClientWithToken } from '../apollo/client';
|
||||
import * as GQL from '../types';
|
||||
import { BaseService } from './base';
|
||||
@ -7,13 +9,23 @@ import { getMinutes } from '@repo/utils/datetime-format';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const ERRORS = {
|
||||
HAS_ORDERS: 'Slot has orders',
|
||||
MISSING_DATE: 'Missing date',
|
||||
MISSING_SERVICE: 'Missing service',
|
||||
MISSING_SERVICE_ID: 'Missing service id',
|
||||
NO_PERMISSION: 'No permission',
|
||||
HAS_ORDERS: 'Слот имеет активные заказы',
|
||||
INVALID_TIME: 'Некорректное время',
|
||||
MISSING_DATE: 'Не указана дата',
|
||||
MISSING_SERVICE_ID: 'Не указана услуга',
|
||||
NO_PERMISSION: 'Нет доступа',
|
||||
OVERLAPPING_TIME: 'Время пересекается с другими слотами',
|
||||
SERVICE_NOT_FOUND: 'Слот не найден',
|
||||
SLOT_NOT_FOUND: 'Слот не найден',
|
||||
};
|
||||
|
||||
const FORBIDDEN_ORDER_STATES: GQL.Enum_Order_State[] = [
|
||||
GQL.Enum_Order_State.Scheduled,
|
||||
GQL.Enum_Order_State.Approved,
|
||||
GQL.Enum_Order_State.Completed,
|
||||
GQL.Enum_Order_State.Cancelled,
|
||||
];
|
||||
|
||||
export class SlotsService extends BaseService {
|
||||
async createSlot(variables: VariablesOf<typeof GQL.CreateSlotDocument>) {
|
||||
const { customer } = await this._getUser();
|
||||
@ -86,7 +98,7 @@ export class SlotsService extends BaseService {
|
||||
documentId: context.service.documentId.eq,
|
||||
});
|
||||
|
||||
if (!service) throw new Error(ERRORS.MISSING_SERVICE);
|
||||
if (!service) throw new Error(ERRORS.SERVICE_NOT_FOUND);
|
||||
|
||||
const serviceDuration = getMinutes(service.duration);
|
||||
|
||||
@ -149,6 +161,7 @@ export class SlotsService extends BaseService {
|
||||
|
||||
async updateSlot(variables: VariablesOf<typeof GQL.UpdateSlotDocument>) {
|
||||
await this.checkPermission(variables);
|
||||
await this.checkIsTimeChanging(variables);
|
||||
|
||||
const { mutate } = await getClientWithToken();
|
||||
|
||||
@ -163,6 +176,55 @@ export class SlotsService extends BaseService {
|
||||
return mutationResult.data;
|
||||
}
|
||||
|
||||
private async checkIsTimeChanging(variables: VariablesOf<typeof GQL.UpdateSlotDocument>) {
|
||||
const { slot } = await this.getSlot({ documentId: variables.documentId });
|
||||
|
||||
if (!slot) throw new Error(ERRORS.SLOT_NOT_FOUND);
|
||||
|
||||
const isTimeChanging = variables?.data?.datetime_start || variables?.data?.datetime_end;
|
||||
|
||||
if (!isTimeChanging) return;
|
||||
|
||||
let datetime_start = variables.data.datetime_start;
|
||||
let datetime_end = variables.data.datetime_end;
|
||||
|
||||
if (!datetime_start) datetime_start = slot?.datetime_start;
|
||||
if (!datetime_end) datetime_end = slot?.datetime_end;
|
||||
|
||||
// Проверка: оба времени должны быть определены
|
||||
if (!datetime_start || !datetime_end) {
|
||||
throw new Error(ERRORS.INVALID_TIME);
|
||||
}
|
||||
|
||||
// Проверка валидности времени
|
||||
if (new Date(datetime_end) <= new Date(datetime_start)) {
|
||||
throw new Error(ERRORS.INVALID_TIME);
|
||||
}
|
||||
|
||||
const orders = slot?.orders;
|
||||
|
||||
if (
|
||||
orders?.length &&
|
||||
orders?.some((order) => order?.state && FORBIDDEN_ORDER_STATES.includes(order.state))
|
||||
) {
|
||||
throw new Error(ERRORS.HAS_ORDERS);
|
||||
}
|
||||
|
||||
const { documentId } = slot;
|
||||
|
||||
const overlappingEntities = await this.getSlots({
|
||||
filters: {
|
||||
datetime_end: { gt: datetime_start },
|
||||
datetime_start: { lt: datetime_end },
|
||||
documentId: { not: { eq: documentId } },
|
||||
},
|
||||
});
|
||||
|
||||
if (overlappingEntities?.slots?.length) {
|
||||
throw new Error(ERRORS.OVERLAPPING_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
private async checkPermission(
|
||||
variables: Pick<VariablesOf<typeof GQL.GetSlotDocument>, 'documentId'>,
|
||||
) {
|
||||
@ -170,6 +232,8 @@ export class SlotsService extends BaseService {
|
||||
|
||||
const { slot } = await this.getSlot({ documentId: variables.documentId });
|
||||
|
||||
if (!slot) throw new Error(ERRORS.SLOT_NOT_FOUND);
|
||||
|
||||
if (slot?.master?.documentId !== customer?.documentId) throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user