Repository: joke2k/faker

Issue:
states_postcode fails with territories
* Faker version: since 15.1.2
* OS:  All

Brief summary of the issue goes here.
en_US state fix in 15.1.2 added territories, which are not in states_postcode

Looking up postcode by state fails with territories enabled, but didn't fail prior to 15.1.2.

The fix made here:
  https://github.com/joke2k/faker/blob/master/faker/providers/address/en_US/__init__.py#L496
Combined with the default to include territories here:
  https://github.com/joke2k/faker/blob/master/faker/providers/address/en_US/__init__.py#L488
Causes this to fail whereas it did not prior to the correction made in 15.1.2.
  https://github.com/joke2k/faker/blob/master/faker/providers/address/en_US/__init__.py#L505

### Steps to reproduce

1. fake.add_provider(providers.address.en_US.Provider)
2. state = fake.state_abbr()  # include_territories=True is default.  user can fix by setting to False?
3. repeatedly call fake.zipcode_in_state(state)  # or alternatively pass in GU or other territory

### Expected behavior
Ideally this would not give an error when combining these functions.
It looks like territories to have zip codes, so best solution may be to update the state/postalcode mapping.

--> from internet:  "Within the U.S. Territories, American Samoa (postal abbreviation AS) uses zip code 96799, and Guam (postal abbreviation GU) uses zip codes in the range 96910–96932."

### Actual behavior
Fails on Step 3 noted above.



Discussion:
Related PR is https://github.com/joke2k/faker/pull/1743. Would you mind submitting a PR with a fix and ideally a corresponding test which ensures that this issue will indeed not occur?
Thanks for reviewing.  I will work on PR with tests after reading the contributing guidelines.

Resolving patch:
diff --git a/faker/providers/address/en_US/__init__.py b/faker/providers/address/en_US/__init__.py
--- a/faker/providers/address/en_US/__init__.py
+++ b/faker/providers/address/en_US/__init__.py
@@ -419,6 +419,15 @@ class Provider(AddressProvider):
         "WV": (24701, 26886),
         "WI": (53001, 54990),
         "WY": (82001, 83128),
+        # Territories - incomplete ranges with accurate subsets - https://www.geonames.org/postalcode-search.html
+        "AS": (96799, 96799),
+        "FM": (96941, 96944),
+        "GU": (96910, 96932),
+        "MH": (96960, 96970),
+        "MP": (96950, 96952),
+        "PW": (96940, 96940),
+        "PR": (600, 799),
+        "VI": (801, 805),
     }
 
     territories_abbr = (
@@ -511,7 +520,7 @@ def postcode_in_state(self, state_abbr: Optional[str] = None) -> str:
         if state_abbr is None:
             state_abbr = self.random_element(self.states_abbr)
 
-        if state_abbr in self.states_abbr:
+        if state_abbr in self.states_and_territories_abbr:
             postcode = "%d" % (
                 self.generator.random.randint(
                     self.states_postcode[state_abbr][0],
@@ -519,8 +528,12 @@ def postcode_in_state(self, state_abbr: Optional[str] = None) -> str:
                 )
             )
 
-            if len(postcode) == 4:
-                postcode = "0%s" % postcode
+            # zero left pad up until desired length (some have length 3 or 4)
+            target_postcode_len = 5
+            current_postcode_len = len(postcode)
+            if current_postcode_len < target_postcode_len:
+                pad = target_postcode_len - current_postcode_len
+                postcode = f"{'0'*pad}{postcode}"
 
             return postcode
 


Test patch:
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -536,7 +536,7 @@ def test_postcode(self, faker, num_samples):
 
     def test_postcode_in_state(self, faker, num_samples):
         for _ in range(num_samples):
-            for state_abbr in EnUsAddressProvider.states_abbr:
+            for state_abbr in EnUsAddressProvider.states_and_territories_abbr:
                 code = faker.postcode_in_state(state_abbr)
                 assert re.fullmatch(r"\d{5}", code)
                 assert int(code) >= EnUsAddressProvider.states_postcode[state_abbr][0]
@@ -553,7 +553,7 @@ def test_zipcode(self, faker, num_samples):
 
     def test_zipcode_in_state(self, faker, num_samples):
         for _ in range(num_samples):
-            for state_abbr in EnUsAddressProvider.states_abbr:
+            for state_abbr in EnUsAddressProvider.states_and_territories_abbr:
                 code = faker.zipcode_in_state(state_abbr)
                 assert re.fullmatch(r"\d{5}", code)
                 assert int(code) >= EnUsAddressProvider.states_postcode[state_abbr][0]