Merge remote-tracking branch 'origin/master' into hermes/hermes-1e775b03
# Conflicts: # server-rs/crates/api-server/src/app.rs # server-rs/crates/api-server/src/creation_entry_config.rs # server-rs/crates/api-server/src/puzzle.rs # server-rs/crates/spacetime-client/src/lib.rs # src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
7. `auth/wechat/start`
|
||||
8. `auth/wechat/callback`
|
||||
9. `auth/wechat/bind-phone`
|
||||
10. `auth/wechat/miniprogram-login`
|
||||
|
||||
当前阶段继续补齐的 Stage3 公开请求 DTO:
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ pub struct AuthSessionsResponse {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthSessionSummaryPayload {
|
||||
pub session_id: String,
|
||||
pub session_ids: Vec<String>,
|
||||
pub session_count: u32,
|
||||
pub client_type: String,
|
||||
pub client_runtime: String,
|
||||
pub client_platform: String,
|
||||
@@ -144,6 +146,11 @@ pub struct LogoutAllResponse {
|
||||
pub ok: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct RevokeAuthSessionResponse {
|
||||
pub ok: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PhoneSendCodeRequest {
|
||||
@@ -211,8 +218,12 @@ pub struct WechatCallbackQuery {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WechatBindPhoneRequest {
|
||||
pub phone: String,
|
||||
pub code: String,
|
||||
#[serde(default)]
|
||||
pub phone: Option<String>,
|
||||
#[serde(default)]
|
||||
pub code: Option<String>,
|
||||
#[serde(default)]
|
||||
pub wechat_phone_code: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -222,6 +233,20 @@ pub struct WechatBindPhoneResponse {
|
||||
pub user: AuthUserPayload,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WechatMiniProgramLoginRequest {
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WechatMiniProgramLoginResponse {
|
||||
pub token: String,
|
||||
pub binding_status: String,
|
||||
pub user: AuthUserPayload,
|
||||
}
|
||||
|
||||
pub fn build_available_login_methods(
|
||||
sms_auth_enabled: bool,
|
||||
password_auth_enabled: bool,
|
||||
@@ -318,4 +343,23 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wechat_bind_phone_request_accepts_mini_program_phone_code() {
|
||||
let payload = serde_json::to_value(WechatBindPhoneRequest {
|
||||
phone: None,
|
||||
code: None,
|
||||
wechat_phone_code: Some("wx-phone-code-001".to_string()),
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!({
|
||||
"phone": null,
|
||||
"code": null,
|
||||
"wechatPhoneCode": "wx-phone-code-001"
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ pub struct CreateMatch3DAgentSessionRequest {
|
||||
pub asset_style_label: Option<String>,
|
||||
#[serde(default)]
|
||||
pub asset_style_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub generate_click_sound: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -50,6 +52,8 @@ pub struct ExecuteMatch3DAgentActionRequest {
|
||||
pub clear_count: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub difficulty: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub generate_click_sound: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -66,6 +70,8 @@ pub struct Match3DCreatorConfigResponse {
|
||||
pub asset_style_label: Option<String>,
|
||||
#[serde(default)]
|
||||
pub asset_style_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub generate_click_sound: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -87,10 +93,48 @@ pub struct Match3DResultDraftResponse {
|
||||
pub total_item_count: u32,
|
||||
pub publish_ready: bool,
|
||||
pub blockers: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub background_prompt: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub background_image_src: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub background_image_object_key: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub generated_background_asset: Option<Match3DGeneratedBackgroundAssetResponse>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub generated_item_assets: Vec<Match3DGeneratedItemAssetResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Match3DGeneratedBackgroundAssetResponse {
|
||||
pub prompt: String,
|
||||
#[serde(default)]
|
||||
pub image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub image_object_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub container_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub container_image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub container_image_object_key: Option<String>,
|
||||
pub status: String,
|
||||
#[serde(default)]
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Match3DGeneratedItemImageViewResponse {
|
||||
pub view_id: String,
|
||||
pub view_index: u32,
|
||||
#[serde(default)]
|
||||
pub image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub image_object_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Match3DGeneratedItemAssetResponse {
|
||||
@@ -100,6 +144,8 @@ pub struct Match3DGeneratedItemAssetResponse {
|
||||
pub image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub image_object_key: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub image_views: Vec<Match3DGeneratedItemImageViewResponse>,
|
||||
#[serde(default)]
|
||||
pub model_src: Option<String>,
|
||||
#[serde(default)]
|
||||
@@ -111,9 +157,19 @@ pub struct Match3DGeneratedItemAssetResponse {
|
||||
#[serde(default)]
|
||||
pub subscription_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub sound_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music_title: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music_style: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music: Option<CreationAudioAsset>,
|
||||
#[serde(default)]
|
||||
pub click_sound: Option<CreationAudioAsset>,
|
||||
#[serde(default)]
|
||||
pub background_asset: Option<Match3DGeneratedBackgroundAssetResponse>,
|
||||
pub status: String,
|
||||
#[serde(default)]
|
||||
pub error: Option<String>,
|
||||
@@ -191,9 +247,10 @@ mod tests {
|
||||
reference_image_src: Some("data:image/png;base64,abc".to_string()),
|
||||
clear_count: Some(4),
|
||||
difficulty: Some(3),
|
||||
asset_style_id: Some("clay-toy".to_string()),
|
||||
asset_style_label: Some("黏土手作".to_string()),
|
||||
asset_style_prompt: Some("圆润黏土手作风".to_string()),
|
||||
asset_style_id: Some("flat-icon".to_string()),
|
||||
asset_style_label: Some("扁平图标".to_string()),
|
||||
asset_style_prompt: Some("干净扁平 2D 游戏道具图标风格".to_string()),
|
||||
generate_click_sound: Some(true),
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
@@ -204,6 +261,25 @@ mod tests {
|
||||
json!("data:image/png;base64,abc")
|
||||
);
|
||||
assert_eq!(payload["clearCount"], json!(4));
|
||||
assert_eq!(payload["assetStyleId"], json!("clay-toy"));
|
||||
assert_eq!(payload["assetStyleId"], json!("flat-icon"));
|
||||
assert_eq!(payload["generateClickSound"], json!(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_match3d_action_request_serializes_click_sound_toggle() {
|
||||
let payload = serde_json::to_value(ExecuteMatch3DAgentActionRequest {
|
||||
action: "match3d_compile_draft".to_string(),
|
||||
game_name: None,
|
||||
summary: None,
|
||||
tags: None,
|
||||
cover_image_src: None,
|
||||
clear_count: None,
|
||||
difficulty: None,
|
||||
generate_click_sound: Some(true),
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
assert_eq!(payload["action"], json!("match3d_compile_draft"));
|
||||
assert_eq!(payload["generateClickSound"], json!(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize};
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StartMatch3DRunRequest {
|
||||
pub profile_id: String,
|
||||
#[serde(default)]
|
||||
pub item_type_count_override: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -18,12 +18,93 @@ pub struct PutMatch3DWorkRequest {
|
||||
pub difficulty: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateMatch3DWorkTagsRequest {
|
||||
pub game_name: String,
|
||||
pub theme_text: String,
|
||||
#[serde(default)]
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateMatch3DWorkTagsResponse {
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PutMatch3DAudioAssetsRequest {
|
||||
pub generated_item_assets: Vec<Match3DGeneratedItemAssetResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersistMatch3DGeneratedModelRequest {
|
||||
pub item_id: String,
|
||||
pub item_name: String,
|
||||
pub source_url: String,
|
||||
#[serde(default)]
|
||||
pub file_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub task_uuid: Option<String>,
|
||||
#[serde(default)]
|
||||
pub subscription_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersistMatch3DGeneratedModelResponse {
|
||||
pub asset: Match3DGeneratedItemAssetResponse,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateMatch3DCoverImageRequest {
|
||||
pub prompt: String,
|
||||
#[serde(default)]
|
||||
pub reference_image_src: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateMatch3DCoverImageResponse {
|
||||
pub item: Match3DWorkProfileResponse,
|
||||
pub cover_image_src: String,
|
||||
pub cover_image_object_key: String,
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateMatch3DBackgroundImageRequest {
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateMatch3DBackgroundImageResponse {
|
||||
pub item: Match3DWorkProfileResponse,
|
||||
pub background_image_src: String,
|
||||
pub background_image_object_key: String,
|
||||
pub generated_background_asset: Match3DGeneratedBackgroundAssetResponse,
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateMatch3DItemAssetsRequest {
|
||||
pub item_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateMatch3DItemAssetsResponse {
|
||||
pub item: Match3DWorkProfileResponse,
|
||||
pub generated_item_assets: Vec<Match3DGeneratedItemAssetResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Match3DWorkSummaryResponse {
|
||||
@@ -48,10 +129,48 @@ pub struct Match3DWorkSummaryResponse {
|
||||
#[serde(default)]
|
||||
pub published_at: Option<String>,
|
||||
pub publish_ready: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub background_prompt: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub background_image_src: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub background_image_object_key: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub generated_background_asset: Option<Match3DGeneratedBackgroundAssetResponse>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub generated_item_assets: Vec<Match3DGeneratedItemAssetResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Match3DGeneratedBackgroundAssetResponse {
|
||||
pub prompt: String,
|
||||
#[serde(default)]
|
||||
pub image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub image_object_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub container_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub container_image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub container_image_object_key: Option<String>,
|
||||
pub status: String,
|
||||
#[serde(default)]
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Match3DGeneratedItemImageViewResponse {
|
||||
pub view_id: String,
|
||||
pub view_index: u32,
|
||||
#[serde(default)]
|
||||
pub image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub image_object_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Match3DGeneratedItemAssetResponse {
|
||||
@@ -61,6 +180,8 @@ pub struct Match3DGeneratedItemAssetResponse {
|
||||
pub image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub image_object_key: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub image_views: Vec<Match3DGeneratedItemImageViewResponse>,
|
||||
#[serde(default)]
|
||||
pub model_src: Option<String>,
|
||||
#[serde(default)]
|
||||
@@ -72,9 +193,19 @@ pub struct Match3DGeneratedItemAssetResponse {
|
||||
#[serde(default)]
|
||||
pub subscription_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub sound_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music_title: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music_style: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music: Option<CreationAudioAsset>,
|
||||
#[serde(default)]
|
||||
pub click_sound: Option<CreationAudioAsset>,
|
||||
#[serde(default)]
|
||||
pub background_asset: Option<Match3DGeneratedBackgroundAssetResponse>,
|
||||
pub status: String,
|
||||
#[serde(default)]
|
||||
pub error: Option<String>,
|
||||
|
||||
@@ -154,6 +154,12 @@ pub struct PuzzleDraftLevelResponse {
|
||||
#[serde(default)]
|
||||
pub picture_reference: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ui_background_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ui_background_image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ui_background_image_object_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music: Option<CreationAudioAsset>,
|
||||
pub candidates: Vec<PuzzleGeneratedImageCandidateResponse>,
|
||||
#[serde(default)]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::creation_audio::CreationAudioAsset;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -116,6 +117,10 @@ pub struct PuzzleRuntimeLevelSnapshotResponse {
|
||||
pub theme_tags: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub cover_image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ui_background_image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub background_music: Option<CreationAudioAsset>,
|
||||
pub board: PuzzleBoardSnapshotResponse,
|
||||
pub status: String,
|
||||
#[serde(default)]
|
||||
|
||||
@@ -222,12 +222,23 @@ pub struct ProfileRechargeOrderResponse {
|
||||
pub amount_cents: u64,
|
||||
pub status: String,
|
||||
pub payment_channel: String,
|
||||
pub paid_at: String,
|
||||
pub paid_at: Option<String>,
|
||||
pub provider_transaction_id: Option<String>,
|
||||
pub created_at: String,
|
||||
pub points_delta: i64,
|
||||
pub membership_expires_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WechatMiniProgramPayParamsResponse {
|
||||
pub time_stamp: String,
|
||||
pub nonce_str: String,
|
||||
pub package: String,
|
||||
pub sign_type: String,
|
||||
pub pay_sign: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileRechargeCenterResponse {
|
||||
@@ -253,6 +264,8 @@ pub struct CreateProfileRechargeOrderRequest {
|
||||
pub struct CreateProfileRechargeOrderResponse {
|
||||
pub order: ProfileRechargeOrderResponse,
|
||||
pub center: ProfileRechargeCenterResponse,
|
||||
#[serde(default)]
|
||||
pub wechat_mini_program_pay_params: Option<WechatMiniProgramPayParamsResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -1264,14 +1277,14 @@ mod tests {
|
||||
},
|
||||
point_products: vec![ProfileRechargeProductResponse {
|
||||
product_id: "points_60".to_string(),
|
||||
title: "60光点".to_string(),
|
||||
title: "60泥点".to_string(),
|
||||
price_cents: 600,
|
||||
kind: "points".to_string(),
|
||||
points_amount: 60,
|
||||
bonus_points: 60,
|
||||
duration_days: 0,
|
||||
badge_label: "首充双倍".to_string(),
|
||||
description: "首充送60光点".to_string(),
|
||||
description: "首充送60泥点".to_string(),
|
||||
tier: "normal".to_string(),
|
||||
}],
|
||||
membership_products: vec![],
|
||||
@@ -1287,11 +1300,11 @@ mod tests {
|
||||
json!("2026-05-25T10:00:00Z")
|
||||
);
|
||||
assert_eq!(payload["pointProducts"][0]["productId"], json!("points_60"));
|
||||
assert_eq!(payload["pointProducts"][0]["title"], json!("60光点"));
|
||||
assert_eq!(payload["pointProducts"][0]["title"], json!("60泥点"));
|
||||
assert_eq!(payload["pointProducts"][0]["priceCents"], json!(600));
|
||||
assert_eq!(
|
||||
payload["pointProducts"][0]["description"],
|
||||
json!("首充送60光点")
|
||||
json!("首充送60泥点")
|
||||
);
|
||||
assert_eq!(payload["hasPointsRecharged"], json!(false));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user