Some checks failed
Build and Push Backend / docker (push) Has been cancelled
- Add llm/wechat-subsystem-test-matrix.md and tests covering Wx controllers/services\n- Make ApiTestDataSeeder personnel group seeding idempotent for full-suite stability
32 KiB
32 KiB
WeChat Subsystem — Characterization / Integration Test Matrix
This document is a behavior pin (characterization) test matrix for the current WeChat-related subsystem. The goal is to lock observable behavior (HTTP + DB + Redis + external calls) so later refactoring can be done safely.
Repo root: /Volumes/main/code/yunpei/peipei-backend
Source Inventory (entry points)
Controllers
/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxOauthController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxPlayController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxCommonController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxCustomController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxOrderInfoController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxCouponController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkMediaController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxBlindBoxController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxPkController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxGiftController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkCommodityController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxCommodityController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxLevelController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxShopController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkWagesController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxPersonnelGroupInfoController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxPlayOrderRankingController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/withdraw/controller/WxWithdrawController.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/withdraw/controller/WxWithdrawPayeeController.java
Services / Cross-cutting
/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxOauthService.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxCustomMpService.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxTokenService.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxCustomUserService.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxGiftOrderService.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxBlindBoxOrderService.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/MediaUploadService.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/common/security/filter/JwtAuthenticationTokenFilter.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/common/aspect/CustomUserLoginAspect.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/common/aspect/ClerkUserLoginAspect.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/NotificationSender.java/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/java/com/starry/admin/modules/weichat/service/MockNotificationSender.java
DB schema touchpoints
/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/resources/db/migration/V1__init_schema.sql/Volumes/main/code/yunpei/peipei-backend/play-admin/src/main/resources/db/migration/V17__create_media.sql
Key fields to pin:
sys_tenant:tenant_key,app_id,secret,mch_id,mch_key,*_template_id,profitsharing_rateplay_custom_user_info:openid,unionid,wei_chat_code,tokenplay_clerk_user_info:openid(NOT NULL),wei_chat_code,token,online_state,onboarding_state,listing_state,clerk_stateplay_order_info:order_type,pay_method,pay_state,place_type,reward_type,wei_chat_code,profit_sharing_amountplay_media/play_clerk_media_asset:status,usage,review_state,deleted,order_index+ unique constraintuk_clerk_usage_media
Test Harness Guidelines
Required runtime surfaces to observe
- HTTP: status code + response schema (normalized) for every
/wx/**route. - DB: before/after snapshots of rows touched by the route (focus tables above).
- Redis: keys under:
TENANT_INFO:*login_codes:*- PK keys under
PkRedisKeyConstants(if enabled)
- External calls: record interactions with:
WxMpService/ WeChat MP template message serviceWxPayService(unified order + profitsharing)IOssFileServiceSmsUtils- background tasks like
OverdueOrderHandlerTask
Profiles
- Prefer running integration tests under
apitestprofile (seeded DB + mock notifier). - For tests that must hit real async behavior, use deterministic executors in test config (or replace
ThreadPoolTaskExecutorwith inline executor via@MockBean).
Normalization rules (avoid flaky tests)
- Normalize dynamic fields: timestamps, random IDs (UUID), and generated nonces.
- For money: always compare as
BigDecimalwith explicit scale/rounding. - For lists: assert stable ordering only when the endpoint guarantees ordering.
1) Gateway / Tenant / AuthN (Filter + AOP)
| ID | Surface | Route / Component | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| GW-001 | HTTP | JwtAuthenticationTokenFilter |
/wx/** without tenantkey header on a login-required route |
none | none | response is error (pin: code/body shape + status) |
| GW-002 | HTTP | JwtAuthenticationTokenFilter |
/wx/** without tenantkey on a no-login whitelist route |
none | none | response is error (pin: JSON body written by filter) |
| GW-003 | HTTP | JwtAuthenticationTokenFilter |
/wx/pay/jsCallback bypasses auth and does not require tenantkey |
none | none | handler executes and returns success string |
| GW-004 | HTTP/Redis | filter getTenantId |
with tenantkey only (no token) |
tenant exists | ISysTenantService real or stub |
SecurityUtils.getTenantId() set; request proceeds |
| GW-005 | HTTP/Redis | filter getTenantId |
with clerk token | seed clerk with token + tenantId | none | tenantId resolved from DB; TENANT_INFO:{userId} read does not break flow |
| GW-006 | HTTP/Redis | filter getTenantId |
with custom token | seed customer with token + tenantId | none | tenantId resolved from DB |
| AOP-001 | HTTP | @CustomUserLogin endpoints |
missing customusertoken header |
none | none | 401 token为空 |
| AOP-002 | HTTP | @ClerkUserLogin endpoints |
missing clerkusertoken header |
none | none | 401 token为空 |
| AOP-003 | HTTP | CustomUserLoginAspect |
invalid token format/signature | none | none | 401 获取用户信息异常 |
| AOP-004 | HTTP | CustomUserLoginAspect |
token ok but DB token mismatch | seed user with different token | none | 401 token异常 |
| AOP-005 | HTTP | ClerkUserLoginAspect |
ensureClerkSessionIsValid rejects |
seed clerk state invalid | none | 401 message matches current CustomException message |
| TOK-001 | unit/contract | WxTokenService |
token generated and parsed (with/without Bearer ) |
config secret + expireTime | none | same userId extracted |
| TOK-002 | unit/contract | WxTokenService |
expired token fails | short expireTime | none | parsing throws; upstream maps to 401 |
2) WeChat OAuth (WxOauthController + WxOauthService)
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| OAUTH-001 | HTTP | POST /wx/oauth2/getConfigAddress |
url omitted -> uses default callback |
valid tenant | mock WxMpService signature |
signature returned; default URL pinned |
| OAUTH-002 | HTTP | POST /wx/oauth2/getConfigAddress |
url provided -> overrides default |
valid tenant | mock signature | uses provided URL |
| OAUTH-003 | HTTP | POST /wx/oauth2/getClerkLoginAddress |
returns auth URL with scope snsapi_userinfo |
valid tenant | mock OAuth2 service build URL | response contains expected scope |
| OAUTH-004 | HTTP | POST /wx/oauth2/getCustomLoginAddress |
same as clerk | valid tenant | mock OAuth2 service | response contains expected scope |
| OAUTH-005 | HTTP/DB/Redis | POST /wx/oauth2/custom/login |
success -> returns token payload + persists token | seed tenant + seeded customer openid | mock WxMpService.getOAuth2Service().getAccessToken/getUserInfo |
response includes tokenValue/tokenName/loginDate; DB token updated; Redis TENANT_INFO:{id} set |
| OAUTH-006 | HTTP | POST /wx/oauth2/custom/login |
upstream WeChat oauth fails -> unauthorized | none | mock WxMpService.getOAuth2Service().getAccessToken to throw |
response code=401 (and success=false) |
| OAUTH-007 | HTTP/DB/Redis | POST /wx/oauth2/clerk/login |
success -> token persisted + tenant cached; pcData present |
seed a clerk with sysUserId="" to avoid PC login dependency |
mock WxMpService.getOAuth2Service().getAccessToken/getUserInfo |
response includes pcData.token/role (empty strings); DB token updated; Redis TENANT_INFO:{id} set |
| OAUTH-008 | HTTP/DB | GET /wx/oauth2/custom/logout |
token invalidated | seed user + token | none | DB token set to empty; subsequent access 401 |
| OAUTH-009 | service/DB | WxOauthService.customUserLogin |
first login creates new user with registrationTime | empty DB | mock MP OAuth2 | row inserted with expected fields |
| OAUTH-010 | service/DB | WxOauthService.clerkUserLogin |
deleted clerk restored | seed clerk deleted=true | mock MP OAuth2 | deleted=false; default states filled; token/online reset |
| OAUTH-011 | HTTP | GET /wx/oauth2/checkSubscribe |
returns boolean subscribe state | logged-in customer | mock WxMpService.getUserService().userInfo |
response data is true/false |
3) WeChat Pay Recharge + Callback + Profit Sharing (WxPlayController)
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| PAY-001 | HTTP | GET /wx/pay/custom/getCustomPaymentAmount |
money empty -> 500 | logged-in customer | none | error message pinned |
| PAY-002 | HTTP | GET /wx/pay/custom/getCustomPaymentAmount |
money valid -> BigDecimal returned | logged-in customer | none | response number pinned (scale/rounding) |
| PAY-003 | HTTP/DB | GET /wx/pay/custom/createOrder |
money < 1 -> 500 | logged-in customer | none | error message pinned |
| PAY-004 | HTTP/DB | GET /wx/pay/custom/createOrder |
creates recharge order + returns JSAPI pay params | logged-in customer + tenant with mch config | mock WxPayService.unifiedOrder and SignUtils inputs via capturing request |
order row created (type/pay_state); response has required fields; unifiedOrder request fields pinned |
| PAY-005 | HTTP | GET /wx/pay/custom/createOrder |
subscribe check fails -> error | customer openid + tenant | mock WxCustomMpService.checkSubscribeThrowsExp to throw |
HTTP error pinned |
| PAY-006 | HTTP/DB | POST/GET /wx/pay/jsCallback |
invalid XML -> still returns success string, no DB changes | existing DB | none | response equals success; no order updates |
| PAY-007 | HTTP/DB | /wx/pay/jsCallback |
unknown out_trade_no -> no changes | none | none | no DB changes |
| PAY-008 | HTTP/DB | /wx/pay/jsCallback |
order_type!=0 OR pay_state!=0 -> no reprocessing | seed paid/non-recharge order | none | no balance change; no state change |
| PAY-009 | HTTP/DB/Redis | /wx/pay/jsCallback |
happy path updates pay_state and balance | seed recharge order pay_state=0 + tenant attach | mock customAccountBalanceRecharge if needed or assert real side-effects |
pay_state becomes 1; balance updated; template message called |
| PAY-010 | HTTP/DB | /wx/pay/jsCallback |
replay same callback twice is idempotent | seed recharge order | none | balance does not double-add; profit_sharing_amount not duplicated |
| PAY-011 | HTTP/DB | profitSharing | profitsharing_rate<=0 -> no call and no write | tenant rate=0 | mock WxPay profitSharing service | no API call; no DB update |
| PAY-012 | HTTP/DB | profitSharing | rate>0 and computed amount=0 -> no call | tiny amount | mock | no call |
| PAY-013 | HTTP/DB | profitSharing | rate>0 -> writes profit_sharing_amount | tenant rate set | mock profitSharing result | DB field set to expected BigDecimal |
4) WeChat MP Notifications (WxCustomMpService)
| ID | Surface | Component | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| MP-001 | unit/contract | proxyWxMpService |
missing tenantId -> CustomException | tenantId unset | none | exception message pinned |
| MP-002 | unit/contract | getWxPay |
tenant missing mch_id -> CustomException | tenant has empty mchId | none | message pinned |
| MP-003 | integration | sendCreateOrderMessage |
template data fields mapping | tenant has templateId + tenantKey | mock template msg service | short_thing5 label resolved; URL pinned |
| MP-004 | integration | sendCreateOrderMessageBatch |
filters offboarded/delisted clerks | clerk list mix | deterministic executor + mock template | only eligible clerks called |
| MP-005 | integration | sendBalanceMessage |
sends recharge success template | order + tenant + customer | mock template | data keys pinned |
| MP-006 | integration | sendOrderFinishMessage |
only placeType "1"/"2" triggers | orders with other placeType | mock template | no calls when not matched |
| MP-007 | integration | async wrappers | exceptions inside async do not bubble | throw in underlying send | deterministic executor | caller does not fail |
| MP-008 | integration | subscribe checks | subscribe=false -> ServiceException message pinned | mock WxMpUser.subscribe=false |
mock user service | message pinned |
5) Common WeChat Tools (WxCommonController + WxFileUtils)
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| COM-001 | HTTP | GET /wx/common/area/tree |
returns area tree | tenantKey only | mock area service or real | response schema pinned |
| COM-002 | HTTP | GET /wx/common/setting/info |
returns global UI config (NOT tenant-scoped) | call with two different X-Tenant values |
real service | both responses share same data.id |
| COM-003 | HTTP | POST /wx/common/file/upload |
uploads to OSS returns URL | multipart file | mock IOssFileService.upload |
returned URL pinned |
| COM-004 | HTTP | GET /wx/common/audio/upload |
mediaId empty -> error | none | none | error message pinned |
| COM-005 | HTTP | GET /wx/common/audio/upload |
successful path uploads mp3 | provide accessToken + temp audio stream | mock WxAccessTokenService, stub WxFileUtils.getTemporaryMaterial (via wrapper or test seam), mock OSS |
returns URL; temp files cleaned |
| COM-006 | unit/contract | WxFileUtils.audioConvert2Mp3 |
invalid source -> throws | empty file | none | error message pinned |
6) Customer (WxCustomController) — Orders, Gifts, Complaints, Follow, Leave Msg
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| CUS-001 | HTTP | GET /wx/custom/queryClerkDetailedById |
not logged in still works | tenantKey only | none | response pinned (no crash) |
| CUS-002 | HTTP/DB | GET /wx/custom/queryById |
returns clerkState=1 when openid matches clerk | seed custom+clerk share openid | none | response clerkState pinned |
| CUS-003 | HTTP/DB | POST /wx/custom/updateHideLevelState |
ignores client id and uses session id | logged-in custom | none | DB update on session row only |
| CUS-004 | HTTP/DB | POST /wx/custom/order/reward |
creates completed reward order | logged-in custom + balance | none | order row fields pinned; ledger/balance delta pinned |
| CUS-005 | HTTP/DB | POST /wx/custom/order/gift |
calls WxGiftOrderService and increments gift counts | logged-in custom + gift seeded | none | order created; both gift counters incremented |
| CUS-006 | HTTP/DB | POST /wx/custom/order/commodity |
creates specified order pending | logged-in custom + clerk + commodity | mock pricing if needed | order fields pinned |
| CUS-007 | HTTP/DB | POST /wx/custom/order/random |
success triggers notification + overdue task | logged-in custom + clerks eligible | mock WxCustomMpService, mock OverdueOrderHandlerTask |
expected calls + order fields pinned |
| CUS-008 | HTTP/DB | POST /wx/custom/order/random |
insufficient balance fails and no order created | set balance=0 | none | error code pinned; no DB insert |
| CUS-009 | HTTP | POST /wx/custom/order/queryByPage |
only returns self purchaserBy | seed orders for multiple users | none | result contains only self |
| CUS-010 | HTTP/DB | GET /wx/custom/order/end |
state transition invoked | seed order | none | order status moved (pin) |
| CUS-011 | HTTP/DB | POST /wx/custom/order/cancellation |
pins cancellation refund record (images ignored) | seed pending/accepted order + send non-empty images | none | order canceled; refund record has refundReason; images stays null; order refundReason stays null |
| CUS-012 | HTTP/DB | POST /wx/custom/order/evaluate/add |
non-purchaser cannot evaluate | order purchaser different | none | error message pinned |
| CUS-013 | HTTP | GET /wx/custom/order/evaluate/queryByOrderId |
not evaluated -> error | seed order no eval | none | 当前订单未评价 |
| CUS-014 | HTTP/DB | POST /wx/custom/order/complaint/add |
non-purchaser cannot complain | order purchaser different | none | error message pinned |
| CUS-015 | HTTP/DB | POST /wx/custom/leave/add |
creates leave msg; response message pinned | logged-in custom | none | DB insert; response message currently "取消成功" |
| CUS-016 | HTTP | GET /wx/custom/leave/queryPermission |
permission true/false schema pinned | set conditions | none | response JSON has permission boolean and msg |
| CUS-017 | HTTP/DB | POST /wx/custom/followState/update |
idempotency and correctness | seed follow relation | none | follow state pinned |
| CUS-018 | HTTP | POST /wx/custom/follow/queryByPage |
paging/filters pinned | seed relations | none | page schema pinned |
7) Clerk (WxClerkController) — Apply, Profile Review, Order Ops, Privacy Rules
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| CLK-001 | HTTP | POST /wx/clerk/user/queryPerformanceInfo |
date normalize behavior pinned | seed orders | none | output stable for same input |
| CLK-002 | HTTP | GET /wx/clerk/user/queryLevelInfo |
hardcoded levelAndRanking list pinned |
logged-in clerk | none | list size/content pinned |
| CLK-003 | HTTP/Redis | POST /wx/clerk/user/sendCode |
writes redis key with TTL and returns code | logged-in clerk | none | redis key format + TTL; response contains code |
| CLK-004 | HTTP/Redis/DB | POST /wx/clerk/user/bindCode |
wrong code -> error | seed redis code | none | 验证码错误 |
| CLK-005 | HTTP/Redis/DB | POST /wx/clerk/user/bindCode |
success updates phone and clears redis | seed redis code | none | DB phone updated; redis deleted |
| CLK-006 | HTTP/DB | POST /wx/clerk/user/add |
already clerk -> error | seed clerkState=1 | none | message pinned |
| CLK-007 | HTTP/DB | POST /wx/clerk/user/add |
already has pending review -> error | seed reviewState=0 | none | message pinned |
| CLK-008 | HTTP | POST /wx/clerk/user/add |
subscribe required | mock subscribe=false | mock WxCustomMpService.checkSubscribeThrowsExp |
error message pinned |
| CLK-009 | HTTP/DB | POST /wx/clerk/user/updateNickname |
creates data review row with correct type and content | logged-in clerk | none | DB insert pinned |
| CLK-010 | HTTP/DB | POST /wx/clerk/user/updateAlbum |
empty album -> error | logged-in clerk | none | 最少上传一张照片 |
| CLK-011 | HTTP/DB | POST /wx/clerk/user/updateAlbum |
invalid new media -> error | seed play_media with mismatched owner/tenant/status | none | error message pinned |
| CLK-012 | HTTP/DB | POST /wx/clerk/user/updateAlbum |
legacy IDs not found -> should not fail (current behavior) | album includes missing IDs | none | request succeeds; review content contains legacy strings |
| CLK-013 | HTTP/DB | GET /wx/clerk/order/queryById |
privacy: non-owner clears weiChatCode | seed order acceptBy other clerk | none | weiChatCode empty |
| CLK-014 | HTTP/DB | GET /wx/clerk/order/queryById |
canceled order clears weiChatCode | seed orderStatus=4 | none | weiChatCode empty |
| CLK-015 | HTTP | GET /wx/clerk/order/accept |
subscribe required | mock subscribe=false | mock WxCustomMpService |
fails |
| CLK-016 | HTTP/DB | GET /wx/clerk/order/start |
state transition pinned | seed order | none | state updated |
| CLK-017 | HTTP/DB | POST /wx/clerk/order/complete |
sysUserId missing -> error | seed clerk no sysUserId | none | message pinned |
| CLK-018 | HTTP/DB | POST /wx/clerk/order/complete |
permission resolution pinned (admin vs group leader) | seed sysUser mapping | none | correct operatorType chosen or error |
| CLK-019 | HTTP/DB | POST /wx/clerk/order/cancellation |
cancellation state pinned | seed order | none | status/cancel fields updated |
| CLK-020 | HTTP | POST /wx/clerk/user/queryEvaluateByPage |
forces hidden=VISIBLE | seed hidden evaluations | none | response excludes hidden |
8) Orders (WxOrderInfoController) — Continuation + Random Order Masking
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| ORD-001 | HTTP/DB | POST /wx/order/clerk/continue |
non-owner cannot continue | order acceptBy != clerk | none | message pinned |
| ORD-002 | HTTP/DB | POST /wx/order/clerk/continue |
second continuation blocked | seed continue record | none | message pinned |
| ORD-003 | HTTP | GET /wx/order/clerk/selectRandomOrderById |
masking for non-owner pinned | seed random order accepted by other | none | fields blanked as implemented |
| ORD-004 | HTTP/DB | POST /wx/order/custom/updateReviewState |
reviewedState != 0 -> error | seed reviewedState=1 | none | 续单已处理 |
| ORD-005 | HTTP/DB | POST /wx/order/custom/continueListByPage |
customId forced to session | seed multiple users | none | only self results |
9) Coupons (WxCouponController)
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| CP-001 | HTTP | GET /wx/coupon/custom/obtainCoupon |
id empty -> error message pinned | logged-in custom | none | error message pinned |
| CP-002 | HTTP/DB | obtain coupon | not eligible -> returns reason | seed coupon restrictions | none | response contains reason |
| CP-003 | HTTP/DB | obtain coupon | eligible -> claim succeeds | seed coupon inventory | none | coupon_details inserted; response success |
| CP-004 | HTTP | query all | whitelist hides coupons from non-whitelisted | seed coupon whitelist | none | coupon not present |
| CP-005 | HTTP | query by order | clerkId+levelId both empty -> error | none | none | message pinned |
| CP-006 | HTTP/DB | query by order | unavailable coupon includes reasonForUnavailableUse | seed coupon restrictions | none | available=0 and reason set |
| CP-007 | HTTP/DB | query by order | exceptions inside loop are swallowed (current behavior) | seed one broken coupon | none | endpoint still returns 200 with remaining coupons |
10) Media (WxClerkMediaController + MediaUploadService)
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| MED-001 | HTTP | POST /wx/clerk/media/upload |
missing file -> error | logged-in clerk | none | message pinned |
| MED-002 | HTTP/DB | upload image | creates play_media + play_clerk_media_asset |
image multipart | mock OSS | DB rows inserted; kind=image; owner=clerk |
| MED-003 | HTTP/DB | upload video too large | exceeds 30MB -> error | video > limit | none | error message pinned |
| MED-004 | HTTP/DB | upload video too long | duration>45s -> error | long video | none | message pinned |
| MED-005 | HTTP/DB | PUT /wx/clerk/media/order |
distinct mediaIds and ordering pinned | seed assets | none | order_index updates pinned; duplicates removed |
| MED-006 | HTTP/DB | DELETE /wx/clerk/media/{id} |
marks review_state=rejected and sets play_media.status=rejected (asset deleted stays 0 currently) |
seed media + asset | none | asset review_state becomes rejected; play_media.status becomes rejected; asset.deleted remains 0 |
| MED-007 | HTTP | GET /wx/clerk/media/list |
returns only draft/pending/rejected | seed assets states | none | filtering pinned |
| MED-008 | HTTP | GET /wx/clerk/media/approved |
returns only approved | seed assets | none | filtering pinned |
| MED-009 | DB constraint | uk_clerk_usage_media |
duplicate submit behavior pinned | seed duplicate row | none | error or ignore (pin current) |
11) Blind Box (WxBlindBoxController + WxBlindBoxOrderService)
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| BB-001 | HTTP | GET /wx/blind-box/config/list |
not logged in -> error | none | none | message pinned |
| BB-002 | HTTP | list configs | only active configs for tenant | seed configs | none | list content pinned |
| BB-003 | HTTP/DB | POST /wx/blind-box/order/purchase |
tenant mismatch -> “not found” (TenantLine current behavior) | config tenant != user tenant | none | message pinned (盲盒不存在) |
| BB-004 | HTTP/DB | purchase | creates completed order + reward | seed config + balance | none | order type pinned; reward row created |
| BB-005 | HTTP/DB | POST /wx/blind-box/reward/{id}/dispatch |
reward not found -> error | none | none | message pinned |
| BB-006 | HTTP/DB | dispatch | status transition pinned | seed reward | none | status updated; response view pinned |
| BB-007 | HTTP | GET /wx/blind-box/reward/list |
status filter pinned | seed rewards | none | filter results pinned |
| BB-008 | HTTP/DB | POST /wx/blind-box/order/purchase |
insufficient balance -> error and no new order | customer balance < config price | none | message pinned; no new order inserted |
12) PK (WxPkController)
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| PK-001 | HTTP | GET /wx/pk/clerk/live |
clerkId missing -> error | none | none | message pinned |
| PK-002 | HTTP | live | no pk -> inactive dto | seed none | none | returns inactive dto |
| PK-003 | HTTP | live | pk exists but status!=IN_PROGRESS -> inactive | seed pk | none | inactive |
| PK-004 | HTTP/Redis | upcoming | tenant missing -> error | no tenant context | none | message pinned |
| PK-005 | HTTP/Redis | upcoming | redis hit/miss produces stable behavior | seed redis keys | none | response pinned |
| PK-006 | HTTP | schedule/history | limit/page normalization pinned | none | none | safeLimit behavior pinned |
13) Shop + Articles + Misc
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| SHOP-001 | HTTP | GET /wx/shop/custom/getShopHomeCarouseInfo |
returns carousel list | seed carousel | none | mapping pinned |
| SHOP-002 | HTTP/DB | GET /wx/shop/clerk/readShopArticleInfo |
visitsNumber increments | seed article | none | visitsNumber +1 persisted |
| ART-001 | HTTP/DB | POST /wx/article/clerk/add |
creates article with clerkId from session | logged-in clerk | none | DB insert pinned |
| ART-002 | HTTP/DB | GET /wx/article/clerk/deleteById |
deletes clerk article + custom article links | seed both | none | rows removed/soft-deleted pinned |
| ART-003 | HTTP/DB | POST /wx/article/custom/updateGreedState |
pins current toggle behavior (not strictly idempotent) | seed article | none | at least one row flips to endorseState=0 after toggle |
| ART-004 | HTTP/DB | POST /wx/article/custom/updateFollowState |
same for follow | seed article | none | record updated |
14) Wages + Withdraw (WxClerkWagesController + WxWithdrawController)
| ID | Surface | Route | Scenario | Setup | Mocks | Assertions |
|---|---|---|---|---|---|---|
| WAGE-001 | HTTP/DB | GET /wx/wages/clerk/queryUnsettledWages |
sums orders correctly | seed settlement_state=0 orders | none | totals pinned |
| WAGE-002 | HTTP | GET /wx/wages/clerk/queryCurrentPeriodWages |
missing wages row returns constructed zeros | no wages row | none | response has zeros and dates set |
| WAGE-003 | HTTP | POST /wx/wages/clerk/queryHistoricalWages |
current hard-coded page meta pinned | seed some rows | none | total=5,size=10,pages=1 pinned |
| WD-001 | HTTP | GET /wx/withdraw/balance |
returns available/pending/nextUnlock | seed earnings lines | none | values pinned |
| WD-002 | HTTP | GET /wx/withdraw/earnings |
time parsing supports multiple formats | seed earnings | none | filter correctness pinned |
| WD-003 | HTTP/DB | POST /wx/withdraw/requests |
amount<=0 error | none | none | message pinned |
| WD-004 | HTTP/DB | create request | creates withdrawal request and reserves lines | seed available lines | none | statuses pinned |
| WD-005 | HTTP | request logs | non-owner forbidden | seed request other clerk | none | 无权查看 |
| PAYEE-001 | HTTP | GET /wx/withdraw/payee |
no profile returns null data (current behavior) | none | none | response pinned |
| PAYEE-002 | HTTP/DB | POST /wx/withdraw/payee |
missing qrCodeUrl -> error | none | none | message pinned |
| PAYEE-003 | HTTP/DB | upsert defaulting | defaults channel/displayName | seed clerk | none | stored defaults pinned |
| PAYEE-004 | HTTP/DB | confirm requires qrCodeUrl | no profile | none | error message pinned |
Coverage Checklist (for “done”)
- Every
@RequestMapping("/wx...")route has at least:- 1 happy-path integration test
- 1 auth/tenant gating test (where applicable)
- 1 validation failure test (where applicable)
- 1 DB side-effect snapshot assertion for routes that mutate state
- WeChat Pay callback has:
- replay/idempotency tests
- malformed XML test
- missing attach test
- Media pipeline has:
- type detection + size/duration limits pinned
- DB uniqueness behavior pinned
Test Case ID Naming Convention (important)
- In this doc, case IDs are written like
OAUTH-007. - In Java test method names, we normalize them as
OAUTH_007because-is not valid in Java identifiers.
Implemented Coverage (current)
/Volumes/main/code/yunpei/peipei-backend/play-admin/src/test/java/com/starry/admin/api/WxOauthControllerApiTest.java:OAUTH-001..008,OAUTH-011/Volumes/main/code/yunpei/peipei-backend/play-admin/src/test/java/com/starry/admin/modules/weichat/service/WxOauthServiceTest.java:OAUTH-009,OAUTH-010/Volumes/main/code/yunpei/peipei-backend/play-admin/src/test/java/com/starry/admin/api/WxPayControllerApiTest.java:PAY-001..011/Volumes/main/code/yunpei/peipei-backend/play-admin/src/test/java/com/starry/admin/api/WxAuthAspectApiTest.java:AOP-001..005/Volumes/main/code/yunpei/peipei-backend/play-admin/src/test/java/com/starry/admin/modules/weichat/service/WxTokenServiceTest.java:TOK-001..002/Volumes/main/code/yunpei/peipei-backend/play-admin/src/test/java/com/starry/admin/api/WxClerkMediaControllerEndpointsApiTest.java:MED-005..008(note:MED-006currently keepsasset.deleted=0)/Volumes/main/code/yunpei/peipei-backend/play-admin/src/test/java/com/starry/admin/api/WxCommonControllerAudioUploadApiTest.java: covers audio upload behavior (seeMED-*section for alignment)