| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552 |
- import pytest
- from fastapi import HTTPException
- from sqlalchemy import create_engine, event
- from sqlalchemy.exc import IntegrityError
- from sqlalchemy.orm import sessionmaker
- from app.models.base import Base
- from app.models.models import (
- BizAttachment,
- BizCellSampleType,
- BizCollectionHospital,
- BizCollectionProtocol,
- BizCollectionRegion,
- BizCollectionRegionStaff,
- BizBizNoSequence,
- BizPresignOrder,
- BizPresignOrderSampleType,
- BizPresignTransfer,
- BizProtocolCollectionInfo,
- BizProtocolPartyCustomer,
- BizProtocolSample,
- SysPost,
- SysPostRole,
- SysRole,
- SysRoleDataScopeDetail,
- SysUser,
- SysUserPost,
- SysUserRole,
- )
- from app.models.research_donation import (
- BizResearchDonation,
- BizResearchDonationSample,
- )
- _TEST_TABLES = [
- BizCollectionRegionStaff.__table__,
- BizCollectionRegion.__table__,
- BizCollectionHospital.__table__,
- BizBizNoSequence.__table__,
- BizPresignOrder.__table__,
- BizPresignOrderSampleType.__table__,
- BizPresignTransfer.__table__,
- BizCellSampleType.__table__,
- BizCollectionProtocol.__table__,
- BizProtocolPartyCustomer.__table__,
- BizProtocolCollectionInfo.__table__,
- BizProtocolSample.__table__,
- BizResearchDonation.__table__,
- BizResearchDonationSample.__table__,
- BizAttachment.__table__,
- SysUser.__table__,
- SysRole.__table__,
- SysRoleDataScopeDetail.__table__,
- SysUserRole.__table__,
- SysPost.__table__,
- SysPostRole.__table__,
- SysUserPost.__table__,
- ]
- def _session_factory(database_url="sqlite:///:memory:"):
- engine = create_engine(database_url)
- Base.metadata.create_all(
- engine,
- tables=_TEST_TABLES,
- )
- return sessionmaker(bind=engine)
- def _session():
- return _session_factory()()
- def _order(
- order_id,
- *,
- tenant_no="tenant-a",
- region_id="region-a",
- sales_user_id="sales-a",
- manager_user_id="manager-a",
- ):
- return BizPresignOrder(
- id=order_id,
- tenant_no=tenant_no,
- region_id=region_id,
- sales_user_id=sales_user_id,
- region_manager_user_id=manager_user_id,
- order_status="active",
- create_by="creator-a",
- create_dept_id="dept-a",
- del_flag=0,
- )
- def _transfer(
- transfer_id,
- *,
- order_id="own",
- tenant_no="tenant-a",
- pay_status="dfj",
- ):
- return BizPresignTransfer(
- id=transfer_id,
- presign_order_id=order_id,
- transfer_amount=100,
- transfer_type="wechat",
- pay_status=pay_status,
- tenant_no=tenant_no,
- create_by="creator-a",
- create_dept_id="dept-a",
- del_flag=0,
- )
- def _ctx(scope_kind, user_id="u1"):
- from app.services.market_field_scope import MarketFieldContext
- return MarketFieldContext("tenant-a", user_id, scope_kind)
- def _user(user_id="u1"):
- return {
- "user_id": user_id,
- "tenant_no": "tenant-a",
- "role": "super_admin",
- "dept_id": "dept-a",
- }
- def _real_auth_user(user_id="u1"):
- return {
- "user_id": user_id,
- "tenant_no": "tenant-a",
- "role": "super_admin",
- "region_id": "dept-from-auth",
- }
- def test_converted_presign_rejects_update_and_convert_actions():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import convert_presign_order, update_presign_order
- db = _session()
- _add_master_data(db)
- db.add(_order('terminal', sales_user_id='u1'))
- db.commit()
- row = db.query(BizPresignOrder).filter_by(id='terminal').one()
- row.order_status = 'converted'
- db.commit()
- with pytest.raises(ValueError, match='不可|已转'):
- update_presign_order(
- db,
- order_id='terminal',
- body=MarketPresignOrderBody(remark='不应保存'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- with pytest.raises(ValueError, match='不可|已转'):
- convert_presign_order(
- db,
- order_id='terminal',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- def test_void_presign_allows_update_but_rejects_convert():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import convert_presign_order, update_presign_order
- db = _session()
- _add_master_data(db)
- db.add(_order('void-order', sales_user_id='u1'))
- db.commit()
- row = db.query(BizPresignOrder).filter_by(id='void-order').one()
- row.order_status = 'void'
- db.commit()
- data = update_presign_order(
- db,
- order_id='void-order',
- body=MarketPresignOrderBody(remark='作废单仍可修订'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert data['remark'] == '作废单仍可修订'
- with pytest.raises(ValueError, match='不可|作废'):
- convert_presign_order(
- db,
- order_id='void-order',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- def test_presign_create_rejects_unregistered_transfer_attachment_url():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- body = MarketPresignOrderBody.model_validate(
- {
- 'regionId': 'region-a',
- 'hospitalId': 'hospital-a',
- 'transfers': [
- {
- 'transferAmount': 100,
- 'transferAttachments': 'https://evil.test/proof.pdf',
- }
- ],
- }
- )
- with pytest.raises(ValueError, match='附件'):
- create_presign_order(
- db,
- body=body,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- @pytest.mark.parametrize('order_status', ['converted', 'void'])
- def test_presign_create_rejects_embedded_transfer_for_non_writable_order(
- order_status,
- ):
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- body = MarketPresignOrderBody.model_validate(
- {
- 'regionId': 'region-a',
- 'hospitalId': 'hospital-a',
- 'orderStatus': order_status,
- 'transfers': [
- {
- 'transferAmount': 100,
- 'transferType': 'bank',
- }
- ],
- }
- )
- with pytest.raises(ValueError, match='订单|状态|转账'):
- create_presign_order(
- db,
- body=body,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert db.query(BizPresignOrder).count() == 0
- assert db.query(BizPresignTransfer).count() == 0
- def _add_master_data(db):
- db.add_all(
- [
- BizCollectionRegion(
- id="region-a",
- region_name="A区",
- tenant_no="tenant-a",
- create_dept_id="",
- del_flag=0,
- ),
- BizCollectionRegion(
- id="region-b",
- region_name="B区",
- tenant_no="tenant-a",
- create_dept_id="",
- del_flag=0,
- ),
- BizCollectionRegion(
- id="region-cross",
- region_name="跨租户地区",
- tenant_no="tenant-b",
- create_dept_id="",
- del_flag=0,
- ),
- BizCollectionHospital(
- id="hospital-a",
- region_id="region-a",
- hospital_name="A医院",
- tenant_no="tenant-a",
- create_dept_id="",
- del_flag=0,
- ),
- BizCollectionHospital(
- id="hospital-b",
- region_id="region-b",
- hospital_name="B医院",
- tenant_no="tenant-a",
- create_dept_id="",
- del_flag=0,
- ),
- BizCellSampleType(
- id="sample-a",
- type_code="A",
- type_name="样本A",
- tenant_no="tenant-a",
- create_dept_id="",
- del_flag=0,
- ),
- SysUser(
- id="u1",
- user_no="u1",
- nick_name="当前用户",
- status="1",
- tenant_no="tenant-a",
- create_dept_id="dept-a",
- del_flag=0,
- ),
- SysUser(
- id="collector-a",
- user_no="collector-a",
- nick_name="有效采集员",
- status="1",
- tenant_no="tenant-a",
- create_dept_id="dept-a",
- del_flag=0,
- ),
- SysUser(
- id="manager-a",
- user_no="manager-a",
- nick_name="有效经理",
- status="1",
- tenant_no="tenant-a",
- create_dept_id="dept-a",
- del_flag=0,
- ),
- SysUser(
- id="cross-user",
- user_no="cross-secret",
- nick_name="跨租户秘密姓名",
- status="1",
- tenant_no="tenant-b",
- create_dept_id="dept-b",
- del_flag=0,
- ),
- SysUser(
- id="deleted-user",
- user_no="deleted-secret",
- nick_name="已删除秘密姓名",
- status="1",
- tenant_no="tenant-a",
- create_dept_id="dept-a",
- del_flag=1,
- ),
- SysUser(
- id="disabled-user",
- user_no="disabled-secret",
- nick_name="停用秘密姓名",
- status="0",
- tenant_no="tenant-a",
- create_dept_id="dept-a",
- del_flag=0,
- ),
- BizCellSampleType(
- id="sample-cross",
- type_code="X",
- type_name="跨租户样本",
- tenant_no="tenant-b",
- create_dept_id="",
- del_flag=0,
- ),
- ]
- )
- db.commit()
- def test_specialist_presign_list_only_returns_own_sales_orders():
- from app.services.market_presign import list_presign_orders
- db = _session()
- db.add_all(
- [
- _order("own", sales_user_id="u1"),
- _order("other", sales_user_id="u2"),
- _order("cross-tenant", tenant_no="tenant-b", sales_user_id="u1"),
- ]
- )
- db.commit()
- items, total = list_presign_orders(
- db, ctx=_ctx("specialist"), user_info=_user()
- )
- assert total == 1
- assert [item["id"] for item in items] == ["own"]
- def test_manager_presign_list_uses_region_or_manager_union():
- from app.services.market_presign import list_presign_orders
- db = _session()
- db.add(
- BizCollectionRegionStaff(
- id="leader",
- region_id="region-led",
- user_id="u1",
- is_leader=1,
- tenant_no="tenant-a",
- create_dept_id="dept-a",
- del_flag=0,
- )
- )
- db.add_all(
- [
- _order("by-region", region_id="region-led", manager_user_id="u2"),
- _order("by-manager", region_id="region-other", manager_user_id="u1"),
- _order("outside", region_id="region-other", manager_user_id="u2"),
- ]
- )
- db.commit()
- items, total = list_presign_orders(
- db, ctx=_ctx("manager"), user_info=_user()
- )
- assert total == 2
- assert {item["id"] for item in items} == {"by-region", "by-manager"}
- def test_presign_list_batches_deposits_and_user_names_without_n_plus_one():
- from app.services.market_presign import list_presign_orders
- db = _session()
- db.add(
- SysUser(
- id="u1",
- user_no="u1",
- nick_name="当前用户",
- status="1",
- tenant_no="tenant-a",
- create_dept_id="dept-a",
- del_flag=0,
- )
- )
- db.add_all(
- [_order(f"order-{index}", sales_user_id="u1") for index in range(3)]
- )
- db.commit()
- statements = []
- def capture_statement(*args):
- statements.append(args[2])
- event.listen(db.get_bind(), "before_cursor_execute", capture_statement)
- try:
- items, total = list_presign_orders(
- db, ctx=_ctx("specialist"), user_info=_user(), page_size=10
- )
- finally:
- event.remove(db.get_bind(), "before_cursor_execute", capture_statement)
- assert total == 3
- assert len(items) == 3
- assert len(statements) <= 6
- def test_presign_list_and_detail_resolve_referenced_master_names_in_batches():
- from app.services.market_presign import get_presign_order, list_presign_orders
- db = _session()
- _add_master_data(db)
- db.add_all(
- [
- BizCollectionRegion(id='region-deleted', region_name='历史地区', tenant_no='tenant-a', create_dept_id='', del_flag=1),
- BizCollectionHospital(id='hospital-deleted', region_id='region-deleted', hospital_name='历史医院', tenant_no='tenant-a', create_dept_id='', del_flag=1),
- BizCellSampleType(id='sample-deleted', type_code='D', type_name='历史样本', tenant_no='tenant-a', create_dept_id='', del_flag=1),
- BizCollectionHospital(id='hospital-cross', region_id='region-cross', hospital_name='跨租户医院', tenant_no='tenant-b', create_dept_id='', del_flag=1),
- ]
- )
- normal = _order('normal-master', region_id='region-a', sales_user_id='u1')
- normal.hospital_id = 'hospital-a'
- historical = _order(
- 'historical-master', region_id='region-deleted', sales_user_id='u1'
- )
- historical.hospital_id = 'hospital-deleted'
- cross = _order('cross-master', region_id='region-cross', sales_user_id='u1')
- cross.hospital_id = 'hospital-cross'
- db.add_all(
- [
- normal,
- historical,
- cross,
- BizPresignOrderSampleType(id='normal-master-type', presign_order_id=normal.id, sample_type_id='sample-a', tenant_no='tenant-a', create_dept_id='', order_num=1, del_flag=0),
- BizPresignOrderSampleType(id='historical-master-type', presign_order_id=historical.id, sample_type_id='sample-deleted', tenant_no='tenant-a', create_dept_id='', order_num=1, del_flag=0),
- BizPresignOrderSampleType(id='cross-master-type', presign_order_id=cross.id, sample_type_id='sample-cross', tenant_no='tenant-a', create_dept_id='', order_num=1, del_flag=0),
- ]
- )
- db.commit()
- statements = []
- def capture_statement(*args):
- statements.append(args[2])
- event.listen(db.get_bind(), 'before_cursor_execute', capture_statement)
- try:
- items, total = list_presign_orders(
- db, ctx=_ctx('specialist'), user_info=_user(), page_size=10
- )
- finally:
- event.remove(db.get_bind(), 'before_cursor_execute', capture_statement)
- by_id = {item['id']: item for item in items}
- normal_detail = get_presign_order(
- db,
- order_id=normal.id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- historical_detail = get_presign_order(
- db,
- order_id=historical.id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert total == 3
- assert by_id[normal.id]['regionName'] == 'A区'
- assert by_id[normal.id]['hospitalName'] == 'A医院'
- assert by_id[normal.id]['sampleTypeIds'] == ['sample-a']
- assert by_id[normal.id]['sampleTypeNames'] == ['样本A']
- assert normal_detail['regionName'] == 'A区'
- assert normal_detail['hospitalName'] == 'A医院'
- assert normal_detail['sampleTypeNames'] == ['样本A']
- assert by_id[historical.id]['regionName'] == '历史地区'
- assert by_id[historical.id]['hospitalName'] == '历史医院'
- assert by_id[historical.id]['sampleTypeNames'] == ['历史样本']
- assert historical_detail['regionName'] == '历史地区'
- assert historical_detail['hospitalName'] == '历史医院'
- assert historical_detail['sampleTypeNames'] == ['历史样本']
- assert by_id[cross.id]['regionName'] == ''
- assert by_id[cross.id]['hospitalName'] == ''
- assert by_id[cross.id]['sampleTypeNames'] == ['']
- assert len(statements) <= 8
- def test_presign_response_uses_historical_nicknames_without_raw_fallbacks():
- from app.services.market_presign import list_presign_orders
- db = _session()
- _add_master_data(db)
- db.get(SysUser, 'u1').nick_name = ''
- db.add_all(
- [
- _order(
- "own",
- sales_user_id="u1",
- manager_user_id="disabled-user",
- ),
- _order(
- "cross-reference",
- sales_user_id="u1",
- manager_user_id="cross-user",
- ),
- ]
- )
- row = db.query(BizPresignOrder).filter_by(id="own").one_or_none()
- if row is None:
- db.flush()
- row = db.query(BizPresignOrder).filter_by(id="own").one()
- row.collector_user_id = "deleted-user"
- db.commit()
- items, total = list_presign_orders(
- db, ctx=_ctx("specialist"), user_info=_user()
- )
- by_id = {item['id']: item for item in items}
- assert total == 2
- assert by_id['own']["salesUserName"] == ""
- assert by_id['own']["collectorUserName"] == "已删除秘密姓名"
- assert by_id['own']["regionManagerUserName"] == "停用秘密姓名"
- assert by_id['cross-reference']["regionManagerUserName"] == ""
- assert "跨租户秘密姓名" not in str(items)
- def test_presign_detail_rejects_guessed_out_of_scope_id():
- from app.services.market_presign import get_presign_order
- db = _session()
- db.add(_order("other", sales_user_id="u2"))
- db.commit()
- with pytest.raises(HTTPException) as exc_info:
- get_presign_order(
- db,
- order_id="other",
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- assert exc_info.value.status_code == 404
- def test_presign_body_rejects_client_controlled_sales_user():
- from pydantic import ValidationError
- from app.schemas.market_mobile import MarketPresignOrderBody
- with pytest.raises(ValidationError):
- MarketPresignOrderBody.model_validate({'salesUserId': 'attacker-selected-user'})
- @pytest.mark.parametrize('payload', [{'motherName': ' '}, {'sampleTypeIds': []}])
- def test_presign_update_body_rejects_explicitly_empty_required_fields(payload):
- from pydantic import ValidationError
- from app.schemas.market_mobile import MarketPresignOrderBody
- with pytest.raises(ValidationError):
- MarketPresignOrderBody.model_validate(payload)
- def test_specialist_create_forces_current_sales_user_and_accepts_order_status():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- body = MarketPresignOrderBody.model_validate(
- {
- "motherName": "测试客户",
- "regionId": "region-a",
- "hospitalId": "hospital-a",
- "sampleTypeIds": ["sample-a"],
- "orderStatus": "void",
- }
- )
- data = create_presign_order(
- db,
- body=body,
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- row = db.query(BizPresignOrder).filter_by(id=data["id"]).one()
- assert row.tenant_no == "tenant-a"
- assert row.sales_user_id == "u1"
- assert row.order_status == "void"
- def test_create_rejects_client_requested_converted_status_without_writing():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- with pytest.raises(ValueError, match='转正式|converted'):
- create_presign_order(
- db,
- body=MarketPresignOrderBody.model_validate(
- {
- 'motherName': '不可直转客户',
- 'regionId': 'region-a',
- 'hospitalId': 'hospital-a',
- 'orderStatus': 'converted',
- }
- ),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert db.query(BizPresignOrder).count() == 0
- @pytest.mark.parametrize('current_status', ['active', 'draft', 'void'])
- def test_update_rejects_client_requested_converted_status_without_writing(
- current_status,
- ):
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import update_presign_order
- db = _session()
- _add_master_data(db)
- order = _order('own', sales_user_id='u1')
- order.order_status = current_status
- order.remark = 'before'
- db.add(order)
- db.commit()
- with pytest.raises(ValueError, match='转正式|converted'):
- update_presign_order(
- db,
- order_id='own',
- body=MarketPresignOrderBody.model_validate(
- {'orderStatus': 'converted', 'remark': 'after'}
- ),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- db.expire_all()
- unchanged = db.query(BizPresignOrder).filter_by(id='own').one()
- assert unchanged.order_status == current_status
- assert unchanged.remark == 'before'
- def test_update_rolls_back_when_response_serialization_fails(monkeypatch):
- from app.crud import presign_order as presign_crud
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import update_presign_order
- db = _session()
- _add_master_data(db)
- order = _order('own', sales_user_id='u1')
- order.remark = 'before'
- db.add(order)
- db.commit()
- monkeypatch.setattr(
- presign_crud,
- 'order_to_dict',
- lambda *args, **kwargs: (_ for _ in ()).throw(
- RuntimeError('serialize failed')
- ),
- )
- with pytest.raises(RuntimeError, match='serialize failed'):
- update_presign_order(
- db,
- order_id='own',
- body=MarketPresignOrderBody(remark='after'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- db.expire_all()
- assert db.query(BizPresignOrder).filter_by(id='own').one().remark == 'before'
- def test_create_rolls_back_order_children_and_transfers_when_data_scope_rejects():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- restricted_user = {
- "user_id": "u1",
- "tenant_no": "tenant-a",
- "role": "ordinary",
- "dept_id": "dept-a",
- }
- with pytest.raises(HTTPException):
- create_presign_order(
- db,
- body=MarketPresignOrderBody.model_validate(
- {
- "regionId": "region-a",
- "hospitalId": "hospital-a",
- "sampleTypeIds": ["sample-a"],
- "transfers": [{"transferAmount": 100}],
- }
- ),
- ctx=_ctx("specialist"),
- user_info=restricted_user,
- )
- assert db.query(BizPresignOrder).count() == 0
- assert db.query(BizPresignOrderSampleType).count() == 0
- assert db.query(BizPresignTransfer).count() == 0
- def test_create_uses_real_auth_region_id_as_department_for_all_rows():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- data = create_presign_order(
- db,
- body=MarketPresignOrderBody.model_validate(
- {
- "regionId": "region-a",
- "hospitalId": "hospital-a",
- "sampleTypeIds": ["sample-a"],
- "transfers": [{"transferAmount": 100}],
- }
- ),
- ctx=_ctx("specialist"),
- user_info=_real_auth_user(),
- )
- assert db.query(BizPresignOrder).filter_by(id=data["id"]).one().create_dept_id == "dept-from-auth"
- assert db.query(BizPresignOrderSampleType).one().create_dept_id == "dept-from-auth"
- assert db.query(BizPresignTransfer).one().create_dept_id == "dept-from-auth"
- @pytest.mark.parametrize("user_id", ["cross-user", "deleted-user", "disabled-user"])
- def test_create_rejects_cross_tenant_deleted_or_disabled_person(user_id):
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- with pytest.raises(ValueError, match="用户"):
- create_presign_order(
- db,
- body=MarketPresignOrderBody.model_validate(
- {"regionId": "region-a", "collectorUserId": user_id}
- ),
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- assert db.query(BizPresignOrder).count() == 0
- def test_manager_create_cannot_self_assign_an_unmanaged_region():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- db.add(
- BizCollectionRegionStaff(
- id='manager-region-a',
- region_id='region-a',
- user_id='u1',
- is_leader=1,
- tenant_no='tenant-a',
- create_dept_id='dept-a',
- del_flag=0,
- )
- )
- db.commit()
- with pytest.raises(ValueError, match="地区经理范围"):
- create_presign_order(
- db,
- body=MarketPresignOrderBody.model_validate(
- {
- "motherName": "越权订单",
- "regionId": "region-b",
- "regionManagerUserId": "u1",
- "sampleTypeIds": ["sample-a"],
- }
- ),
- ctx=_ctx("manager"),
- user_info=_user(),
- )
- assert db.query(BizPresignOrder).count() == 0
- @pytest.mark.parametrize(
- 'payload',
- [
- {'sampleTypeIds': ['sample-a']},
- {'motherName': ' ', 'sampleTypeIds': ['sample-a']},
- {'motherName': '测试客户'},
- {'motherName': '测试客户', 'sampleTypeIds': []},
- ],
- )
- def test_create_requires_mother_name_and_sample_types(payload):
- from pydantic import ValidationError
- from app.schemas.market_mobile import MarketPresignOrderCreateBody
- with pytest.raises(ValidationError, match='motherName|sampleTypeIds|母亲姓名'):
- MarketPresignOrderCreateBody.model_validate(payload)
- @pytest.mark.parametrize(
- ("payload", "message"),
- [
- ({"regionId": "region-cross"}, "采集地区"),
- ({"regionId": "region-missing"}, "采集地区"),
- (
- {"regionId": "region-a", "hospitalId": "hospital-b"},
- "医院",
- ),
- (
- {"regionId": "region-a", "sampleTypeIds": ["sample-cross"]},
- "样本类型",
- ),
- (
- {"regionId": "region-a", "sampleTypeIds": ["sample-missing"]},
- "样本类型",
- ),
- ],
- )
- def test_create_rejects_invalid_or_cross_tenant_master_data(payload, message):
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import create_presign_order
- db = _session()
- _add_master_data(db)
- with pytest.raises(ValueError, match=message):
- create_presign_order(
- db,
- body=MarketPresignOrderBody.model_validate(payload),
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- assert db.query(BizPresignOrder).count() == 0
- def test_update_validates_resulting_hospital_region_and_sample_types_before_write():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import update_presign_order
- db = _session()
- _add_master_data(db)
- db.add(
- _order(
- "own",
- region_id="region-a",
- sales_user_id="u1",
- )
- )
- db.commit()
- with pytest.raises(ValueError, match="医院"):
- update_presign_order(
- db,
- order_id="own",
- body=MarketPresignOrderBody.model_validate(
- {"hospitalId": "hospital-b", "sampleTypeIds": ["sample-cross"]}
- ),
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- row = db.query(BizPresignOrder).filter_by(id="own").one()
- assert row.hospital_id is None
- assert db.query(BizPresignOrderSampleType).count() == 0
- def test_update_rebuilt_sample_types_keep_current_user_department():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import update_presign_order
- db = _session()
- _add_master_data(db)
- db.add(_order("own", region_id="region-a", sales_user_id="u1"))
- db.commit()
- update_presign_order(
- db,
- order_id="own",
- body=MarketPresignOrderBody.model_validate(
- {"sampleTypeIds": ["sample-a"]}
- ),
- ctx=_ctx("specialist"),
- user_info=_real_auth_user(),
- )
- child = db.query(BizPresignOrderSampleType).one()
- assert child.create_dept_id == "dept-from-auth"
- def test_update_rejects_disabled_person_before_write():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import update_presign_order
- db = _session()
- _add_master_data(db)
- db.add(_order("own", region_id="region-a", sales_user_id="u1"))
- db.commit()
- with pytest.raises(ValueError, match="用户"):
- update_presign_order(
- db,
- order_id="own",
- body=MarketPresignOrderBody.model_validate(
- {"collectorUserId": "disabled-user"}
- ),
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- assert db.query(BizPresignOrder).filter_by(id="own").one().collector_user_id is None
- def test_update_rejects_resulting_assignment_outside_manager_scope():
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import update_presign_order
- db = _session()
- _add_master_data(db)
- db.add(_order("managed", region_id="region-a", manager_user_id="u1"))
- db.commit()
- with pytest.raises(ValueError, match="地区经理范围"):
- update_presign_order(
- db,
- order_id="managed",
- body=MarketPresignOrderBody.model_validate(
- {
- "regionId": "region-b",
- "regionManagerUserId": "u2",
- }
- ),
- ctx=_ctx("manager"),
- user_info=_user(),
- )
- row = db.query(BizPresignOrder).filter_by(id="managed").one()
- assert row.region_id == "region-a"
- assert row.region_manager_user_id == "u1"
- def test_convert_rechecks_scope_before_reusing_presign_conversion(monkeypatch):
- from app.crud import presign_order as presign_crud
- from app.services.market_presign import convert_presign_order
- db = _session()
- _add_master_data(db)
- db.add_all(
- [
- _order("own", sales_user_id="u1", manager_user_id="manager-a"),
- _order("other", sales_user_id="u2", manager_user_id="manager-b"),
- ]
- )
- db.commit()
- calls = []
- def fake_convert(db, *, order_id, tenant_no, user_id, create_dept_id=""):
- calls.append((order_id, tenant_no, user_id, create_dept_id))
- return {"protocolId": "protocol-1"}
- monkeypatch.setattr(presign_crud, "convert_to_protocol", fake_convert)
- with pytest.raises(HTTPException) as exc_info:
- convert_presign_order(
- db,
- order_id="other",
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- assert exc_info.value.status_code == 404
- assert calls == []
- assert convert_presign_order(
- db,
- order_id="own",
- ctx=_ctx("specialist"),
- user_info=_real_auth_user(),
- ) == {"protocolId": "protocol-1"}
- assert calls == [("own", "tenant-a", "u1", "dept-from-auth")]
- def test_convert_rolls_back_protocol_children_and_presign_status_on_midway_failure(
- monkeypatch,
- ):
- from app.crud import collection_protocol as protocol_crud
- from app.services.market_presign import convert_presign_order
- db = _session()
- _add_master_data(db)
- order = _order(
- "own",
- region_id="region-a",
- sales_user_id="u1",
- manager_user_id="manager-a",
- )
- order.protocol_no = "P-ATOMIC-1"
- order.hospital_id = "hospital-a"
- db.add(order)
- db.add(
- BizPresignOrderSampleType(
- id="presign-sample",
- presign_order_id="own",
- sample_type_id="sample-a",
- tenant_no="tenant-a",
- create_dept_id="dept-a",
- del_flag=0,
- )
- )
- db.commit()
- def fail_after_collection(*args, **kwargs):
- raise RuntimeError("injected sample failure")
- monkeypatch.setattr(protocol_crud, "save_samples", fail_after_collection)
- with pytest.raises(RuntimeError, match="injected sample failure"):
- convert_presign_order(
- db,
- order_id="own",
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- db.expire_all()
- unchanged = db.query(BizPresignOrder).filter_by(id="own").one()
- assert unchanged.order_status == "active"
- assert unchanged.converted_protocol_id is None
- assert db.query(BizCollectionProtocol).count() == 0
- assert db.query(BizProtocolPartyCustomer).count() == 0
- assert db.query(BizProtocolCollectionInfo).count() == 0
- assert db.query(BizProtocolSample).count() == 0
- def test_convert_creates_protocol_in_effective_status():
- """预签约转正式后,正式协议状态应为生效中(sxz)。"""
- from app.services.market_presign import convert_presign_order
- db = _session()
- _add_master_data(db)
- order = _order(
- "own",
- region_id="region-a",
- sales_user_id="u1",
- manager_user_id="manager-a",
- )
- order.protocol_no = "P-CONVERT-SXZ"
- order.hospital_id = "hospital-a"
- db.add(order)
- db.commit()
- result = convert_presign_order(
- db,
- order_id="own",
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- protocol = db.query(BizCollectionProtocol).filter_by(id=result["protocolId"]).one()
- assert protocol.protocol_status == "sxz"
- def test_convert_rejects_invalid_person_before_creating_protocol():
- from app.services.market_presign import convert_presign_order
- db = _session()
- _add_master_data(db)
- order = _order("own", sales_user_id="u1")
- order.protocol_no = "P-INVALID-USER"
- order.collector_user_id = "cross-user"
- db.add(order)
- db.commit()
- with pytest.raises(ValueError, match="用户"):
- convert_presign_order(
- db,
- order_id="own",
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- assert db.query(BizCollectionProtocol).count() == 0
- def test_convert_fetches_presign_row_with_for_update(monkeypatch):
- from app.crud import presign_order as presign_crud
- class QueryProbe:
- def __init__(self):
- self.locked = False
- def filter(self, *args):
- return self
- def with_for_update(self):
- self.locked = True
- return self
- def populate_existing(self):
- return self
- def first(self):
- return object()
- class DbProbe:
- def __init__(self):
- self.query_probe = QueryProbe()
- def query(self, model):
- return self.query_probe
- db = DbProbe()
- assert presign_crud._get_order_or_raise(
- db, order_id="order-1", tenant_no="tenant-a", for_update=True
- ) is not None
- assert db.query_probe.locked is True
- def test_locked_convert_read_refreshes_preloaded_stale_identity(tmp_path):
- from app.crud import presign_order as presign_crud
- engine = create_engine(f"sqlite:///{tmp_path / 'stale.db'}")
- Base.metadata.create_all(engine, tables=[BizPresignOrder.__table__])
- Session = sessionmaker(bind=engine)
- first = Session()
- second = Session()
- try:
- first.add(_order("race", sales_user_id="u1"))
- first.commit()
- cached = first.query(BizPresignOrder).filter_by(id="race").one()
- assert cached.order_status == "active"
- concurrent = second.query(BizPresignOrder).filter_by(id="race").one()
- concurrent.order_status = "converted"
- concurrent.converted_protocol_id = "protocol-existing"
- second.commit()
- locked = presign_crud._get_order_or_raise(
- first,
- order_id="race",
- tenant_no="tenant-a",
- for_update=True,
- )
- assert locked is cached
- assert locked.order_status == "converted"
- assert locked.converted_protocol_id == "protocol-existing"
- with pytest.raises(ValueError, match="已转正式"):
- presign_crud.assert_presign_convertible(
- order_status=locked.order_status
- )
- finally:
- first.close()
- second.close()
- def test_convert_translates_unique_race_to_already_converted_domain_error(
- monkeypatch,
- ):
- from app.crud import collection_protocol as protocol_crud
- from app.services.market_presign import convert_presign_order
- db = _session()
- _add_master_data(db)
- order = _order("own", sales_user_id="u1", manager_user_id="manager-a")
- order.protocol_no = "P-RACE"
- db.add(order)
- db.commit()
- def duplicate_source(*args, **kwargs):
- raise IntegrityError(
- "INSERT INTO biz_collection_protocol",
- {},
- Exception("Duplicate entry for key 'uk_collection_protocol_presign_source'"),
- )
- monkeypatch.setattr(
- protocol_crud, "create_collection_protocol", duplicate_source
- )
- with pytest.raises(ValueError, match="已转正式"):
- convert_presign_order(
- db,
- order_id="own",
- ctx=_ctx("specialist"),
- user_info=_user(),
- )
- assert db.query(BizPresignOrder).filter_by(id="own").one().order_status == "active"
- def test_market_presign_bodies_forbid_unknown_fields_and_validate_transfer_limits():
- from pydantic import ValidationError
- from app.schemas.market_mobile import (
- MarketPresignOrderBody,
- MarketPresignTransferBody,
- )
- with pytest.raises(ValidationError, match='tenantNo'):
- MarketPresignOrderBody.model_validate({'tenantNo': 'tenant-b'})
- assert (
- MarketPresignOrderBody.model_validate(
- {'orderStatus': 'void'}
- ).order_status
- == 'void'
- )
- valid = MarketPresignTransferBody.model_validate(
- {
- 'transferAmount': '123.45',
- 'transferTime': '2026-10-11 12:34:56',
- 'transferType': 'bank',
- 'transferAttachments': 'a' * 4000,
- 'remark': 'r' * 512,
- }
- )
- assert str(valid.transfer_amount) == '123.45'
- assert valid.transfer_time == '2026-10-11 12:34:56'
- assert (
- MarketPresignTransferBody.model_validate(
- {'transferTime': '2026-10-11T12:34'}
- ).transfer_time
- == '2026-10-11T12:34'
- )
- for payload in (
- {'unknown': 'value'},
- {'transferAmount': 0},
- {'transferTime': 'not-a-datetime'},
- {'transferTime': '2026-10-11'},
- {'transferTime': '2026-10-11 12:34'},
- {'transferTime': '2026-10-11T12'},
- {'transferTime': '2026-10-11T12:34+08:00'},
- {'transferType': 'x' * 17},
- {'transferAttachments': 'x' * 4001},
- {'remark': 'x' * 513},
- ):
- with pytest.raises(ValidationError):
- MarketPresignTransferBody.model_validate(payload)
- def test_market_presign_order_body_accepts_database_sized_values():
- from decimal import Decimal
- from app.schemas.market_mobile import MarketPresignOrderBody
- body = MarketPresignOrderBody.model_validate(
- {
- 'orderDate': '20261011',
- 'protocolNo': 'P' * 64,
- 'motherName': '母' * 64,
- 'expectedDeliveryDate': '20261231',
- 'regionId': 'r' * 64,
- 'hospitalId': 'h' * 64,
- 'sampleCollectTime': '2026-10-11 12:34:56',
- 'payMode': 'x' * 8,
- 'contractAmount': '9999999999999999.99',
- 'collectorUserId': 'c' * 64,
- 'regionManagerUserId': 'm' * 64,
- 'remark': '备' * 512,
- 'sampleTypeIds': [f'sample-{index}' for index in range(100)],
- }
- )
- assert body.order_date == '20261011'
- assert body.expected_delivery_date == '20261231'
- assert body.contract_amount == Decimal('9999999999999999.99')
- assert len(body.sample_type_ids or []) == 100
- @pytest.mark.parametrize(
- 'payload',
- [
- {'orderDate': '2026-10-11'},
- {'orderDate': '20260230'},
- {'expectedDeliveryDate': '20261301'},
- {'sampleCollectTime': 'not-a-datetime'},
- {'protocolNo': 'p' * 65},
- {'motherName': 'm' * 65},
- {'regionId': 'r' * 65},
- {'hospitalId': 'h' * 65},
- {'collectorUserId': 'c' * 65},
- {'salesUserId': 's' * 65},
- {'regionManagerUserId': 'm' * 65},
- {'payMode': 'p' * 9},
- {'remark': 'r' * 513},
- {'sampleTypeIds': ['s' * 65]},
- {'sampleTypeIds': [f'sample-{index}' for index in range(101)]},
- {'contractAmount': 'NaN'},
- {'contractAmount': 'Infinity'},
- {'contractAmount': '-0.01'},
- {'contractAmount': '10000000000000000.00'},
- {'contractAmount': '1.001'},
- ],
- )
- def test_market_presign_order_body_rejects_values_outside_database_bounds(
- payload,
- ):
- from pydantic import ValidationError
- from app.schemas.market_mobile import MarketPresignOrderBody
- with pytest.raises(ValidationError):
- MarketPresignOrderBody.model_validate(payload)
- @pytest.mark.parametrize('status', ['active', 'void'])
- def test_specialist_can_soft_delete_own_non_converted_presign(status):
- from app.services.market_presign import delete_presign_order
- db = _session()
- order = _order('own', sales_user_id='u1')
- order.order_status = status
- db.add(order)
- db.commit()
- delete_presign_order(
- db,
- order_id='own',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- deleted = db.query(BizPresignOrder).filter_by(id='own').one()
- assert deleted.del_flag == 1
- assert deleted.update_by == 'u1'
- def test_specialist_cannot_delete_another_specialists_presign():
- from app.services.market_presign import delete_presign_order
- db = _session()
- db.add(_order('other', sales_user_id='u2'))
- db.commit()
- with pytest.raises(HTTPException) as exc_info:
- delete_presign_order(
- db,
- order_id='other',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert exc_info.value.status_code == 404
- assert db.query(BizPresignOrder).filter_by(id='other').one().del_flag == 0
- def test_manager_delete_is_limited_to_manager_scope():
- from app.services.market_presign import delete_presign_order
- db = _session()
- db.add(
- BizCollectionRegionStaff(
- id='manager-region',
- region_id='region-led',
- user_id='u1',
- is_leader=1,
- tenant_no='tenant-a',
- create_dept_id='dept-a',
- del_flag=0,
- )
- )
- db.add_all(
- [
- _order(
- 'managed',
- region_id='region-led',
- sales_user_id='u2',
- manager_user_id='u2',
- ),
- _order(
- 'outside',
- region_id='region-other',
- sales_user_id='u2',
- manager_user_id='u2',
- ),
- ]
- )
- db.commit()
- delete_presign_order(
- db,
- order_id='managed',
- ctx=_ctx('manager'),
- user_info=_user(),
- )
- with pytest.raises(HTTPException) as exc_info:
- delete_presign_order(
- db,
- order_id='outside',
- ctx=_ctx('manager'),
- user_info=_user(),
- )
- assert exc_info.value.status_code == 404
- assert db.query(BizPresignOrder).filter_by(id='managed').one().del_flag == 1
- assert db.query(BizPresignOrder).filter_by(id='outside').one().del_flag == 0
- def test_converted_presign_cannot_be_deleted():
- from app.services.market_presign import delete_presign_order
- db = _session()
- order = _order('converted', sales_user_id='u1')
- order.order_status = 'converted'
- db.add(order)
- db.commit()
- with pytest.raises(ValueError, match='已转|converted'):
- delete_presign_order(
- db,
- order_id='converted',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert db.query(BizPresignOrder).filter_by(id='converted').one().del_flag == 0
- @pytest.mark.parametrize('scope_kind', ['specialist', 'manager'])
- @pytest.mark.parametrize(
- ('action', 'pay_status'),
- [
- ('create', 'dfj'),
- ('update', 'dfj'),
- ('delete', 'dfj'),
- ('submit', 'dfj'),
- ('withdraw', 'jfqrz'),
- ],
- )
- def test_presign_transfer_writes_recheck_parent_order_scope(
- scope_kind, action, pay_status
- ):
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import (
- create_presign_transfer,
- delete_presign_transfer,
- submit_presign_transfer,
- update_presign_transfer,
- withdraw_presign_transfer,
- )
- db = _session()
- db.add(
- _order(
- 'outside',
- region_id='region-outside',
- sales_user_id='u2',
- manager_user_id='u2',
- )
- )
- db.add(_transfer('outside-transfer', order_id='outside', pay_status=pay_status))
- db.commit()
- body = MarketPresignTransferBody(
- transferAmount=200,
- transferType='bank',
- )
- calls = {
- 'create': lambda: create_presign_transfer(
- db,
- order_id='outside',
- body=body,
- ctx=_ctx(scope_kind),
- user_info=_user(),
- ),
- 'update': lambda: update_presign_transfer(
- db,
- transfer_id='outside-transfer',
- body=body,
- ctx=_ctx(scope_kind),
- user_info=_user(),
- ),
- 'delete': lambda: delete_presign_transfer(
- db,
- transfer_id='outside-transfer',
- ctx=_ctx(scope_kind),
- user_info=_user(),
- ),
- 'submit': lambda: submit_presign_transfer(
- db,
- transfer_id='outside-transfer',
- ctx=_ctx(scope_kind),
- user_info=_user(),
- ),
- 'withdraw': lambda: withdraw_presign_transfer(
- db,
- transfer_id='outside-transfer',
- ctx=_ctx(scope_kind),
- user_info=_user(),
- ),
- }
- with pytest.raises(HTTPException) as exc_info:
- calls[action]()
- assert exc_info.value.status_code == 404
- row = db.query(BizPresignTransfer).filter_by(id='outside-transfer').one()
- assert row.del_flag == 0
- assert row.pay_status == pay_status
- def test_dfj_presign_transfer_can_edit_submit_withdraw_and_soft_delete():
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import (
- create_presign_transfer,
- delete_presign_transfer,
- submit_presign_transfer,
- update_presign_transfer,
- withdraw_presign_transfer,
- )
- db = _session()
- db.add(_order('own', sales_user_id='u1'))
- db.commit()
- created = create_presign_transfer(
- db,
- order_id='own',
- body=MarketPresignTransferBody(
- transferAmount='100.50',
- transferType='bank',
- ),
- ctx=_ctx('specialist'),
- user_info=_real_auth_user(),
- )
- transfer_id = created['id']
- updated = update_presign_transfer(
- db,
- transfer_id=transfer_id,
- body=MarketPresignTransferBody(transferAmount='200.25', remark='更新'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert updated['transferAmount'] == '200.25'
- assert updated['remark'] == '更新'
- assert (
- db.query(BizPresignTransfer).filter_by(id=transfer_id).one().create_dept_id
- == 'dept-from-auth'
- )
- submitted = submit_presign_transfer(
- db,
- transfer_id=transfer_id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert submitted['payStatus'] == 'jfqrz'
- with pytest.raises(ValueError, match='状态|待缴费|已缴费|已付款'):
- delete_presign_transfer(
- db,
- transfer_id=transfer_id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- withdrawn = withdraw_presign_transfer(
- db,
- transfer_id=transfer_id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert withdrawn['payStatus'] == 'dfj'
- delete_presign_transfer(
- db,
- transfer_id=transfer_id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- deleted = db.query(BizPresignTransfer).filter_by(id=transfer_id).one()
- assert deleted.del_flag == 1
- assert deleted.update_by == 'u1'
- def test_jfqrz_presign_transfer_only_allows_withdraw():
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import (
- delete_presign_transfer,
- submit_presign_transfer,
- update_presign_transfer,
- withdraw_presign_transfer,
- )
- db = _session()
- db.add(_order('own', sales_user_id='u1'))
- db.add(_transfer('pending', pay_status='jfqrz'))
- db.commit()
- for action in (
- lambda: update_presign_transfer(
- db,
- transfer_id='pending',
- body=MarketPresignTransferBody(remark='不允许'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- lambda: delete_presign_transfer(
- db,
- transfer_id='pending',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- lambda: submit_presign_transfer(
- db,
- transfer_id='pending',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- ):
- with pytest.raises(ValueError):
- action()
- assert withdraw_presign_transfer(
- db,
- transfer_id='pending',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )['payStatus'] == 'dfj'
- @pytest.mark.parametrize('action', ['update', 'delete', 'submit', 'withdraw'])
- def test_yjf_presign_transfer_rejects_all_market_writes(action):
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import (
- delete_presign_transfer,
- submit_presign_transfer,
- update_presign_transfer,
- withdraw_presign_transfer,
- )
- db = _session()
- db.add(_order('own', sales_user_id='u1'))
- db.add(_transfer('paid', pay_status='yjf'))
- db.commit()
- calls = {
- 'update': lambda: update_presign_transfer(
- db,
- transfer_id='paid',
- body=MarketPresignTransferBody(remark='不允许'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'delete': lambda: delete_presign_transfer(
- db,
- transfer_id='paid',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'submit': lambda: submit_presign_transfer(
- db,
- transfer_id='paid',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'withdraw': lambda: withdraw_presign_transfer(
- db,
- transfer_id='paid',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- }
- with pytest.raises(ValueError):
- calls[action]()
- @pytest.mark.parametrize('order_status', ['converted', 'void'])
- @pytest.mark.parametrize('action', ['create', 'update', 'delete', 'submit', 'withdraw'])
- def test_non_writable_presign_order_rejects_every_transfer_write(
- order_status, action
- ):
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import (
- create_presign_transfer,
- delete_presign_transfer,
- submit_presign_transfer,
- update_presign_transfer,
- withdraw_presign_transfer,
- )
- db = _session()
- order = _order('own', sales_user_id='u1')
- order.order_status = order_status
- db.add(order)
- db.add(
- _transfer(
- 'transfer',
- pay_status='jfqrz' if action == 'withdraw' else 'dfj',
- )
- )
- db.commit()
- body = MarketPresignTransferBody(
- transferAmount=100,
- transferType='bank',
- )
- calls = {
- 'create': lambda: create_presign_transfer(
- db,
- order_id='own',
- body=body,
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'update': lambda: update_presign_transfer(
- db,
- transfer_id='transfer',
- body=body,
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'delete': lambda: delete_presign_transfer(
- db,
- transfer_id='transfer',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'submit': lambda: submit_presign_transfer(
- db,
- transfer_id='transfer',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'withdraw': lambda: withdraw_presign_transfer(
- db,
- transfer_id='transfer',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- }
- with pytest.raises(ValueError, match='订单|状态|转账'):
- calls[action]()
- @pytest.mark.parametrize('action', ['create', 'update'])
- def test_presign_transfer_rejects_unregistered_attachment_url(action):
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import (
- create_presign_transfer,
- update_presign_transfer,
- )
- db = _session()
- db.add(_order('own', sales_user_id='u1'))
- db.add(_transfer('draft'))
- db.commit()
- body = MarketPresignTransferBody(
- transferAttachments='https://evil.test/proof.pdf'
- )
- with pytest.raises(ValueError, match='附件'):
- if action == 'create':
- create_presign_transfer(
- db,
- order_id='own',
- body=body,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- else:
- update_presign_transfer(
- db,
- transfer_id='draft',
- body=body,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- def test_presign_transfer_attachments_bind_to_parent_order():
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import (
- create_presign_transfer,
- update_presign_transfer,
- )
- db = _session()
- db.add(_order('own', sales_user_id='u1'))
- db.add_all(
- [
- BizAttachment(
- id='attachment-create',
- business_type='market:specialist:presign:staging',
- original_name='create.pdf',
- file_url='presign/create.pdf',
- create_by='u1',
- create_dept_id='dept-a',
- tenant_no='tenant-a',
- del_flag=0,
- ),
- BizAttachment(
- id='attachment-update',
- business_type='market:specialist:presign:staging',
- original_name='update.pdf',
- file_url='presign/update.pdf',
- create_by='u1',
- create_dept_id='dept-a',
- tenant_no='tenant-a',
- del_flag=0,
- ),
- ]
- )
- db.commit()
- created = create_presign_transfer(
- db,
- order_id='own',
- body=MarketPresignTransferBody(
- transferAmount=100,
- transferType='bank',
- transferAttachments='attachment-create',
- ),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- updated = update_presign_transfer(
- db,
- transfer_id=created['id'],
- body=MarketPresignTransferBody(
- transferAttachments='attachment-create,attachment-update'
- ),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert updated['transferAttachments'] == (
- 'presign/create.pdf,presign/update.pdf'
- )
- attachments = db.query(BizAttachment).order_by(BizAttachment.id).all()
- assert {row.business_id for row in attachments} == {'own'}
- assert {row.business_type for row in attachments} == {
- 'market:specialist:presign:bound'
- }
- def test_stale_withdraw_does_not_overwrite_later_finance_confirmation(tmp_path):
- from app.crud import presign_order as presign_crud
- from app.services.market_presign import withdraw_presign_transfer
- Session = _session_factory(f"sqlite:///{tmp_path / 'withdraw-race.db'}")
- market_db = Session()
- finance_db = Session()
- try:
- market_db.add(_order('own', sales_user_id='u1'))
- market_db.add(_transfer('transfer', pay_status='jfqrz'))
- market_db.commit()
- stale = market_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert stale.pay_status == 'jfqrz'
- presign_crud.confirm_receipt(
- finance_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='finance-user',
- )
- with pytest.raises(ValueError, match='状态|撤回'):
- withdraw_presign_transfer(
- market_db,
- transfer_id='transfer',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- finance_db.expire_all()
- current = finance_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert current.pay_status == 'yjf'
- assert current.update_by == 'finance-user'
- finally:
- market_db.close()
- finance_db.close()
- def test_stale_finance_confirmation_does_not_overwrite_later_mobile_withdraw(
- tmp_path,
- ):
- from app.crud import presign_order as presign_crud
- from app.services.market_presign import withdraw_presign_transfer
- Session = _session_factory(
- f"sqlite:///{tmp_path / 'finance-confirm-after-withdraw-race.db'}"
- )
- finance_db = Session()
- market_db = Session()
- try:
- finance_db.add(_order('own', sales_user_id='u1'))
- finance_db.add(_transfer('transfer', pay_status='jfqrz'))
- finance_db.commit()
- stale = finance_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert stale.pay_status == 'jfqrz'
- withdrawn = withdraw_presign_transfer(
- market_db,
- transfer_id='transfer',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert withdrawn['payStatus'] == 'dfj'
- with pytest.raises(ValueError, match='状态'):
- presign_crud.confirm_receipt(
- finance_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='finance-user',
- )
- market_db.expire_all()
- current = market_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert current.pay_status == 'dfj'
- assert current.update_by == 'u1'
- finally:
- finance_db.close()
- market_db.close()
- @pytest.mark.parametrize('action', ['update', 'submit'])
- def test_stale_web_draft_action_does_not_mutate_later_paid_transfer(
- tmp_path, action
- ):
- from app.crud import presign_order as presign_crud
- Session = _session_factory(
- f"sqlite:///{tmp_path / f'web-{action}-after-paid-race.db'}"
- )
- web_db = Session()
- finance_db = Session()
- try:
- web_db.add(_order('own', sales_user_id='u1'))
- transfer = _transfer('transfer', pay_status='dfj')
- transfer.remark = 'original'
- web_db.add(transfer)
- web_db.commit()
- stale = web_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert stale.pay_status == 'dfj'
- presign_crud.submit_receipt_finance(
- finance_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='finance-user',
- )
- presign_crud.confirm_receipt(
- finance_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='finance-user',
- )
- calls = {
- 'update': lambda: presign_crud.update_transfer_draft(
- web_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='web-user',
- fields={'remark': 'stale-write'},
- ),
- 'submit': lambda: presign_crud.submit_receipt_finance(
- web_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='web-user',
- ),
- }
- with pytest.raises(ValueError, match='状态'):
- calls[action]()
- finance_db.expire_all()
- current = finance_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert current.pay_status == 'yjf'
- assert current.remark == 'original'
- assert current.update_by == 'finance-user'
- finally:
- web_db.close()
- finance_db.close()
- @pytest.mark.parametrize('action', ['withdraw', 'confirm', 'reject'])
- def test_stale_shared_pending_action_does_not_mutate_later_draft_transfer(
- tmp_path, action
- ):
- from app.crud import presign_order as presign_crud
- from app.services.market_presign import withdraw_presign_transfer
- Session = _session_factory(
- f"sqlite:///{tmp_path / f'shared-{action}-after-withdraw-race.db'}"
- )
- stale_db = Session()
- market_db = Session()
- try:
- stale_db.add(_order('own', sales_user_id='u1'))
- stale_db.add(_transfer('transfer', pay_status='jfqrz'))
- stale_db.commit()
- stale = stale_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert stale.pay_status == 'jfqrz'
- withdraw_presign_transfer(
- market_db,
- transfer_id='transfer',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- calls = {
- 'withdraw': lambda: presign_crud.withdraw_receipt(
- stale_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='stale-user',
- ),
- 'confirm': lambda: presign_crud.confirm_receipt(
- stale_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='stale-user',
- ),
- 'reject': lambda: presign_crud.reject_receipt(
- stale_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='stale-user',
- ),
- }
- with pytest.raises(ValueError, match='状态'):
- calls[action]()
- market_db.expire_all()
- current = market_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert current.pay_status == 'dfj'
- assert current.update_by == 'u1'
- finally:
- stale_db.close()
- market_db.close()
- def test_mobile_transfer_update_serializes_before_commit(monkeypatch):
- from app.crud import presign_order as presign_crud
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import update_presign_transfer
- db = _session()
- db.add(_order('own', sales_user_id='u1'))
- db.add(_transfer('transfer', pay_status='dfj'))
- db.commit()
- committed = False
- original_commit = db.commit
- original_to_dict = presign_crud._transfer_to_dict
- def tracked_commit():
- nonlocal committed
- committed = True
- original_commit()
- def snapshot_before_commit(*args, **kwargs):
- assert not committed
- return original_to_dict(*args, **kwargs)
- monkeypatch.setattr(db, 'commit', tracked_commit)
- monkeypatch.setattr(presign_crud, '_transfer_to_dict', snapshot_before_commit)
- result = update_presign_transfer(
- db,
- transfer_id='transfer',
- body=MarketPresignTransferBody(remark='updated'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert committed
- assert result['remark'] == 'updated'
- @pytest.mark.parametrize('action', ['update', 'delete', 'submit'])
- def test_stale_dfj_action_does_not_mutate_later_paid_transfer(tmp_path, action):
- from app.crud import presign_order as presign_crud
- from app.schemas.market_mobile import MarketPresignTransferBody
- from app.services.market_presign import (
- delete_presign_transfer,
- submit_presign_transfer,
- update_presign_transfer,
- )
- Session = _session_factory(
- f"sqlite:///{tmp_path / f'transfer-{action}-race.db'}"
- )
- market_db = Session()
- finance_db = Session()
- try:
- market_db.add(_order('own', sales_user_id='u1'))
- transfer = _transfer('transfer', pay_status='dfj')
- transfer.remark = 'original'
- market_db.add(transfer)
- market_db.commit()
- stale = market_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert stale.pay_status == 'dfj'
- presign_crud.submit_receipt_finance(
- finance_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='finance-user',
- )
- presign_crud.confirm_receipt(
- finance_db,
- transfer_id='transfer',
- tenant_no='tenant-a',
- user_id='finance-user',
- )
- calls = {
- 'update': lambda: update_presign_transfer(
- market_db,
- transfer_id='transfer',
- body=MarketPresignTransferBody(remark='stale-write'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'delete': lambda: delete_presign_transfer(
- market_db,
- transfer_id='transfer',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'submit': lambda: submit_presign_transfer(
- market_db,
- transfer_id='transfer',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- }
- with pytest.raises(ValueError, match='状态|待缴费|提交'):
- calls[action]()
- finance_db.expire_all()
- current = finance_db.query(BizPresignTransfer).filter_by(id='transfer').one()
- assert current.pay_status == 'yjf'
- assert current.del_flag == 0
- assert current.remark == 'original'
- finally:
- market_db.close()
- finance_db.close()
- @pytest.mark.parametrize('action', ['update', 'delete'])
- def test_stale_order_write_does_not_mutate_later_conversion(tmp_path, action):
- from app.schemas.market_mobile import MarketPresignOrderBody
- from app.services.market_presign import (
- convert_presign_order,
- delete_presign_order,
- update_presign_order,
- )
- Session = _session_factory(
- f"sqlite:///{tmp_path / f'order-{action}-race.db'}"
- )
- market_db = Session()
- convert_db = Session()
- try:
- _add_master_data(market_db)
- order = _order('own', sales_user_id='u1')
- order.protocol_no = f'P-RACE-{action}'
- market_db.add(order)
- market_db.commit()
- stale = market_db.query(BizPresignOrder).filter_by(id='own').one()
- assert stale.order_status == 'active'
- convert_presign_order(
- convert_db,
- order_id='own',
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- calls = {
- 'update': lambda: update_presign_order(
- market_db,
- order_id='own',
- body=MarketPresignOrderBody(remark='stale-write'),
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- 'delete': lambda: delete_presign_order(
- market_db,
- order_id='own',
- ctx=_ctx('specialist'),
- user_info=_user(),
- ),
- }
- with pytest.raises(ValueError, match='不可|已转'):
- calls[action]()
- convert_db.expire_all()
- current = convert_db.query(BizPresignOrder).filter_by(id='own').one()
- assert current.order_status == 'converted'
- assert current.converted_protocol_id
- assert current.del_flag == 0
- assert current.remark is None
- finally:
- market_db.close()
- convert_db.close()
- def test_conversion_unique_index_migration_is_mysql57_idempotent():
- from pathlib import Path
- sql = (
- Path(__file__).parents[2]
- / "docs/sql/migrations/2026-10-10-presign-convert-unique.sql"
- ).read_text(encoding="utf-8")
- normalized = " ".join(sql.lower().replace("`", "").split())
- assert "information_schema.statistics" in normalized
- assert "create procedure ensure_presign_convert_unique" in normalized
- assert "call ensure_presign_convert_unique()" in normalized
- assert "group by tenant_no, source_presign_order_id having count(*) > 1" in normalized
- assert "signal sqlstate '45000'" in normalized
- assert "duplicate" in normalized
- assert "non_unique" in normalized
- assert "group_concat(column_name order by seq_in_index" in normalized
- assert "tenant_no,source_presign_order_id" in normalized
- assert "wrong index" in normalized
- assert "unique index uk_collection_protocol_presign_source" in normalized
- def test_market_presign_routes_are_fixed_and_expose_scoped_transfer_actions():
- from app.api.market_mobile import router
- actual = {
- (method, route.path)
- for route in router.routes
- if "/presign-orders" in route.path
- for method in route.methods
- }
- expected = set()
- for role in ("specialist", "manager"):
- base = f"/mini/market/{role}/presign-orders"
- expected.update(
- {
- ("GET", base),
- ("POST", base),
- ("GET", f"{base}/form-options"),
- ("GET", f"{base}/{{order_id}}"),
- ("PUT", f"{base}/{{order_id}}"),
- ("DELETE", f"{base}/{{order_id}}"),
- ("POST", f"{base}/{{order_id}}/convert"),
- ("POST", f"{base}/{{order_id}}/transfers"),
- }
- )
- assert actual == expected
- transfer_actual = {
- (method, route.path)
- for route in router.routes
- if "/presign-transfers" in route.path
- for method in route.methods
- }
- transfer_expected = set()
- for role in ("specialist", "manager"):
- base = f"/mini/market/{role}/presign-transfers/{{transfer_id}}"
- transfer_expected.update(
- {
- ("PUT", base),
- ("DELETE", base),
- ("POST", f"{base}/submit"),
- ("POST", f"{base}/withdraw"),
- }
- )
- assert transfer_actual == transfer_expected
- def test_mobile_presign_money_dto_preserves_decimal_strings_for_presenter(
- monkeypatch,
- ):
- import json
- import subprocess
- from decimal import Decimal
- from pathlib import Path
- from app.crud import presign_order as presign_crud
- from app.services import market_presign
- db = _session()
- normal = _order('money-normal', sales_user_id='u1')
- normal.contract_amount = Decimal('1234.50')
- empty = _order('money-empty', sales_user_id='u1')
- empty.contract_amount = None
- paid = _transfer(
- 'money-paid', order_id=normal.id, pay_status='yjf'
- )
- paid.transfer_amount = Decimal('200.25')
- draft = _transfer(
- 'money-draft', order_id=normal.id, pay_status='dfj'
- )
- draft.transfer_amount = Decimal('12.30')
- db.add_all([normal, empty, paid, draft])
- db.commit()
- items, total = market_presign.list_presign_orders(
- db,
- ctx=_ctx('specialist'),
- user_info=_user(),
- page=1,
- page_size=10,
- )
- by_id = {item['id']: item for item in items}
- assert total == 2
- assert by_id['money-normal']['contractAmount'] == '1234.50'
- assert by_id['money-normal']['confirmedDepositAmount'] == '200.25'
- assert by_id['money-empty']['contractAmount'] is None
- assert by_id['money-empty']['confirmedDepositAmount'] == '0.00'
- normal_detail = market_presign.get_presign_order(
- db,
- order_id=normal.id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- transfer_amounts = {
- transfer['id']: transfer['transferAmount']
- for transfer in normal_detail['transfers']
- }
- assert transfer_amounts == {
- 'money-paid': '200.25',
- 'money-draft': '12.30',
- }
- maximum = Decimal('9999999999999999.99')
- maximum_order = _order('money-maximum', sales_user_id='u1')
- maximum_order.contract_amount = maximum
- monkeypatch.setattr(
- market_presign,
- 'assert_presign_access',
- lambda *_args, **_kwargs: maximum_order,
- )
- monkeypatch.setattr(
- presign_crud,
- 'confirmed_deposit_amount',
- lambda *_args, **_kwargs: maximum,
- )
- maximum_detail = market_presign.get_presign_order(
- db,
- order_id=maximum_order.id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- assert maximum_detail['contractAmount'] == str(maximum)
- assert maximum_detail['confirmedDepositAmount'] == str(maximum)
- maximum_transfer = _transfer(
- 'money-maximum-transfer', order_id=maximum_order.id
- )
- maximum_transfer.transfer_amount = maximum
- maximum_transfer_dto = market_presign._mobile_transfer_to_dict(
- db, maximum_transfer, 'tenant-a'
- )
- assert maximum_transfer_dto['transferAmount'] == str(maximum)
- serialized = json.dumps(
- {
- 'maximum': maximum_detail,
- 'maximumTransfer': maximum_transfer_dto,
- 'normal': normal_detail,
- 'empty': by_id['money-empty'],
- },
- ensure_ascii=False,
- )
- presenter_url = (
- Path(__file__).resolve().parents[2]
- / 'mobile-oa-h5/src/market/presign/presign-presenter.js'
- ).as_uri()
- script = """
- import fs from 'node:fs'
- const { formatPresignMoney } = await import(process.argv[1])
- const dto = JSON.parse(fs.readFileSync(0, 'utf8'))
- const values = [
- dto.maximum.contractAmount,
- dto.maximum.confirmedDepositAmount,
- dto.maximumTransfer.transferAmount,
- dto.normal.contractAmount,
- dto.normal.transfers.find((item) => item.id === 'money-paid').transferAmount,
- dto.empty.contractAmount,
- dto.empty.confirmedDepositAmount
- ]
- process.stdout.write(JSON.stringify(values.map(formatPresignMoney)))
- """
- formatted = subprocess.run(
- ['node', '--input-type=module', '-e', script, presenter_url],
- input=serialized,
- text=True,
- capture_output=True,
- check=True,
- )
- assert json.loads(formatted.stdout) == [
- '¥9,999,999,999,999,999.99',
- '¥9,999,999,999,999,999.99',
- '¥9,999,999,999,999,999.99',
- '¥1,234.50',
- '¥200.25',
- '-',
- '¥0.00',
- ]
- def test_mobile_presign_convert_response_preserves_decimal_for_presenter(
- monkeypatch,
- ):
- import json
- import subprocess
- from decimal import Decimal
- from pathlib import Path
- from app.crud import presign_order as presign_crud
- from app.services import market_presign
- db = _session()
- _add_master_data(db)
- db.commit()
- maximum = Decimal('9999999999999999.99')
- order = _order('convert-money', sales_user_id='u1')
- order.protocol_no = 'P-CONVERT-MONEY'
- order.contract_amount = maximum
- monkeypatch.setattr(
- market_presign,
- '_scoped_order_for_update',
- lambda *_args, **_kwargs: order,
- )
- monkeypatch.setattr(
- presign_crud,
- '_get_order_or_raise',
- lambda *_args, **_kwargs: order,
- )
- monkeypatch.setattr(
- presign_crud,
- 'confirmed_deposit_amount',
- lambda *_args, **_kwargs: maximum,
- )
- # SQLite binds Numeric through float. Keep the exact Decimal on the real ORM
- # instance while exercising the complete core conversion and mobile response.
- monkeypatch.setattr(db, 'commit', db.flush)
- monkeypatch.setattr(db, 'refresh', lambda *_args, **_kwargs: None)
- response = market_presign.convert_presign_order(
- db,
- order_id=order.id,
- ctx=_ctx('specialist'),
- user_info=_user(),
- )
- nested = response['presignOrder']
- assert response['protocolId']
- assert order.order_status == 'converted'
- assert nested['contractAmount'] == str(maximum)
- assert nested['confirmedDepositAmount'] == str(maximum)
- presenter_url = (
- Path(__file__).resolve().parents[2]
- / 'mobile-oa-h5/src/market/presign/presign-presenter.js'
- ).as_uri()
- script = """
- import fs from 'node:fs'
- const { formatPresignMoney } = await import(process.argv[1])
- const dto = JSON.parse(fs.readFileSync(0, 'utf8'))
- process.stdout.write(JSON.stringify([
- formatPresignMoney(dto.contractAmount),
- formatPresignMoney(dto.confirmedDepositAmount)
- ]))
- """
- formatted = subprocess.run(
- ['node', '--input-type=module', '-e', script, presenter_url],
- input=json.dumps(nested),
- text=True,
- capture_output=True,
- check=True,
- )
- assert json.loads(formatted.stdout) == [
- '¥9,999,999,999,999,999.99',
- '¥9,999,999,999,999,999.99',
- ]
|