diff --git a/addons/crm_iap_mine/models/crm_iap_lead_mining_request.py b/addons/crm_iap_mine/models/crm_iap_lead_mining_request.py index cf874ad3239..57251d96954 100644 --- a/addons/crm_iap_mine/models/crm_iap_lead_mining_request.py +++ b/addons/crm_iap_mine/models/crm_iap_lead_mining_request.py @@ -233,14 +233,13 @@ class CRMLeadMiningRequest(models.Model): server_payload = self._prepare_iap_payload() reveal_account = self.env['iap.account'].get('reveal') dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid') - endpoint = self.env['ir.config_parameter'].sudo().get_param('reveal.endpoint', DEFAULT_ENDPOINT) + '/iap/clearbit/1/lead_mining_request' params = { 'account_token': reveal_account.account_token, 'dbuuid': dbuuid, 'data': server_payload } try: - response = iap_tools.iap_jsonrpc(endpoint, params=params, timeout=300) + response = self._iap_contact_mining(params, timeout=300) if not response.get('data'): self.error_type = 'no_result' return False @@ -253,6 +252,10 @@ class CRMLeadMiningRequest(models.Model): except Exception as e: raise UserError(_("Your request could not be executed: %s", e)) + def _iap_contact_mining(self, params, timeout=300): + endpoint = self.env['ir.config_parameter'].sudo().get_param('reveal.endpoint', DEFAULT_ENDPOINT) + '/iap/clearbit/1/lead_mining_request' + return iap_tools.iap_jsonrpc(endpoint, params=params, timeout=timeout) + def _create_leads_from_response(self, result): """ This method will get the response from the service and create the leads accordingly """ self.ensure_one() diff --git a/addons/crm_iap_mine/tests/__init__.py b/addons/crm_iap_mine/tests/__init__.py new file mode 100644 index 00000000000..0c2ed496e47 --- /dev/null +++ b/addons/crm_iap_mine/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_lead_mine diff --git a/addons/crm_iap_mine/tests/common.py b/addons/crm_iap_mine/tests/common.py new file mode 100644 index 00000000000..9a6fd3a11af --- /dev/null +++ b/addons/crm_iap_mine/tests/common.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +from contextlib import contextmanager +from unittest.mock import patch + +from odoo import exceptions +from odoo.addons.crm.models.crm_lead import Lead +from odoo.addons.crm_iap_mine.models.crm_iap_lead_mining_request import CRMLeadMiningRequest +from odoo.addons.iap.tests.common import MockIAPEnrich +from odoo.addons.iap.tools import iap_tools + + +class MockIAPReveal(MockIAPEnrich): + + @classmethod + def setUpClass(cls): + super(MockIAPReveal, cls).setUpClass() + cls._new_leads = cls.env['crm.lead'].sudo() + cls.mine = False + + @contextmanager + def mock_IAP_mine(self, mine, name_list=None, default_data=None, sim_error=None): + self._new_leads = self.env['crm.lead'].sudo() + self.mine = mine + crm_lead_create_origin = Lead.create + + def _crm_lead_create(model, *args, **kwargs): + res = crm_lead_create_origin(model, *args, **kwargs) + self._new_leads += res.sudo() + return res + + def _iap_contact_mining(params, timeout): + self.assertMineCallParams(params) + self.assertMinePayload(mine, params['data']) + + if sim_error and sim_error == 'credit': + raise iap_tools.InsufficientCreditError('InsufficientCreditError') + if 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 [STRIPPED]' + ) + if sim_error and sim_error == 'no_result': + return {'credit_error': False, 'data': []} + + response = [] + for counter in range(0, mine.lead_number): + if name_list: + base_name = name_list[counter % len(name_list)] + else: + base_name = 'heinrich_%d' % counter + + iap_payload = {} + company_data = self._get_iap_company_data(base_name, service='mine') + if default_data: + company_data.update(default_data) + iap_payload['company_data'] = company_data + + if mine.search_type == 'people': + people_data = self._get_iap_contact_data(base_name, service='mine') + iap_payload['people_data'] = people_data + + response.append(iap_payload) + + return { + 'data': response, + 'credit_error': False + } + + with patch.object(CRMLeadMiningRequest, '_iap_contact_mining', side_effect=_iap_contact_mining), \ + patch.object(Lead, 'create', autospec=True, wraps=Lead, side_effect=_crm_lead_create): + yield + + def _get_iap_company_data(self, base_name, service=None, add_values=None): + company_data = super(MockIAPReveal, self)._get_iap_company_data(base_name, service=service, add_values=add_values) + if service == 'mine': + company_data['phone'] = company_data['phone_numbers'][0] + company_data['sector'] = 'Sector Info' + return company_data + + def assertMineCallParams(self, params): + self.assertTrue(bool(params['account_token'])) + self.assertTrue(bool(params['dbuuid'])) + + def assertMinePayload(self, mine, payload): + if mine.search_type == 'people': + self.assertEqual(payload['contact_number'], mine.contact_number) + else: + self.assertTrue('contact_number' not in payload) + self.assertEqual(payload['countries'], mine.mapped('country_ids.code')) + self.assertEqual(payload['lead_number'], mine.lead_number) + self.assertEqual(payload['search_type'], mine.search_type) diff --git a/addons/crm_iap_mine/tests/test_lead_mine.py b/addons/crm_iap_mine/tests/test_lead_mine.py new file mode 100644 index 00000000000..6be53ca3ed8 --- /dev/null +++ b/addons/crm_iap_mine/tests/test_lead_mine.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import exceptions +from odoo.addons.crm.tests.common import TestCrmCommon +from odoo.addons.crm_iap_mine.tests.common import MockIAPReveal +from odoo.tests.common import users + + +class TestLeadMine(TestCrmCommon, MockIAPReveal): + + @classmethod + def setUpClass(cls): + super(TestLeadMine, cls).setUpClass() + cls.registry.enter_test_mode(cls.cr) + + cls.test_crm_tags = cls.env['crm.tag'].create([ + {'name': 'TestTag1'}, + {'name': 'TestTag2'} + ]) + cls.test_request = cls.env['crm.iap.lead.mining.request'].create({ + 'contact_number': 3, + 'error_type': False, + 'lead_number': 3, + 'lead_type': 'lead', + 'name': 'Test Mine', + 'search_type': 'people', + 'state': 'draft', + 'tag_ids': [(6, 0, cls.test_crm_tags.ids)], + 'team_id': cls.sales_team_1.id, + 'user_id': cls.user_sales_leads.id, + }) + + @classmethod + def tearDownClass(cls): + cls.registry.leave_test_mode() + super().tearDownClass() + + @users('user_sales_manager') + def test_mine_error_credit(self): + mine_request = self.env['crm.iap.lead.mining.request'].browse(self.test_request.ids) + with self.mock_IAP_mine(mine_request, sim_error='credit'): + mine_request.action_submit() + + self.assertEqual(mine_request.error_type, 'credits') + self.assertEqual(mine_request.state, 'error') + + @users('user_sales_manager') + def test_mine_error_jsonrpc_exception(self): + mine_request = self.env['crm.iap.lead.mining.request'].browse(self.test_request.ids) + with self.assertRaises(exceptions.UserError): + with self.mock_IAP_mine(mine_request, sim_error='jsonrpc_exception'): + mine_request.action_submit() + + @users('user_sales_manager') + def test_mine_error_no_result(self): + mine_request = self.env['crm.iap.lead.mining.request'].browse(self.test_request.ids) + with self.mock_IAP_mine(mine_request, sim_error='no_result'): + mine_request.action_submit() + + self.assertEqual(mine_request.error_type, 'no_result') + self.assertEqual(mine_request.state, 'draft') + + @users('user_sales_manager') + def test_mine_wpeople(self): + country_de = self.base_de + state_de = self.de_state_st + + mine_request = self.env['crm.iap.lead.mining.request'].browse(self.test_request.ids) + with self.mock_IAP_mine(mine_request, name_list=['Heinrich', 'Rivil', 'LidGen']): + mine_request.action_submit() + + self.assertFalse(mine_request.error_type) + self.assertEqual(mine_request.state, 'done') + + self.assertEqual(len(self._new_leads), 3, 'Number of leads should match mine request') + + for base_name in ['Heinrich', 'Rivil', 'LidGen']: + lead = self._new_leads.filtered(lambda lead: lead.name == '%s GmbH' % base_name) + self.assertTrue(bool(lead)) + + # mine information + self.assertEqual(lead.type, 'lead') + self.assertEqual(lead.tag_ids, self.test_crm_tags) + self.assertEqual(lead.team_id, self.sales_team_1) + self.assertEqual(lead.user_id, self.user_sales_leads) + # iap + self.assertEqual(lead.reveal_id, '123_ClearbitID_%s' % base_name, 'Ensure reveal_id is set to clearbit ID') + # clearbit information + self.assertEqual(lead.contact_name, 'Contact %s 0' % base_name) + self.assertEqual(lead.city, 'Mönchengladbach') + self.assertEqual(lead.country_id, country_de) + self.assertEqual(lead.email_from, 'test.contact.0@%s.example.com' % base_name, + 'Lead email should be the one from first contact if search_type people is given') + self.assertEqual(lead.function, 'Doing stuff') + self.assertFalse(lead.partner_id) + self.assertEqual(lead.partner_name, '%s GmbH legal_name' % base_name) + self.assertEqual(lead.phone, '+4930499193937') + self.assertEqual(lead.state_id, state_de) + self.assertEqual(lead.street, 'Mennrather Str. 123456') + self.assertEqual(lead.website, 'https://www.%s.de' % base_name) + self.assertEqual(lead.zip, '41179') + + @users('user_sales_manager') + def test_mine_wcompany(self): + country_de = self.base_de + state_de = self.de_state_st + + mine_request = self.env['crm.iap.lead.mining.request'].browse(self.test_request.ids) + mine_request.write({'search_type': 'companies'}) + with self.mock_IAP_mine(mine_request, name_list=['Heinrich', 'Rivil', 'LidGen']): + mine_request.action_submit() + + self.assertFalse(mine_request.error_type) + self.assertEqual(mine_request.state, 'done') + + self.assertEqual(len(self._new_leads), 3, 'Number of leads should match mine request') + + for base_name in ['Heinrich', 'Rivil', 'LidGen']: + lead = self._new_leads.filtered(lambda lead: lead.name == '%s GmbH' % base_name) + self.assertTrue(bool(lead)) + + # mine information + self.assertEqual(lead.type, 'lead') + self.assertEqual(lead.tag_ids, self.test_crm_tags) + self.assertEqual(lead.team_id, self.sales_team_1) + self.assertEqual(lead.user_id, self.user_sales_leads) + # iap + self.assertEqual(lead.reveal_id, '123_ClearbitID_%s' % base_name, 'Ensure reveal_id is set to clearbit ID') + # clearbit information + self.assertFalse(lead.contact_name) + self.assertEqual(lead.city, 'Mönchengladbach') + self.assertEqual(lead.country_id, country_de) + self.assertEqual(lead.email_from, 'info@%s.example.com' % base_name) + self.assertFalse(lead.function) + self.assertFalse(lead.partner_id) + self.assertEqual(lead.partner_name, '%s GmbH legal_name' % base_name) + self.assertEqual(lead.phone, '+4930499193937') + self.assertEqual(lead.state_id, state_de) + self.assertEqual(lead.street, 'Mennrather Str. 123456') + self.assertEqual(lead.website, 'https://www.%s.de' % base_name) + self.assertEqual(lead.zip, '41179') diff --git a/addons/iap/tests/common.py b/addons/iap/tests/common.py index 1abed724698..fa91b1f7ad1 100644 --- a/addons/iap/tests/common.py +++ b/addons/iap/tests/common.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from contextlib import contextmanager -from functools import partial from unittest.mock import patch from odoo import exceptions @@ -10,7 +9,12 @@ from odoo.addons.iap.models.iap_enrich_api import IapEnrichAPI from odoo.tests import common -class MockIAPEnrich(common.BaseCase): +class MockIAPEnrich(common.TransactionCase): + + @classmethod + def setUpClass(cls): + super(MockIAPEnrich, cls).setUpClass() + cls._init_iap_mock() @contextmanager def mockIAPEnrichGateway(self, default_data=None, email_data=None, sim_error=None, failing_emails=None): @@ -49,3 +53,99 @@ class MockIAPEnrich(common.BaseCase): yield finally: pass + + @classmethod + def _init_iap_mock(cls): + cls.base_de = cls.env.ref('base.de') + cls.de_state_st = cls.env['res.country.state'].create({ + 'name': 'DE ST State', + 'code': 'st', + 'country_id': cls.base_de.id + }) + 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 + }) + + def _get_iap_company_data(self, base_name, service=None, add_values=None): + return { + 'domain': '%s.de' % base_name, + 'clearbit_id': '123_ClearbitID_%s' % base_name, + + # Company Info + 'name': '%s GmbH' % base_name, + 'legal_name': '%s GmbH legal_name' % base_name, + 'description': '%s GmbH description' % base_name, + 'founded_year': '1930', + 'logo': 'https://logo.clearbit.com/%slogo.com' % base_name, + 'company_type': 'private', + + # Contacts + 'phone_numbers': ['+4930499193937', '+4930653376208'], + 'email': [ + 'info@%s.example.com' % base_name, + 'info2@%s.example.com' % base_name + ], + + # Timezone + 'timezone': 'Europe/Berlin', + 'timezone_url': 'https://time.is/Berlin', + + # Social + 'facebook': "%s Facebook Handle" % base_name, + 'linkedin': "%s Linkedin Handle" % base_name, + 'crunchbase': "organization/%s" % base_name, + + # Twitter + 'twitter': '%s Twitter Handle' % base_name, + 'twitter_bio': '%s Twitter Bio' % base_name, + 'twitter_followers': 1250, + 'twitter_location': 'Berlin', + + # Metrics + 'estimated_annual_revenue': '1000000', + 'employees': 3.14, + 'market_cap': 6.28, + 'raised': 15000, + 'annual_revenue': 1000000, + + # Category + 'sector': '%s sector' % base_name, + 'sector_primary': '%s sector_primary' % base_name, + 'industry': '%s industry' % base_name, + 'industry_group': '%s industry_group' % base_name, + 'sub_industry': '%s sub_industry' % base_name, + 'tag': ['Automation', 'Construction'], + 'tech': ['3d_cart', 'nginx'], + + # Site + 'website_title': '%s Website Title' % base_name, + + # GEO Data + 'location': 'Mennrather Str. 123456', + 'street_number': '123456', + 'street_name': 'Mennrather Str.', + 'sub_premise': 'sub premise', + 'postal_code': '41179', + 'city': 'Mönchengladbach', + 'state_code': self.de_state_st.code, + 'state_name': self.de_state_st.name, + 'country_code': self.base_de.code, + 'country_name': self.base_de.name, + } + + def _get_iap_contact_data(self, base_name, service=None, add_values=None): + people_data = [] + for index in range(2): + payload = { + 'full_name': 'Contact %s %s' % (base_name, index), + 'email': 'test.contact.%s@%s.example.com' % (index, base_name), + 'phone': '+49 30 548406496', + 'seniority': 'manager', + 'title': 'Doing stuff', + 'role': 'health_professional', + } + people_data.append(payload) + return people_data