Files
inbuxa-server/tools/fork/tests/test_privacy_check.py
T
jcoffey-dev 966241070e
ci / fork-checks (pull_request) Skipped
ci / build (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 6m29s
Name Coffey Labs LLC as the copyright holder
Coffey Labs is now Coffey Labs LLC, an Arizona limited liability company. Copyright lines and SPDX-FileCopyrightText headers naming Coffey Labs or John Coffey now name Coffey Labs LLC. Upstream copyright notices are unchanged.
2026-10-05 23:33:00 -07:00

178 lines
6.9 KiB
Python

# SPDX-FileCopyrightText: 2026 Coffey Labs LLC
# SPDX-License-Identifier: AGPL-3.0-only
"""Tests for tools/fork/privacy-check.py: python3 -m unittest discover tools/fork/tests"""
import gzip
import importlib.util
import json
import os
import tempfile
import tomllib
import unittest
HERE = os.path.dirname(os.path.abspath(__file__))
spec = importlib.util.spec_from_file_location('privacy_check', os.path.join(HERE, '..', 'privacy-check.py'))
check = importlib.util.module_from_spec(spec)
spec.loader.exec_module(check)
VOCAB = """
[vocabulary]
categories = ["identifier", "contact", "network", "content", "metadata", "credential"]
whose = ["holder", "correspondent", "administrator"]
where = ["data-store", "blob-store", "search-store", "in-memory-store", "memory", "log-file", "external"]
scope = ["tenant", "server"]
retention = ["unbounded", "object-life", "receiver"]
"""
GOOD = VOCAB + """
[object."x:Widget"]
default = "none"
whose = ["holder"]
where = ["data-store"]
scope = "tenant"
retention = { setting = "x:Widget.keepFor" }
[object."x:Widget".properties]
owner = ["identifier"]
[object."inbuxa:Gadget"]
file = "inbuxa_gadget.rs"
default = "none"
[object."inbuxa:Gadget".properties]
remoteIp = ["network"]
[source."widget-log"]
categories = ["network"]
where = ["log-file"]
scope = "server"
retention = "unbounded"
enabled_by = ["x:Widget.enable", "inbuxa:Gadget.remoteIp"]
leaves_host = false
written_by = ["crates/widget.rs"]
"""
def make_tree(fields):
root = tempfile.mkdtemp()
os.makedirs(os.path.join(root, 'resources/schema'))
with gzip.open(os.path.join(root, check.SCHEMA), 'wt') as f:
json.dump({'fields': fields}, f)
os.makedirs(os.path.join(root, check.OBJECTS_DIR))
with open(os.path.join(root, check.OBJECTS_DIR, 'inbuxa_gadget.rs'), 'w') as f:
f.write('Property::RemoteIp => "remoteIp",\nProperty::Id => "id",\n')
os.makedirs(os.path.join(root, 'crates/jmap-proto/src/request'), exist_ok=True)
with open(os.path.join(root, check.METHODS), 'w') as f:
f.write('"inbuxa:Gadget" => MethodObject::Gadget,\n')
with open(os.path.join(root, 'crates/widget.rs'), 'w') as f:
f.write('')
return root
def prop(type_, fmt=None):
t = {'type': type_}
if fmt:
t['format'] = fmt
return {'type': t}
FIELDS = {
'x:Widget': {'properties': {
'owner': prop('string', 'emailAddress'),
'enable': prop('boolean'),
'keepFor': prop('number', 'duration'),
'label': prop('string', 'string'),
}},
}
class PrivacyCheck(unittest.TestCase):
def run_check(self, catalog, fields=FIELDS):
return check.findings(make_tree(fields), tomllib.loads(catalog))
def test_the_repository_passes(self):
with open(os.path.join(check.ROOT, check.CATALOG), 'rb') as f:
catalog = tomllib.load(f)
self.assertEqual(check.findings(check.ROOT, catalog), [])
def test_a_consistent_catalog_passes(self):
self.assertEqual(self.run_check(GOOD), [])
def test_an_unclassified_object_fails(self):
fields = dict(FIELDS, **{'x:Sprocket': {'properties': {'size': prop('number', 'size')}}})
found = self.run_check(GOOD, fields)
self.assertEqual(found, ['x:Sprocket: schema object has no catalog entry'])
def test_an_address_hidden_behind_the_default_fails(self):
fields = {'x:Widget': {'properties': dict(FIELDS['x:Widget']['properties'],
contactEmail=prop('string', 'emailAddress'))}}
found = self.run_check(GOOD, fields)
self.assertEqual(found, ['x:Widget.contactEmail: typed as identifier but not listed'])
def test_a_secret_in_a_set_or_object_counts(self):
fields = {'x:Widget': {'properties': dict(
FIELDS['x:Widget']['properties'],
ips={'type': {'type': 'set', 'class': {'type': 'string', 'format': 'ipNetwork'}}},
key={'type': {'type': 'object', 'objectName': 'x:SecretKey'}},
)}}
found = self.run_check(GOOD, fields)
self.assertIn('x:Widget.ips: typed as network but not listed', found)
self.assertIn('x:Widget.key: typed as credential but not listed', found)
def test_a_stale_property_fails(self):
found = self.run_check(GOOD.replace('owner = ["identifier"]', 'owner = ["identifier"]\ngone = ["content"]'))
self.assertEqual(found, ['x:Widget.gone: no such property (stale)'])
def test_a_stale_object_fails(self):
found = self.run_check(GOOD + '\n[object."x:Removed"]\ndefault = "none"\n')
self.assertEqual(found, ['x:Removed: no such schema object (stale)'])
def test_a_stale_setting_fails(self):
found = self.run_check(GOOD.replace('x:Widget.keepFor', 'x:Widget.keepForever'))
self.assertEqual(found, ['x:Widget: setting x:Widget.keepForever names no property of x:Widget'])
def test_a_stale_code_path_fails(self):
found = self.run_check(GOOD.replace('crates/widget.rs', 'crates/gone.rs'))
self.assertEqual(found, ['source "widget-log": written_by crates/gone.rs doesn\'t exist (stale)'])
def test_an_unlisted_inbuxa_object_fails(self):
catalog = VOCAB + """
[object."x:Widget"]
default = "none"
[object."x:Widget".properties]
owner = ["identifier"]
"""
found = self.run_check(catalog)
self.assertEqual(found, ['inbuxa:Gadget: inbuxa object has no catalog entry'])
def test_words_outside_the_vocabulary_fail(self):
found = self.run_check(GOOD.replace('owner = ["identifier"]', 'owner = ["personal"]'))
self.assertEqual(found, ['x:Widget.owner: "personal" is not in vocabulary.categories'])
def test_unlisted_prints_a_starting_entry(self):
fields = dict(FIELDS, **{'x:Sprocket': {'properties': {'mail': prop('string', 'emailAddress')}}})
lines = check.unlisted(make_tree(fields), tomllib.loads(GOOD))
self.assertIn('[object."x:Sprocket"]', lines)
self.assertIn('mail = ["identifier"]', lines)
class StripReport(unittest.TestCase):
def test_an_import_reports_what_is_new_and_unclassified(self):
import pathlib
strip_spec = importlib.util.spec_from_file_location('strip', os.path.join(HERE, '..', 'strip.py'))
strip = importlib.util.module_from_spec(strip_spec)
strip_spec.loader.exec_module(strip)
with gzip.open(os.path.join(check.ROOT, check.SCHEMA)) as f:
upstream = json.load(f)
upstream['fields']['x:NewThing'] = {'properties': {}}
upstream['fields']['x:UserAccount']['properties']['backupEmail'] = prop('string', 'emailAddress')
tree = pathlib.Path(tempfile.mkdtemp())
(tree / 'resources/schema').mkdir(parents=True)
with gzip.open(tree / 'resources/schema/schema.json.gz', 'wt') as f:
json.dump(upstream, f)
self.assertEqual(strip.privacy_flags(tree), {
'objects': ['x:NewThing'],
'fields': ['x:UserAccount.backupEmail (identifier)'],
})
if __name__ == '__main__':
unittest.main()