1121 lines
50 KiB
Python
1121 lines
50 KiB
Python
import calendar as cal
|
|
import json
|
|
|
|
from datetime import datetime, timedelta
|
|
from freezegun import freeze_time
|
|
from unittest.mock import patch
|
|
|
|
|
|
from odoo.addons.appointment_google_reserve.tests.common import GoogleReserveCommon
|
|
from odoo.tests import common, tagged
|
|
|
|
|
|
@tagged('appointment_google_reserve')
|
|
class AppointmentGoogleReserveControllerTest(GoogleReserveCommon, common.HttpCase):
|
|
|
|
# --------------------------------------
|
|
# AVAILABILITIES - RESOURCES
|
|
# --------------------------------------
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
def test_google_reserve_availability_lookup(self):
|
|
"""" Test the batch availability endpoint and make sure it returns proper availabilities
|
|
based on the appointment type configuration. """
|
|
|
|
start_slot = datetime(2022, 2, 14, 15, 0, 0)
|
|
# Batch (page load)
|
|
response = self.opener.get(
|
|
f'{self.base_url()}/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/availabilities',
|
|
data=json.dumps(
|
|
[{
|
|
'duration_sec': '3600',
|
|
'resource_ids': {
|
|
'party_size': party_size,
|
|
},
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=i)),
|
|
} for i in range(4) for party_size in range(1, 6)]
|
|
),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
}
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(bool(response.json()))
|
|
|
|
expected_availabilities = [
|
|
{'party_size': 1, 'start_time': 15, 'available': True},
|
|
{'party_size': 2, 'start_time': 15, 'available': True},
|
|
{'party_size': 3, 'start_time': 15, 'available': True},
|
|
{'party_size': 4, 'start_time': 15, 'available': True},
|
|
{'party_size': 5, 'start_time': 15, 'available': False}, # party size too big
|
|
{'party_size': 1, 'start_time': 16, 'available': True},
|
|
{'party_size': 2, 'start_time': 16, 'available': True},
|
|
{'party_size': 3, 'start_time': 16, 'available': True},
|
|
{'party_size': 4, 'start_time': 16, 'available': True},
|
|
{'party_size': 5, 'start_time': 16, 'available': False}, # party size too big
|
|
{'party_size': 1, 'start_time': 17, 'available': True},
|
|
{'party_size': 2, 'start_time': 17, 'available': True},
|
|
{'party_size': 3, 'start_time': 17, 'available': True},
|
|
{'party_size': 4, 'start_time': 17, 'available': True},
|
|
{'party_size': 5, 'start_time': 17, 'available': False}, # party size too big
|
|
{'party_size': 1, 'start_time': 18, 'available': False}, # too late
|
|
{'party_size': 2, 'start_time': 18, 'available': False}, # too late
|
|
{'party_size': 3, 'start_time': 18, 'available': False}, # too late
|
|
{'party_size': 4, 'start_time': 18, 'available': False}, # too late
|
|
{'party_size': 5, 'start_time': 18, 'available': False}, # too late & party size too big
|
|
]
|
|
|
|
for availability, expected_availability in zip(response.json(), expected_availabilities):
|
|
slot_time = availability.get('slot_time')
|
|
self.assertEqual(slot_time.get('resource_ids').get('party_size'), expected_availability['party_size'])
|
|
expected_start_sec = self._to_utc(start_slot.replace(hour=expected_availability['start_time']))
|
|
self.assertEqual(slot_time.get('start_sec'), expected_start_sec)
|
|
self.assertEqual(availability.get('available'), expected_availability['available'])
|
|
|
|
# Single (slot click)
|
|
response = self.opener.get(
|
|
f'{self.base_url()}/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/availabilities',
|
|
data=json.dumps(
|
|
[{
|
|
'duration_sec': '3600',
|
|
'resource_ids': {
|
|
'party_size': 4,
|
|
},
|
|
'start_sec': self._to_utc(start_slot),
|
|
}]
|
|
),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(bool(response.json()))
|
|
availability = response.json()[0]
|
|
slot_time = availability.get('slot_time')
|
|
self.assertEqual(slot_time.get('resource_ids').get('party_size'), 4)
|
|
self.assertEqual(slot_time.get('start_sec'), self._to_utc(start_slot))
|
|
self.assertEqual(availability.get('available'), True)
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.GoogleReserveIAP.update_availabilities', autospec=True)
|
|
def test_google_reserve_availability_lookup_resource_on_leave(self, update_availabilities):
|
|
start_slot = datetime(2022, 2, 14, 15, 0, 0)
|
|
self.env['resource.calendar.leaves'].create({
|
|
'name': 'Table 1 on leave',
|
|
'resource_id': self.apt_type_resources_table_1.resource_id.id,
|
|
'date_from': datetime(2022, 2, 14, 14, 0, 0),
|
|
'date_to': datetime(2022, 2, 14, 17, 0, 0),
|
|
})
|
|
|
|
response = self.opener.get(
|
|
f'{self.base_url()}/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/availabilities',
|
|
data=json.dumps([{
|
|
'duration_sec': '3600',
|
|
'resource_ids': {
|
|
'party_size': 4,
|
|
},
|
|
'start_sec': self._to_utc(start_slot),
|
|
}]),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(bool(response.json()))
|
|
availability = response.json()[0]
|
|
slot_time = availability.get('slot_time')
|
|
self.assertEqual(slot_time.get('resource_ids').get('party_size'), 4)
|
|
self.assertEqual(slot_time.get('start_sec'), self._to_utc(start_slot))
|
|
self.assertFalse(availability.get('available'), False)
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.GoogleReserveIAP.update_availabilities', autospec=True)
|
|
def test_google_reserve_availability_lookup_combinable(self, update_availabilities):
|
|
self.apt_type_resources_table_1.write({
|
|
'linked_resource_ids': [(4, self.apt_type_resources_table_2.id)],
|
|
})
|
|
|
|
self.apt_type_resources_table_2.write({
|
|
'linked_resource_ids': [(4, self.apt_type_resources_table_1.id)],
|
|
})
|
|
|
|
# create some dummy bookings to test more cases
|
|
self.env['calendar.event'].create([{
|
|
'appointment_type_id': self.apt_type_resource_google.id,
|
|
'name': "Booking 1",
|
|
'start': datetime(2022, 2, 14, 15, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 16, 0, 0),
|
|
'booking_line_ids': [(0, 0, {
|
|
'appointment_resource_id': self.apt_type_resources_table_1.id,
|
|
'capacity_reserved': 3,
|
|
'capacity_used': 4
|
|
})],
|
|
}, {
|
|
'appointment_type_id': self.apt_type_resource_google.id,
|
|
'name': "Booking 2",
|
|
'start': datetime(2022, 2, 14, 16, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 17, 0, 0),
|
|
'booking_line_ids': [(0, 0, {
|
|
'appointment_resource_id': self.apt_type_resources_table_1.id,
|
|
'capacity_reserved': 4,
|
|
'capacity_used': 4
|
|
}), (0, 0, {
|
|
'appointment_resource_id': self.apt_type_resources_table_2.id,
|
|
'capacity_reserved': 1,
|
|
'capacity_used': 2
|
|
})],
|
|
}])
|
|
|
|
start_slot = datetime(2022, 2, 14, 15, 0, 0)
|
|
# Batch (page load)
|
|
response = self.opener.get(
|
|
f'{self.base_url()}/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/availabilities',
|
|
data=json.dumps(
|
|
[{
|
|
'duration_sec': '3600',
|
|
'resource_ids': {
|
|
'party_size': party_size,
|
|
},
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=i)),
|
|
} for i in range(3) for party_size in range(1, 9)]
|
|
),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
}
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(bool(response.json()))
|
|
|
|
expected_availabilities = [
|
|
{'party_size': 1, 'start_time': 15, 'available': True},
|
|
{'party_size': 2, 'start_time': 15, 'available': True},
|
|
{'party_size': 3, 'start_time': 15, 'available': False}, # table 1 not available
|
|
{'party_size': 4, 'start_time': 15, 'available': False}, # table 1 not available
|
|
{'party_size': 5, 'start_time': 15, 'available': False}, # table 1 not available
|
|
{'party_size': 6, 'start_time': 15, 'available': False}, # table 1 not available
|
|
{'party_size': 7, 'start_time': 15, 'available': False}, # party size too big
|
|
{'party_size': 8, 'start_time': 15, 'available': False}, # party size too big
|
|
{'party_size': 1, 'start_time': 16, 'available': False}, # no tables available
|
|
{'party_size': 2, 'start_time': 16, 'available': False}, # no tables available
|
|
{'party_size': 3, 'start_time': 16, 'available': False}, # no tables available
|
|
{'party_size': 4, 'start_time': 16, 'available': False}, # no tables available
|
|
{'party_size': 5, 'start_time': 16, 'available': False}, # no tables available
|
|
{'party_size': 6, 'start_time': 16, 'available': False}, # no tables available
|
|
{'party_size': 7, 'start_time': 16, 'available': False}, # no tables available
|
|
{'party_size': 8, 'start_time': 16, 'available': False}, # no tables available
|
|
{'party_size': 1, 'start_time': 17, 'available': True},
|
|
{'party_size': 2, 'start_time': 17, 'available': True},
|
|
{'party_size': 3, 'start_time': 17, 'available': True},
|
|
{'party_size': 4, 'start_time': 17, 'available': True},
|
|
{'party_size': 5, 'start_time': 17, 'available': True},
|
|
{'party_size': 6, 'start_time': 17, 'available': True},
|
|
{'party_size': 7, 'start_time': 17, 'available': False}, # party size too big
|
|
{'party_size': 8, 'start_time': 17, 'available': False}, # party size too big
|
|
]
|
|
|
|
for availability, expected_availability in zip(response.json(), expected_availabilities):
|
|
slot_time = availability.get('slot_time')
|
|
self.assertEqual(slot_time.get('resource_ids').get('party_size'), expected_availability['party_size'])
|
|
expected_start_sec = self._to_utc(start_slot.replace(hour=expected_availability['start_time']))
|
|
self.assertEqual(slot_time.get('start_sec'), expected_start_sec)
|
|
self.assertEqual(availability.get('available'), expected_availability['available'])
|
|
|
|
# --------------------------------------
|
|
# BOOKINGS - RESOURCES
|
|
# --------------------------------------
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.GoogleReserveIAP.update_availabilities', autospec=True)
|
|
def test_google_reserve_create_cancel_booking(self, update_availabilities):
|
|
""" Test a complete create/cancel scenario:
|
|
- A first booking is made
|
|
- A second booking is made but conflicts with the first one (no more room)
|
|
- The first booking is canceled
|
|
- The second booking is retried and no-longer conflicts. """
|
|
|
|
start_slot = datetime(2022, 2, 14, 15, 0, 0)
|
|
|
|
# STEP 1: create booking
|
|
slot_creation_data = {
|
|
'confirmation_mode': 'CONFIRMATION_MODE_SYNCHRONOUS',
|
|
'duration_sec': '3600',
|
|
'merchant_id': str(self.apt_type_resource_google.id),
|
|
'resources': {
|
|
'party_size': '4',
|
|
},
|
|
'service_id': str(self.apt_type_resource_google.id),
|
|
'start_sec': self._to_utc(start_slot),
|
|
}
|
|
|
|
create_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/create',
|
|
data=json.dumps({
|
|
'idempotency_token': 'token1',
|
|
'slot': slot_creation_data,
|
|
'user_information': {
|
|
'email': 'john.doe@test.com',
|
|
'family_name': 'Doe',
|
|
'given_name': 'John',
|
|
'telephone': '+32476112233',
|
|
'user_id': '1234567890',
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(create_response.status_code, 200)
|
|
create_data = create_response.json()
|
|
self.env['calendar.event'].flush_model()
|
|
|
|
self.assertFalse(bool(create_data.get('booking_failure')))
|
|
self.assertTrue(bool(create_data.get('booking').get('booking_id')))
|
|
booking_id = create_data['booking']['booking_id']
|
|
calendar_event = self.env['calendar.event'].browse(int(booking_id))
|
|
self.assertTrue(bool(calendar_event.exists()))
|
|
self.assertEqual(calendar_event.appointment_type_id, self.apt_type_resource_google)
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 4)
|
|
self.assertEqual(calendar_event.start, start_slot)
|
|
self.assertEqual(calendar_event.appointment_booker_id.name, 'John Doe')
|
|
|
|
self.assertEqual(update_availabilities.call_count, 1)
|
|
args, _kwargs = update_availabilities.call_args_list[0]
|
|
self.assertEqual(args[1], calendar_event.appointment_type_id)
|
|
self.assertEqual(args[2], calendar_event.start)
|
|
self.assertEqual(args[3], calendar_event.stop)
|
|
|
|
# STEP 2: try to create another booking on the same slot, with no more room available
|
|
second_booking_data = {
|
|
'idempotency_token': 'token2',
|
|
'slot': slot_creation_data,
|
|
'user_information': {
|
|
'email': 'raoulette.poilvache@test.com',
|
|
'family_name': 'poilvache',
|
|
'given_name': 'raoulette',
|
|
'telephone': '+32476112244',
|
|
'user_id': '1234567891',
|
|
}
|
|
}
|
|
|
|
create_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/create',
|
|
data=json.dumps({
|
|
**second_booking_data
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(create_response.status_code, 200)
|
|
create_data = create_response.json()
|
|
|
|
self.assertEqual(create_data.get('booking_failure').get('cause'), 'SLOT_UNAVAILABLE')
|
|
self.assertFalse(bool(create_data.get('booking').get('booking_id')))
|
|
|
|
# STEP 3: CANCEL THE FIRST BOOKING
|
|
cancel_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'status': 'CANCELED',
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
self.assertEqual(cancel_response.status_code, 200)
|
|
cancel_data = cancel_response.json()
|
|
|
|
self.assertFalse(bool(cancel_data.get('booking_failure')))
|
|
|
|
self.assertEqual(update_availabilities.call_count, 2)
|
|
args, _kwargs = update_availabilities.call_args_list[0]
|
|
self.assertEqual(args[1], calendar_event.appointment_type_id)
|
|
self.assertEqual(args[2], calendar_event.start)
|
|
self.assertEqual(args[3], calendar_event.stop)
|
|
|
|
self.env['calendar.event'].flush_model()
|
|
self.assertFalse(calendar_event.active)
|
|
|
|
# step 3b: cancel the first booking again (custom booking_failure)
|
|
cancel_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'status': 'CANCELED',
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
self.assertEqual(cancel_response.status_code, 200)
|
|
cancel_data = cancel_response.json()
|
|
self.assertEqual(cancel_data.get('booking_failure').get('cause'), 'BOOKING_ALREADY_CANCELLED')
|
|
|
|
# did not call update availabilities as no update
|
|
self.assertEqual(update_availabilities.call_count, 2)
|
|
|
|
# step 4: retry second booking, should work now
|
|
create_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/create',
|
|
data=json.dumps({
|
|
'idempotency_token': 'token3',
|
|
**second_booking_data
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(create_response.status_code, 200)
|
|
create_data = create_response.json()
|
|
self.env['calendar.event'].flush_model()
|
|
|
|
self.assertFalse(bool(create_data.get('booking_failure')))
|
|
self.assertTrue(bool(create_data.get('booking').get('booking_id')))
|
|
booking_id = create_data['booking']['booking_id']
|
|
calendar_event_2 = self.env['calendar.event'].browse(int(booking_id))
|
|
self.assertNotEqual(calendar_event, calendar_event_2)
|
|
self.assertTrue(bool(calendar_event_2.exists()))
|
|
self.assertEqual(calendar_event_2.appointment_type_id, self.apt_type_resource_google)
|
|
|
|
self.assertEqual(update_availabilities.call_count, 3)
|
|
args, _kwargs = update_availabilities.call_args_list[1]
|
|
self.assertEqual(args[1], calendar_event_2.appointment_type_id)
|
|
self.assertEqual(args[2], calendar_event_2.start)
|
|
self.assertEqual(args[3], calendar_event_2.stop)
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.GoogleReserveIAP.update_availabilities', autospec=True)
|
|
def test_google_reserve_create_update_booking(self, update_availabilities):
|
|
""" Test a complete create/update scenario:
|
|
- A first booking is made (2 people, 3PM)
|
|
- It is then updated (4 people, 5PM)
|
|
- It is updated again for 5 people but there is not enough room (USER_OVER_BOOKING_LIMIT)
|
|
- It is updated again for another time outside appointment slots (SLOT_UNAVAILABLE)"""
|
|
|
|
start_slot = datetime(2022, 2, 14, 15, 0, 0)
|
|
|
|
# STEP 1: create booking (2 people, 3PM)
|
|
create_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/create',
|
|
data=json.dumps({
|
|
'idempotency_token': 'token1',
|
|
'slot': {
|
|
'confirmation_mode': 'CONFIRMATION_MODE_SYNCHRONOUS',
|
|
'duration_sec': '3600',
|
|
'merchant_id': str(self.apt_type_resource_google.id),
|
|
'resources': {
|
|
'party_size': '2',
|
|
},
|
|
'service_id': str(self.apt_type_resource_google.id),
|
|
'start_sec': self._to_utc(start_slot)
|
|
},
|
|
'user_information': {
|
|
'email': 'john.doe@test.com',
|
|
'family_name': 'Doe',
|
|
'given_name': 'John',
|
|
'telephone': '+32476112233',
|
|
'user_id': '1234567890',
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(create_response.status_code, 200)
|
|
create_data = create_response.json()
|
|
self.env['calendar.event'].flush_model()
|
|
|
|
self.assertFalse(bool(create_data.get('booking_failure')))
|
|
self.assertTrue(bool(create_data.get('booking').get('booking_id')))
|
|
booking_id = create_data['booking']['booking_id']
|
|
calendar_event = self.env['calendar.event'].browse(int(booking_id))
|
|
self.assertTrue(bool(calendar_event.exists()))
|
|
self.assertEqual(calendar_event.appointment_type_id, self.apt_type_resource_google)
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 2)
|
|
self.assertEqual(calendar_event.start, start_slot)
|
|
|
|
self.assertEqual(update_availabilities.call_count, 1)
|
|
args, _kwargs = update_availabilities.call_args_list[0]
|
|
self.assertEqual(args[1], calendar_event.appointment_type_id)
|
|
self.assertEqual(args[2], calendar_event.start)
|
|
self.assertEqual(args[3], calendar_event.stop)
|
|
|
|
# STEP 2: update booking -> 4 people, 5PM
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'duration_sec': '3600',
|
|
'resources': {
|
|
'party_size': 4,
|
|
},
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=2)),
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = json.loads(update_response.content)
|
|
self.assertFalse(bool(update_data.get('booking_failure')))
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 4)
|
|
self.assertEqual(calendar_event.start, start_slot + timedelta(hours=2))
|
|
|
|
self.assertEqual(update_availabilities.call_count, 2)
|
|
args, _kwargs = update_availabilities.call_args_list[1]
|
|
self.assertEqual(args[1], calendar_event.appointment_type_id)
|
|
self.assertEqual(
|
|
args[2],
|
|
start_slot,
|
|
"Should update availabilities on Google side based from old event start and not new one"
|
|
)
|
|
self.assertEqual(args[3], calendar_event.stop)
|
|
|
|
# STEP 3: update booking -> 5 people -> not enough room
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'resources': {
|
|
'party_size': 5,
|
|
},
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = json.loads(update_response.content)
|
|
self.assertEqual(update_data.get('booking_failure').get('cause'), 'USER_OVER_BOOKING_LIMIT')
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 4)
|
|
self.assertEqual(calendar_event.start, start_slot + timedelta(hours=2))
|
|
|
|
# did not call update availabilities as no update
|
|
self.assertEqual(update_availabilities.call_count, 2)
|
|
|
|
# STEP 4: update booking -> 6PM -> outside appointment type time slots
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'duration_sec': '3600',
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=3)),
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = json.loads(update_response.content)
|
|
self.assertEqual(update_data.get('booking_failure').get('cause'), 'SLOT_UNAVAILABLE')
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 4)
|
|
self.assertEqual(calendar_event.start, start_slot + timedelta(hours=2))
|
|
|
|
# did not call update availabilities as no update
|
|
self.assertEqual(update_availabilities.call_count, 2)
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.GoogleReserveIAP.update_availabilities', autospec=True)
|
|
def test_google_reserve_create_update_booking_combinable(self, update_availabilities):
|
|
""" Test a complete create/update scenario for combinable resources (more complex backend logic):
|
|
- A first booking is made (6 people)
|
|
- It is then updated (11 people)
|
|
- It is updated again for 15 people but there is not enough room (USER_OVER_BOOKING_LIMIT)
|
|
- And finally updated again to 4 people to check that we also handle removing booking lines. """
|
|
|
|
self.apt_type_resources_table_3 = self.env['appointment.resource'].create({
|
|
'appointment_type_ids': self.apt_type_resource_google.ids,
|
|
'capacity': 6,
|
|
'name': 'Table 3',
|
|
'sequence': 3,
|
|
'linked_resource_ids': [
|
|
(4, self.apt_type_resources_table_1.id),
|
|
(4, self.apt_type_resources_table_2.id),
|
|
],
|
|
})
|
|
self.apt_resources_google += self.apt_type_resources_table_3
|
|
|
|
self.apt_type_resources_table_1.write({
|
|
'linked_resource_ids': [
|
|
(4, self.apt_type_resources_table_2.id),
|
|
(4, self.apt_type_resources_table_3.id),
|
|
],
|
|
})
|
|
|
|
self.apt_type_resources_table_2.write({
|
|
'linked_resource_ids': [
|
|
(4, self.apt_type_resources_table_1.id),
|
|
(4, self.apt_type_resources_table_3.id),
|
|
],
|
|
})
|
|
|
|
# STEP 1: create booking (6 people)
|
|
create_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/create',
|
|
data=json.dumps({
|
|
'idempotency_token': 'token1',
|
|
'slot': {
|
|
'confirmation_mode': 'CONFIRMATION_MODE_SYNCHRONOUS',
|
|
'duration_sec': '3600',
|
|
'merchant_id': str(self.apt_type_resource_google.id),
|
|
'resources': {
|
|
'party_size': '6',
|
|
},
|
|
'service_id': str(self.apt_type_resource_google.id),
|
|
'start_sec': self._to_utc(datetime(2022, 2, 14, 15, 0, 0))
|
|
},
|
|
'user_information': {
|
|
'email': 'john.doe@test.com',
|
|
'family_name': 'Doe',
|
|
'given_name': 'John',
|
|
'telephone': '+32476112233',
|
|
'user_id': '1234567890',
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(create_response.status_code, 200)
|
|
create_data = create_response.json()
|
|
self.env['calendar.event'].flush_model()
|
|
|
|
self.assertFalse(bool(create_data.get('booking_failure')))
|
|
self.assertTrue(bool(create_data.get('booking').get('booking_id')))
|
|
booking_id = create_data['booking']['booking_id']
|
|
calendar_event = self.env['calendar.event'].browse(int(booking_id))
|
|
self.assertTrue(bool(calendar_event.exists()))
|
|
self.assertEqual(calendar_event.appointment_type_id, self.apt_type_resource_google)
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 6)
|
|
self.assertEqual(
|
|
len(calendar_event.booking_line_ids), 1,
|
|
"Table 3 can accommodate 6 people -> should only use one booking"
|
|
)
|
|
self.assertEqual(
|
|
calendar_event.booking_line_ids[0].appointment_resource_id,
|
|
self.apt_type_resources_table_3,
|
|
)
|
|
|
|
# STEP 2: update booking -> 11 people
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'resources': {
|
|
'party_size': 11,
|
|
},
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = json.loads(update_response.content)
|
|
self.assertFalse(bool(update_data.get('booking_failure')))
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 11)
|
|
self.assertEqual(
|
|
len(calendar_event.booking_line_ids), 3,
|
|
"By combining all 3 tables we can accommodate 11 people"
|
|
)
|
|
|
|
for resource in self.apt_resources_google:
|
|
self.assertEqual(
|
|
len(calendar_event.booking_line_ids.filtered(
|
|
lambda booking: booking.appointment_resource_id == resource
|
|
)),
|
|
1,
|
|
)
|
|
|
|
# STEP 3: update booking -> 15 people -> not enough room
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'resources': {
|
|
'party_size': 15,
|
|
},
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = json.loads(update_response.content)
|
|
self.assertEqual(update_data.get('booking_failure').get('cause'), 'USER_OVER_BOOKING_LIMIT')
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 11)
|
|
|
|
# STEP 4: update booking -> 4 people
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'resources': {
|
|
'party_size': 4,
|
|
},
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = update_response.json()
|
|
self.env['calendar.event'].flush_model()
|
|
|
|
self.assertFalse(bool(update_data.get('booking_failure')))
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 4)
|
|
self.assertEqual(
|
|
len(calendar_event.booking_line_ids), 1,
|
|
"Table 1 can accommodate 4 people -> should only use one booking"
|
|
)
|
|
self.assertEqual(
|
|
calendar_event.booking_line_ids[0].appointment_resource_id,
|
|
self.apt_type_resources_table_1,
|
|
)
|
|
|
|
# STEP 5: test creation process that combines tables
|
|
create_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/create',
|
|
data=json.dumps({
|
|
'idempotency_token': 'token2',
|
|
'slot': {
|
|
'confirmation_mode': 'CONFIRMATION_MODE_SYNCHRONOUS',
|
|
'duration_sec': '3600',
|
|
'merchant_id': str(self.apt_type_resource_google.id),
|
|
'resources': {
|
|
'party_size': '11',
|
|
},
|
|
'service_id': str(self.apt_type_resource_google.id),
|
|
'start_sec': self._to_utc(datetime(2022, 2, 14, 16, 0, 0))
|
|
},
|
|
'user_information': {
|
|
'email': 'john.doe@test.com',
|
|
'family_name': 'Doe',
|
|
'given_name': 'John',
|
|
'telephone': '+32476112233',
|
|
'user_id': '1234567890',
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(create_response.status_code, 200)
|
|
create_data = create_response.json()
|
|
self.env['calendar.event'].flush_model()
|
|
|
|
self.assertFalse(bool(create_data.get('booking_failure')))
|
|
self.assertTrue(bool(create_data.get('booking').get('booking_id')))
|
|
booking_id = create_data['booking']['booking_id']
|
|
calendar_event = self.env['calendar.event'].browse(int(booking_id))
|
|
self.assertTrue(bool(calendar_event.exists()))
|
|
self.assertEqual(calendar_event.appointment_type_id, self.apt_type_resource_google)
|
|
self.assertEqual(calendar_event.total_capacity_reserved, 11)
|
|
self.assertEqual(
|
|
len(calendar_event.booking_line_ids), 3,
|
|
"By combining all 3 tables we can accommodate 11 people"
|
|
)
|
|
self.assertEqual(
|
|
calendar_event.booking_line_ids.mapped('capacity_reserved'),
|
|
[5, 4, 2]
|
|
)
|
|
self.assertEqual(
|
|
calendar_event.booking_line_ids.mapped('capacity_used'),
|
|
[6, 4, 2]
|
|
)
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.GoogleReserveIAP.update_availabilities', autospec=True)
|
|
def test_google_reserve_move_booking(self, update_availabilities):
|
|
""" When moving a booking to a new date, if the currently used resources are not available
|
|
at the new time, make sure we check if other resources are available. """
|
|
|
|
bookings = self.env['calendar.event'].create([{
|
|
'appointment_type_id': self.apt_type_resource_google.id,
|
|
'name': "Booking 1",
|
|
'start': datetime(2022, 2, 14, 15, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 16, 0, 0),
|
|
'total_capacity_reserved': 2,
|
|
'booking_line_ids': [(0, 0, {
|
|
'appointment_resource_id': self.apt_type_resources_table_2.id,
|
|
'capacity_reserved': 2,
|
|
'capacity_used': 2,
|
|
})],
|
|
}, {
|
|
'appointment_type_id': self.apt_type_resource_google.id,
|
|
'name': "Booking 2",
|
|
'start': datetime(2022, 2, 14, 16, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 17, 0, 0),
|
|
'total_capacity_reserved': 2,
|
|
'booking_line_ids': [(0, 0, {
|
|
'appointment_resource_id': self.apt_type_resources_table_2.id,
|
|
'capacity_reserved': 2,
|
|
'capacity_used': 2,
|
|
})],
|
|
}, {
|
|
'appointment_type_id': self.apt_type_resource_google.id,
|
|
'name': "Booking 3",
|
|
'start': datetime(2022, 2, 14, 17, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 18, 0, 0),
|
|
'total_capacity_reserved': 6,
|
|
'booking_line_ids': [(0, 0, {
|
|
'appointment_resource_id': self.apt_type_resources_table_1.id,
|
|
'capacity_reserved': 4,
|
|
'capacity_used': 4
|
|
}), (0, 0, {
|
|
'appointment_resource_id': self.apt_type_resources_table_2.id,
|
|
'capacity_reserved': 1,
|
|
'capacity_used': 2
|
|
})],
|
|
}])
|
|
|
|
edited_booking = bookings[0]
|
|
|
|
# STEP 1: update to take a different time where used resource is not available
|
|
# -> but another table is available
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{edited_booking.id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(edited_booking.id),
|
|
'slot': {
|
|
'duration_sec': '3600',
|
|
'start_sec': self._to_utc(datetime(2022, 2, 14, 16, 0, 0)),
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = update_response.json()
|
|
edited_booking.flush_recordset()
|
|
self.assertFalse(bool(update_data.get('booking_failure')))
|
|
self.assertEqual(edited_booking.total_capacity_reserved, 2)
|
|
self.assertEqual(
|
|
len(edited_booking.booking_line_ids), 1,
|
|
"Table 1 is available can accommodate 4 people -> should only use one booking"
|
|
)
|
|
self.assertEqual(
|
|
edited_booking.booking_line_ids[0].appointment_resource_id,
|
|
self.apt_type_resources_table_1,
|
|
)
|
|
|
|
# STEP 2: update to take a different time where no resources are available
|
|
# -> should fail and not update the existing calendar.event
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_resource_google.id}/{self.apt_type_resource_google.google_reserve_access_token}/google_reserve/booking/{edited_booking.id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(edited_booking.id),
|
|
'slot': {
|
|
'duration_sec': '3600',
|
|
'start_sec': self._to_utc(datetime(2022, 2, 14, 17, 0, 0)),
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = update_response.json()
|
|
self.assertEqual(update_data.get('booking_failure').get('cause'), 'SLOT_UNAVAILABLE')
|
|
edited_booking.flush_recordset()
|
|
self.assertEqual(edited_booking.total_capacity_reserved, 2)
|
|
self.assertEqual(
|
|
len(edited_booking.booking_line_ids), 1,
|
|
)
|
|
self.assertEqual(
|
|
edited_booking.booking_line_ids[0].appointment_resource_id,
|
|
self.apt_type_resources_table_1,
|
|
)
|
|
|
|
# --------------------------------------
|
|
# AVAILABILITIES - STAFF USERS
|
|
# --------------------------------------
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.GoogleReserveIAP.update_availabilities', autospec=True)
|
|
def test_google_reserve_availability_lookup_staff(self, update_availabilities):
|
|
"""" Same logic but for staff users and not resources.
|
|
Main difference is that we don't have a party size. """
|
|
|
|
start_slot = datetime(2022, 2, 14, 15, 0, 0)
|
|
response = self.opener.get(
|
|
f'{self.base_url()}/appointment/{self.apt_type_staff_google.id}/{self.apt_type_staff_google.google_reserve_access_token}/google_reserve/availabilities',
|
|
data=json.dumps(
|
|
[{
|
|
'duration_sec': '3600',
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=i)),
|
|
} for i in range(4)]
|
|
),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
}
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(bool(response.json()))
|
|
|
|
expected_availabilities = [
|
|
{'start_time': 15, 'available': True},
|
|
{'start_time': 16, 'available': True},
|
|
{'start_time': 17, 'available': True},
|
|
{'start_time': 18, 'available': False}, # False
|
|
]
|
|
|
|
for availability, expected_availability in zip(response.json(), expected_availabilities):
|
|
slot_time = availability.get('slot_time')
|
|
expected_start_sec = self._to_utc(start_slot.replace(hour=expected_availability['start_time']))
|
|
self.assertEqual(slot_time.get('start_sec'), expected_start_sec)
|
|
self.assertEqual(availability.get('available'), expected_availability['available'])
|
|
|
|
# add some meetings to our staff
|
|
self.env['calendar.event'].create([{
|
|
'name': "Manager Meeting",
|
|
'start': datetime(2022, 2, 14, 15, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 16, 0, 0),
|
|
'partner_ids': [(4, self.apt_manager.partner_id.id)],
|
|
}, {
|
|
'name': "Whole Company Meeting",
|
|
'start': datetime(2022, 2, 14, 16, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 17, 0, 0),
|
|
'partner_ids': [
|
|
(4, self.apt_manager.partner_id.id),
|
|
(4, self.staff_user_bxls.partner_id.id),
|
|
],
|
|
}])
|
|
response = self.opener.get(
|
|
f'{self.base_url()}/appointment/{self.apt_type_staff_google.id}/{self.apt_type_staff_google.google_reserve_access_token}/google_reserve/availabilities',
|
|
data=json.dumps(
|
|
[{
|
|
'duration_sec': '3600',
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=i)),
|
|
} for i in range(3)]
|
|
),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
}
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(bool(response.json()))
|
|
|
|
expected_availabilities = [
|
|
{'start_time': 15, 'available': True}, # staff_user_bxls is available
|
|
{'start_time': 16, 'available': False}, # no staff users available
|
|
{'start_time': 17, 'available': True}, # both staff users are available
|
|
]
|
|
|
|
for availability, expected_availability in zip(response.json(), expected_availabilities):
|
|
with self.subTest(
|
|
start_time=expected_availability['start_time'],
|
|
available=expected_availability['available'],
|
|
):
|
|
slot_time = availability.get('slot_time')
|
|
expected_start_sec = self._to_utc(start_slot.replace(hour=expected_availability['start_time']))
|
|
self.assertEqual(slot_time.get('start_sec'), expected_start_sec)
|
|
self.assertEqual(availability.get('available'), expected_availability['available'])
|
|
|
|
# --------------------------------------
|
|
# AVAILABILITIES - STAFF USERS
|
|
# --------------------------------------
|
|
|
|
@freeze_time('2022-02-14 07-00-00')
|
|
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.GoogleReserveIAP.update_availabilities', autospec=True)
|
|
def test_google_reserve_create_update_booking_staff(self, update_availabilities):
|
|
""" Test a complete create/update scenario:
|
|
- A first booking is made (3PM)
|
|
- It is then updated (4PM)
|
|
- It is updated again (5PM) but the staff user is no longer available (SLOT_UNAVAILABLE)
|
|
- And finally updated again for another time outside appointment slots (SLOT_UNAVAILABLE) """
|
|
|
|
start_slot = datetime(2022, 2, 14, 15, 0, 0)
|
|
|
|
# only keep one staff user to make checks more simple
|
|
self.apt_type_staff_google.staff_user_ids = [(6, 0, [self.staff_user_bxls.id])]
|
|
self.env['calendar.event'].create({
|
|
'name': "Whole Company Meeting",
|
|
'start': datetime(2022, 2, 14, 17, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 18, 0, 0),
|
|
'partner_ids': [(4, self.staff_user_bxls.partner_id.id)],
|
|
})
|
|
|
|
# STEP 1: create booking (3PM)
|
|
create_response = self.url_open(
|
|
f'/appointment/{self.apt_type_staff_google.id}/{self.apt_type_staff_google.google_reserve_access_token}/google_reserve/booking/create',
|
|
data=json.dumps({
|
|
'idempotency_token': 'token1',
|
|
'slot': {
|
|
'confirmation_mode': 'CONFIRMATION_MODE_SYNCHRONOUS',
|
|
'duration_sec': '3600',
|
|
'merchant_id': str(self.apt_type_staff_google.id),
|
|
'service_id': str(self.apt_type_staff_google.id),
|
|
'start_sec': self._to_utc(start_slot)
|
|
},
|
|
'user_information': {
|
|
'email': 'john.doe@test.com',
|
|
'family_name': 'Doe',
|
|
'given_name': 'John',
|
|
'telephone': '+32476112233',
|
|
'user_id': '1234567890',
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(create_response.status_code, 200)
|
|
create_data = create_response.json()
|
|
self.env['calendar.event'].flush_model()
|
|
|
|
self.assertFalse(bool(create_data.get('booking_failure')))
|
|
self.assertTrue(bool(create_data.get('booking').get('booking_id')))
|
|
booking_id = create_data['booking']['booking_id']
|
|
calendar_event = self.env['calendar.event'].browse(int(booking_id))
|
|
self.assertTrue(bool(calendar_event.exists()))
|
|
self.assertEqual(calendar_event.appointment_type_id, self.apt_type_staff_google)
|
|
self.assertEqual(calendar_event.start, start_slot)
|
|
self.assertIn(self.staff_user_bxls.partner_id, calendar_event.partner_ids)
|
|
|
|
self.assertEqual(update_availabilities.call_count, 1)
|
|
args, _kwargs = update_availabilities.call_args_list[0]
|
|
self.assertEqual(args[1], calendar_event.appointment_type_id)
|
|
self.assertEqual(args[2], calendar_event.start)
|
|
self.assertEqual(args[3], calendar_event.stop)
|
|
|
|
# STEP 2: update booking -> 4PM
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_staff_google.id}/{self.apt_type_staff_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'duration_sec': '3600',
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=1)),
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = json.loads(update_response.content)
|
|
self.assertFalse(bool(update_data.get('booking_failure')))
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.start, start_slot + timedelta(hours=1))
|
|
|
|
self.assertEqual(update_availabilities.call_count, 2)
|
|
|
|
# STEP 3: update booking -> 5PM -> staff user is busy
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_staff_google.id}/{self.apt_type_staff_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'duration_sec': '3600',
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=2)),
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = json.loads(update_response.content)
|
|
self.assertEqual(update_data.get('booking_failure').get('cause'), 'SLOT_UNAVAILABLE')
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.start, start_slot + timedelta(hours=1))
|
|
|
|
# did not call update availabilities as no update
|
|
self.assertEqual(update_availabilities.call_count, 2)
|
|
|
|
# STEP 4: update booking -> 6PM -> outside appointment type time slots
|
|
update_response = self.url_open(
|
|
f'/appointment/{self.apt_type_staff_google.id}/{self.apt_type_staff_google.google_reserve_access_token}/google_reserve/booking/{booking_id}/update',
|
|
data=json.dumps({
|
|
'booking': {
|
|
'booking_id': str(booking_id),
|
|
'slot': {
|
|
'duration_sec': '3600',
|
|
'start_sec': self._to_utc(start_slot + timedelta(hours=3)),
|
|
},
|
|
}
|
|
}),
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
},
|
|
)
|
|
|
|
self.assertEqual(update_response.status_code, 200)
|
|
update_data = json.loads(update_response.content)
|
|
self.assertEqual(update_data.get('booking_failure').get('cause'), 'SLOT_UNAVAILABLE')
|
|
calendar_event.flush_recordset()
|
|
self.assertEqual(calendar_event.start, start_slot + timedelta(hours=1))
|
|
|
|
# did not call update availabilities as no update
|
|
self.assertEqual(update_availabilities.call_count, 2)
|
|
|
|
# --------------------------------------
|
|
# UTILS
|
|
# --------------------------------------
|
|
|
|
def _to_utc(self, slot_datetime):
|
|
return str(cal.timegm((slot_datetime).timetuple()))
|