Integrate unfinished server-rs refactor worklists

This commit is contained in:
2026-04-30 13:39:06 +08:00
parent 62934b0809
commit 7ab0933f6d
676 changed files with 24487 additions and 21531 deletions

View File

@@ -500,6 +500,7 @@ fn current_utc_micros() -> i64 {
#[cfg(test)]
mod tests {
use axum::{
Router,
body::Body,
http::{Request, StatusCode},
};
@@ -639,6 +640,129 @@ mod tests {
);
}
#[tokio::test]
async fn ai_task_mutation_routes_require_authentication() {
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
for route in ai_task_mutation_route_cases() {
let (status, _) = post_ai_task_route(app.clone(), route.uri, None, route.body).await;
assert_eq!(status, StatusCode::UNAUTHORIZED, "{}", route.uri);
}
}
#[tokio::test]
async fn ai_task_mutation_routes_return_bad_gateway_when_spacetime_not_published() {
let state = seed_authenticated_state().await;
let token = issue_access_token(&state);
let app = build_router(state);
for route in ai_task_mutation_route_cases() {
let (status, payload) =
post_ai_task_route(app.clone(), route.uri, Some(&token), route.body).await;
assert_eq!(status, StatusCode::BAD_GATEWAY, "{}", route.uri);
assert_eq!(
payload["error"]["details"]["provider"],
Value::String("spacetimedb".to_string()),
"{}",
route.uri
);
}
}
struct AiTaskRouteCase {
uri: &'static str,
body: Option<Value>,
}
fn ai_task_mutation_route_cases() -> Vec<AiTaskRouteCase> {
vec![
AiTaskRouteCase {
uri: "/api/ai/tasks/aitask_001/stages/request_model/start",
body: None,
},
AiTaskRouteCase {
uri: "/api/ai/tasks/aitask_001/chunks",
body: Some(json!({
"stageKind": "request_model",
"sequence": 1,
"deltaText": "你听见远处的铃声。"
})),
},
AiTaskRouteCase {
uri: "/api/ai/tasks/aitask_001/stages/request_model/complete",
body: Some(json!({
"textOutput": "你听见远处的铃声。",
"structuredPayloadJson": "{\"scene\":\"camp\"}",
"warningMessages": []
})),
},
AiTaskRouteCase {
uri: "/api/ai/tasks/aitask_001/references",
body: Some(json!({
"referenceKind": "story_event",
"referenceId": "storyevt_001",
"label": "营地开场"
})),
},
AiTaskRouteCase {
uri: "/api/ai/tasks/aitask_001/complete",
body: None,
},
AiTaskRouteCase {
uri: "/api/ai/tasks/aitask_001/fail",
body: Some(json!({
"failureMessage": "模型返回内容为空"
})),
},
AiTaskRouteCase {
uri: "/api/ai/tasks/aitask_001/cancel",
body: None,
},
]
}
async fn post_ai_task_route(
app: Router,
uri: &str,
bearer_token: Option<&str>,
body: Option<Value>,
) -> (StatusCode, Value) {
let mut request = Request::builder()
.method("POST")
.uri(uri)
.header("x-genarrative-response-envelope", "v1");
if let Some(token) = bearer_token {
request = request.header("authorization", format!("Bearer {token}"));
}
let body = if let Some(payload) = body {
request = request.header("content-type", "application/json");
Body::from(payload.to_string())
} else {
Body::empty()
};
let response = app
.oneshot(request.body(body).expect("request should build"))
.await
.expect("request should succeed");
let status = response.status();
let body = response
.into_body()
.collect()
.await
.expect("body should collect")
.to_bytes();
let payload = if body.is_empty() {
Value::Null
} else {
serde_json::from_slice(&body).expect("response body should be valid json")
};
(status, payload)
}
async fn seed_authenticated_state() -> AppState {
let state = AppState::new(AppConfig::default()).expect("state should build");
state