from app import create_app from app.database import initialize_database, migrate_database from app.services import calculate_costs, invoice_period from datetime import datetime def make_app(tmp_path, name): return create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": f"sqlite:///{tmp_path / name}"}) def test_health(tmp_path): app=make_app(tmp_path, "test.db") assert app.test_client().get('/health').json=={"status":"ok"} def test_costs_50_percent_vat(): result=calculate_costs(123,23,50) assert float(result['net'])==100.0 assert float(result['deductible_vat'])==11.5 assert float(result['final_cost'])==111.5 def test_invoice_period(): assert invoice_period(datetime(2026,7,15),15).endswith('01–15') assert invoice_period(datetime(2026,7,16),15).endswith('16–koniec') def test_admin_settings_ajax_has_working_route(tmp_path): app=make_app(tmp_path, "admin.db") client=app.test_client() client.post('/login',data={'email':'admin@example.com','password':'admin123!'}) response=client.post('/admin',data={'action':'settings','name':'Firma','entity_type':'JDG','vat_rate':'23','vat_deduction_percent':'50','invoice_split_day':'15','invoice_split_enabled':'on','region':'mazowieckie','theme':'darkly'},headers={'X-Requested-With':'XMLHttpRequest'}) assert response.status_code==200 assert response.json['ok'] is True def test_ure_brand_aggregation(tmp_path): from app.integrations.ure import aggregate_ure_companies app=make_app(tmp_path, "ure.db") with app.app_context(): rows=[{"nazwa":"ORLEN S.A.","nip":"7740001454","regon":"610188201","nazwaStacji":"100","wojewodztwo":"mazowieckie","benzynySilnikowe":True}, {"nazwa":"ORLEN S.A.","nip":"7740001454","regon":"610188201","nazwaStacji":"101","wojewodztwo":"śląskie","olejeNapedowe":True}] result=aggregate_ure_companies(rows) assert len(result)==1 assert result[0]["brand_name"]=="ORLEN" assert result[0]["station_count"]==2 def test_ure_duplicate_dkn_is_merged(tmp_path): from app.integrations.ure import aggregate_ure_companies app=make_app(tmp_path, "dup.db") rows=[ {"dkn":977,"nazwa":"Test Sp. z o.o.","nip":"123","nazwaStacji":"Punkt paliw","infraUlica":"A","infraNrLokalu":"1","infraPoczta":"Miasto","benzynySilnikowe":True}, {"dkn":977,"nazwa":"Test Sp. z o.o.","nip":"123","nazwaStacji":"Punkt paliw i LPG","infraUlica":"A","infraNrLokalu":"1","infraPoczta":"Miasto","gazPlynnyLPG":True}, ] with app.app_context(): result=aggregate_ure_companies(rows) assert len(result)==1 assert result[0]["station_count"]==1 assert len(result[0]["points"])==1 assert result[0]["points"][0]["has_petrol"] is True assert result[0]["points"][0]["has_lpg"] is True def test_company_and_card_forms_use_post(tmp_path): app=make_app(tmp_path, "forms.db") client=app.test_client() client.post('/login',data={'email':'admin@example.com','password':'admin123!'}) html=client.get('/admin/companies').get_data(as_text=True) assert 'action="/api/companies" method="post"' in html assert 'action="/api/fuel-cards" method="post"' in html assert 'Nazwa firmy' in html assert 'Operator karty' in html def test_cannot_delete_last_admin(tmp_path): app=make_app(tmp_path, "delete.db") client=app.test_client() client.post('/api/auth/login',data={'email':'admin@example.com','password':'admin123!'}) with app.app_context(): from app.models import User admin=User.query.filter_by(email='admin@example.com').first() admin_id=admin.id response=client.delete(f'/api/users/{admin_id}') assert response.status_code == 400 assert response.json['ok'] is False def test_cli_reset_admin_password(tmp_path): app=make_app(tmp_path, "cli.db") runner=app.test_cli_runner() result=runner.invoke(args=['reset-admin-password','--email','admin@example.com','--password','NoweHaslo123!']) assert result.exit_code == 0 client=app.test_client() response=client.post('/api/auth/login',data={'email':'admin@example.com','password':'NoweHaslo123!'}) assert response.status_code == 200 def test_html_cache_headers_and_static_versioning(tmp_path): app=make_app(tmp_path, "cache.db") client=app.test_client() response=client.get('/login') assert response.headers['Cache-Control'] == 'no-store, private, must-revalidate' assert 'Pragma' not in response.headers assert 'Expires' not in response.headers html=response.get_data(as_text=True) assert '/static/css/layout.css?v=' in html assert '/static/js/core.js?v=' in html def test_css_cache_for_30_days(tmp_path): app=make_app(tmp_path, "static.db") response=app.test_client().get('/static/css/layout.css') assert response.status_code == 200 assert response.headers['Cache-Control'] == 'public, max-age=2592000, immutable' def test_create_app_initializes_database(tmp_path): from sqlalchemy import inspect from app.extensions import db app = create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": f"sqlite:///{tmp_path / 'empty.db'}"}) with app.app_context(): tables = inspect(db.engine).get_table_names() assert "user" in tables assert "schema_migration" in tables def test_migration_runner_creates_registry_without_legacy_migrations(tmp_path): from sqlalchemy import inspect from app.extensions import db app = make_app(tmp_path, "migrations.db") with app.app_context(): assert migrate_database() == [] assert "schema_migration" in inspect(db.engine).get_table_names() assert migrate_database() == [] def test_global_vat_override_changes_50_percent_deduction(tmp_path): from decimal import Decimal from datetime import date from app.extensions import db from app.models import AppSetting from app.vat import effective_vat_rate app = make_app(tmp_path, "global-vat.db") with app.app_context(): for key, value in { "global_vat_override_enabled": "1", "global_vat_override_name": "CPN", "global_vat_override_rate": "8", "global_vat_override_start": "2026-07-01", "global_vat_override_end": "2026-07-31", }.items(): db.session.merge(AppSetting(key=key, value=value)) db.session.commit() rate = effective_vat_rate(Decimal("23"), date(2026, 7, 15)) result = calculate_costs(Decimal("108"), rate, 50) assert rate == Decimal("8") assert result["net"] == Decimal("100.00") assert result["vat"] == Decimal("8.00") assert result["deductible_vat"] == Decimal("4.00") assert effective_vat_rate(Decimal("23"), date(2026, 8, 1)) == Decimal("23") def test_only_admin_can_change_global_vat(tmp_path): app = make_app(tmp_path, "global-vat-auth.db") client = app.test_client() client.post('/login', data={'email':'admin@example.com','password':'admin123!'}) response = client.post('/admin/global-vat', data={'enabled':'on','name':'CPN','rate':'8','start':'2026-07-01','end':'2026-07-31'}, headers={'X-Requested-With':'XMLHttpRequest'}) assert response.status_code == 200 assert response.json['ok'] is True def test_api_bearer_token_authentication(tmp_path): app = make_app(tmp_path, "bearer.db") client = app.test_client() token_response = client.post('/api/auth/token', data={ 'username': 'admin@example.com', 'password': 'admin123!', }) assert token_response.status_code == 200 token = token_response.json['access_token'] assert token_response.json['token_type'] == 'Bearer' fresh_client = app.test_client() response = fresh_client.get('/api/me', headers={ 'Authorization': f'Bearer {token}', }) assert response.status_code == 200 assert response.json['data']['email'] == 'admin@example.com' def test_api_rejects_invalid_bearer_token(tmp_path): app = make_app(tmp_path, "invalid-bearer.db") response = app.test_client().get('/api/me', headers={ 'Authorization': 'Bearer invalid-token', }) assert response.status_code == 401 assert response.json['ok'] is False def test_openapi_contains_password_flow(tmp_path): app = make_app(tmp_path, "openapi-auth.db") spec = app.test_client().get('/openapi.json').json password_flow = spec['components']['securitySchemes']['oauth2Password']['flows']['password'] assert password_flow['tokenUrl'] == '/api/auth/token' assert '/api/auth/token' in spec['paths'] def test_admin_can_delete_empty_company(tmp_path): from app.extensions import db from app.models import CompanySettings app = make_app(tmp_path, "delete-company.db") client = app.test_client() client.post('/api/auth/login', data={'email':'admin@example.com','password':'admin123!'}) with app.app_context(): company = CompanySettings(name='Firma do usunięcia', region='mazowieckie') db.session.add(company) db.session.commit() company_id = company.id response = client.delete(f'/api/companies/{company_id}') assert response.status_code == 200 assert response.json['ok'] is True with app.app_context(): assert db.session.get(CompanySettings, company_id) is None def test_company_with_users_cannot_be_deleted(tmp_path): from app.extensions import db from app.models import CompanySettings, User app = make_app(tmp_path, "delete-company-blocked.db") client = app.test_client() client.post('/api/auth/login', data={'email':'admin@example.com','password':'admin123!'}) with app.app_context(): company = CompanySettings(name='Firma z użytkownikiem', region='mazowieckie') db.session.add(company) db.session.flush() user = User(name='Pracownik', email='pracownik@example.com', role='user', company_id=company.id) user.set_password('Haslo123!') db.session.add(user) db.session.commit() company_id = company.id response = client.delete(f'/api/companies/{company_id}') assert response.status_code == 409 assert 'użytkowników' in response.json['message'] def test_company_settings_show_station_access_and_favorite_pagination(tmp_path): app = make_app(tmp_path, "company-station-ui.db") client = app.test_client() client.post('/login', data={'email':'admin@example.com','password':'admin123!'}) html = client.get('/admin/companies').get_data(as_text=True) assert 'Konfiguruj dostęp' in html assert 'data-page-size="10"' in html assert 'favorite-pagination' in html assert 'Zapisz ulubione' in html assert 'Usuń firmę' in html def test_admin_stations_without_company_shows_company_picker(tmp_path): app = make_app(tmp_path, "stations-picker.db") client = app.test_client() client.post('/login', data={'email':'admin@example.com','password':'admin123!'}) response = client.get('/stations') html = response.get_data(as_text=True) assert response.status_code == 200 assert 'Wybierz firmę' in html assert 'name="company_id"' in html assert 'Przejdź do konfiguracji' in html def test_fuel_without_company_redirects_instead_of_500(tmp_path): from app.extensions import db from app.models import CompanySettings, User app = make_app(tmp_path, "fuel-no-company.db") client = app.test_client() client.post('/login', data={'email':'admin@example.com','password':'admin123!'}) with app.app_context(): User.query.update({User.company_id: None}) CompanySettings.query.delete() db.session.commit() response = client.get('/fuel', follow_redirects=False) assert response.status_code == 302 assert '/admin/companies' in response.headers['Location'] def test_delete_unused_fuel_card(tmp_path): app=make_app(tmp_path, "delete_card.db") client=app.test_client() client.post('/login',data={'email':'admin@example.com','password':'admin123!'}) with app.app_context(): from app.extensions import db from app.models import CompanySettings, FuelCard company=CompanySettings.query.first() card=FuelCard(name='Karta do usunięcia',provider='Test',company_id=company.id) db.session.add(card);db.session.commit();card_id=card.id response=client.delete(f'/api/fuel-cards/{card_id}') assert response.status_code == 200 assert response.json['ok'] is True with app.app_context(): from app.models import FuelCard assert FuelCard.query.get(card_id) is None def test_cannot_delete_assigned_fuel_card(tmp_path): app=make_app(tmp_path, "assigned_card.db") client=app.test_client() client.post('/login',data={'email':'admin@example.com','password':'admin123!'}) with app.app_context(): from app.extensions import db from app.models import CompanySettings, FuelCard, User company=CompanySettings.query.first() card=FuelCard(name='Karta przypisana',provider='Test',company_id=company.id) db.session.add(card);db.session.flush() user=User.query.filter_by(email='admin@example.com').first() user.fuel_card_id=card.id db.session.commit();card_id=card.id response=client.delete(f'/api/fuel-cards/{card_id}') assert response.status_code == 409 assert response.json['ok'] is False assert 'użytkowników' in response.json['message'] def test_orlen_page_has_history_and_comparison_modes(tmp_path): app = make_app(tmp_path, "orlen-ui.db") client = app.test_client() client.post('/login', data={'email':'admin@example.com','password':'admin123!'}) history = client.get('/orlen?mode=history').get_data(as_text=True) assert 'Przegląd danych' in history assert 'Porównanie okresów' in history comparison = client.get('/orlen?mode=compare&compare_fuel=PB98&comparison_type=years&year=2026&compare_year=2025').get_data(as_text=True) assert 'Porównanie zawsze dotyczy jednego rodzaju paliwa.' in comparison assert 'Porównanie: PB98' in comparison assert 'Dowolne okresy' in comparison def test_orlen_comparison_filters_to_one_fuel(tmp_path): from datetime import date from decimal import Decimal from app.extensions import db from app.models import OrlenPrice app = make_app(tmp_path, "orlen-compare.db") client = app.test_client() client.post('/login', data={'email':'admin@example.com','password':'admin123!'}) with app.app_context(): db.session.add_all([ OrlenPrice(fuel_type='PB95', effective_date=date(2025,1,2), price_per_liter=Decimal('4.1000'), raw_value=Decimal('4100'), region='', product_name='PB95', source='test'), OrlenPrice(fuel_type='PB95', effective_date=date(2026,1,2), price_per_liter=Decimal('4.5000'), raw_value=Decimal('4500'), region='', product_name='PB95', source='test'), OrlenPrice(fuel_type='DIESEL', effective_date=date(2026,1,2), price_per_liter=Decimal('5.0000'), raw_value=Decimal('5000'), region='', product_name='DIESEL', source='test'), ]) db.session.commit() html = client.get('/orlen?mode=compare&compare_fuel=PB95&comparison_type=years&year=2026&compare_year=2025').get_data(as_text=True) assert '4.5000' in html assert '4.1000' in html assert '>DIESEL' not in html