[IMP] partner_autocomplete: extract iap calls in a separate model and add a mock
In this commit we move iap functions onto a small tool model. It allows to hide some details in a sub model. It also allows to define a mock that will be used in tests afterwards. LINKS Task ID-2322455 PR #64600 PR odoo/upgrade#2086 Co-Authored-By: David Beguin <dbe@odoo.com> Co-Authored-By: Thibault Delavallee <tde@odoo.com>
This commit is contained in:
co-authored by
David Beguin
parent
57b52e80e7
commit
d3c5300272
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import iap_autocomplete_api
|
||||
from . import res_partner
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import api, models, exceptions, _
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IapAutocompleteEnrichAPI(models.AbstractModel):
|
||||
_name = 'iap.autocomplete.api'
|
||||
_description = 'IAP Partner Autocomplete API'
|
||||
_DEFAULT_ENDPOINT = 'https://partner-autocomplete.odoo.com'
|
||||
|
||||
@api.model
|
||||
def _contact_iap(self, local_endpoint, action, params, timeout=15):
|
||||
if self.env.registry.in_test_mode():
|
||||
raise exceptions.ValidationError(_('Test mode'))
|
||||
account = self.env['iap.account'].get('partner_autocomplete')
|
||||
if not account.account_token:
|
||||
raise ValueError(_('No account token'))
|
||||
params.update({
|
||||
'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
||||
'account_token': account.account_token,
|
||||
'country_code': self.env.company.country_id.code,
|
||||
'zip': self.env.company.zip,
|
||||
})
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('iap.partner_autocomplete.endpoint', self._DEFAULT_ENDPOINT)
|
||||
return iap_tools.iap_jsonrpc(base_url + local_endpoint + '/' + action, params=params, timeout=timeout)
|
||||
|
||||
@api.model
|
||||
def _request_partner_autocomplete(self, action, params, timeout=15):
|
||||
""" Contact endpoint to get autocomplete data.
|
||||
|
||||
:return tuple: results, error code
|
||||
"""
|
||||
try:
|
||||
results = self._contact_iap('/iap/partner_autocomplete', action, params, timeout=timeout)
|
||||
except exceptions.ValidationError:
|
||||
return False, 'Insufficient Credit'
|
||||
except (ConnectionError, HTTPError, exceptions.AccessError, exceptions.UserError) as exception:
|
||||
_logger.error('Autocomplete API error: %s', str(exception))
|
||||
return False, str(exception)
|
||||
except iap_tools.InsufficientCreditError as exception:
|
||||
_logger.warning('Insufficient Credits for Autocomplete Service: %s', str(exception))
|
||||
return False, 'Insufficient Credit'
|
||||
except ValueError:
|
||||
return False, 'No account token'
|
||||
return results, False
|
||||
@@ -1,16 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import json
|
||||
from odoo import api, fields, models, exceptions, _
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
# TDE FIXME: check those errors at iap level ?
|
||||
from requests.exceptions import ConnectionError, HTTPError
|
||||
import logging
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_ENDPOINT = 'https://partner-autocomplete.odoo.com'
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_name = 'res.partner'
|
||||
@@ -75,38 +72,9 @@ class ResPartner(models.Model):
|
||||
|
||||
return country_id, state_id
|
||||
|
||||
@api.model
|
||||
def get_endpoint(self):
|
||||
url = self.env['ir.config_parameter'].sudo().get_param('iap.partner_autocomplete.endpoint', DEFAULT_ENDPOINT)
|
||||
url += '/iap/partner_autocomplete'
|
||||
return url
|
||||
|
||||
@api.model
|
||||
def _rpc_remote_api(self, action, params, timeout=15):
|
||||
if self.env.registry.in_test_mode() :
|
||||
return False, 'Insufficient Credit'
|
||||
url = '%s/%s' % (self.get_endpoint(), action)
|
||||
account = self.env['iap.account'].get('partner_autocomplete')
|
||||
if not account.account_token:
|
||||
return False, 'No Account Token'
|
||||
params.update({
|
||||
'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
||||
'account_token': account.account_token,
|
||||
'country_code': self.env.company.country_id.code,
|
||||
'zip': self.env.company.zip,
|
||||
})
|
||||
try:
|
||||
return iap_tools.iap_jsonrpc(url=url, params=params, timeout=timeout), False
|
||||
except (ConnectionError, HTTPError, exceptions.AccessError, exceptions.UserError) as exception:
|
||||
_logger.error('Autocomplete API error: %s' % str(exception))
|
||||
return False, str(exception)
|
||||
except iap_tools.InsufficientCreditError as exception:
|
||||
_logger.warning('Insufficient Credits for Autocomplete Service: %s' % str(exception))
|
||||
return False, 'Insufficient Credit'
|
||||
|
||||
@api.model
|
||||
def autocomplete(self, query, timeout=15):
|
||||
suggestions, error = self._rpc_remote_api('search', {
|
||||
suggestions, _ = self.env['iap.autocomplete.api']._request_partner_autocomplete('search', {
|
||||
'query': query,
|
||||
}, timeout=timeout)
|
||||
if suggestions:
|
||||
@@ -119,7 +87,7 @@ class ResPartner(models.Model):
|
||||
|
||||
@api.model
|
||||
def enrich_company(self, company_domain, partner_gid, vat, timeout=15):
|
||||
response, error = self._rpc_remote_api('enrich', {
|
||||
response, error = self.env['iap.autocomplete.api']._request_partner_autocomplete('enrich', {
|
||||
'domain': company_domain,
|
||||
'partner_gid': partner_gid,
|
||||
'vat': vat,
|
||||
@@ -144,7 +112,7 @@ class ResPartner(models.Model):
|
||||
|
||||
@api.model
|
||||
def read_by_vat(self, vat, timeout=15):
|
||||
vies_vat_data, error = self._rpc_remote_api('search_vat', {
|
||||
vies_vat_data, _ = self.env['iap.autocomplete.api']._request_partner_autocomplete('search_vat', {
|
||||
'vat': vat,
|
||||
}, timeout=timeout)
|
||||
if vies_vat_data:
|
||||
|
||||
@@ -25,7 +25,7 @@ class ResPartnerAutocompleteSync(models.Model):
|
||||
|
||||
if partner.vat and partner._is_vat_syncable(partner.vat):
|
||||
params['vat'] = partner.vat
|
||||
result, error = partner._rpc_remote_api('update', params)
|
||||
_, error = self.env['iap.autocomplete.api']._request_partner_autocomplete('update', params)
|
||||
if error:
|
||||
_logger.error('Send Partner to sync failed: %s' % str(error))
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import exceptions
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
from odoo.addons.partner_autocomplete.models.iap_autocomplete_api import IapAutocompleteEnrichAPI
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class MockIAPPartnerAutocomplete(common.BaseCase):
|
||||
""" Mock PartnerAutocomplete IAP calls for testing purpose.
|
||||
|
||||
Example of company_data {
|
||||
'partner_gid': 51580, 'website': 'mywebsite.be',
|
||||
'additional_info': {
|
||||
"name": "Mywebsite",
|
||||
"description": "Mywebsite is the largest of Belgium\'s custom companies and part of Mywebsite Group.",
|
||||
"facebook": "mywebsitebe", "twitter": "mywebsite", "linkedin": "company/mywebsite",
|
||||
"twitter_followers": 99999, "twitter_bio": "This is the official Twitter account of MyWebsite.",
|
||||
"industry_group": "Technology Hardware & Equipment", "sub_industry": "Computer Networking",
|
||||
"industry": "Communications Equipment",
|
||||
"sector": ["Information Technology", "Technology Hardware & Equipment"],
|
||||
"sector_primary": "Information Technology"
|
||||
"tech": ["Tealium", "Hotjar", "Google Analytics", "Instagram", "Facebook Advertiser", "Facebook Connect", "Google Tag Manager", "Mandrill", "Bazaarvoice", "Mailgun", "Conversio"],
|
||||
"email": [], "crunchbase": "organization/mywebsite-group",
|
||||
"phone_numbers": ["+32 800 00 000", "+32 800 00 001", "+32 800 00 002"],
|
||||
"timezone": "Europe/Brussels", "timezone_url": "https://time.is/Brussels",
|
||||
"company_type": "private", "employees": 15000.0, "annual_revenue": 0.0, "estimated_annual_revenue": false, "founded_year": 0,
|
||||
"logo": "https://logo.clearbit.com/mywebsite.be"},
|
||||
'child_ids': [{
|
||||
'is_company': False, 'type': 'contact', 'city': False, 'email': False,
|
||||
'name': 'Client support - SMEs', 'street': 'False False', 'phone': '0800 00 500',
|
||||
'zip': False, 'country_id': False, 'state_id': False}, {
|
||||
'is_company': False, 'type': 'contact', 'city': False, 'email': False,
|
||||
'name': 'Client Support - Large Business', 'street': 'False False', 'phone': '0800 00 501',
|
||||
'zip': False, 'country_id': False, 'state_id': False}],
|
||||
'city': 'Brussel', 'vat': 'BE0202239951',
|
||||
'email': False, 'logo': 'https://logo.clearbit.com/mywebsite.be',
|
||||
'name': 'Proximus', 'zip': '1000', 'ignored': False, 'phone': '+32 800 00 800',
|
||||
'bank_ids': [{
|
||||
'acc_number': 'BE012012012', 'acc_holder_name': 'MyWebsite'}, {
|
||||
'acc_number': 'BE013013013', 'acc_holder_name': 'MyWebsite Online'}],
|
||||
'street': 'Rue Perdues 27',
|
||||
'country_code': 'de', 'country_name': 'Germany',
|
||||
'state_id': False
|
||||
}
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _init_mock_partner_autocomplete(cls):
|
||||
super(MockIAPPartnerAutocomplete, cls).setUpClass()
|
||||
cls.base_de = cls.env.ref('base.de')
|
||||
cls.base_be = cls.env.ref('base.be')
|
||||
cls.be_state_bw = cls.env['res.country.state'].create({'name': 'Béwééé dis', 'code': 'bw', 'country_id': cls.base_be.id})
|
||||
|
||||
@contextmanager
|
||||
def mockPartnerAutocomplete(self, default_data=None, sim_error=None):
|
||||
def _contact_iap(local_endpoint, action, params, timeout):
|
||||
sim_result = {
|
||||
'partner_gid': '9876', 'website': 'https://www.heinrich.de',
|
||||
'additional_info': {},
|
||||
'city': 'Mönchengladbach',
|
||||
'email': False, 'logo': 'https://logo.clearbit.com/heinrichsroofing.com',
|
||||
'name': 'Heinrich', 'zip': '41179', 'ignored': False, 'phone': '+49 0000 112233',
|
||||
'street': 'Mennrather Str. 123456',
|
||||
'country_code': self.base_de.code, 'country_name': self.base_de.name,
|
||||
'state_id': False,
|
||||
'child_ids': [{
|
||||
'is_company': False, 'type': 'contact', 'city': 'Orcq',
|
||||
'name': 'Heinrich Support'
|
||||
}, {
|
||||
'is_company': False, 'type': 'contact', 'email': 'heinrich.clien@test.example.com',
|
||||
'name': 'Heinrich Client Support', 'street': 'Rue des Bourlottes, 9', 'phone': '0456 00 11 22',
|
||||
'zip': '1367', 'country_code': self.base_be.code, 'country_name': self.base_be.name,
|
||||
'state_code': self.be_state_bw.code, 'state_name': self.be_state_bw.name
|
||||
}],
|
||||
}
|
||||
if default_data:
|
||||
sim_result.update(default_data)
|
||||
# mock enrich only currently, to update further
|
||||
if action == 'enrich':
|
||||
if sim_error and sim_error == 'credit':
|
||||
raise iap_tools.InsufficientCreditError('InsufficientCreditError')
|
||||
elif sim_error and sim_error == 'jsonrpc_exception':
|
||||
raise exceptions.AccessError(
|
||||
'The url that this service requested returned an error. Please contact the author of the app. The url it tried to contact was ' + local_endpoint
|
||||
)
|
||||
elif sim_error and sim_error == 'token':
|
||||
raise ValueError('No account token')
|
||||
return {'company_data': sim_result}
|
||||
|
||||
try:
|
||||
with patch.object(IapAutocompleteEnrichAPI, '_contact_iap', side_effect=_contact_iap):
|
||||
yield
|
||||
finally:
|
||||
pass
|
||||
Reference in New Issue
Block a user