190 lines
10 KiB
Python
190 lines
10 KiB
Python
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
|