Files
enterprise-addons/appointment_google_reserve/tests/test_appointment_google_reserve_api.py

524 lines
21 KiB
Python

from datetime import datetime
from freezegun import freeze_time
from unittest.mock import patch
from odoo.addons.appointment_google_reserve.tests.common import GoogleReserveCommon
from odoo.tests import tagged
@tagged('appointment_google_reserve')
class AppointmentGoogleReserveAPITest(GoogleReserveCommon):
@freeze_time('2022-02-14 07-00-00')
def test_appointment_resource_sync(self):
# initial assertion
self.assertFalse(self.apt_type_resource_google.google_reserve_pending_sync)
# 1. write on unrelated field: nothing happens
self.apt_resources_google.write({'name': 'Updated Name'})
self.assertFalse(self.apt_type_resource_google.google_reserve_pending_sync)
# 2. write on capacity: update related appointment availabilities
self.apt_resources_google.write({'capacity': 3})
self.assertTrue(self.apt_type_resource_google.google_reserve_pending_sync)
# reset sync
self.apt_type_resource_google.google_reserve_pending_sync = False
# 3. archive -> update related appointment availabilities
self.apt_resources_google.action_archive()
self.assertTrue(self.apt_type_resource_google.google_reserve_pending_sync)
# reset sync
self.apt_type_resource_google.google_reserve_pending_sync = False
# 4. restore -> update related appointment availabilities (again)
self.apt_resources_google.action_unarchive()
self.assertTrue(self.apt_type_resource_google.google_reserve_pending_sync)
# reset sync
self.apt_type_resource_google.google_reserve_pending_sync = False
# 4. restore -> update related appointment availabilities (again)
self.apt_resources_google.unlink()
self.assertTrue(self.apt_type_resource_google.google_reserve_pending_sync)
@freeze_time('2022-02-14 07-00-00')
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.iap_jsonrpc')
def test_appointment_type_sync(self, iap_jsonrpc):
""" When an appointment is created, we need to check that it's registered on IAP.
When an appointment is modified, we need to check that:
- It's updated on IAP
- It's marked as requiring sync
The following use cases are taken into account:
- Creation of an appointment.type
- Modification of an appointment.type
- Only for relevant fields
- Some fields need to re-register the appointment on IAP
- Some fields need to update the availabilities on IAP
- (Some need to do both)
- Archive/Restore an appointment.type
- Unlink an appointment.type """
db_uuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
self.env['ir.config_parameter'].sudo().set_param(
'appointment_google_reserve.google_reserve_iap_endpoint',
'https://google-reserve.api.odoo.com'
)
# 1. create an appointment.type: needs to be registered on IAP
apt_type_resource_google = self.env['appointment.type'].create({
'appointment_tz': 'UTC',
'is_auto_assign': True,
'is_date_first': True,
'location_id': self.test_location.id,
'min_schedule_hours': 1.0,
'max_schedule_days': 5,
'name': 'Test Google Reserve',
'manage_capacity': True,
'schedule_based_on': 'resources',
'google_reserve_enable': True,
'google_reserve_merchant_id': self.google_reserve_merchant.id,
'slot_ids': [(0, 0, {
'weekday': str(self.reference_monday.isoweekday()),
'start_hour': 15,
'end_hour': 18,
})],
})
access_token = apt_type_resource_google.google_reserve_access_token
# assert initial data
self.assertFalse(apt_type_resource_google.google_reserve_pending_sync)
self.env['appointment.resource'].create([{
'appointment_type_ids': apt_type_resource_google.ids,
'capacity': 2,
'name': 'Table 1',
'sequence': 2,
}, {
'appointment_type_ids': apt_type_resource_google.ids,
'capacity': 4,
'name': 'Table 2',
'sequence': 1,
}])
appointment_data = {
'db_uuid': db_uuid,
'merchant_details': {
'name': 'My Company',
'id': self.google_reserve_merchant.id,
'callback_url': apt_type_resource_google.get_base_url(),
'business_category': 'Restaurant',
'phone': '+32499123456',
'website_url': 'https://www.example.com',
'location': {
'country_code': 'BE',
'city': 'Bloups',
'region': False,
'zip': '6666',
'street': 'Zboing Street 42',
}
},
'appointment_details': {
'appointment_type_id': apt_type_resource_google.id,
'google_reserve_access_token': apt_type_resource_google.google_reserve_access_token,
'service_name': 'Test Google Reserve',
'min_cancellation_hours': 1.0,
'min_schedule_hours': 1.0
}
}
self.assertEqual(iap_jsonrpc.call_count, 1)
self.assertIapRpc(
iap_jsonrpc.call_args_list[0],
'https://google-reserve.api.odoo.com/api/google_reserve/1/appointment/register',
appointment_data,
)
# 2a. writing on an unrelated field, should not call IAP
apt_type_resource_google.write({'sequence': 42})
self.assertEqual(iap_jsonrpc.call_count, 1)
self.assertFalse(apt_type_resource_google.google_reserve_pending_sync)
# 2b. writing on a field that alters both the slots and the appointment definition on IAP
# -> call register and mark as needing sync
apt_type_resource_google.write({'min_cancellation_hours': 2})
appointment_data['appointment_details']['min_cancellation_hours'] = 2
self.assertEqual(iap_jsonrpc.call_count, 2)
self.assertIapRpc(
iap_jsonrpc.call_args_list[1],
'https://google-reserve.api.odoo.com/api/google_reserve/1/appointment/register',
appointment_data,
)
self.assertTrue(apt_type_resource_google.google_reserve_pending_sync)
# reset sync
apt_type_resource_google.google_reserve_pending_sync = False
# 2c. writing on a field that alters only the appointment definition
# -> call appointment registration route for update
apt_type_resource_google.write({'name': 'Test Google Reserve Edited'})
self.assertTrue(apt_type_resource_google.google_reserve_pending_sync)
self.assertEqual(iap_jsonrpc.call_count, 3)
self.assertIapRpc(
iap_jsonrpc.call_args_list[2],
'https://google-reserve.api.odoo.com/api/google_reserve/1/appointment/register',
False, # already tested before
)
# reset sync
apt_type_resource_google.google_reserve_pending_sync = False
# 2d. writing on a field that alters only the appointment availabilities
# -> call appointment availabilities replace route
apt_type_resource_google.write({'appointment_duration': 2})
self.assertTrue(apt_type_resource_google.google_reserve_pending_sync)
# reset sync
apt_type_resource_google.google_reserve_pending_sync = False
# 3. archive -> unregister the appointment from IAP
apt_type_resource_google.action_archive()
self.assertEqual(iap_jsonrpc.call_count, 4)
self.assertIapRpc(
iap_jsonrpc.call_args_list[3],
f'https://google-reserve.api.odoo.com/api/google_reserve/1/appointment/{access_token}/unregister',
False, # nothing relevant to test
)
self.assertTrue(apt_type_resource_google.google_reserve_pending_sync)
# reset sync
apt_type_resource_google.google_reserve_pending_sync = False
# 4. restore -> register the appointment on IAP again
apt_type_resource_google.action_unarchive()
self.assertEqual(iap_jsonrpc.call_count, 5)
self.assertIapRpc(
iap_jsonrpc.call_args_list[4],
'https://google-reserve.api.odoo.com/api/google_reserve/1/appointment/register',
False, # already tested before
)
self.assertTrue(apt_type_resource_google.google_reserve_pending_sync)
# reset sync
apt_type_resource_google.google_reserve_pending_sync = False
# 5. unlink -> unregister the appointment from IAP
apt_type_resource_google.unlink()
self.assertEqual(iap_jsonrpc.call_count, 6)
self.assertIapRpc(
iap_jsonrpc.call_args_list[5],
f'https://google-reserve.api.odoo.com/api/google_reserve/1/appointment/{access_token}/unregister',
False, # already tested before
)
@freeze_time('2022-02-14 07-00-00')
@patch('odoo.addons.appointment_google_reserve.tools.google_reserve_iap.iap_jsonrpc')
def test_booking_update(self, iap_jsonrpc):
""" When a calendar.event was created from a Google Reserve booking, we need to notify Google
every time that booking is updated (or cancelled)
The following use cases are taken into account:
- Update of a unrelated calendar.event
- Update of a Google Reserve calendar.event
- Date change
- Party size change
- Unrelated field change
- Archive a Google Reserve calendar.event
- Unlink a Google Reserve calendar.event """
update_availabilities_url = f'https://google-reserve.api.odoo.com/api/google_reserve/1/appointment/{self.apt_type_resource_google.google_reserve_access_token}/update_availabilities'
update_booking_url = f'https://google-reserve.api.odoo.com/api/google_reserve/1/appointment/{self.apt_type_resource_google.google_reserve_access_token}/update_booking'
calendar_event = self.env['calendar.event'].create({
'appointment_type_id': self.apt_type_resource_google.id,
'name': "New Meeting 1",
'total_capacity_reserved': 4,
'resource_ids': self.apt_type_resources_table_1.ids,
'start': datetime(2022, 2, 14, 10, 0, 0),
'stop': datetime(2022, 2, 14, 14, 0, 0),
})
calendar_event.write({
'stop': datetime(2022, 2, 14, 15, 0, 0)
})
# 1. not a Google Reserve event: no need to update Google
# we however did 2 calls to "update_availabilities"
self.assertEqual(iap_jsonrpc.call_count, 2)
for i in range(2):
self.assertIapRpc(
iap_jsonrpc.call_args_list[i],
update_availabilities_url,
)
# 2. write on dates -> update Google to reflect calendar event changes
calendar_event.write({
'is_google_reserve': True,
'stop': datetime(2022, 2, 14, 14, 0, 0),
})
self.assertEqual(iap_jsonrpc.call_count, 4)
self.assertIapRpc(
iap_jsonrpc.call_args_list[2],
update_availabilities_url,
)
self.assertIapRpc(
iap_jsonrpc.call_args_list[3],
update_booking_url,
{
'booking_ids': calendar_event.ids,
'booking_values': {
'startTime': '2022-02-14T10:00:00Z',
'duration': str(4 * 3600) + 's' # 4 hours
},
'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
}
)
# 3. book for more people -> update Google to reflect calendar event changes
calendar_event.write({
'resource_ids': [(4, self.apt_type_resources_table_2.id, 0)],
'total_capacity_reserved': 6,
})
self.assertEqual(iap_jsonrpc.call_count, 6)
self.assertIapRpc(
iap_jsonrpc.call_args_list[4],
update_availabilities_url,
)
self.assertIapRpc(
iap_jsonrpc.call_args_list[5],
update_booking_url,
{
'booking_ids': calendar_event.ids,
'booking_values': {
'partySize': '6',
},
'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
}
)
# 4. name change -> nothing happens
calendar_event.write({
'name': 'Booking for Paul',
})
self.assertEqual(iap_jsonrpc.call_count, 6)
# 5. archive event -> cancel on Google
calendar_event.write({
'active': False,
})
self.assertEqual(iap_jsonrpc.call_count, 8)
self.assertIapRpc(
iap_jsonrpc.call_args_list[6],
update_availabilities_url,
)
self.assertIapRpc(
iap_jsonrpc.call_args_list[7],
update_booking_url,
{
'booking_ids': calendar_event.ids,
'booking_values': {
'status': 'CANCELED',
},
'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
}
)
# 6. unlink event -> cancel on Google
[regular_event, google_event] = self.env['calendar.event'].create([{
'appointment_type_id': self.apt_type_resource_google.id,
'name': "New Meeting 2",
'resource_ids': self.apt_type_resources_table_1.ids,
'total_capacity_reserved': 4,
'start': datetime(2022, 2, 15, 10, 0, 0),
'stop': datetime(2022, 2, 15, 14, 0, 0),
}, {
'appointment_type_id': self.apt_type_resource_google.id,
'name': "New Meeting 3",
'resource_ids': self.apt_type_resources_table_1.ids,
'total_capacity_reserved': 4,
'start': datetime(2022, 2, 16, 10, 0, 0),
'stop': datetime(2022, 2, 16, 14, 0, 0),
'is_google_reserve': True,
}])
self.assertEqual(iap_jsonrpc.call_count, 9)
self.assertIapRpc(
iap_jsonrpc.call_args_list[8],
update_availabilities_url,
)
(regular_event + google_event).unlink()
self.assertEqual(iap_jsonrpc.call_count, 11)
self.assertIapRpc(
iap_jsonrpc.call_args_list[9],
update_availabilities_url,
)
self.assertIapRpc(
iap_jsonrpc.call_args_list[10],
update_booking_url,
{
'booking_ids': google_event.ids,
'booking_values': {
'status': 'CANCELED',
},
'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
}
)
@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_calendar_event_sync(self, update_availabilities):
""" When a calendar.event is created or modified, we need to sync the availabilities of the
underlying appointments that are google reserve enabled.
The following use cases are taken into account:
- Creation of a calendar.event
- Modification of a calendar.event (for relevant fields)
- Archive/Restore a calendar.event
- Unlink a calendar.event """
# 1. create bookings: update related appointment availabilities
events = self.env['calendar.event'].create([{
'appointment_type_id': self.apt_type_resource_google.id,
'name': "New Meeting 1",
'start': datetime(2022, 2, 14, 10, 0, 0),
'stop': datetime(2022, 2, 14, 15, 0, 0),
}, {
'appointment_type_id': self.apt_type_resource_google.id,
'name': "New Meeting 2",
'start': datetime(2022, 2, 14, 17, 0, 0),
'stop': datetime(2022, 2, 14, 19, 0, 0),
}])
self.assertEqual(update_availabilities.call_count, 1)
self.assertUpdateAvailabilities(
update_availabilities.call_args_list[0],
self.apt_type_resource_google,
datetime(2022, 2, 14, 10, 0, 0),
datetime(2022, 2, 14, 19, 0, 0)
)
# 2. rename bookings: do nothing, it doesn't affect availabilities
events.write({'name': "Renamed Meeting"})
self.assertEqual(update_availabilities.call_count, 1)
# 3. move bookings: update related appointment availabilities
# -> take min (old start, new start) and max (old stop, new stop)
events.write({
'start': datetime(2022, 2, 14, 11, 0, 0),
'stop': datetime(2022, 2, 14, 20, 0, 0)
})
self.assertEqual(update_availabilities.call_count, 2)
self.assertUpdateAvailabilities(
update_availabilities.call_args_list[1],
self.apt_type_resource_google,
datetime(2022, 2, 14, 10, 0, 0),
datetime(2022, 2, 14, 20, 0, 0)
)
# 4. archive bookings: update related appointment availabilities
events.action_archive()
self.assertEqual(update_availabilities.call_count, 3)
self.assertUpdateAvailabilities(
update_availabilities.call_args_list[2],
self.apt_type_resource_google,
datetime(2022, 2, 14, 11, 0, 0),
datetime(2022, 2, 14, 20, 0, 0)
)
# 5. restore bookings: update related appointment availabilities
events.action_unarchive()
self.assertEqual(update_availabilities.call_count, 4)
self.assertUpdateAvailabilities(
update_availabilities.call_args_list[3],
self.apt_type_resource_google,
datetime(2022, 2, 14, 11, 0, 0),
datetime(2022, 2, 14, 20, 0, 0)
)
# 6. delete bookings: update related appointment availabilities
events.unlink()
self.assertEqual(update_availabilities.call_count, 5)
self.assertUpdateAvailabilities(
update_availabilities.call_args_list[4],
self.apt_type_resource_google,
datetime(2022, 2, 14, 11, 0, 0),
datetime(2022, 2, 14, 20, 0, 0)
)
self.assertFalse(events.exists())
@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_resource_calendar_leaves_sync(self, update_availabilities):
""" When a resource.calendar.leaves is created or modified, we need to sync the
availabilities of the underlying appointment resources' appointments that are google reserve enabled.
The following use cases are taken into account:
- Creation of a calendar.resource.leaves
- Modification of a calendar.resource.leaves (for relevant fields)
- Unlink a calendar.resource.leaves """
# 1. create leaves: update related appointment availabilities
calendar_leaves = self.env['resource.calendar.leaves'].create([{
'resource_id': appointment_resource.resource_id.id,
'date_from': datetime(2022, 2, 17, 8, 0, 0),
'date_to': datetime(2022, 2, 17, 17, 0, 0),
} for appointment_resource in self.apt_resources_google])
self.assertEqual(update_availabilities.call_count, 1)
self.assertUpdateAvailabilities(
update_availabilities.call_args_list[0],
self.apt_type_resource_google,
datetime(2022, 2, 17, 8, 0, 0),
datetime(2022, 2, 17, 17, 0, 0)
)
# 2. change reason: do nothing, it doesn't affect availabilities
calendar_leaves.write({'name': "New Reason"})
self.assertEqual(update_availabilities.call_count, 1)
# 3. move leaves: update related appointment availabilities
# -> take min (old start, new start) and max (old stop, new stop)
calendar_leaves.write({
'date_from': datetime(2022, 2, 17, 11, 0, 0),
'date_to': datetime(2022, 2, 17, 20, 0, 0)
})
self.assertEqual(update_availabilities.call_count, 2)
self.assertUpdateAvailabilities(
update_availabilities.call_args_list[1],
self.apt_type_resource_google,
datetime(2022, 2, 17, 8, 0, 0),
datetime(2022, 2, 17, 20, 0, 0)
)
# 4. unlink: update related appointment availabilities
calendar_leaves.unlink()
self.assertUpdateAvailabilities(
update_availabilities.call_args_list[2],
self.apt_type_resource_google,
datetime(2022, 2, 17, 11, 0, 0),
datetime(2022, 2, 17, 20, 0, 0)
)
# ------------------------------------------------------------
# UTILS
# ------------------------------------------------------------
def assertIapRpc(self, post_mock_call_args, post_url, post_data=False):
args, kwargs = post_mock_call_args
self.assertEqual(args[0], post_url)
if post_data:
self.assertDictEqual(kwargs.get('params'), post_data)
def assertUpdateAvailabilities(self, update_availabilities_call_args, appointment, start=None, stop=None):
args, _kwargs = update_availabilities_call_args
self.assertEqual(args[1], appointment)
if start is not None:
self.assertEqual(args[2], start)
if stop is not None:
self.assertEqual(args[3], stop)