import unittest from .production_preflight import config_errors, source_errors def valid_config(): digest = '1' * 64 values = { 'PRODUCTION_DEPLOY_ENABLED': 'approved', 'PRODUCTION_INPUTS_APPROVED': 'approved', 'PRODUCTION_SECURITY_REVIEW_APPROVED': 'approved', 'PRODUCTION_RESTORE_REHEARSED': 'approved', 'API_IMAGE': 'registry.example.com/dropstar/dtf-api', 'WEB_IMAGE': 'registry.example.com/dropstar/dtf-web', 'IMAGE_TAG': 'latest', 'POSTGRES_IMAGE': f'postgres@sha256:{digest}', 'CLAMAV_IMAGE': f'clamav/clamav@sha256:{digest}', 'PUBLIC_ORIGIN': 'https://dtf.example.com', 'PUBLIC_HOST': 'dtf.example.com', 'KANBAN_HOST': 'kanban-dtf.example.com', 'SITE_PORT': '8080', 'KANBAN_PORT': '8081', 'R2_ENDPOINT': 'https://account.r2.cloudflarestorage.com', 'R2_PUBLIC_ENDPOINT': 'https://account.r2.cloudflarestorage.com', 'R2_BUCKET': 'dtf-production-artwork', 'POSTGRES_DB': 'dtf', 'POSTGRES_USER': 'dtf_admin', 'APP_DB_USER': 'dtf_app', 'POSTGRES_VOLUME': 'dtf-postgres-data', 'OPERATOR_USER': 'dtf-operator', 'STORAGE_QUOTA_BYTES': '53687091200', 'OWNER_UPLOAD_QUOTA_BYTES': '10737418240', 'MAX_UPLOAD_BYTES': '5368709120', 'UPLOAD_PART_BYTES': '8388608', 'MAX_PENDING_UPLOADS': '10', 'SCAN_MAX_BYTES': '134217728', 'PAYMENT_ADAPTER': 'mercado-pago', 'FREIGHT_ADAPTER': 'approved-freight', 'TINY_ADAPTER': 'tiny-olist', 'WHATSAPP_ADAPTER': 'approved-whatsapp', } for name in ( 'DATABASE_URL_SECRET', 'DATABASE_ADMIN_URL_SECRET', 'DB_ADMIN_PASSWORD_SECRET', 'APP_DB_PASSWORD_SECRET', 'R2_ACCESS_KEY_ID_SECRET', 'R2_SECRET_ACCESS_KEY_SECRET', 'OPERATOR_PASSWORD_SECRET', 'PAYMENT_TOKEN_SECRET', 'PAYMENT_WEBHOOK_SECRET', 'TINY_TOKEN_SECRET', 'WHATSAPP_TOKEN_SECRET', ): values[name] = 'dtf_prod_' + name.lower() return values class ProductionPreflightTests(unittest.TestCase): def test_structurally_complete_metadata_passes(self): self.assertEqual(config_errors(valid_config()), []) def test_mutable_images_and_fake_adapters_fail(self): values = valid_config() values['API_IMAGE'] = 'registry.example.com/dropstar/dtf-api:latest' values['PAYMENT_ADAPTER'] = 'fake' errors = config_errors(values) self.assertTrue(any('API_IMAGE' in error for error in errors)) self.assertTrue(any('PAYMENT_ADAPTER' in error for error in errors)) def test_image_tag_and_public_ports_are_validated(self): values = valid_config() values['IMAGE_TAG'] = 'main' values['KANBAN_PORT'] = values['SITE_PORT'] errors = config_errors(values) self.assertTrue(any('IMAGE_TAG' in error for error in errors)) self.assertTrue(any('must differ' in error for error in errors)) def test_approval_and_secret_name_are_enforced(self): values = valid_config() values['PRODUCTION_DEPLOY_ENABLED'] = 'yes' values['DATABASE_URL_SECRET'] = '../unsafe' errors = config_errors(values) self.assertTrue(any('PRODUCTION_DEPLOY_ENABLED' in error for error in errors)) self.assertTrue(any('DATABASE_URL_SECRET' in error for error in errors)) def test_current_application_is_explicitly_blocked(self): errors = source_errors() self.assertTrue(any('local/adapters.py remains local-only' in error for error in errors)) self.assertTrue(any('local/app.py remains local-only' in error for error in errors)) def test_secret_reuse_and_incoherent_limits_are_rejected(self): values = valid_config() values['PAYMENT_TOKEN_SECRET'] = values['TINY_TOKEN_SECRET'] values['OWNER_UPLOAD_QUOTA_BYTES'] = str(int(values['STORAGE_QUOTA_BYTES']) + 1) values['SCAN_MAX_BYTES'] = str(int(values['MAX_UPLOAD_BYTES']) + 1) errors = config_errors(values) self.assertTrue(any('distinct external Swarm secret' in error for error in errors)) self.assertIn('OWNER_UPLOAD_QUOTA_BYTES cannot exceed STORAGE_QUOTA_BYTES', errors) self.assertIn('SCAN_MAX_BYTES cannot exceed MAX_UPLOAD_BYTES', errors) if __name__ == '__main__': unittest.main()