Add tenant-scoped teams and projects, order attribution across four businesses, conversion inheritance, scoped statistics and audited reassignment. Reuse existing Web and mobile UI patterns; team regions and hospitals remain informational. Include incremental SQL and isolated regression coverage. Actual MySQL and browser/WebView integration validation remains pending; relates to issue #13.
173 lines
9.1 KiB
Python
173 lines
9.1 KiB
Python
"""Mobile attribution writes and scoped reads against isolated SQLite records."""
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
from app.core.config import settings
|
|
from app.models.project_management import Project, ProjectTeam
|
|
from app.models.models import BizPresignOrder, BizCollectionProtocol
|
|
from app.models.research_donation import BizResearchDonation
|
|
from app.schemas.market_mobile import MarketPresignOrderBody, MarketProtocolBody
|
|
from app.services import market_presign, market_protocol
|
|
from test_market_protocol_api import _session, _masters, _ctx, _user, _body, _cell, _donation
|
|
|
|
|
|
@pytest.fixture
|
|
def db():
|
|
session = _session()
|
|
_masters(session)
|
|
session.add_all([
|
|
ProjectTeam(id='team', tenant_no='tenant-a', team_name='团队', leader_user_id='u1'),
|
|
Project(id='project', tenant_no='tenant-a', project_no='P', project_name='项目', team_id='team', leader_user_id='u1', status='active', biz_types='presign,cell_storage,research_donation'),
|
|
])
|
|
session.commit()
|
|
yield session
|
|
session.close()
|
|
|
|
|
|
@pytest.mark.parametrize('kind', ['presign', 'cell_storage', 'research_donation'])
|
|
def test_mobile_create_persists_and_returns_project(db, kind):
|
|
if kind == 'presign':
|
|
result = market_presign.create_presign_order(db, body=MarketPresignOrderBody(motherName='母亲', projectId='project'), ctx=_ctx('specialist'), user_info=_user())
|
|
basic = result
|
|
else:
|
|
result = market_protocol.create_protocol(db, body=_body(kind, projectId='project', samples=[], attachments={}), ctx=_ctx('specialist'), user_info=_user())
|
|
basic = result['basic']
|
|
model = {'presign': BizPresignOrder, 'cell_storage': BizCollectionProtocol, 'research_donation': BizResearchDonation}[kind]
|
|
db.expire_all()
|
|
assert db.get(model, basic['id']).project_id == 'project'
|
|
assert basic['projectId'] == 'project'
|
|
assert basic['projectName'] == '项目'
|
|
assert basic['teamId'] == 'team'
|
|
assert basic['teamName'] == '团队'
|
|
|
|
|
|
@pytest.mark.parametrize('kind', ['presign', 'cell_storage', 'research_donation'])
|
|
def test_mobile_creation_obeys_required_configuration(db, monkeypatch, kind):
|
|
monkeypatch.setattr(settings, 'PROJECT_ASSIGNMENT_REQUIRED', True)
|
|
with pytest.raises(HTTPException):
|
|
if kind == 'presign':
|
|
market_presign.create_presign_order(db, body=MarketPresignOrderBody(motherName='母亲'), ctx=_ctx('specialist'), user_info=_user())
|
|
else:
|
|
market_protocol.create_protocol(db, body=_body(kind, samples=[], attachments={}), ctx=_ctx('specialist'), user_info=_user())
|
|
|
|
|
|
@pytest.mark.parametrize('kind', ['presign', 'cell_storage', 'research_donation'])
|
|
def test_scoped_lists_and_immutable_edits(db, kind):
|
|
if kind == 'presign':
|
|
own = BizPresignOrder(id='own', tenant_no='tenant-a', sales_user_id='u1', order_status='active', project_id='project')
|
|
hidden = BizPresignOrder(id='hidden', tenant_no='tenant-a', sales_user_id='u2', order_status='active', project_id='project')
|
|
db.add_all([own, hidden])
|
|
else:
|
|
factory = _cell if kind == 'cell_storage' else _donation
|
|
own = factory(db, 'own')
|
|
hidden = factory(db, 'hidden', sales='u2')
|
|
own.project_id = hidden.project_id = 'project'
|
|
db.commit()
|
|
service = market_presign if kind == 'presign' else market_protocol
|
|
listing = service.list_presign_orders if kind == 'presign' else service.list_protocols
|
|
params = {} if kind == 'presign' else {'source': kind}
|
|
rows, total = listing(db, ctx=_ctx('specialist'), user_info=_user(), project_id='project', team_id='team', **params)
|
|
assert total == 1
|
|
assert rows[0]['id'] == 'own'
|
|
assert rows[0]['projectName'] == '项目'
|
|
assert listing(db, ctx=_ctx('specialist'), user_info=_user(), project_id='missing', **params)[1] == 0
|
|
db.get(Project, 'project').status = 'disabled'
|
|
db.commit()
|
|
kwargs = {'order_id': 'own'} if kind == 'presign' else {'protocol_id': 'own', 'source': kind}
|
|
update = service.update_presign_order if kind == 'presign' else service.update_protocol
|
|
body = MarketPresignOrderBody(remark='保留历史') if kind == 'presign' else MarketProtocolBody(source=kind, remark='保留历史')
|
|
update(db, ctx=_ctx('specialist'), user_info=_user(), body=body, **kwargs)
|
|
db.refresh(own)
|
|
assert own.project_id == 'project'
|
|
body = MarketPresignOrderBody(projectId=None) if kind == 'presign' else MarketProtocolBody(source=kind, projectId=None)
|
|
with pytest.raises(HTTPException, match='项目归属维护'):
|
|
update(db, ctx=_ctx('specialist'), user_info=_user(), body=body, **kwargs)
|
|
|
|
|
|
@pytest.mark.parametrize('kind', ['presign', 'cell_storage', 'research_donation'])
|
|
def test_mobile_rejects_foreign_project_and_legacy_backfill(db, kind):
|
|
db.get(Project, 'project').tenant_no = 'tenant-other'
|
|
db.commit()
|
|
with pytest.raises(HTTPException):
|
|
if kind == 'presign':
|
|
market_presign.create_presign_order(db, body=MarketPresignOrderBody(motherName='母亲', projectId='project'), ctx=_ctx('specialist'), user_info=_user())
|
|
else:
|
|
market_protocol.create_protocol(db, body=_body(kind, projectId='project', samples=[], attachments={}), ctx=_ctx('specialist'), user_info=_user())
|
|
if kind == 'presign':
|
|
row = BizPresignOrder(id='legacy', tenant_no='tenant-a', sales_user_id='u1', order_status='active')
|
|
db.add(row)
|
|
else:
|
|
row = (_cell if kind == 'cell_storage' else _donation)(db, 'legacy')
|
|
db.commit()
|
|
with pytest.raises(HTTPException, match='项目归属维护'):
|
|
if kind == 'presign':
|
|
market_presign.update_presign_order(db, order_id='legacy', body=MarketPresignOrderBody(projectId='project'), ctx=_ctx('specialist'), user_info=_user())
|
|
else:
|
|
market_protocol.update_protocol(db, source=kind, protocol_id='legacy', body=MarketProtocolBody(source=kind, projectId='project'), ctx=_ctx('specialist'), user_info=_user())
|
|
db.refresh(row)
|
|
assert row.project_id is None
|
|
|
|
|
|
@pytest.mark.parametrize('role', ['specialist', 'manager'])
|
|
@pytest.mark.parametrize('resource', ['protocols', 'presign-orders'])
|
|
def test_mobile_http_list_binds_project_and_team_aliases(db, role, resource):
|
|
import asyncio
|
|
import httpx
|
|
from fastapi import FastAPI
|
|
from app.api.market_mobile import router
|
|
from app.core.database import get_db
|
|
|
|
row = _cell(db, 'visible')
|
|
row.project_id = 'project'
|
|
hidden = _cell(db, 'hidden', sales='u2', manager='manager-other')
|
|
hidden.project_id = 'project'
|
|
db.add_all([
|
|
BizPresignOrder(id='presign-own', tenant_no='tenant-a', sales_user_id='u1', region_manager_user_id='manager-a', order_status='active', project_id='project'),
|
|
BizPresignOrder(id='presign-hidden', tenant_no='tenant-a', sales_user_id='u2', region_manager_user_id='manager-other', order_status='active', project_id='project'),
|
|
])
|
|
db.commit()
|
|
app = FastAPI()
|
|
app.include_router(router)
|
|
|
|
async def database():
|
|
yield db
|
|
|
|
async def user():
|
|
return _user('manager-a' if role == 'manager' else 'u1')
|
|
|
|
app.dependency_overrides[get_db] = database
|
|
for route in router.routes:
|
|
for dependency in route.dependant.dependencies:
|
|
if dependency.name == 'user':
|
|
app.dependency_overrides[dependency.call] = user
|
|
|
|
async def check():
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url='http://test') as client:
|
|
url = f'/mini/market/{role}/{resource}'
|
|
response = await client.get(url, params={'source': 'cell_storage', 'projectId': 'project', 'teamId': 'team'})
|
|
assert response.status_code == 200
|
|
data = response.json()['data']
|
|
assert data['total'] == 1
|
|
assert data['list'][0]['projectName'] == '项目'
|
|
response = await client.get(url, params={'source': 'cell_storage', 'projectId': 'missing'})
|
|
assert response.json()['data']['total'] == 0
|
|
response = await client.get(url, params={'source': 'cell_storage', 'teamId': 'missing'})
|
|
assert response.json()['data']['total'] == 0
|
|
asyncio.run(check())
|
|
|
|
|
|
@pytest.mark.parametrize('role', ['specialist', 'manager'])
|
|
def test_mobile_history_filter_options_keep_field_scope(db, role):
|
|
from app.services.project_management import filter_options
|
|
from app.services.market_field_scope import apply_presign_scope
|
|
db.get(Project, 'project').status = 'disabled'
|
|
db.add(Project(id='hidden-project', tenant_no='tenant-a', project_no='H', project_name='隐藏', team_id='team', leader_user_id='u2', status='active', biz_types='cell_storage'))
|
|
db.add_all([
|
|
BizPresignOrder(id='own', tenant_no='tenant-a', sales_user_id='u1', region_manager_user_id='manager-a', project_id='project'),
|
|
BizPresignOrder(id='hidden', tenant_no='tenant-a', sales_user_id='u2', region_manager_user_id='manager-other', project_id='hidden-project'),
|
|
])
|
|
db.commit()
|
|
user = _user('manager-a' if role == 'manager' else 'u1')
|
|
query = apply_presign_scope(db.query(BizPresignOrder), db, _ctx(role, user['user_id']), user_info=user)
|
|
result = filter_options(db, 'tenant-a', user, 'presign', business_scope=query)
|
|
assert [row['id'] for row in result['list']] == ['project']
|