from contextlib import contextmanager import pytest from fastapi import FastAPI, HTTPException from fastapi.testclient import TestClient from app.workflows import decisions @pytest.fixture def api(monkeypatch): app = FastAPI() app.include_router(decisions.router) app.dependency_overrides[decisions.edit_access] = lambda: {'id': 4, 'permissions': []} app.dependency_overrides[decisions.view_access] = lambda: {'id': 4} state = {'entry': {'id': 1, 'workflow_revision': 3, 'status': 'draft'}, 'previous': {'id': 2, 'status': 'decided', 'manual_override': True}, 'writes': []} class Cursor: def execute(self, sql, params=None): self.sql = sql if not sql.startswith('SELECT'): state['writes'].append(sql) def fetchone(self): if 'FROM tmodule_times' in self.sql: return state['entry'] return state['previous'] def fetchall(self): return [] @contextmanager def transaction(): yield Cursor() monkeypatch.setattr(decisions.service, 'transaction', transaction) return TestClient(app), state def test_recalculate_preserves_manual_override_without_writes(api): client, state = api result = client.post('/timetracking/time/1/workflow-recalculate', json={}) assert result.status_code == 200 assert result.json()['preserved_override'] is True assert not state['writes'] def test_reset_requires_admin_permission(api): client, state = api result = client.post('/timetracking/time/1/workflow-recalculate', json={'reset_override': True, 'reason': 'Ny aftale'}) assert result.status_code == 403 assert not state['writes'] @pytest.mark.parametrize('field,value', [('status', 'approved'), ('status', 'billed'), ('economy_settled_at', 'now'), ('economy_order_draft_id', 24), ('exported_at', 'now')]) def test_settled_or_exported_cannot_recompute(api, field, value): client, state = api state['entry'][field] = value result = client.post('/timetracking/time/1/workflow-recalculate', json={}) assert result.status_code == 409 assert not state['writes'] def test_unauthenticated_cannot_override(): app = FastAPI() app.include_router(decisions.router) response = TestClient(app).post('/timetracking/time/1/workflow-override', json={'method': 'invoice', 'reason': 'Ny aftale'}) assert response.status_code == 401 def test_not_active_customer_rejected(monkeypatch): monkeypatch.setattr(decisions, 'active_for_customer', lambda _: False) with pytest.raises(HTTPException) as exc: decisions._require_active({'customer_id': '24'}) assert exc.value.status_code == 409