from datetime import datetime, timedelta from pathlib import Path from requests import exceptions as requests_exceptions from app.extensions import db from app.models.audit_log import AuditLog from app.models.company import Company from app.models.invoice import MailDelivery, NotificationLog, SyncEvent from app.models.setting import AppSetting from app.models.sync_log import SyncLog from app.services.ksef_service import KSeFService, RequestsKSeFAdapter class DummyResponse: def __init__(self, payload, status_code=200): self._payload = payload self.status_code = status_code self.content = b'{}' self.text = '{}' def json(self): return self._payload def test_admin_system_data_page(auth_client, monkeypatch): from app.services.ceidg_service import CeidgService monkeypatch.setattr(CeidgService, 'diagnostics', lambda self: { 'status': 'ok', 'message': 'HTTP 200', 'environment': 'test', 'url': 'https://test.example/ceidg', 'sample': {'ok': True}, 'technical_details': None, }) response = auth_client.get('/admin/system-data') body = response.get_data(as_text=True) assert response.status_code == 200 assert 'Dane systemowe' in body assert 'Połączenie KSeF' in body assert 'Połączenie CEIDG' in body assert 'Proces i health systemu' in body assert 'Użytkownicy' in body assert 'Typ bazy:' in body assert 'Silnik SQLAlchemy:' in body assert 'Diagnoza' not in body assert 'Co sprawdzić' not in body def test_admin_health_redirects_to_system_data(auth_client): response = auth_client.get('/admin/health', follow_redirects=False) assert response.status_code == 302 assert '/admin/system-data' in response.headers['Location'] def test_ksef_diagnostics_mock(app): with app.app_context(): company = Company.query.first() data = KSeFService(company_id=company.id).diagnostics() assert data['status'] == 'mock' assert data['base_url'] == 'mock://ksef' assert 'documentExample' in data['sample'] def test_ksef_diagnostics_requests_adapter(app, monkeypatch): with app.app_context(): company = Company.query.first() AppSetting.set(f'company.{company.id}.ksef.mock_mode', 'false') db.session.commit() monkeypatch.setattr(RequestsKSeFAdapter, '_request', lambda self, method, path, params=None, json=None: {'status': 'healthy', 'version': 'demo'}) data = KSeFService(company_id=company.id).diagnostics() assert data['status'] == 'ok' assert data['sample']['status'] == 'healthy' assert data['base_url'].startswith('https://') def test_ceidg_diagnostics_timeout(app, monkeypatch): from app.services.ceidg_service import CeidgService def raise_timeout(*args, **kwargs): raise requests_exceptions.ConnectTimeout('connect timeout') monkeypatch.setattr('app.services.ceidg_service.requests.get', raise_timeout) with app.app_context(): AppSetting.set('ceidg.environment', 'test') AppSetting.set('ceidg.api_key', 'diagnostic-key', encrypt=True) db.session.commit() data = CeidgService().diagnostics() assert data['status'] == 'error' assert 'Timeout' in data['message'] assert data['environment'] == 'test' def test_cleanup_logs_removes_old_records(app, auth_client): with app.app_context(): old_dt = datetime.utcnow() - timedelta(days=120) company = Company.query.first() invoice = company.invoices.first() if invoice is None: from app.models.invoice import Invoice, InvoiceType, InvoiceStatus invoice = Invoice(company_id=company.id, ksef_number='X1', invoice_number='FV/1', contractor_name='Test', issue_date=old_dt.date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW) db.session.add(invoice) db.session.flush() db.session.add(AuditLog(action='old', target_type='system', created_at=old_dt, updated_at=old_dt)) db.session.add(SyncLog(sync_type='manual', status='done', started_at=old_dt, company_id=company.id, created_at=old_dt, updated_at=old_dt)) db.session.add(NotificationLog(invoice_id=invoice.id, channel='mail', status='done', created_at=old_dt, updated_at=old_dt)) db.session.add(MailDelivery(invoice_id=invoice.id, recipient='a@example.com', status='sent', created_at=old_dt, updated_at=old_dt)) db.session.add(SyncEvent(invoice_id=invoice.id, status='ok', created_at=old_dt, updated_at=old_dt)) db.session.commit() response = auth_client.post('/admin/logs/cleanup', data={'days': 90}, follow_redirects=True) assert response.status_code == 200 body = response.get_data(as_text=True) assert 'Usunięto stare logi starsze niż 90 dni' in body with app.app_context(): assert AuditLog.query.filter_by(action='old').count() == 0 assert SyncLog.query.filter_by(sync_type='manual').count() == 0 assert NotificationLog.query.count() == 0 assert MailDelivery.query.count() == 0 def test_database_backup_download(auth_client, app): response = auth_client.post('/admin/database/backup') assert response.status_code == 200 assert 'attachment;' in response.headers.get('Content-Disposition', '') assert 'db_backup_' in response.headers.get('Content-Disposition', '') with app.app_context(): backup_dir = Path(app.config['BACKUP_PATH']) assert any(path.name.startswith('db_backup_') for path in backup_dir.iterdir()) def test_clear_mock_data_removes_legacy_mock_invoices(app, auth_client): from app.models.invoice import Invoice, InvoiceStatus, InvoiceType with app.app_context(): company = Company.query.first() company_id = company.id rows = [ Invoice(company_id=company_id, ksef_number='KSEF/MOCK/C1/2026/10001', invoice_number='FV/1/001/2026', contractor_name='Firma 1-1', issue_date=datetime.utcnow().date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW, source='ksef', issued_status='received', external_metadata={'source': 'mock', 'sequence': 1, 'company_id': company_id}), Invoice(company_id=company_id, ksef_number='KSEF/MOCK/C1/2026/10002', invoice_number='FV/1/002/2026', contractor_name='Firma 1-2', issue_date=datetime.utcnow().date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW, source='ksef', issued_status='received', external_metadata={'source': 'mock', 'sequence': 2, 'company_id': company_id}), Invoice(company_id=company_id, ksef_number='KSEF/MOCK/C1/2026/10003', invoice_number='FV/1/003/2026', contractor_name='Firma 1-3', issue_date=datetime.utcnow().date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW, source='ksef', issued_status='received', external_metadata={'source': 'mock', 'sequence': 3, 'company_id': company_id}), Invoice(company_id=company_id, ksef_number='KSEF/MOCK/C1/2026/10004', invoice_number='FV/1/004/2026', contractor_name='Firma 1-4', issue_date=datetime.utcnow().date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW, source='ksef', issued_status='received', external_metadata={'source': 'mock', 'sequence': 4, 'company_id': company_id}), Invoice(company_id=company_id, ksef_number='KSEF/MOCK/C1/2026/10005', invoice_number='FV/1/005/2026', contractor_name='Firma 1-5', issue_date=datetime.utcnow().date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW, source='ksef', issued_status='received', external_metadata={'source': 'mock', 'sequence': 5, 'company_id': company_id}), Invoice(company_id=company_id, ksef_number='PENDING/FV/2026/03/0002', invoice_number='FV/2026/03/0002', contractor_name='aaaa', issue_date=datetime.utcnow().date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW, source='issued', issued_status='draft', external_metadata={}), Invoice(company_id=company_id, ksef_number='NFZ-PENDING/FV/2026/03/0003', invoice_number='FV/2026/03/0003', contractor_name='NFZ', issue_date=datetime.utcnow().date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW, source='nfz', issued_status='draft', external_metadata={'nfz': {'x': 1}}), Invoice(company_id=company_id, ksef_number='KSEF/MOCK/ISSUED/C1/2026/1773052148', invoice_number='FV/2026/03/0004', contractor_name='aaaa', issue_date=datetime.utcnow().date(), net_amount=1, vat_amount=0.23, gross_amount=1.23, invoice_type=InvoiceType.SALE, status=InvoiceStatus.NEW, source='issued', issued_status='issued_mock', external_metadata={}), ] db.session.add_all(rows) AppSetting.set(f'company.{company_id}.ksef.mock_mode', 'true') db.session.commit() assert Invoice.query.count() == 8 assert Invoice.query.filter(Invoice.ksef_number.like('%MOCK%')).count() == 6 response = auth_client.post('/admin/mock-data/clear', follow_redirects=True) assert response.status_code == 200 body = response.get_data(as_text=True) assert 'Usunięto dane mock: faktury 6' in body with app.app_context(): remaining_numbers = [row[0] for row in db.session.query(Invoice.invoice_number).order_by(Invoice.id).all()] assert remaining_numbers == ['FV/2026/03/0002', 'FV/2026/03/0003'] assert AppSetting.get(f'company.{company_id}.ksef.mock_mode') == 'false' def test_save_ceidg_settings_persists_api_key(app, auth_client): long_api_key = 'A1B2C3D4' * 32 response = auth_client.post( '/admin/ceidg/save', data={'environment': 'test', 'api_key': long_api_key}, follow_redirects=True, ) assert response.status_code == 200 with app.app_context(): assert AppSetting.get('ceidg.environment') == 'test' assert AppSetting.get('ceidg.api_key', decrypt=True) == long_api_key stored = AppSetting.query.filter_by(key='ceidg.api_key').first() assert stored is not None assert stored.is_encrypted is True assert stored.value != long_api_key