test_web_freight_permissions.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. """Web 运费数据权限与写入边界,使用内存数据库避免改动业务数据。"""
  2. import asyncio
  3. from datetime import datetime
  4. from decimal import Decimal
  5. import pytest
  6. from sqlalchemy import create_engine
  7. from sqlalchemy.orm import sessionmaker
  8. from app.api import sample_freight as api
  9. from app.crud.sample_freight import _parse_amount
  10. from app.models.base import Base
  11. from app.models.models import (
  12. BizCollectionProtocol, BizProtocolSample, BizSampleFreight,
  13. BizSampleFreightItem, SysDictData, SysDictType, SysRole,
  14. SysRoleDataScopeDetail, SysUser, SysUserRole,
  15. )
  16. USER = {'user_id': 'u1', 'tenant_no': 'tenant-a', 'role': 'admin', 'dept_id': 'dept-a'}
  17. @pytest.fixture
  18. def db():
  19. engine = create_engine('sqlite:///:memory:')
  20. Base.metadata.create_all(engine)
  21. session = sessionmaker(bind=engine)()
  22. now = datetime.now()
  23. common = dict(tenant_no='tenant-a', create_dept_id='dept-a', create_time=now, del_flag=0)
  24. session.add_all([
  25. SysUser(id='u1', user_no='u1', nick_name='用户', password='', dept_id='dept-a', **common),
  26. SysRole(id='self', role_name='普通人员', data_scope='4', status='1', **common),
  27. SysUserRole(id='ur', user_id='u1', role_id='self', **common),
  28. SysDictType(id='dt', dict_name='运输方式', dict_type='sample_transport_mode', **common),
  29. SysDictData(id='dd', dict_label='顺丰', dict_value='sf', dict_type='sample_transport_mode', **common),
  30. ])
  31. for owner in ('u1', 'u2'):
  32. session.add_all([
  33. BizCollectionProtocol(id='p-' + owner, protocol_no='P-' + owner, create_by=owner, **common),
  34. BizProtocolSample(id='s-' + owner, collection_protocol_id='p-' + owner, sample_code='S-' + owner, create_by=owner, **common),
  35. BizSampleFreight(id='f-' + owner, freight_no='F-' + owner, transport_mode='sf', freight_amount=Decimal('10'), create_by=owner, **common),
  36. ])
  37. session.commit()
  38. yield session
  39. session.close()
  40. engine.dispose()
  41. def call(endpoint, db, **kwargs):
  42. return asyncio.run(endpoint(db=db, user=USER, **kwargs))
  43. def test_list_obeys_self_scope(db):
  44. result = call(api.list_sample_freights, db, page=1, pageSize=10, includeItems=False)
  45. assert [r['id'] for r in result['data']['list']] == ['f-u1']
  46. assert result['data']['total'] == 1
  47. def test_multiple_roles_union_preserves_allowed_records(db):
  48. common = dict(tenant_no='tenant-a', create_dept_id='', del_flag=0)
  49. db.add_all([
  50. SysRole(id='custom', role_name='扩展权限', data_scope='5', status='1', **common),
  51. SysUserRole(id='ur2', user_id='u1', role_id='custom', **common),
  52. SysRoleDataScopeDetail(id='detail', role_id='custom', scope_type='user', scope_value='u2', **common),
  53. ])
  54. db.commit()
  55. result = call(api.list_sample_freights, db, page=1, pageSize=10, includeItems=False)
  56. assert {r['id'] for r in result['data']['list']} == {'f-u1', 'f-u2'}
  57. @pytest.mark.parametrize('operation', ['detail', 'update', 'delete', 'link', 'unlink'])
  58. def test_outside_scope_batch_is_not_addressable(db, operation):
  59. endpoints = {
  60. 'detail': (api.get_sample_freight, {}),
  61. 'update': (api.update_sample_freight, {'body': api.SampleFreightUpdateBody(freightAmount=999)}),
  62. 'delete': (api.delete_sample_freight, {}),
  63. 'link': (api.link_sample_freight, {'body': api.SampleFreightItemsBody(items=[{'sampleSource': 'cell_storage', 'sampleId': 's-u1'}])}),
  64. 'unlink': (api.unlink_sample_freight, {'body': api.SampleFreightItemsBody(items=[])}),
  65. }
  66. endpoint, kwargs = endpoints[operation]
  67. result = call(endpoint, db, freight_id='f-u2', **kwargs)
  68. assert result['code'] in (403, 404)
  69. obj = db.query(BizSampleFreight).filter_by(id='f-u2').one()
  70. assert obj.del_flag == 0
  71. assert obj.freight_amount == Decimal('10')
  72. def test_sample_options_obey_parent_scope(db):
  73. result = call(api.list_sample_freight_options, db, page=1, pageSize=20, sampleSource='cell_storage')
  74. assert [r['sampleId'] for r in result['data']['list']] == ['s-u1']
  75. @pytest.mark.parametrize('sample_id', ['s-u2', 'does-not-exist'])
  76. @pytest.mark.parametrize('operation', ['create', 'link'])
  77. def test_cannot_link_hidden_or_missing_sample(db, sample_id, operation):
  78. items = [{'sampleSource': 'cell_storage', 'sampleId': sample_id}]
  79. if operation == 'create':
  80. result = call(api.create_sample_freight, db, body=api.SampleFreightCreateBody(transportMode='sf', freightAmount=10, items=items))
  81. else:
  82. result = call(api.link_sample_freight, db, freight_id='f-u1', body=api.SampleFreightItemsBody(items=items))
  83. assert result['code'] in (400, 403, 404)
  84. assert db.query(BizSampleFreightItem).count() == 0
  85. assert db.query(BizSampleFreight).count() == 2
  86. def test_created_batch_has_server_department_and_sample_snapshot(db):
  87. result = call(api.create_sample_freight, db, body=api.SampleFreightCreateBody(
  88. transportMode='sf', freightAmount=10,
  89. items=[{'sampleSource': 'cell_storage', 'sampleId': 's-u1', 'sampleCode': 'forged', 'protocolId': 'p-u2'}],
  90. ))
  91. assert result['code'] == 200
  92. row = db.query(BizSampleFreight).filter_by(id=result['data']['id']).one()
  93. assert row.create_dept_id == 'dept-a'
  94. assert result['data']['items'][0]['sampleCode'] == 'S-u1'
  95. assert result['data']['items'][0]['protocolId'] == 'p-u1'
  96. @pytest.mark.parametrize('amount', ['NaN', 'Infinity', '-Infinity'])
  97. def test_nonfinite_freight_amount_is_validation_error(amount):
  98. with pytest.raises(ValueError):
  99. _parse_amount(amount)
  100. @pytest.mark.parametrize('operation', ['create', 'link'])
  101. def test_link_cannot_remove_sample_from_an_inaccessible_batch(db, operation):
  102. db.add(BizSampleFreightItem(
  103. id='existing-link', freight_id='f-u2', sample_source='cell_storage',
  104. sample_id='s-u1', sample_code='S-u1', protocol_id='p-u1',
  105. tenant_no='tenant-a', create_dept_id='', del_flag=0,
  106. ))
  107. db.commit()
  108. items = [{'sampleSource': 'cell_storage', 'sampleId': 's-u1'}]
  109. if operation == 'create':
  110. result = call(api.create_sample_freight, db, body=api.SampleFreightCreateBody(transportMode='sf', freightAmount=10, items=items))
  111. else:
  112. result = call(api.link_sample_freight, db, freight_id='f-u1', body=api.SampleFreightItemsBody(items=items))
  113. assert result['code'] in (403, 404)
  114. assert db.query(BizSampleFreightItem).filter_by(id='existing-link').one().freight_id == 'f-u2'