field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

test_package_privacy.py (4467B)


      1 from __future__ import annotations
      2 
      3 import copy
      4 import sys
      5 import unittest
      6 from pathlib import Path
      7 
      8 SCRIPTS = Path(__file__).resolve().parent
      9 if str(SCRIPTS) not in sys.path:
     10     sys.path.insert(0, str(SCRIPTS))
     11 
     12 import package_contract as contract  # noqa: E402
     13 import package_privacy as privacy  # noqa: E402
     14 
     15 
     16 class PrivacyContractTests(unittest.TestCase):
     17     def test_real_manifest_and_permission_contact_inputs_match_the_product(
     18         self,
     19     ) -> None:
     20         root = SCRIPTS.parent
     21         contract._validate_privacy(
     22             contract._read_plist(root / "Tera/Resources/PrivacyInfo.xcprivacy")
     23         )
     24         contract._validate_app_plist(contract._read_plist(root / "Tera/Info.plist"))
     25 
     26     def test_tracking_and_linkage_cannot_hide_behind_numeric_boolean_aliases(
     27         self,
     28     ) -> None:
     29         for numeric in [0, 0.0, 1, 1.0]:
     30             for target in ["tracking", "linked", "entry_tracking"]:
     31                 with self.subTest(numeric=numeric, target=target):
     32                     value = privacy.manifest()
     33                     if target == "tracking":
     34                         value["NSPrivacyTracking"] = numeric
     35                     elif target == "linked":
     36                         value["NSPrivacyCollectedDataTypes"][0][
     37                             "NSPrivacyCollectedDataTypeLinked"
     38                         ] = numeric
     39                     else:
     40                         value["NSPrivacyCollectedDataTypes"][0][
     41                             "NSPrivacyCollectedDataTypeTracking"
     42                         ] = numeric
     43                     with self.assertRaises(contract.PackageContractError):
     44                         contract._validate_privacy(value)
     45 
     46     def test_missing_data_type_reason_or_tracking_declaration_fails_closed(
     47         self,
     48     ) -> None:
     49         base = privacy.manifest()
     50         for key in base:
     51             with self.subTest(key=key):
     52                 value = copy.deepcopy(base)
     53                 del value[key]
     54                 with self.assertRaises(contract.PackageContractError):
     55                     contract._validate_privacy(value)
     56         for inventory in ["NSPrivacyCollectedDataTypes", "NSPrivacyAccessedAPITypes"]:
     57             for index in range(len(base[inventory])):
     58                 with self.subTest(inventory=inventory, index=index):
     59                     value = copy.deepcopy(base)
     60                     value[inventory].pop(index)
     61                     with self.assertRaises(contract.PackageContractError):
     62                         contract._validate_privacy(value)
     63 
     64     def test_duplicate_foreign_purpose_or_unreviewed_api_reason_fails_closed(
     65         self,
     66     ) -> None:
     67         for inventory in ["NSPrivacyCollectedDataTypes", "NSPrivacyAccessedAPITypes"]:
     68             value = privacy.manifest()
     69             value[inventory].append(copy.deepcopy(value[inventory][0]))
     70             with self.assertRaises(contract.PackageContractError):
     71                 contract._validate_privacy(value)
     72         for kind, key, replacement in [
     73             (
     74                 "NSPrivacyCollectedDataTypes",
     75                 "NSPrivacyCollectedDataTypePurposes",
     76                 ["NSPrivacyCollectedDataTypePurposeAnalytics"],
     77             ),
     78             (
     79                 "NSPrivacyAccessedAPITypes",
     80                 "NSPrivacyAccessedAPITypeReasons",
     81                 ["3B52.1"],
     82             ),
     83         ]:
     84             value = privacy.manifest()
     85             value[kind][0][key] = replacement
     86             with self.assertRaises(contract.PackageContractError):
     87                 contract._validate_privacy(value)
     88 
     89     def test_stale_permission_and_foreign_contact_cannot_pass_nonempty_string_checks(
     90         self,
     91     ) -> None:
     92         root = SCRIPTS.parent
     93         base = contract._read_plist(root / "Tera/Info.plist")
     94         for key, bad in [
     95             ("NSCameraUsageDescription", "Add a farm photo"),
     96             ("TeraSupportEmail", "operator@example.invalid"),
     97             ("TeraSupportEmail", None),
     98         ]:
     99             value = {**base, key: bad}
    100             with self.assertRaises(contract.PackageContractError):
    101                 contract._validate_app_plist(value)
    102 
    103     def test_unsupported_or_nonfinite_values_return_a_stable_rejection(self) -> None:
    104         for value in [b"private", float("nan"), float("inf")]:
    105             document = privacy.manifest()
    106             document["unexpected"] = value
    107             with self.assertRaises(contract.PackageContractError):
    108                 contract._validate_privacy(document)