import os import sys import unittest from unittest import mock sys.path.insert(0, os.path.dirname(__file__)) class TestPinVaultSecurity(unittest.TestCase): def test_api_key_required(self): import app self.assertTrue(hasattr(app, 'require_api_key')) self.assertTrue(len(app.API_KEY) >= 32) def test_rate_limit_exists(self): import app self.assertTrue(hasattr(app, '_check_pin_rate_limit')) self.assertEqual(app._PIN_MAX_ATTEMPTS, 5) self.assertEqual(app._PIN_LOCKOUT_SECONDS, 900) def test_rate_limit_blocks_after_max(self): import app app._pin_attempt_locks.clear() for i in range(app._PIN_MAX_ATTEMPTS): allowed, _ = app._check_pin_rate_limit(999) self.assertTrue(allowed, f"Attempt {i+1} should be allowed") allowed, remaining = app._check_pin_rate_limit(999) self.assertFalse(allowed, "Should be locked out after max attempts") self.assertGreater(remaining, 0) app._pin_attempt_locks.clear() def test_rate_limit_resets_on_success(self): import app app._pin_attempt_locks.clear() for i in range(3): app._check_pin_rate_limit(999) app._reset_pin_rate_limit(999) allowed, _ = app._check_pin_rate_limit(999) self.assertTrue(allowed, "Should allow after reset") app._pin_attempt_locks.clear() def test_nas_backup_configurable(self): import app self.assertEqual(app.NAS_BACKUP_DIR, os.environ.get("PINVAULT_NAS_BACKUP_DIR", "")) def test_bootstrap_returns_bool(self): import app self.assertIsInstance(app._bootstrap_ok, bool) def test_no_sys_exit_in_bootstrap(self): import inspect, app src = inspect.getsource(app.bootstrap) self.assertNotIn("sys.exit", src, "bootstrap must not call sys.exit") if __name__ == "__main__": unittest.main()