bmc_hub/tests/test_workflow_customer_activation.py

123 lines
6.5 KiB
Python
Raw Permalink Normal View History

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])