1#!/usr/bin/env python
2"""Unit tests for geocoder.py"""
3
4# Based on original Java code:
5#     java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java
6# Copyright (C) 2011 The Libphonenumber Authors
7#
8# Licensed under the Apache License, Version 2.0 (the "License");
9# you may not use this file except in compliance with the License.
10# You may obtain a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS,
16# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19
20import unittest
21
22from phonenumbers import PhoneNumber, FrozenPhoneNumber
23from phonenumbers import geocoder
24from phonenumbers.geocoder import description_for_number, country_name_for_number
25from phonenumbers.geocoder import description_for_valid_number
26from phonenumbers.prefix import _prefix_description_for_number
27from phonenumbers.util import u
28
29# Allow override library geocoding metadata with the test metadata.
30REAL_GEOCODE_DATA = geocoder.GEOCODE_DATA
31REAL_GEOCODE_LONGEST_PREFIX = geocoder.GEOCODE_LONGEST_PREFIX
32from .testgeodata import GEOCODE_DATA as TEST_GEOCODE_DATA
33from .testgeodata import GEOCODE_LONGEST_PREFIX as TEST_GEOCODE_LONGEST_PREFIX
34
35
36def reinstate_real_geodata():
37    """Reinstate real phone number geocoding metadata"""
38    geocoder.GEOCODE_DATA = REAL_GEOCODE_DATA
39    geocoder.GEOCODE_LONGEST_PREFIX = REAL_GEOCODE_LONGEST_PREFIX
40
41
42def insert_test_geodata():
43    """Insert test geocoding metadata into library"""
44    geocoder.GEOCODE_DATA = TEST_GEOCODE_DATA
45    geocoder.GEOCODE_LONGEST_PREFIX = TEST_GEOCODE_LONGEST_PREFIX
46
47
48# Set up some test numbers to re-use.
49KO_NUMBER1 = FrozenPhoneNumber(country_code=82, national_number=22123456)
50KO_NUMBER2 = FrozenPhoneNumber(country_code=82, national_number=322123456)
51KO_NUMBER3 = FrozenPhoneNumber(country_code=82, national_number=6421234567)
52KO_INVALID_NUMBER = FrozenPhoneNumber(country_code=82, national_number=1234)
53KO_MOBILE = FrozenPhoneNumber(country_code=82, national_number=101234567)
54US_NUMBER1 = FrozenPhoneNumber(country_code=1, national_number=6502530000)
55US_NUMBER2 = FrozenPhoneNumber(country_code=1, national_number=6509600000)
56US_NUMBER3 = FrozenPhoneNumber(country_code=1, national_number=2128120000)
57US_NUMBER4 = FrozenPhoneNumber(country_code=1, national_number=6174240000)
58US_INVALID_NUMBER = FrozenPhoneNumber(country_code=1, national_number=123456789)
59NANPA_TOLL_FREE = FrozenPhoneNumber(country_code=1, national_number=8002431234)
60BS_NUMBER1 = FrozenPhoneNumber(country_code=1, national_number=2423651234)
61AU_NUMBER = FrozenPhoneNumber(country_code=61, national_number=236618300)
62AR_MOBILE_NUMBER = FrozenPhoneNumber(country_code=54, national_number=92214000000)
63NUMBER_WITH_INVALID_COUNTRY_CODE = FrozenPhoneNumber(country_code=999, national_number=2423651234)
64INTERNATIONAL_TOLL_FREE = FrozenPhoneNumber(country_code=800, national_number=12345678)
65
66# Language/country codes
67_CHINA = "CN"
68_CHINESE = "zh"
69_ITALIAN = "it"
70_ENGLISH = "en"
71_KOREAN = "ko"
72_GERMAN = "de"
73_FRENCH = "fr"
74_SPANISH = "es"
75_USA = "US"
76
77
78class PhoneNumberGeocoderTest(unittest.TestCase):
79    """Unit tests for geocoder.py"""
80
81    def setUp(self):
82        insert_test_geodata()
83
84    def tearDown(self):
85        reinstate_real_geodata()
86
87    def testGetDescriptionForNumberWithNoDataFile(self):
88        # No data file containing mappings for US numbers is available in Chinese for the unittests. As
89        # a result, the country name of United States in simplified Chinese is returned.
90        self.assertEqual(u("\u7F8E\u56FD"),
91                         description_for_number(US_NUMBER1, _CHINESE, region=_CHINA))
92        self.assertEqual("Bahamas",
93                         description_for_number(BS_NUMBER1, _ENGLISH, region=_USA))
94        self.assertEqual("Australia",
95                         description_for_number(AU_NUMBER, _ENGLISH, region=_USA))
96        self.assertEqual("",
97                         description_for_number(NUMBER_WITH_INVALID_COUNTRY_CODE, _ENGLISH, region=_USA))
98        self.assertEqual("",
99                         description_for_number(INTERNATIONAL_TOLL_FREE, _ENGLISH, region=_USA))
100
101    def testGetDescriptionForNumberWithMissingPrefix(self):
102        # Test that the name of the country is returned when the number passed in
103        # is valid but not covered by the geocoding data file.
104        self.assertEqual("United States",
105                         description_for_number(US_NUMBER4, _ENGLISH, region=_USA))
106
107    def testGetDescriptionForNumberBelongingToMultipleCountriesIsEmpty(self):
108        # Test that nothing is returned when the number passed in is valid but
109        # not covered by the geocoding data file and belongs to multiple
110        # countries
111        self.assertEqual("", description_for_number(NANPA_TOLL_FREE, _ENGLISH, region=_USA))
112
113    def testGetDescriptionForNumber_en_US(self):
114        self.assertEqual("CA",
115                         description_for_number(US_NUMBER1, _ENGLISH, region=_USA))
116        self.assertEqual("Mountain View, CA",
117                         description_for_number(US_NUMBER2, _ENGLISH, region=_USA))
118        self.assertEqual("New York, NY",
119                         description_for_number(US_NUMBER3, _ENGLISH, region=_USA))
120
121    def testGetDescriptionForKoreanNumber(self):
122        self.assertEqual("Seoul",
123                         description_for_number(KO_NUMBER1, _ENGLISH))
124        self.assertEqual("Incheon",
125                         description_for_number(KO_NUMBER2, _ENGLISH))
126        self.assertEqual("Jeju",
127                         description_for_number(KO_NUMBER3, _ENGLISH))
128        self.assertEqual(u("\uC11C\uC6B8"),
129                         description_for_number(KO_NUMBER1, _KOREAN))
130        self.assertEqual(u("\uC778\uCC9C"),
131                         description_for_number(KO_NUMBER2, _KOREAN))
132
133    def testGetDescriptionForArgentinianMobileNumber(self):
134        self.assertEqual("La Plata", description_for_number(AR_MOBILE_NUMBER, _ENGLISH))
135        # Python version extra test
136        # Put an invalid number after the mobile token ("9") and lie about
137        # this being a valid number
138        arInvalidMobileNumber = PhoneNumber(country_code=54, national_number=91)
139        self.assertEqual("Argentina", description_for_valid_number(arInvalidMobileNumber, _ENGLISH))
140
141    def testGetDescriptionForFallBack(self):
142        # No fallback, as the location name for the given phone number is
143        # available in the requested language.
144        self.assertEqual("Kalifornien",
145                         description_for_number(US_NUMBER1, _GERMAN))
146        # German falls back to English.
147        self.assertEqual("New York, NY",
148                         description_for_number(US_NUMBER3, _GERMAN))
149        # Italian falls back to English.
150        self.assertEqual("CA",
151                         description_for_number(US_NUMBER1, _ITALIAN))
152        # Korean doesn't fall back to English.
153        self.assertEqual(u("\uB300\uD55C\uBBFC\uAD6D"),
154                         description_for_number(KO_NUMBER3, _KOREAN))
155
156    def testGetDescriptionForNumberWithUserRegion(self):
157        # User in Italy, American number. We should just show United States, in
158        # Spanish, and not more detailed information.
159        self.assertEqual("Estados Unidos",
160                         description_for_number(US_NUMBER1, _SPANISH, region="IT"))
161        # Unknown region - should just show country name.
162        self.assertEqual("Estados Unidos",
163                         description_for_number(US_NUMBER1, _SPANISH, region="ZZ"))
164        # User in the States, language German, should show detailed data.
165        self.assertEqual("Kalifornien",
166                         description_for_number(US_NUMBER1, _GERMAN, region="US"))
167        # User in the States, language French, no data for French, so we fallback
168        # to English detailed data.
169        self.assertEqual("CA",
170                         description_for_number(US_NUMBER1, _FRENCH, region="US"))
171        # Invalid number - return an empty string.
172        self.assertEqual("", description_for_number(US_INVALID_NUMBER, _ENGLISH, region="US"))
173
174    def testGetDescriptionForInvalidNumber(self):
175        self.assertEqual("", description_for_number(KO_INVALID_NUMBER, _ENGLISH))
176        self.assertEqual("", description_for_number(US_INVALID_NUMBER, _ENGLISH))
177
178    def testGetDescriptionForNonGeographicalNumberWithGeocodingPrefix(self):
179        # We have a geocoding prefix, but we shouldn't use it since this is not geographical.
180        self.assertEqual("South Korea", description_for_number(KO_MOBILE, _ENGLISH))
181
182    def testCoverage(self):
183        # Python version extra tests
184        invalid_number = PhoneNumber(country_code=210, national_number=123456)
185        self.assertEqual("", country_name_for_number(invalid_number, "en"))
186        # Ensure we exercise all public entrypoints directly
187        self.assertEqual("CA", _prefix_description_for_number(TEST_GEOCODE_DATA, TEST_GEOCODE_LONGEST_PREFIX, US_NUMBER1, "en"))
188        self.assertEqual("CA", description_for_valid_number(US_NUMBER1, "en"))
189        self.assertEqual("", description_for_valid_number(US_INVALID_NUMBER, "en"))
190        # Add in some script and region specific fictional names
191        TEST_GEOCODE_DATA['1650960'] = {'en': u("Mountain View, CA"),
192                                        "en_GB": u("Mountain View California"),
193                                        "en_US": u("Mountain View, Sunny California"),
194                                        "en_Xyzz_US": u("MTV - xyzz"),
195                                        "en_Latn": u("MountainView")}
196        # The following test might one day return "Mountain View California"
197        self.assertEqual("United States",
198                         description_for_number(US_NUMBER2, _ENGLISH, region="GB"))
199        self.assertEqual("Mountain View, Sunny California",
200                         description_for_number(US_NUMBER2, _ENGLISH, region="US"))
201        self.assertEqual("MountainView",
202                         description_for_number(US_NUMBER2, _ENGLISH, script="Latn"))
203        self.assertEqual("United States",
204                         description_for_number(US_NUMBER2, _ENGLISH, script="Latn", region="GB"))
205        self.assertEqual("MTV - xyzz",
206                         description_for_number(US_NUMBER2, _ENGLISH, script="Xyzz", region="US"))
207        self.assertEqual("Mountain View, Sunny California",
208                         description_for_number(US_NUMBER2, _ENGLISH, script="Zazz", region="US"))
209        # Get a different result when there is a script-specific variant
210        self.assertEqual("MountainView",
211                         description_for_number(US_NUMBER2, _ENGLISH, script="Latn", region="US"))
212        TEST_GEOCODE_DATA['1650960'] = {'en': u("Mountain View, CA")}
213
214        # Test the locale mapping
215        TEST_GEOCODE_DATA['8868'] = {'zh': u("Chinese"), 'zh_Hant': u("Hant-specific")}
216        tw_number = FrozenPhoneNumber(country_code=886, national_number=810080123)
217        self.assertEqual("Hant-specific",
218                         description_for_number(tw_number, "zh", region="TW"))
219        del TEST_GEOCODE_DATA['8868']
220