# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from typing import NamedTuple, Iterable try: import phonenumbers except ImportError: phonenumbers = None from odoo.tests.common import BaseCase class TestPhonenumbersPatch(BaseCase): class PhoneInputOutputLine(NamedTuple): """ Datastructure to store data for phone number parsing tests. Consist of single set of: - input phone data to be parsed - (optionally) ground-truths, i.e expected results Input phone data can be one of the following: - international phone number like: "+23057654321" - national phone number + region code, like: "57654321" + "MU" :param number: The input phone number to be parsed. :param region: (optional) The two-letter ISO country code, used when parsing national number without country prefix eg. "SN" :param gt_national_number: (optional) ground-truth to compare parsed national number with :param gt_country_code: (optional) ground-truth to compare country calling code eg. 221 :param gt_italian_leading_zero: (optional) ground-truth for italian_leading_zero, True if expected to find 1 leading zero after parsing :param gt_number_of_leading_zeros: (optional) ground-truth to compare number_of_leading_zeros to code, Set if expected more then one leading zero ┌────────┐ INPUT ─►│ PARSER ├─► OUTPUT (EXPECTED ground-truth) └────────┘ compare number number ◄───────► (number) (region) code (code) Placeholders in parenthesis () are optional, why are they optional? The idea is that in the most basic parse check we would only parse number and check if it's valid (according to implicit phonenumbers implementation). However, we might want to perform additional validation on the parsed number, then we'd use optional expected fields that should trigger additional checks. """ number: str region: str = "" gt_national_number: int = None gt_country_code: int = None gt_italian_leading_zero: bool = None gt_number_of_leading_zeros: int = None def _assert_parsing_phonenumbers(self, parse_test_lines: Iterable[PhoneInputOutputLine]): """ Iterates over test_lines, performs asserts according to what data each test_line contains. Simple cases: 1. test_line contains only the international number -> check if phonenumbers can parse it 2. test_line contains national number and country code -> check if phonenumbers can parse it Presence of expected data: In case test line contains some ground-truth this function will compare parsed data against the ground truths. :param parse_test_lines: An iterable consisting of PhoneInputOutputLine """ if not phonenumbers: self.skipTest('Cannot test parsing without phonenumbers module installed.') for parse_test_line in parse_test_lines: with self.subTest(**parse_test_line._asdict()): parsed_phone = phonenumbers.parse(parse_test_line.number, region=parse_test_line.region) self.assertTrue(phonenumbers.is_valid_number(parsed_phone), "Phone number does not match any patterns in the metadata.") if parse_test_line.gt_national_number: self.assertEqual(parsed_phone.national_number, parse_test_line.gt_national_number, "Parsed national number differs from expected national number") if parse_test_line.gt_country_code: self.assertEqual(parsed_phone.country_code, parse_test_line.gt_country_code, "Parsed country code number differs from expected country code") if parse_test_line.gt_italian_leading_zero: self.assertEqual(parsed_phone.italian_leading_zero, parse_test_line.gt_italian_leading_zero, "Parsed country code number differs from expected country code") if parse_test_line.gt_number_of_leading_zeros: self.assertEqual(parsed_phone.number_of_leading_zeros, parse_test_line.gt_number_of_leading_zeros, "Parsed country code number differs from expected country code") def test_region_BR_monkey_patch(self): """ Test Brazil phone numbers patch for added 9 in mobile numbers It should not be added for fixed lines numbers""" if not phonenumbers: self.skipTest('Cannot test without phonenumbers module installed.') # Mobile number => 9 should be added parsed = phonenumbers.parse('11 6123 4567', region="BR") formatted = phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.INTERNATIONAL) self.assertEqual(formatted, '+55 11 96123-4567') # Fixed line => 9 should not be added parsed = phonenumbers.parse('11 2345 6789', region="BR") formatted = phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.INTERNATIONAL) self.assertEqual(formatted, '+55 11 2345-6789') def test_region_CI_monkey_patch(self): """Makes sure that patch for Ivory Coast phone numbers work""" parse_test_lines_CI = ( self.PhoneInputOutputLine("+2250506007995"), self.PhoneInputOutputLine("0506007995", region='CI', gt_national_number=506007995, gt_country_code=225, gt_italian_leading_zero=True), self.PhoneInputOutputLine("+225 05 20 963 777", gt_national_number=520963777, gt_country_code=225, gt_italian_leading_zero=True), ) self._assert_parsing_phonenumbers(parse_test_lines_CI) def test_region_CO_monkey_patch(self): """Makes sure that patch for Colombian phone numbers work""" parse_test_lines_CO = ( self.PhoneInputOutputLine("3241234567", "CO"), self.PhoneInputOutputLine("+57 324 1234567"), ) self._assert_parsing_phonenumbers(parse_test_lines_CO) def test_region_IL_monkey_patch(self): """ Makes sure that patch for Israeli phone numbers work Example of wrong phone number: +972 55 731 1234 """ parse_test_lines_IL = ( self.PhoneInputOutputLine("055 294 1234", "IL"), self.PhoneInputOutputLine("+972 55 295 1235"), ) self._assert_parsing_phonenumbers(parse_test_lines_IL) def test_region_MA_monkey_patch(self): """Makes sure that patch for Morocco phone numbers work""" parse_test_lines_MA = ( self.PhoneInputOutputLine("+212 6 23 24 56 28"), self.PhoneInputOutputLine("+212603190852"), self.PhoneInputOutputLine("+212780137429"), self.PhoneInputOutputLine("+212546547649"), self.PhoneInputOutputLine("+212690979618"), ) self._assert_parsing_phonenumbers(parse_test_lines_MA) def test_region_MU_monkey_patch(self): """Makes sure that patch for Mauritius phone numbers work""" gt_MU_number = 57654321 # what national number we expect after parsing gt_MU_code = 230 # what country code we expect after parsing parse_test_lines_MU = ( self.PhoneInputOutputLine("+23057654321", gt_national_number=gt_MU_number, gt_country_code=gt_MU_code), self.PhoneInputOutputLine("+2305 76/54 3-21 ", gt_national_number=gt_MU_number, gt_country_code=gt_MU_code), self.PhoneInputOutputLine("57654321", region="MU", gt_national_number=gt_MU_number, gt_country_code=gt_MU_code), self.PhoneInputOutputLine("5 76/54 3-21 ", region="MU", gt_national_number=gt_MU_number, gt_country_code=gt_MU_code), ) self._assert_parsing_phonenumbers(parse_test_lines_MU) def test_region_KE_monkey_patch(self): """Makes sure that patch for kenyan phone numbers work""" gt_KE_number = 711123456 # what national number we expect after parsing gt_KE_code = 254 # what country code we expect after parsing parse_test_lines_KE = ( self.PhoneInputOutputLine("+254711123456", gt_national_number=gt_KE_number, gt_country_code=gt_KE_code), self.PhoneInputOutputLine("+254 711 123 456", gt_national_number=gt_KE_number, gt_country_code=gt_KE_code), self.PhoneInputOutputLine("+254-711-123-456", gt_national_number=gt_KE_number, gt_country_code=gt_KE_code), self.PhoneInputOutputLine("+254 711/123/456", gt_national_number=gt_KE_number, gt_country_code=gt_KE_code), self.PhoneInputOutputLine("0711123456", region="KE", gt_national_number=gt_KE_number, gt_country_code=gt_KE_code), self.PhoneInputOutputLine("0711 123 456", region="KE", gt_national_number=gt_KE_number, gt_country_code=gt_KE_code), self.PhoneInputOutputLine("0711-123-456", region="KE", gt_national_number=gt_KE_number, gt_country_code=gt_KE_code), self.PhoneInputOutputLine("0711/123/456", region="KE", gt_national_number=gt_KE_number, gt_country_code=gt_KE_code), ) self._assert_parsing_phonenumbers(parse_test_lines_KE) def test_region_PA_monkey_patch(self): """Makes sure that patch for Panama's phone numbers work""" parse_test_lines_PA = ( self.PhoneInputOutputLine("6198 5462", "PA", gt_country_code=507), self.PhoneInputOutputLine("+507 833 8744", gt_national_number=8338744), ) self._assert_parsing_phonenumbers(parse_test_lines_PA) def test_region_SN_monkey_patch(self): """Makes sure that patch for Senegalese phone numbers work""" parse_test_lines_SN = ( self.PhoneInputOutputLine("+221750142092"), self.PhoneInputOutputLine("+22176 707 0065"), ) self._assert_parsing_phonenumbers(parse_test_lines_SN)