mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(admin): add Done-hub provider ops template
- 新增 Done-hub Cookie 认证架构 - 使用 /api/user/profile 查询余额,按 quota / 500000 换算 - 补充余额解析、认证头和校验相关测试
This commit is contained in:
@@ -165,10 +165,10 @@ pub(super) async fn admin_provider_ops_run_query_balance_action(
|
|||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(message) => {
|
Err(message) => {
|
||||||
return admin_provider_ops_action_error(
|
return admin_provider_ops_action_error(
|
||||||
if architecture.architecture_id == "generic_api"
|
if matches!(
|
||||||
|| architecture.architecture_id == "new_api"
|
architecture.architecture_id,
|
||||||
|| architecture.architecture_id == "anyrouter"
|
"generic_api" | "new_api" | "anyrouter" | "done_hub"
|
||||||
{
|
) {
|
||||||
"unknown_error"
|
"unknown_error"
|
||||||
} else {
|
} else {
|
||||||
"parse_error"
|
"parse_error"
|
||||||
|
|||||||
@@ -60,6 +60,39 @@ async fn redis_runtime_state_for_test(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_provider_ops_architectures_payload(payload: &serde_json::Value) {
|
||||||
|
let items = payload.as_array().expect("items should be array");
|
||||||
|
let architecture_ids = items
|
||||||
|
.iter()
|
||||||
|
.map(|item| {
|
||||||
|
item["architecture_id"]
|
||||||
|
.as_str()
|
||||||
|
.expect("architecture_id should be string")
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let expected_ids = aether_admin::provider::ops::list_architectures(false)
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.architecture_id)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(architecture_ids, expected_ids);
|
||||||
|
assert!(items
|
||||||
|
.iter()
|
||||||
|
.all(|item| item["architecture_id"] != "generic_api"));
|
||||||
|
|
||||||
|
let anyrouter = items
|
||||||
|
.iter()
|
||||||
|
.find(|item| item["architecture_id"] == "anyrouter")
|
||||||
|
.expect("anyrouter architecture should exist");
|
||||||
|
assert_eq!(anyrouter["default_connector"], "cookie");
|
||||||
|
|
||||||
|
let new_api = items
|
||||||
|
.iter()
|
||||||
|
.find(|item| item["architecture_id"] == "new_api")
|
||||||
|
.expect("new_api architecture should exist");
|
||||||
|
assert_eq!(new_api["supported_auth_types"][0]["type"], "api_key");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gateway_handles_admin_provider_ops_architectures_locally_with_trusted_admin_principal() {
|
async fn gateway_handles_admin_provider_ops_architectures_locally_with_trusted_admin_principal() {
|
||||||
let upstream_hits = Arc::new(Mutex::new(0usize));
|
let upstream_hits = Arc::new(Mutex::new(0usize));
|
||||||
@@ -93,15 +126,7 @@ async fn gateway_handles_admin_provider_ops_architectures_locally_with_trusted_a
|
|||||||
|
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||||
let items = payload.as_array().expect("items should be array");
|
assert_provider_ops_architectures_payload(&payload);
|
||||||
assert_eq!(items.len(), 6);
|
|
||||||
assert!(items
|
|
||||||
.iter()
|
|
||||||
.all(|item| item["architecture_id"] != "generic_api"));
|
|
||||||
assert_eq!(items[0]["architecture_id"], "anyrouter");
|
|
||||||
assert_eq!(items[0]["default_connector"], "cookie");
|
|
||||||
assert_eq!(items[3]["architecture_id"], "new_api");
|
|
||||||
assert_eq!(items[3]["supported_auth_types"][0]["type"], "api_key");
|
|
||||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||||
|
|
||||||
gateway_handle.abort();
|
gateway_handle.abort();
|
||||||
@@ -141,15 +166,7 @@ async fn gateway_handles_admin_provider_ops_architectures_locally_with_bearer_ad
|
|||||||
|
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||||
let items = payload.as_array().expect("items should be array");
|
assert_provider_ops_architectures_payload(&payload);
|
||||||
assert_eq!(items.len(), 6);
|
|
||||||
assert!(items
|
|
||||||
.iter()
|
|
||||||
.all(|item| item["architecture_id"] != "generic_api"));
|
|
||||||
assert_eq!(items[0]["architecture_id"], "anyrouter");
|
|
||||||
assert_eq!(items[0]["default_connector"], "cookie");
|
|
||||||
assert_eq!(items[3]["architecture_id"], "new_api");
|
|
||||||
assert_eq!(items[3]["supported_auth_types"][0]["type"], "api_key");
|
|
||||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||||
|
|
||||||
gateway_handle.abort();
|
gateway_handle.abort();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub fn parse_query_balance_payload(
|
|||||||
response_json: &Value,
|
response_json: &Value,
|
||||||
) -> Result<Value, String> {
|
) -> Result<Value, String> {
|
||||||
match architecture_id {
|
match architecture_id {
|
||||||
"generic_api" | "new_api" | "anyrouter" => {
|
"generic_api" | "new_api" | "anyrouter" | "done_hub" => {
|
||||||
parse_new_api_balance_payload(action_config, response_json)
|
parse_new_api_balance_payload(action_config, response_json)
|
||||||
}
|
}
|
||||||
"cubence" => parse_cubence_balance_payload(action_config, response_json),
|
"cubence" => parse_cubence_balance_payload(action_config, response_json),
|
||||||
@@ -490,6 +490,28 @@ mod tests {
|
|||||||
assert_eq!(payload["total_used"], json!(1.0));
|
assert_eq!(payload["total_used"], json!(1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn done_hub_single_request_parser_reads_wrapped_quota() {
|
||||||
|
let payload = parse_query_balance_payload(
|
||||||
|
"done_hub",
|
||||||
|
&json!({ "quota_divisor": 500000 })
|
||||||
|
.as_object()
|
||||||
|
.cloned()
|
||||||
|
.expect("config"),
|
||||||
|
&json!({
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"quota": 2_276_139_911_u64,
|
||||||
|
"used_quota": 13860089
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("payload should parse");
|
||||||
|
|
||||||
|
assert_eq!(payload["total_available"], json!(4552.279822));
|
||||||
|
assert_eq!(payload["total_used"], json!(27.720178));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sub2api_parser_sums_balance_and_points() {
|
fn sub2api_parser_sums_balance_and_points() {
|
||||||
let payload = parse_sub2api_balance_payload(
|
let payload = parse_sub2api_balance_payload(
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
use super::{
|
||||||
|
json_object, ProviderOpsActionSpec, ProviderOpsArchitectureSpec, ProviderOpsAuthSpec,
|
||||||
|
ProviderOpsBalanceMode, ProviderOpsCheckinMode, ProviderOpsVerifyMode,
|
||||||
|
};
|
||||||
|
use serde_json::{json, Map, Value};
|
||||||
|
|
||||||
|
pub(super) fn spec() -> ProviderOpsArchitectureSpec {
|
||||||
|
let credentials_schema = json!({
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"base_url": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "站点地址",
|
||||||
|
"description": "API 基础地址"
|
||||||
|
},
|
||||||
|
"session_cookie": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Session Cookie",
|
||||||
|
"description": "从浏览器复制的 session Cookie 值",
|
||||||
|
"x-sensitive": true,
|
||||||
|
"x-input-type": "password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["session_cookie"],
|
||||||
|
"x-auth-type": "cookie",
|
||||||
|
"x-currency": "USD",
|
||||||
|
"x-field-groups": [
|
||||||
|
{ "fields": ["base_url"] },
|
||||||
|
{ "fields": ["session_cookie"] }
|
||||||
|
],
|
||||||
|
"x-quota-divisor": 500000,
|
||||||
|
"x-validation": [
|
||||||
|
{
|
||||||
|
"type": "required",
|
||||||
|
"fields": ["session_cookie"],
|
||||||
|
"message": "请填写 Session Cookie"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ProviderOpsArchitectureSpec {
|
||||||
|
architecture_id: "done_hub",
|
||||||
|
display_name: "Done-hub",
|
||||||
|
description: "Done-hub 中转站预设配置,使用 Cookie 认证",
|
||||||
|
hidden: false,
|
||||||
|
credentials_schema: credentials_schema.clone(),
|
||||||
|
verify_endpoint: "/api/user/profile",
|
||||||
|
verify_mode: ProviderOpsVerifyMode::DirectGet,
|
||||||
|
balance_mode: ProviderOpsBalanceMode::SingleRequest,
|
||||||
|
checkin_mode: ProviderOpsCheckinMode::None,
|
||||||
|
query_balance_cookie_auth_errors: true,
|
||||||
|
supported_auth_types: vec![ProviderOpsAuthSpec {
|
||||||
|
auth_type: "cookie",
|
||||||
|
display_name: "Done-hub Cookie",
|
||||||
|
credentials_schema,
|
||||||
|
}],
|
||||||
|
supported_actions: vec![ProviderOpsActionSpec {
|
||||||
|
action_type: "query_balance",
|
||||||
|
display_name: "查询余额",
|
||||||
|
description: "查询 Done-hub 账户余额信息",
|
||||||
|
config_schema: json!({
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"currency": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "货币单位",
|
||||||
|
"default": "USD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
}),
|
||||||
|
}],
|
||||||
|
default_connector: Some("cookie"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn default_action_config(action_type: &str) -> Option<Map<String, Value>> {
|
||||||
|
match action_type {
|
||||||
|
"query_balance" => Some(json_object(json!({
|
||||||
|
"endpoint": "/api/user/profile",
|
||||||
|
"method": "GET",
|
||||||
|
"quota_divisor": 500000,
|
||||||
|
"currency": "USD"
|
||||||
|
}))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod anyrouter;
|
mod anyrouter;
|
||||||
mod cubence;
|
mod cubence;
|
||||||
|
mod done_hub;
|
||||||
mod generic_api;
|
mod generic_api;
|
||||||
mod nekocode;
|
mod nekocode;
|
||||||
mod new_api;
|
mod new_api;
|
||||||
@@ -92,6 +93,7 @@ static PROVIDER_OPS_ARCHITECTURES: LazyLock<Vec<ProviderOpsArchitectureSpec>> =
|
|||||||
vec![
|
vec![
|
||||||
anyrouter::spec(),
|
anyrouter::spec(),
|
||||||
cubence::spec(),
|
cubence::spec(),
|
||||||
|
done_hub::spec(),
|
||||||
generic_api::spec(),
|
generic_api::spec(),
|
||||||
nekocode::spec(),
|
nekocode::spec(),
|
||||||
new_api::spec(),
|
new_api::spec(),
|
||||||
@@ -122,6 +124,7 @@ pub fn normalize_architecture_id(architecture_id: &str) -> &'static str {
|
|||||||
"generic_api" => "generic_api",
|
"generic_api" => "generic_api",
|
||||||
"new_api" => "new_api",
|
"new_api" => "new_api",
|
||||||
"cubence" => "cubence",
|
"cubence" => "cubence",
|
||||||
|
"done_hub" => "done_hub",
|
||||||
"yescode" => "yescode",
|
"yescode" => "yescode",
|
||||||
"nekocode" => "nekocode",
|
"nekocode" => "nekocode",
|
||||||
"anyrouter" => "anyrouter",
|
"anyrouter" => "anyrouter",
|
||||||
@@ -178,6 +181,7 @@ fn default_action_config(architecture_id: &str, action_type: &str) -> Option<Map
|
|||||||
match architecture_id {
|
match architecture_id {
|
||||||
"anyrouter" => anyrouter::default_action_config(action_type),
|
"anyrouter" => anyrouter::default_action_config(action_type),
|
||||||
"cubence" => cubence::default_action_config(action_type),
|
"cubence" => cubence::default_action_config(action_type),
|
||||||
|
"done_hub" => done_hub::default_action_config(action_type),
|
||||||
"generic_api" => generic_api::default_action_config(action_type),
|
"generic_api" => generic_api::default_action_config(action_type),
|
||||||
"nekocode" => nekocode::default_action_config(action_type),
|
"nekocode" => nekocode::default_action_config(action_type),
|
||||||
"new_api" => new_api::default_action_config(action_type),
|
"new_api" => new_api::default_action_config(action_type),
|
||||||
@@ -201,23 +205,47 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn list_architectures_hides_generic_api_by_default() {
|
fn list_architectures_hides_generic_api_by_default() {
|
||||||
let visible = list_architectures(false);
|
let visible = list_architectures(false);
|
||||||
assert_eq!(visible.len(), 6);
|
assert_eq!(visible.len(), 7);
|
||||||
assert!(visible
|
assert!(visible
|
||||||
.iter()
|
.iter()
|
||||||
.all(|item| item.architecture_id != "generic_api"));
|
.all(|item| item.architecture_id != "generic_api"));
|
||||||
|
|
||||||
let all = list_architectures(true);
|
let all = list_architectures(true);
|
||||||
assert_eq!(all.len(), 7);
|
assert_eq!(all.len(), 8);
|
||||||
assert!(all.iter().any(|item| item.architecture_id == "generic_api"));
|
assert!(all.iter().any(|item| item.architecture_id == "generic_api"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalize_architecture_id_falls_back_to_generic_api() {
|
fn normalize_architecture_id_falls_back_to_generic_api() {
|
||||||
assert_eq!(normalize_architecture_id(""), "generic_api");
|
assert_eq!(normalize_architecture_id(""), "generic_api");
|
||||||
|
assert_eq!(normalize_architecture_id("done_hub"), "done_hub");
|
||||||
assert_eq!(normalize_architecture_id("new_api"), "new_api");
|
assert_eq!(normalize_architecture_id("new_api"), "new_api");
|
||||||
assert_eq!(normalize_architecture_id("unknown"), "generic_api");
|
assert_eq!(normalize_architecture_id("unknown"), "generic_api");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn done_hub_uses_profile_balance_without_checkin() {
|
||||||
|
let architecture = get_architecture("done_hub").expect("architecture should exist");
|
||||||
|
assert_eq!(architecture.architecture_id, "done_hub");
|
||||||
|
assert_eq!(architecture.verify_endpoint, "/api/user/profile");
|
||||||
|
|
||||||
|
let resolved = resolve_action_config(
|
||||||
|
"done_hub",
|
||||||
|
&json!({})
|
||||||
|
.as_object()
|
||||||
|
.cloned()
|
||||||
|
.expect("config should be object"),
|
||||||
|
"query_balance",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("action config should resolve");
|
||||||
|
|
||||||
|
assert_eq!(resolved.get("endpoint"), Some(&json!("/api/user/profile")));
|
||||||
|
assert_eq!(resolved.get("quota_divisor"), Some(&json!(500000)));
|
||||||
|
assert_eq!(resolved.get("method"), Some(&json!("GET")));
|
||||||
|
assert!(resolved.get("checkin_endpoint").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_architecture_returns_generic_api_for_unknown_id() {
|
fn get_architecture_returns_generic_api_for_unknown_id() {
|
||||||
let architecture = get_architecture("unknown").expect("architecture should exist");
|
let architecture = get_architecture("unknown").expect("architecture should exist");
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub fn parse_verify_payload(
|
|||||||
match normalize_architecture_id(architecture_id) {
|
match normalize_architecture_id(architecture_id) {
|
||||||
"anyrouter" => admin_provider_ops_anyrouter_verify_payload(status, response_json),
|
"anyrouter" => admin_provider_ops_anyrouter_verify_payload(status, response_json),
|
||||||
"cubence" => admin_provider_ops_cubence_verify_payload(status, response_json),
|
"cubence" => admin_provider_ops_cubence_verify_payload(status, response_json),
|
||||||
|
"done_hub" => admin_provider_ops_anyrouter_verify_payload(status, response_json),
|
||||||
"yescode" => admin_provider_ops_yescode_verify_payload(status, response_json),
|
"yescode" => admin_provider_ops_yescode_verify_payload(status, response_json),
|
||||||
"nekocode" => admin_provider_ops_nekocode_verify_payload(status, response_json),
|
"nekocode" => admin_provider_ops_nekocode_verify_payload(status, response_json),
|
||||||
"sub2api" => {
|
"sub2api" => {
|
||||||
@@ -111,6 +112,12 @@ fn admin_provider_ops_cubence_cookie_header(cookie_input: &str) -> String {
|
|||||||
cookies.join("; ")
|
cookies.join("; ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn admin_provider_ops_session_cookie_header(cookie_input: &str) -> String {
|
||||||
|
let trimmed = admin_provider_ops_strip_cookie_header_prefix(cookie_input);
|
||||||
|
let session = admin_provider_ops_extract_cookie_value(trimmed, "session");
|
||||||
|
format!("session={session}")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn admin_provider_ops_yescode_cookie_header(cookie_input: &str) -> String {
|
pub fn admin_provider_ops_yescode_cookie_header(cookie_input: &str) -> String {
|
||||||
if cookie_input.contains("yescode_auth=") {
|
if cookie_input.contains("yescode_auth=") {
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
@@ -433,14 +440,17 @@ pub fn admin_provider_ops_verify_headers(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"nekocode" => {
|
"nekocode" | "done_hub" => {
|
||||||
if let Some(session_cookie) = credentials
|
if let Some(session_cookie) = credentials
|
||||||
.get("session_cookie")
|
.get("session_cookie")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.filter(|value| !value.trim().is_empty())
|
.filter(|value| !value.trim().is_empty())
|
||||||
{
|
{
|
||||||
let session = admin_provider_ops_extract_cookie_value(session_cookie, "session");
|
insert_header(
|
||||||
insert_header(&mut headers, "Cookie", &format!("session={session}"))?;
|
&mut headers,
|
||||||
|
"Cookie",
|
||||||
|
&admin_provider_ops_session_cookie_header(session_cookie),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"anyrouter" => {
|
"anyrouter" => {
|
||||||
@@ -817,7 +827,7 @@ mod tests {
|
|||||||
admin_provider_ops_anyrouter_parse_session_user_id,
|
admin_provider_ops_anyrouter_parse_session_user_id,
|
||||||
admin_provider_ops_anyrouter_verify_payload, admin_provider_ops_cubence_verify_payload,
|
admin_provider_ops_anyrouter_verify_payload, admin_provider_ops_cubence_verify_payload,
|
||||||
admin_provider_ops_frontend_updated_credentials, admin_provider_ops_sub2api_verify_payload,
|
admin_provider_ops_frontend_updated_credentials, admin_provider_ops_sub2api_verify_payload,
|
||||||
admin_provider_ops_verify_headers, ADMIN_PROVIDER_OPS_USER_AGENT,
|
admin_provider_ops_verify_headers, parse_verify_payload, ADMIN_PROVIDER_OPS_USER_AGENT,
|
||||||
};
|
};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use reqwest::header::COOKIE;
|
use reqwest::header::COOKIE;
|
||||||
@@ -929,6 +939,50 @@ mod tests {
|
|||||||
assert_eq!(auth_failed["message"], json!("Cookie 已失效,请重新配置"));
|
assert_eq!(auth_failed["message"], json!("Cookie 已失效,请重新配置"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn done_hub_verify_payload_reads_wrapped_profile() {
|
||||||
|
let payload = parse_verify_payload(
|
||||||
|
"done_hub",
|
||||||
|
StatusCode::OK,
|
||||||
|
&json!({
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"username": "linuxdo_850",
|
||||||
|
"display_name": "AAEE86",
|
||||||
|
"quota": 2_276_139_911_u64,
|
||||||
|
"used_quota": 13860089,
|
||||||
|
"request_count": 55
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(payload["success"], json!(true));
|
||||||
|
assert_eq!(payload["data"]["username"], json!("linuxdo_850"));
|
||||||
|
assert_eq!(payload["data"]["display_name"], json!("AAEE86"));
|
||||||
|
assert_eq!(payload["data"]["quota"], json!(2276139911.0));
|
||||||
|
assert_eq!(payload["data"]["used_quota"], json!(13860089.0));
|
||||||
|
assert_eq!(payload["data"]["request_count"], json!(55));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn done_hub_verify_headers_use_session_cookie_only() {
|
||||||
|
let headers = admin_provider_ops_verify_headers(
|
||||||
|
"done_hub",
|
||||||
|
&Map::new(),
|
||||||
|
&Map::from_iter([(
|
||||||
|
"session_cookie".to_string(),
|
||||||
|
json!("Cookie: session=abc; other=ignored"),
|
||||||
|
)]),
|
||||||
|
)
|
||||||
|
.expect("headers should build");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
headers.get(COOKIE).and_then(|value| value.to_str().ok()),
|
||||||
|
Some("session=abc")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn anyrouter_verify_headers_include_shared_user_agent() {
|
fn anyrouter_verify_headers_include_shared_user_agent() {
|
||||||
let headers = admin_provider_ops_verify_headers(
|
let headers = admin_provider_ops_verify_headers(
|
||||||
|
|||||||
Reference in New Issue
Block a user