fix(auth): send sms verify codes via aliyun
This commit is contained in:
@@ -24,7 +24,7 @@ pub const DEFAULT_ACCESS_TOKEN_TTL_SECONDS: u64 = 2 * 60 * 60;
|
||||
pub const DEFAULT_REFRESH_COOKIE_NAME: &str = "genarrative_refresh_session";
|
||||
pub const DEFAULT_REFRESH_COOKIE_PATH: &str = "/api/auth";
|
||||
pub const DEFAULT_REFRESH_SESSION_TTL_DAYS: u32 = 30;
|
||||
pub const DEFAULT_SMS_ENDPOINT: &str = "dypnsapi.aliyuncs.com";
|
||||
pub const DEFAULT_SMS_ENDPOINT: &str = "dysmsapi.aliyuncs.com";
|
||||
pub const DEFAULT_SMS_COUNTRY_CODE: &str = "86";
|
||||
pub const DEFAULT_SMS_TEMPLATE_PARAM_KEY: &str = "code";
|
||||
pub const DEFAULT_SMS_MOCK_VERIFY_CODE: &str = "123456";
|
||||
@@ -164,6 +164,7 @@ pub struct SmsAuthConfig {
|
||||
pub struct SmsSendCodeRequest {
|
||||
pub national_phone_number: String,
|
||||
pub scene: String,
|
||||
pub verify_code: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -174,13 +175,6 @@ pub struct SmsSendCodeResult {
|
||||
pub provider_out_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SmsVerifyCodeRequest {
|
||||
pub national_phone_number: String,
|
||||
pub verify_code: String,
|
||||
pub provider_out_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum WechatAuthScene {
|
||||
Desktop,
|
||||
@@ -380,7 +374,7 @@ struct WechatPhoneNumberInfo {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AliyunSendSmsVerifyCodeResponse {
|
||||
struct AliyunSendSmsResponse {
|
||||
// 阿里云 RPC 原始 JSON 使用首字母大写字段名,这里必须显式映射,避免把成功响应误判成空值。
|
||||
#[serde(default, rename = "Code")]
|
||||
code: Option<String>,
|
||||
@@ -388,41 +382,8 @@ struct AliyunSendSmsVerifyCodeResponse {
|
||||
message: Option<String>,
|
||||
#[serde(default, rename = "RequestId")]
|
||||
request_id: Option<String>,
|
||||
#[serde(default, rename = "Success")]
|
||||
success: Option<bool>,
|
||||
#[serde(default, rename = "Model")]
|
||||
model: Option<AliyunSendSmsVerifyCodeModel>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AliyunSendSmsVerifyCodeModel {
|
||||
#[serde(default, rename = "BizId")]
|
||||
_biz_id: Option<String>,
|
||||
#[serde(default, rename = "OutId")]
|
||||
out_id: Option<String>,
|
||||
#[serde(default, rename = "RequestId")]
|
||||
request_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AliyunCheckSmsVerifyCodeResponse {
|
||||
// 校验接口同样返回首字母大写字段名,保持和发送接口一致的显式映射。
|
||||
#[serde(default, rename = "Code")]
|
||||
code: Option<String>,
|
||||
#[serde(default, rename = "Message")]
|
||||
message: Option<String>,
|
||||
#[serde(default, rename = "Success")]
|
||||
success: Option<bool>,
|
||||
#[serde(default, rename = "Model")]
|
||||
model: Option<AliyunCheckSmsVerifyCodeModel>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AliyunCheckSmsVerifyCodeModel {
|
||||
#[serde(default, rename = "OutId")]
|
||||
_out_id: Option<String>,
|
||||
#[serde(default, rename = "VerifyResult")]
|
||||
verify_result: Option<String>,
|
||||
biz_id: Option<String>,
|
||||
}
|
||||
|
||||
impl JwtConfig {
|
||||
@@ -681,10 +642,10 @@ impl SmsAuthProvider {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn verify_code(&self, request: SmsVerifyCodeRequest) -> Result<(), SmsProviderError> {
|
||||
pub fn mock_verify_code(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Mock(provider) => provider.verify_code(request).await,
|
||||
Self::Aliyun(provider) => provider.verify_code(request).await,
|
||||
Self::Mock(provider) => Some(provider.mock_verify_code()),
|
||||
Self::Aliyun(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1228,6 +1189,7 @@ impl MockSmsAuthProvider {
|
||||
&self,
|
||||
request: SmsSendCodeRequest,
|
||||
) -> Result<SmsSendCodeResult, SmsProviderError> {
|
||||
let _verify_code = request.verify_code;
|
||||
let provider_out_id =
|
||||
build_sms_provider_out_id(&request.scene, &request.national_phone_number);
|
||||
|
||||
@@ -1239,11 +1201,8 @@ impl MockSmsAuthProvider {
|
||||
})
|
||||
}
|
||||
|
||||
async fn verify_code(&self, request: SmsVerifyCodeRequest) -> Result<(), SmsProviderError> {
|
||||
if request.verify_code.trim() != self.config.mock_verify_code {
|
||||
return Err(SmsProviderError::InvalidVerifyCode);
|
||||
}
|
||||
Ok(())
|
||||
fn mock_verify_code(&self) -> &str {
|
||||
self.config.mock_verify_code.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,8 +1215,7 @@ impl AliyunSmsAuthProvider {
|
||||
build_sms_provider_out_id(&request.scene, &request.national_phone_number);
|
||||
let phone_masked = mask_phone_number(&request.national_phone_number);
|
||||
let template_param = serde_json::json!({
|
||||
self.config.template_param_key.clone(): "##code##",
|
||||
"min": self.config.valid_time_seconds,
|
||||
self.config.template_param_key.clone(): request.verify_code.trim(),
|
||||
})
|
||||
.to_string();
|
||||
info!(
|
||||
@@ -1267,54 +1225,28 @@ impl AliyunSmsAuthProvider {
|
||||
endpoint = self.config.endpoint.as_str(),
|
||||
sign_name = self.config.sign_name.as_str(),
|
||||
template_code = self.config.template_code.as_str(),
|
||||
code_length = self.config.code_length,
|
||||
valid_time_seconds = self.config.valid_time_seconds,
|
||||
interval_seconds = self.config.interval_seconds,
|
||||
provider_out_id = provider_out_id.as_str(),
|
||||
"准备调用阿里云短信发送接口"
|
||||
"准备调用阿里云 SendSms 短信发送接口"
|
||||
);
|
||||
|
||||
let mut query = BTreeMap::new();
|
||||
query.insert("Action".to_string(), "SendSmsVerifyCode".to_string());
|
||||
query.insert("Action".to_string(), "SendSms".to_string());
|
||||
query.insert("Format".to_string(), "json".to_string());
|
||||
query.insert("Version".to_string(), "2017-05-25".to_string());
|
||||
query.insert(
|
||||
"PhoneNumber".to_string(),
|
||||
"PhoneNumbers".to_string(),
|
||||
request.national_phone_number.trim().to_string(),
|
||||
);
|
||||
query.insert("CountryCode".to_string(), self.config.country_code.clone());
|
||||
query.insert("SignName".to_string(), self.config.sign_name.clone());
|
||||
query.insert(
|
||||
"TemplateCode".to_string(),
|
||||
self.config.template_code.clone(),
|
||||
);
|
||||
query.insert("TemplateParam".to_string(), template_param);
|
||||
query.insert(
|
||||
"CodeLength".to_string(),
|
||||
self.config.code_length.to_string(),
|
||||
);
|
||||
query.insert("CodeType".to_string(), self.config.code_type.to_string());
|
||||
query.insert(
|
||||
"ValidTime".to_string(),
|
||||
self.config.valid_time_seconds.to_string(),
|
||||
);
|
||||
query.insert(
|
||||
"Interval".to_string(),
|
||||
self.config.interval_seconds.to_string(),
|
||||
);
|
||||
query.insert(
|
||||
"DuplicatePolicy".to_string(),
|
||||
self.config.duplicate_policy.to_string(),
|
||||
);
|
||||
query.insert(
|
||||
"ReturnVerifyCode".to_string(),
|
||||
self.config.return_verify_code.to_string(),
|
||||
);
|
||||
query.insert("OutId".to_string(), provider_out_id.clone());
|
||||
if let Some(scheme_name) = self.config.scheme_name.clone() {
|
||||
query.insert("SchemeName".to_string(), scheme_name);
|
||||
}
|
||||
let signature_headers = self.build_signature_headers("SendSmsVerifyCode", &query)?;
|
||||
let signature_headers = self.build_signature_headers("SendSms", &query)?;
|
||||
|
||||
let payload = self
|
||||
.client
|
||||
@@ -1334,23 +1266,12 @@ impl AliyunSmsAuthProvider {
|
||||
http_status = http_status.as_u16(),
|
||||
provider_code = body.code.as_deref().unwrap_or("unknown"),
|
||||
provider_message = body.message.as_deref().unwrap_or("unknown"),
|
||||
provider_request_id = body
|
||||
.request_id
|
||||
.as_deref()
|
||||
.or_else(|| body
|
||||
.model
|
||||
.as_ref()
|
||||
.and_then(|model| model.request_id.as_deref()))
|
||||
.unwrap_or("unknown"),
|
||||
provider_out_id = body
|
||||
.model
|
||||
.as_ref()
|
||||
.and_then(|model| model.out_id.as_deref())
|
||||
.unwrap_or("unknown"),
|
||||
success = body.success.unwrap_or(false),
|
||||
provider_request_id = body.request_id.as_deref().unwrap_or("unknown"),
|
||||
provider_out_id = provider_out_id.as_str(),
|
||||
provider_biz_id = body.biz_id.as_deref().unwrap_or("unknown"),
|
||||
"阿里云短信发送接口返回响应"
|
||||
);
|
||||
if !body.success.unwrap_or(false) || body.code.as_deref() != Some("OK") {
|
||||
if body.code.as_deref() != Some("OK") {
|
||||
warn!(
|
||||
provider = "aliyun",
|
||||
scene = request.scene.as_str(),
|
||||
@@ -1358,19 +1279,9 @@ impl AliyunSmsAuthProvider {
|
||||
http_status = http_status.as_u16(),
|
||||
provider_code = body.code.as_deref().unwrap_or("unknown"),
|
||||
provider_message = body.message.as_deref().unwrap_or("unknown"),
|
||||
provider_request_id = body
|
||||
.request_id
|
||||
.as_deref()
|
||||
.or_else(|| body
|
||||
.model
|
||||
.as_ref()
|
||||
.and_then(|model| model.request_id.as_deref()))
|
||||
.unwrap_or("unknown"),
|
||||
provider_out_id = body
|
||||
.model
|
||||
.as_ref()
|
||||
.and_then(|model| model.out_id.as_deref())
|
||||
.unwrap_or("unknown"),
|
||||
provider_request_id = body.request_id.as_deref().unwrap_or("unknown"),
|
||||
provider_out_id = provider_out_id.as_str(),
|
||||
provider_biz_id = body.biz_id.as_deref().unwrap_or("unknown"),
|
||||
"阿里云短信发送接口返回业务失败"
|
||||
);
|
||||
return Err(map_aliyun_provider_error(
|
||||
@@ -1383,65 +1294,11 @@ impl AliyunSmsAuthProvider {
|
||||
Ok(SmsSendCodeResult {
|
||||
cooldown_seconds: self.config.interval_seconds,
|
||||
expires_in_seconds: self.config.valid_time_seconds,
|
||||
provider_request_id: body.request_id.or_else(|| {
|
||||
body.model
|
||||
.as_ref()
|
||||
.and_then(|model| model.request_id.clone())
|
||||
}),
|
||||
provider_out_id: body.model.and_then(|model| model.out_id),
|
||||
provider_request_id: body.request_id,
|
||||
provider_out_id: Some(provider_out_id),
|
||||
})
|
||||
}
|
||||
|
||||
async fn verify_code(&self, request: SmsVerifyCodeRequest) -> Result<(), SmsProviderError> {
|
||||
let mut query = BTreeMap::new();
|
||||
query.insert("Action".to_string(), "CheckSmsVerifyCode".to_string());
|
||||
query.insert("Format".to_string(), "json".to_string());
|
||||
query.insert("Version".to_string(), "2017-05-25".to_string());
|
||||
query.insert(
|
||||
"PhoneNumber".to_string(),
|
||||
request.national_phone_number.trim().to_string(),
|
||||
);
|
||||
query.insert("CountryCode".to_string(), self.config.country_code.clone());
|
||||
query.insert(
|
||||
"VerifyCode".to_string(),
|
||||
request.verify_code.trim().to_string(),
|
||||
);
|
||||
query.insert(
|
||||
"CaseAuthPolicy".to_string(),
|
||||
self.config.case_auth_policy.to_string(),
|
||||
);
|
||||
if let Some(scheme_name) = self.config.scheme_name.clone() {
|
||||
query.insert("SchemeName".to_string(), scheme_name);
|
||||
}
|
||||
if let Some(provider_out_id) = request.provider_out_id {
|
||||
query.insert("OutId".to_string(), provider_out_id);
|
||||
}
|
||||
let signature_headers = self.build_signature_headers("CheckSmsVerifyCode", &query)?;
|
||||
|
||||
let payload = self
|
||||
.client
|
||||
.post(build_aliyun_sms_url(&self.config.endpoint)?)
|
||||
.headers(signature_headers)
|
||||
.form(&query)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| SmsProviderError::Upstream(format!("验证码校验失败:{error}")))?;
|
||||
|
||||
let body = parse_aliyun_json_response_for_verify(payload).await?;
|
||||
if !body.success.unwrap_or(false) || body.code.as_deref() != Some("OK") {
|
||||
return Err(map_aliyun_provider_error(
|
||||
"验证码校验失败",
|
||||
body.message,
|
||||
body.code,
|
||||
));
|
||||
}
|
||||
if body.model.and_then(|model| model.verify_result).as_deref() != Some("PASS") {
|
||||
return Err(SmsProviderError::InvalidVerifyCode);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_signature_headers(
|
||||
&self,
|
||||
action: &str,
|
||||
@@ -1972,16 +1829,15 @@ fn aliyun_percent_encode(value: &str) -> String {
|
||||
async fn parse_aliyun_json_response(
|
||||
response: reqwest::Response,
|
||||
fallback_message: &str,
|
||||
) -> Result<AliyunSendSmsVerifyCodeResponse, SmsProviderError> {
|
||||
) -> Result<AliyunSendSmsResponse, SmsProviderError> {
|
||||
let status = response.status();
|
||||
let body = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|error| SmsProviderError::Upstream(format!("{fallback_message}:{error}")))?;
|
||||
let payload =
|
||||
serde_json::from_str::<AliyunSendSmsVerifyCodeResponse>(&body).map_err(|error| {
|
||||
SmsProviderError::Upstream(format!("{fallback_message}:响应解析失败:{error}"))
|
||||
})?;
|
||||
let payload = serde_json::from_str::<AliyunSendSmsResponse>(&body).map_err(|error| {
|
||||
SmsProviderError::Upstream(format!("{fallback_message}:响应解析失败:{error}"))
|
||||
})?;
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
return Err(map_http_status_to_sms_provider_error(
|
||||
fallback_message,
|
||||
@@ -1993,29 +1849,6 @@ async fn parse_aliyun_json_response(
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
async fn parse_aliyun_json_response_for_verify(
|
||||
response: reqwest::Response,
|
||||
) -> Result<AliyunCheckSmsVerifyCodeResponse, SmsProviderError> {
|
||||
let status = response.status();
|
||||
let body = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|error| SmsProviderError::Upstream(format!("验证码校验失败:{error}")))?;
|
||||
let payload =
|
||||
serde_json::from_str::<AliyunCheckSmsVerifyCodeResponse>(&body).map_err(|error| {
|
||||
SmsProviderError::Upstream(format!("验证码校验失败:响应解析失败:{error}"))
|
||||
})?;
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
return Err(map_http_status_to_sms_provider_error(
|
||||
"验证码校验失败",
|
||||
status,
|
||||
serde_json::from_str::<Value>(&body).ok(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
fn map_http_status_to_sms_provider_error(
|
||||
fallback_message: &str,
|
||||
status: StatusCode,
|
||||
@@ -2053,13 +1886,6 @@ fn map_aliyun_provider_error(
|
||||
let provider_code = provider_code.unwrap_or_default();
|
||||
let normalized_code = provider_code.trim().to_ascii_uppercase();
|
||||
|
||||
if normalized_code.contains("VERIFY")
|
||||
|| normalized_code.contains("CODE")
|
||||
|| normalized_code.contains("CHECK")
|
||||
{
|
||||
return SmsProviderError::InvalidVerifyCode;
|
||||
}
|
||||
|
||||
if normalized_code.contains("MOBILE")
|
||||
|| normalized_code.contains("PHONE")
|
||||
|| normalized_code.contains("SIGN")
|
||||
@@ -2350,6 +2176,48 @@ mod tests {
|
||||
.expect("mock sms config should be valid")
|
||||
}
|
||||
|
||||
fn required_env_for_real_sms_test(name: &str) -> String {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|value| normalize_required_string(&value))
|
||||
.unwrap_or_else(|| panic!("{name} must be set to run the real Aliyun SMS test"))
|
||||
}
|
||||
|
||||
fn optional_env_for_real_sms_test(name: &str, default_value: &str) -> String {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|value| normalize_required_string(&value))
|
||||
.unwrap_or_else(|| default_value.to_string())
|
||||
}
|
||||
|
||||
fn build_real_aliyun_sms_config_from_env() -> SmsAuthConfig {
|
||||
SmsAuthConfig::new(
|
||||
SmsAuthProviderKind::Aliyun,
|
||||
optional_env_for_real_sms_test("ALIYUN_SMS_ENDPOINT", DEFAULT_SMS_ENDPOINT),
|
||||
Some(required_env_for_real_sms_test("ALIYUN_SMS_ACCESS_KEY_ID")),
|
||||
Some(required_env_for_real_sms_test(
|
||||
"ALIYUN_SMS_ACCESS_KEY_SECRET",
|
||||
)),
|
||||
optional_env_for_real_sms_test("ALIYUN_SMS_SIGN_NAME", "北京亓盒网络科技"),
|
||||
optional_env_for_real_sms_test("ALIYUN_SMS_TEMPLATE_CODE", "SMS_506245486"),
|
||||
optional_env_for_real_sms_test(
|
||||
"ALIYUN_SMS_TEMPLATE_PARAM_KEY",
|
||||
DEFAULT_SMS_TEMPLATE_PARAM_KEY,
|
||||
),
|
||||
optional_env_for_real_sms_test("ALIYUN_SMS_COUNTRY_CODE", DEFAULT_SMS_COUNTRY_CODE),
|
||||
None,
|
||||
DEFAULT_SMS_CODE_LENGTH,
|
||||
DEFAULT_SMS_CODE_TYPE,
|
||||
DEFAULT_SMS_VALID_TIME_SECONDS,
|
||||
DEFAULT_SMS_INTERVAL_SECONDS,
|
||||
DEFAULT_SMS_DUPLICATE_POLICY,
|
||||
DEFAULT_SMS_CASE_AUTH_POLICY,
|
||||
false,
|
||||
DEFAULT_SMS_MOCK_VERIFY_CODE.to_string(),
|
||||
)
|
||||
.expect("real aliyun sms config should be valid")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_sign_and_verify_access_token() {
|
||||
let config = build_jwt_config();
|
||||
@@ -2491,13 +2359,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mock_sms_provider_sends_and_verifies_code() {
|
||||
async fn mock_sms_provider_sends_code_and_exposes_fixed_verify_code() {
|
||||
let provider =
|
||||
SmsAuthProvider::new(build_mock_sms_config()).expect("provider should build");
|
||||
let send_result = provider
|
||||
.send_code(SmsSendCodeRequest {
|
||||
national_phone_number: "13800138000".to_string(),
|
||||
scene: "login".to_string(),
|
||||
verify_code: DEFAULT_SMS_MOCK_VERIFY_CODE.to_string(),
|
||||
})
|
||||
.await
|
||||
.expect("send code should succeed");
|
||||
@@ -2512,32 +2381,41 @@ mod tests {
|
||||
Some("mock-request-id")
|
||||
);
|
||||
assert!(send_result.provider_out_id.is_some());
|
||||
|
||||
provider
|
||||
.verify_code(SmsVerifyCodeRequest {
|
||||
national_phone_number: "13800138000".to_string(),
|
||||
verify_code: DEFAULT_SMS_MOCK_VERIFY_CODE.to_string(),
|
||||
provider_out_id: send_result.provider_out_id,
|
||||
})
|
||||
.await
|
||||
.expect("verify code should succeed");
|
||||
assert_eq!(
|
||||
provider.mock_verify_code(),
|
||||
Some(DEFAULT_SMS_MOCK_VERIFY_CODE)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mock_sms_provider_rejects_wrong_code() {
|
||||
let provider =
|
||||
SmsAuthProvider::new(build_mock_sms_config()).expect("provider should build");
|
||||
#[ignore = "requires real Aliyun SMS credentials and sends an actual SMS"]
|
||||
async fn aliyun_send_sms_real_provider_sends_verify_code() {
|
||||
let phone_number = required_env_for_real_sms_test("ALIYUN_SMS_REAL_TEST_PHONE_NUMBER");
|
||||
let provider = SmsAuthProvider::new(build_real_aliyun_sms_config_from_env())
|
||||
.expect("real aliyun provider should build");
|
||||
|
||||
let error = provider
|
||||
.verify_code(SmsVerifyCodeRequest {
|
||||
national_phone_number: "13800138000".to_string(),
|
||||
verify_code: "000000".to_string(),
|
||||
provider_out_id: None,
|
||||
let send_result = provider
|
||||
.send_code(SmsSendCodeRequest {
|
||||
national_phone_number: phone_number.clone(),
|
||||
scene: "real_test".to_string(),
|
||||
verify_code: "123456".to_string(),
|
||||
})
|
||||
.await
|
||||
.expect_err("wrong verify code should fail");
|
||||
.expect("real aliyun SendSms call should succeed");
|
||||
|
||||
assert_eq!(error, SmsProviderError::InvalidVerifyCode);
|
||||
println!(
|
||||
"real Aliyun SendSms accepted phone={} request_id={:?} out_id={:?}",
|
||||
mask_phone_number(&phone_number),
|
||||
send_result.provider_request_id,
|
||||
send_result.provider_out_id
|
||||
);
|
||||
assert!(send_result.provider_request_id.is_some());
|
||||
assert!(send_result.provider_out_id.is_some());
|
||||
assert_eq!(send_result.cooldown_seconds, DEFAULT_SMS_INTERVAL_SECONDS);
|
||||
assert_eq!(
|
||||
send_result.expires_in_seconds,
|
||||
DEFAULT_SMS_VALID_TIME_SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2574,14 +2452,14 @@ mod tests {
|
||||
let mut params = BTreeMap::new();
|
||||
params.insert(
|
||||
"TemplateParam".to_string(),
|
||||
"{\"code\":\"##code##\"}".to_string(),
|
||||
"{\"code\":\"123456\"}".to_string(),
|
||||
);
|
||||
params.insert("Action".to_string(), "SendSmsVerifyCode".to_string());
|
||||
params.insert("PhoneNumber".to_string(), "13800138000".to_string());
|
||||
params.insert("Action".to_string(), "SendSms".to_string());
|
||||
params.insert("PhoneNumbers".to_string(), "13800138000".to_string());
|
||||
|
||||
assert_eq!(
|
||||
canonicalize_aliyun_form_params(¶ms),
|
||||
"Action=SendSmsVerifyCode&PhoneNumber=13800138000&TemplateParam=%7B%22code%22%3A%22%23%23code%23%23%22%7D"
|
||||
"Action=SendSms&PhoneNumbers=13800138000&TemplateParam=%7B%22code%22%3A%22123456%22%7D"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2613,8 +2491,8 @@ mod tests {
|
||||
};
|
||||
let headers = provider
|
||||
.build_signature_headers(
|
||||
"SendSmsVerifyCode",
|
||||
&BTreeMap::from([("Action".to_string(), "SendSmsVerifyCode".to_string())]),
|
||||
"SendSms",
|
||||
&BTreeMap::from([("Action".to_string(), "SendSms".to_string())]),
|
||||
)
|
||||
.expect("signature headers should build");
|
||||
|
||||
@@ -2646,17 +2524,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn aliyun_send_response_deserializes_pascal_case_fields() {
|
||||
let payload = serde_json::from_str::<AliyunSendSmsVerifyCodeResponse>(
|
||||
let payload = serde_json::from_str::<AliyunSendSmsResponse>(
|
||||
r#"{
|
||||
"Code": "OK",
|
||||
"Message": "成功",
|
||||
"RequestId": "req_123",
|
||||
"Success": true,
|
||||
"Model": {
|
||||
"BizId": "biz_456",
|
||||
"OutId": "out_789",
|
||||
"RequestId": "req_model_001"
|
||||
}
|
||||
"BizId": "biz_456"
|
||||
}"#,
|
||||
)
|
||||
.expect("aliyun send response should deserialize");
|
||||
@@ -2664,47 +2537,6 @@ mod tests {
|
||||
assert_eq!(payload.code.as_deref(), Some("OK"));
|
||||
assert_eq!(payload.message.as_deref(), Some("成功"));
|
||||
assert_eq!(payload.request_id.as_deref(), Some("req_123"));
|
||||
assert_eq!(payload.success, Some(true));
|
||||
assert_eq!(
|
||||
payload
|
||||
.model
|
||||
.as_ref()
|
||||
.and_then(|model| model.out_id.as_deref()),
|
||||
Some("out_789")
|
||||
);
|
||||
assert_eq!(
|
||||
payload
|
||||
.model
|
||||
.as_ref()
|
||||
.and_then(|model| model.request_id.as_deref()),
|
||||
Some("req_model_001")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aliyun_verify_response_deserializes_pascal_case_fields() {
|
||||
let payload = serde_json::from_str::<AliyunCheckSmsVerifyCodeResponse>(
|
||||
r#"{
|
||||
"Code": "OK",
|
||||
"Message": "成功",
|
||||
"Success": true,
|
||||
"Model": {
|
||||
"OutId": "out_789",
|
||||
"VerifyResult": "PASS"
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.expect("aliyun verify response should deserialize");
|
||||
|
||||
assert_eq!(payload.code.as_deref(), Some("OK"));
|
||||
assert_eq!(payload.message.as_deref(), Some("成功"));
|
||||
assert_eq!(payload.success, Some(true));
|
||||
assert_eq!(
|
||||
payload
|
||||
.model
|
||||
.as_ref()
|
||||
.and_then(|model| model.verify_result.as_deref()),
|
||||
Some("PASS")
|
||||
);
|
||||
assert_eq!(payload.biz_id.as_deref(), Some("biz_456"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user