123 lines
6.5 KiB
Python
123 lines
6.5 KiB
Python
|
|
from contextlib import contextmanager
|
||
|
|
from unittest.mock import MagicMock
|
||
|
|
import pytest
|
||
|
|
from fastapi import FastAPI
|
||
|
|
from fastapi.testclient import TestClient
|
||
|
|
from app.workflows import customer_activation as api, integration, worker
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def client(monkeypatch):
|
||
|
|
state={'exists':True,'schema':True,'override':None,'history':[],'rights':{'customers.view','customers.edit','workflows.activate'}}
|
||
|
|
class Cursor:
|
||
|
|
def execute(self,sql,params=None):
|
||
|
|
self.sql=sql
|
||
|
|
if sql.startswith('INSERT INTO bmc_workflow_customer_activation('):
|
||
|
|
state['override']={'customer_id':params[0],'enabled':params[1],'updated_by':params[2],'updated_at':None}
|
||
|
|
elif sql.startswith('INSERT INTO bmc_workflow_customer_activation_history'):
|
||
|
|
state['history'].append(params)
|
||
|
|
def fetchone(self):
|
||
|
|
if 'FROM customers' in self.sql: return {'id':24} if state['exists'] else None
|
||
|
|
if 'to_regclass' in self.sql: return {'name':'bmc_workflow_customer_activation' if state['schema'] else None}
|
||
|
|
return state['override']
|
||
|
|
@contextmanager
|
||
|
|
def transaction(): yield Cursor()
|
||
|
|
monkeypatch.setattr(api,'transaction',transaction)
|
||
|
|
monkeypatch.setattr(api.AuthService,'user_has_permission',lambda actor,permission:permission in state['rights'])
|
||
|
|
monkeypatch.setattr(api.settings,'WORKFLOWS_ENABLED',False)
|
||
|
|
monkeypatch.setattr(api.settings,'WORKFLOWS_MODE','active')
|
||
|
|
monkeypatch.setattr(api.settings,'WORKFLOWS_CUSTOMER_IDS',[24])
|
||
|
|
app=FastAPI()
|
||
|
|
app.include_router(api.router,prefix='/api/v1')
|
||
|
|
app.dependency_overrides[api.get_current_user]=lambda:{'id':4,'username':'tester','is_superadmin':False}
|
||
|
|
return TestClient(app),state
|
||
|
|
|
||
|
|
def test_inherited_env_preference_does_not_enable_global_master(client):
|
||
|
|
browser,state=client
|
||
|
|
response=browser.get('/api/v1/workflows/customers/24/activation')
|
||
|
|
assert response.status_code==200
|
||
|
|
body=response.json()
|
||
|
|
assert body['enabled'] is True and body['inherited'] is True
|
||
|
|
assert body['explicit_override'] is None and body['effective_enabled'] is False
|
||
|
|
assert state['history']==[]
|
||
|
|
|
||
|
|
def test_false_override_wins_and_records_reason(client):
|
||
|
|
browser,state=client
|
||
|
|
response=browser.put('/api/v1/workflows/customers/24/activation',json={'enabled':False,'reason':'Stop automatik'})
|
||
|
|
assert response.status_code==200
|
||
|
|
body=response.json()
|
||
|
|
assert body['enabled'] is False and body['inherited'] is False and body['explicit_override'] is False
|
||
|
|
assert state['history']==[(24,None,False,True,4,'Stop automatik')]
|
||
|
|
|
||
|
|
def test_saved_true_preference_remains_inactive_in_shadow(client,monkeypatch):
|
||
|
|
browser,state=client
|
||
|
|
monkeypatch.setattr(api.settings,'WORKFLOWS_ENABLED',True)
|
||
|
|
monkeypatch.setattr(api.settings,'WORKFLOWS_MODE','shadow')
|
||
|
|
result=browser.put('/api/v1/workflows/customers/24/activation',json={'enabled':True,'reason':'Kunde har godkendt'})
|
||
|
|
assert result.status_code==200
|
||
|
|
assert result.json()['enabled'] is True and result.json()['effective_enabled'] is False
|
||
|
|
|
||
|
|
@pytest.mark.parametrize('missing',['customers.edit','workflows.activate'])
|
||
|
|
def test_write_requires_both_permissions(client,missing):
|
||
|
|
browser,state=client
|
||
|
|
state['rights'].remove(missing)
|
||
|
|
response=browser.put('/api/v1/workflows/customers/24/activation',json={'enabled':True,'reason':'Test årsag'})
|
||
|
|
assert response.status_code==403 and state['history']==[]
|
||
|
|
|
||
|
|
def test_read_accepts_workflow_view_without_customer_view(client):
|
||
|
|
browser,state=client
|
||
|
|
state['rights']={'workflows.view'}
|
||
|
|
response=browser.get('/api/v1/workflows/customers/24/activation')
|
||
|
|
assert response.status_code==200 and response.json()['can_manage'] is False
|
||
|
|
|
||
|
|
def test_nonexistent_customer_and_short_reason_rejected(client):
|
||
|
|
browser,state=client
|
||
|
|
state['exists']=False
|
||
|
|
assert browser.get('/api/v1/workflows/customers/24/activation').status_code==404
|
||
|
|
state['exists']=True
|
||
|
|
assert browser.put('/api/v1/workflows/customers/24/activation',json={'enabled':True,'reason':' '}).status_code==422
|
||
|
|
assert state['history']==[]
|
||
|
|
|
||
|
|
def test_missing_schema_reads_env_but_refuses_write(client):
|
||
|
|
browser,state=client
|
||
|
|
state['schema']=False
|
||
|
|
assert browser.get('/api/v1/workflows/customers/24/activation').json()['enabled'] is True
|
||
|
|
assert browser.put('/api/v1/workflows/customers/24/activation',json={'enabled':True,'reason':'Test årsag'}).status_code==503
|
||
|
|
|
||
|
|
def test_helper_false_database_override_wins_even_explicit_fallback(monkeypatch):
|
||
|
|
cursor=MagicMock()
|
||
|
|
cursor.fetchone.side_effect=[{'name':'activation'},{'enabled':False}]
|
||
|
|
assert integration.customer_enabled(24,cursor,fallback_ids=[24]) is False
|
||
|
|
assert 'FOR SHARE' in cursor.execute.call_args.args[0]
|
||
|
|
|
||
|
|
def test_effective_ids_merge_true_and_false_overrides(monkeypatch):
|
||
|
|
monkeypatch.setattr(integration.settings,'WORKFLOWS_CUSTOMER_IDS',[24,25])
|
||
|
|
cursor=MagicMock()
|
||
|
|
cursor.fetchone.return_value={'name':'activation'}
|
||
|
|
cursor.fetchall.return_value=[{'customer_id':24,'enabled':False},{'customer_id':26,'enabled':True}]
|
||
|
|
assert integration.effective_customer_ids(cursor)==[25,26]
|
||
|
|
|
||
|
|
def test_active_global_empty_env_allows_saved_customer(monkeypatch):
|
||
|
|
monkeypatch.setattr(integration.settings,'WORKFLOWS_ENABLED',True)
|
||
|
|
monkeypatch.setattr(integration.settings,'WORKFLOWS_MODE','active')
|
||
|
|
monkeypatch.setattr(integration.settings,'WORKFLOWS_CUSTOMER_IDS',[])
|
||
|
|
monkeypatch.setattr(integration,'execute_query_single',lambda sql,params=None:{'name':'activation'} if 'to_regclass' in sql else {'enabled':True})
|
||
|
|
assert integration.active_for_customer(24) is True
|
||
|
|
|
||
|
|
def test_active_empty_environment_does_not_prevent_customer_card_activation(monkeypatch):
|
||
|
|
monkeypatch.setattr(integration.settings,'WORKFLOWS_ENABLED',True)
|
||
|
|
monkeypatch.setattr(integration.settings,'WORKFLOWS_MODE','active')
|
||
|
|
monkeypatch.setattr(integration.settings,'WORKFLOWS_CUSTOMER_IDS',[])
|
||
|
|
monkeypatch.setattr(integration.settings,'WORKFLOWS_INTERNAL_ENABLED',False)
|
||
|
|
monkeypatch.setattr(integration,'execute_query_single',lambda sql:{'name':'runtime'})
|
||
|
|
writes=MagicMock()
|
||
|
|
monkeypatch.setattr(integration,'execute_update',writes)
|
||
|
|
integration.configure_runtime()
|
||
|
|
writes.assert_called_once()
|
||
|
|
assert 'bmc_workflow_customer_activation' not in writes.call_args.args[0]
|
||
|
|
|
||
|
|
def test_real_database_error_does_not_fall_back_to_env_enablement(monkeypatch):
|
||
|
|
cursor=MagicMock()
|
||
|
|
cursor.execute.side_effect=RuntimeError('Database unavailable')
|
||
|
|
with pytest.raises(RuntimeError,match='unavailable'):
|
||
|
|
integration.customer_enabled(24,cursor,fallback_ids=[24])
|