bmc_hub/tests/test_workflow_customer_activation.py

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