Files
fuel_track/tests/test_app.py
T
Mateusz Gruszczyński e200c91881 rewrite styles
2026-07-14 11:52:36 +02:00

319 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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('0115')
assert invoice_period(datetime(2026,7,16),15).endswith('16koniec')
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']