"""Synthetic offline checks; no key files or Firebase requests are needed.""" import os import stat from types import SimpleNamespace import unittest from unittest.mock import patch import push_preflight class PushPreflightTests(unittest.TestCase): account = 'metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com' def setUp(self): env = patch.dict(os.environ, { 'PUSH_ENABLED': 'true', 'FIREBASE_PROJECT_ID': 'metalcircle-30d9b', 'GOOGLE_APPLICATION_CREDENTIALS': '/run/secrets/firebase-service-account.json', }) env.start() self.addCleanup(env.stop) for target, value in ( ('push_preflight.Path.stat', SimpleNamespace(st_mode=stat.S_IFREG | 0o600)), ('push_preflight.os.statvfs', SimpleNamespace(f_flag=os.ST_RDONLY)), ('push_preflight.credentials.Certificate', SimpleNamespace( project_id='metalcircle-30d9b', service_account_email=self.account)), ): mock = patch(target, return_value=value) mock.start() self.addCleanup(mock.stop) def test_expected_read_only_preprod_credential_passes(self): push_preflight.check(self.account) def test_rejects_local_account_even_in_the_same_project(self): with patch('push_preflight.credentials.Certificate', return_value=SimpleNamespace( project_id='metalcircle-30d9b', service_account_email='metalcircle-push-local@metalcircle-30d9b.iam.gserviceaccount.com', )): with self.assertRaisesRegex(push_preflight.PreflightError, '^credential_service_account_mismatch$'): push_preflight.check(self.account) def test_rejects_wrong_project(self): with patch.dict(os.environ, {'FIREBASE_PROJECT_ID': 'wrong-project'}): with self.assertRaisesRegex(push_preflight.PreflightError, '^credential_project_mismatch$'): push_preflight.check(self.account) def test_rejects_missing_config_or_disabled_push(self): for values, code in ( ({'PUSH_ENABLED': 'false'}, 'push_disabled'), ({'FIREBASE_PROJECT_ID': ''}, 'project_missing'), ({'GOOGLE_APPLICATION_CREDENTIALS': '/app/key.json'}, 'container_path_mismatch'), ): with self.subTest(code=code), patch.dict(os.environ, values): with self.assertRaisesRegex(push_preflight.PreflightError, '^' + code + '$'): push_preflight.check(self.account) def test_rejects_broad_permissions_and_directory_mounts(self): for mode, code in ( (stat.S_IFREG | 0o644, 'credential_permissions_too_broad'), (stat.S_IFDIR | 0o700, 'credential_not_a_file'), ): with self.subTest(mode=mode), patch('push_preflight.Path.stat', return_value=SimpleNamespace(st_mode=mode)): with self.assertRaisesRegex(push_preflight.PreflightError, '^' + code + '$'): push_preflight.check(self.account) def test_rejects_writable_mount(self): with patch('push_preflight.os.statvfs', return_value=SimpleNamespace(f_flag=0)): with self.assertRaisesRegex(push_preflight.PreflightError, '^credential_mount_not_read_only$'): push_preflight.check(self.account) def test_invalid_or_unreadable_credential_error_never_exposes_details(self): for target in ('push_preflight.Path.stat', 'push_preflight.credentials.Certificate'): with self.subTest(target=target), patch(target, side_effect=ValueError('sensitive SDK details')): with self.assertRaisesRegex(push_preflight.PreflightError, '^credential_missing_unreadable_or_invalid$'): push_preflight.check(self.account) def test_missing_credential_fails_without_disclosing_path_or_sdk_details(self): with patch('push_preflight.Path.stat', side_effect=FileNotFoundError('private filename')): with self.assertRaisesRegex(push_preflight.PreflightError, '^credential_missing_unreadable_or_invalid$'): push_preflight.check(self.account)