runtime: require http auth
This commit is contained in:
parent
3dda06cbe3
commit
ac6c8b275d
|
|
@ -116,6 +116,7 @@ pub async fn serve_runtime_http(
|
||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
local_token: Option<String>,
|
local_token: Option<String>,
|
||||||
) -> Result<(), RuntimeHttpServerError> {
|
) -> Result<(), RuntimeHttpServerError> {
|
||||||
|
let local_token = local_token.ok_or(RuntimeHttpServerError::AuthRequired)?;
|
||||||
axum::serve(listener, runtime_http_router(runtime, local_token)).await?;
|
axum::serve(listener, runtime_http_router(runtime, local_token)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -127,9 +128,12 @@ pub async fn serve_runtime_http_with_auth(
|
||||||
local_token: Option<String>,
|
local_token: Option<String>,
|
||||||
auth: Option<RuntimeHttpAuthConfig>,
|
auth: Option<RuntimeHttpAuthConfig>,
|
||||||
) -> Result<(), RuntimeHttpServerError> {
|
) -> Result<(), RuntimeHttpServerError> {
|
||||||
|
if local_token.is_none() && auth.is_none() {
|
||||||
|
return Err(RuntimeHttpServerError::AuthRequired);
|
||||||
|
}
|
||||||
axum::serve(
|
axum::serve(
|
||||||
listener,
|
listener,
|
||||||
runtime_http_router_with_auth(runtime, local_token, auth),
|
runtime_http_router_with_optional_auth(runtime, local_token, auth),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -140,12 +144,20 @@ pub async fn serve_runtime_http_with_auth(
|
||||||
/// Handlers delegate to [`Runtime`] methods and keep Worker authority Runtime-local.
|
/// Handlers delegate to [`Runtime`] methods and keep Worker authority Runtime-local.
|
||||||
/// The path contains only a Runtime-local `worker_id`; backend aliases are not
|
/// The path contains only a Runtime-local `worker_id`; backend aliases are not
|
||||||
/// accepted or forwarded as Runtime authority.
|
/// accepted or forwarded as Runtime authority.
|
||||||
pub fn runtime_http_router(runtime: Runtime, local_token: Option<String>) -> Router {
|
pub fn runtime_http_router(runtime: Runtime, local_token: String) -> Router {
|
||||||
runtime_http_router_with_auth(runtime, local_token, None)
|
runtime_http_router_with_optional_auth(runtime, Some(local_token), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the REST router for an existing Runtime with signed capability-token auth.
|
/// Build the REST router for an existing Runtime with signed capability-token auth.
|
||||||
pub fn runtime_http_router_with_auth(
|
pub fn runtime_http_router_with_auth(
|
||||||
|
runtime: Runtime,
|
||||||
|
local_token: Option<String>,
|
||||||
|
auth: RuntimeHttpAuthConfig,
|
||||||
|
) -> Router {
|
||||||
|
runtime_http_router_with_optional_auth(runtime, local_token, Some(auth))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtime_http_router_with_optional_auth(
|
||||||
runtime: Runtime,
|
runtime: Runtime,
|
||||||
local_token: Option<String>,
|
local_token: Option<String>,
|
||||||
auth: Option<RuntimeHttpAuthConfig>,
|
auth: Option<RuntimeHttpAuthConfig>,
|
||||||
|
|
@ -1089,6 +1101,8 @@ fn code_for_runtime_error(error: &RuntimeError) -> String {
|
||||||
pub enum RuntimeHttpServerError {
|
pub enum RuntimeHttpServerError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Runtime(#[from] RuntimeError),
|
Runtime(#[from] RuntimeError),
|
||||||
|
#[error("Runtime HTTP server requires capability-token auth or a local bearer token")]
|
||||||
|
AuthRequired,
|
||||||
#[error("Runtime HTTP server I/O failed: {0}")]
|
#[error("Runtime HTTP server I/O failed: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
@ -1216,7 +1230,7 @@ mod tests {
|
||||||
let (auth, signer) = auth_config_and_signer();
|
let (auth, signer) = auth_config_and_signer();
|
||||||
let token_a = token_for_workspace(&signer, "workspace-a");
|
let token_a = token_for_workspace(&signer, "workspace-a");
|
||||||
let token_b = token_for_workspace(&signer, "workspace-b");
|
let token_b = token_for_workspace(&signer, "workspace-b");
|
||||||
let app = runtime_http_router_with_auth(runtime, None, Some(auth));
|
let app = runtime_http_router_with_auth(runtime, None, auth);
|
||||||
|
|
||||||
let create_a = scoped_task_request("a", "workspace-a");
|
let create_a = scoped_task_request("a", "workspace-a");
|
||||||
let response = app
|
let response = app
|
||||||
|
|
@ -1295,7 +1309,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (auth, signer) = auth_config_and_signer();
|
let (auth, signer) = auth_config_and_signer();
|
||||||
let token = token_for_workspace_with_permissions(&signer, "", ["workers:list"]);
|
let token = token_for_workspace_with_permissions(&signer, "", ["workers:list"]);
|
||||||
let app = runtime_http_router_with_auth(runtime, None, Some(auth));
|
let app = runtime_http_router_with_auth(runtime, None, auth);
|
||||||
|
|
||||||
let response = app
|
let response = app
|
||||||
.oneshot(bearer_request(
|
.oneshot(bearer_request(
|
||||||
|
|
@ -1316,7 +1330,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (auth, signer) = auth_config_and_signer();
|
let (auth, signer) = auth_config_and_signer();
|
||||||
let token = token_for_workspace_with_permissions(&signer, "workspace-a", ["workers:list"]);
|
let token = token_for_workspace_with_permissions(&signer, "workspace-a", ["workers:list"]);
|
||||||
let app = runtime_http_router_with_auth(runtime, None, Some(auth));
|
let app = runtime_http_router_with_auth(runtime, None, auth);
|
||||||
let create = scoped_task_request("a", "workspace-a");
|
let create = scoped_task_request("a", "workspace-a");
|
||||||
|
|
||||||
let response = app
|
let response = app
|
||||||
|
|
@ -1413,16 +1427,18 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn json_request<T: Serialize>(
|
async fn authed_json_request<T: Serialize>(
|
||||||
app: Router,
|
app: Router,
|
||||||
method: Method,
|
method: Method,
|
||||||
uri: &str,
|
uri: &str,
|
||||||
|
token: &str,
|
||||||
body: &T,
|
body: &T,
|
||||||
) -> axum::response::Response {
|
) -> axum::response::Response {
|
||||||
app.oneshot(
|
app.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method(method)
|
.method(method)
|
||||||
.uri(uri)
|
.uri(uri)
|
||||||
|
.header(header::AUTHORIZATION, format!("Bearer {token}"))
|
||||||
.header(header::CONTENT_TYPE, "application/json")
|
.header(header::CONTENT_TYPE, "application/json")
|
||||||
.body(Body::from(serde_json::to_vec(body).unwrap()))
|
.body(Body::from(serde_json::to_vec(body).unwrap()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
@ -1443,6 +1459,24 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn authed_empty_request(
|
||||||
|
app: Router,
|
||||||
|
method: Method,
|
||||||
|
uri: &str,
|
||||||
|
token: &str,
|
||||||
|
) -> axum::response::Response {
|
||||||
|
app.oneshot(
|
||||||
|
Request::builder()
|
||||||
|
.method(method)
|
||||||
|
.uri(uri)
|
||||||
|
.header(header::AUTHORIZATION, format!("Bearer {token}"))
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
async fn read_json<T: for<'de> Deserialize<'de>>(response: Response) -> T {
|
async fn read_json<T: for<'de> Deserialize<'de>>(response: Response) -> T {
|
||||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||||
serde_json::from_slice(&body).unwrap()
|
serde_json::from_slice(&body).unwrap()
|
||||||
|
|
@ -1458,12 +1492,14 @@ mod tests {
|
||||||
"builtin:coder".to_string(),
|
"builtin:coder".to_string(),
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let app = runtime_http_router(runtime.clone(), None);
|
let token = "local-token";
|
||||||
|
let app = runtime_http_router(runtime.clone(), token.to_string());
|
||||||
|
|
||||||
let response = json_request(
|
let response = authed_json_request(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
Method::POST,
|
Method::POST,
|
||||||
"/v1/workers",
|
"/v1/workers",
|
||||||
|
token,
|
||||||
&task_request("rest"),
|
&task_request("rest"),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -1475,77 +1511,84 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let input = WorkerInput::user("hello from backend");
|
let input = WorkerInput::user("hello from backend");
|
||||||
let response = json_request(
|
let response = authed_json_request(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
Method::POST,
|
Method::POST,
|
||||||
&format!("/v1/workers/{}/input", created.worker.worker_id),
|
&format!("/v1/workers/{}/input", created.worker.worker_id),
|
||||||
|
token,
|
||||||
&input,
|
&input,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let _input_ack: RuntimeHttpWorkerInputResponse = read_json(response).await;
|
let _input_ack: RuntimeHttpWorkerInputResponse = read_json(response).await;
|
||||||
|
|
||||||
let response = empty_request(
|
let response = authed_empty_request(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
Method::GET,
|
Method::GET,
|
||||||
&format!("/v1/workers/{}", created.worker.worker_id),
|
&format!("/v1/workers/{}", created.worker.worker_id),
|
||||||
|
token,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let _detail: RuntimeHttpWorkerResponse = read_json(response).await;
|
let _detail: RuntimeHttpWorkerResponse = read_json(response).await;
|
||||||
|
|
||||||
let response = empty_request(
|
let response = authed_empty_request(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
Method::GET,
|
Method::GET,
|
||||||
&format!("/v1/workers/{}/transcript", created.worker.worker_id),
|
&format!("/v1/workers/{}/transcript", created.worker.worker_id),
|
||||||
|
token,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
let response = empty_request(
|
let response = authed_empty_request(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
Method::POST,
|
Method::POST,
|
||||||
&format!("/v1/workers/{}/stop", created.worker.worker_id),
|
&format!("/v1/workers/{}/stop", created.worker.worker_id),
|
||||||
|
token,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let stop: RuntimeHttpWorkerLifecycleResponse = read_json(response).await;
|
let stop: RuntimeHttpWorkerLifecycleResponse = read_json(response).await;
|
||||||
assert_eq!(stop.ack.worker_ref, created.worker.worker_ref);
|
assert_eq!(stop.ack.worker_ref, created.worker.worker_ref);
|
||||||
|
|
||||||
let response = empty_request(
|
let response = authed_empty_request(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
Method::POST,
|
Method::POST,
|
||||||
&format!("/v1/workers/{}/restore", created.worker.worker_id),
|
&format!("/v1/workers/{}/restore", created.worker.worker_id),
|
||||||
|
token,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let restored: RuntimeHttpWorkerResponse = read_json(response).await;
|
let restored: RuntimeHttpWorkerResponse = read_json(response).await;
|
||||||
assert_eq!(restored.worker.status, WorkerStatus::Idle);
|
assert_eq!(restored.worker.status, WorkerStatus::Idle);
|
||||||
|
|
||||||
let response = empty_request(
|
let response = authed_empty_request(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
Method::POST,
|
Method::POST,
|
||||||
&format!("/v1/workers/{}/stop", created.worker.worker_id),
|
&format!("/v1/workers/{}/stop", created.worker.worker_id),
|
||||||
|
token,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let response = empty_request(
|
let response = authed_empty_request(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
Method::POST,
|
Method::POST,
|
||||||
&format!("/v1/workers/{}/cancel", created.worker.worker_id),
|
&format!("/v1/workers/{}/cancel", created.worker.worker_id),
|
||||||
|
token,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let cancel: RuntimeHttpWorkerLifecycleResponse = read_json(response).await;
|
let cancel: RuntimeHttpWorkerLifecycleResponse = read_json(response).await;
|
||||||
assert_eq!(cancel.ack.worker_ref, created.worker.worker_ref);
|
assert_eq!(cancel.ack.worker_ref, created.worker.worker_ref);
|
||||||
|
|
||||||
let response = empty_request(app.clone(), Method::GET, "/v1/workers").await;
|
let response = authed_empty_request(app.clone(), Method::GET, "/v1/workers", token).await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let workers: RuntimeHttpWorkersResponse = read_json(response).await;
|
let workers: RuntimeHttpWorkersResponse = read_json(response).await;
|
||||||
assert_eq!(workers.workers.len(), 1);
|
assert_eq!(workers.workers.len(), 1);
|
||||||
|
|
||||||
let response = empty_request(app, Method::GET, "/v1/runtime").await;
|
let response = authed_empty_request(app, Method::GET, "/v1/runtime", token).await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let summary: RuntimeHttpSummaryResponse = read_json(response).await;
|
let summary: RuntimeHttpSummaryResponse = read_json(response).await;
|
||||||
assert_eq!(summary.runtime.worker_count, 1);
|
assert_eq!(summary.runtime.worker_count, 1);
|
||||||
|
|
@ -1554,7 +1597,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn local_token_placeholder_rejects_missing_bearer_token() {
|
async fn local_token_placeholder_rejects_missing_bearer_token() {
|
||||||
let app = runtime_http_router(Runtime::new_memory(), Some("local-token".to_string()));
|
let app = runtime_http_router(Runtime::new_memory(), "local-token".to_string());
|
||||||
|
|
||||||
let response = empty_request(app.clone(), Method::GET, "/v1/runtime").await;
|
let response = empty_request(app.clone(), Method::GET, "/v1/runtime").await;
|
||||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||||
|
|
@ -1577,8 +1620,9 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn runtime_errors_use_typed_rest_error_shape() {
|
async fn runtime_errors_use_typed_rest_error_shape() {
|
||||||
let app = runtime_http_router(Runtime::new_memory(), None);
|
let token = "local-token";
|
||||||
let response = empty_request(app, Method::GET, "/v1/workers/999").await;
|
let app = runtime_http_router(Runtime::new_memory(), token.to_string());
|
||||||
|
let response = authed_empty_request(app, Method::GET, "/v1/workers/999", token).await;
|
||||||
|
|
||||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||||
let error: RuntimeHttpErrorResponse = read_json(response).await;
|
let error: RuntimeHttpErrorResponse = read_json(response).await;
|
||||||
|
|
@ -1586,6 +1630,21 @@ mod tests {
|
||||||
assert!(error.error.message.contains("999"));
|
assert!(error.error.message.contains("999"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn serve_runtime_http_rejects_missing_auth_configuration() {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let error = serve_runtime_http(Runtime::new_memory(), listener, None)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(matches!(error, RuntimeHttpServerError::AuthRequired));
|
||||||
|
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let error = serve_runtime_http_with_auth(Runtime::new_memory(), listener, None, None)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(matches!(error, RuntimeHttpServerError::AuthRequired));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workdir_runtime_errors_preserve_diagnostic_code() {
|
fn workdir_runtime_errors_preserve_diagnostic_code() {
|
||||||
let error =
|
let error =
|
||||||
|
|
@ -1619,6 +1678,8 @@ mod ws_tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio_tungstenite::connect_async;
|
use tokio_tungstenite::connect_async;
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
|
||||||
|
use tokio_tungstenite::tungstenite::http::header as ws_header;
|
||||||
|
|
||||||
struct WsBackend;
|
struct WsBackend;
|
||||||
|
|
||||||
|
|
@ -1725,12 +1786,18 @@ mod ws_tests {
|
||||||
runtime
|
runtime
|
||||||
.store_config_bundle(ws_test_bundle(ProfileSelector::RuntimeDefault))
|
.store_config_bundle(ws_test_bundle(ProfileSelector::RuntimeDefault))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let worker = runtime.create_worker(ws_create_request()).unwrap();
|
let worker = runtime
|
||||||
|
.create_worker_scoped("local", ws_create_request())
|
||||||
|
.unwrap();
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let runtime = runtime.clone();
|
let runtime = runtime.clone();
|
||||||
async move { serve_runtime_http(runtime, listener, None).await.unwrap() }
|
async move {
|
||||||
|
serve_runtime_http(runtime, listener, Some("local-token".to_string()))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
(
|
(
|
||||||
runtime,
|
runtime,
|
||||||
|
|
@ -1742,6 +1809,15 @@ mod ws_tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn authed_ws_request(url: &str) -> tokio_tungstenite::tungstenite::http::Request<()> {
|
||||||
|
let mut request = url.into_client_request().unwrap();
|
||||||
|
request.headers_mut().insert(
|
||||||
|
ws_header::AUTHORIZATION,
|
||||||
|
"Bearer local-token".parse().unwrap(),
|
||||||
|
);
|
||||||
|
request
|
||||||
|
}
|
||||||
|
|
||||||
async fn next_frame(
|
async fn next_frame(
|
||||||
stream: &mut tokio_tungstenite::WebSocketStream<
|
stream: &mut tokio_tungstenite::WebSocketStream<
|
||||||
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
|
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
|
||||||
|
|
@ -1757,7 +1833,7 @@ mod ws_tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn protocol_ws_connect_sends_snapshot_and_live_worker_events() {
|
async fn protocol_ws_connect_sends_snapshot_and_live_worker_events() {
|
||||||
let (runtime, worker_ref, url) = spawn_runtime_server().await;
|
let (runtime, worker_ref, url) = spawn_runtime_server().await;
|
||||||
let (mut stream, _) = connect_async(&url).await.unwrap();
|
let (mut stream, _) = connect_async(authed_ws_request(&url)).await.unwrap();
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
next_frame(&mut stream).await,
|
next_frame(&mut stream).await,
|
||||||
|
|
@ -1799,9 +1875,8 @@ mod ws_tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (mut stream, _) = connect_async(format!("{url}?cursor={}", first.cursor))
|
let resume_url = format!("{url}?cursor={}", first.cursor);
|
||||||
.await
|
let (mut stream, _) = connect_async(authed_ws_request(&resume_url)).await.unwrap();
|
||||||
.unwrap();
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
next_frame(&mut stream).await,
|
next_frame(&mut stream).await,
|
||||||
protocol::Event::Snapshot { .. }
|
protocol::Event::Snapshot { .. }
|
||||||
|
|
@ -1824,13 +1899,16 @@ mod ws_tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn protocol_ws_reports_malformed_cursor_and_method_frame() {
|
async fn protocol_ws_reports_malformed_cursor_and_method_frame() {
|
||||||
let (_runtime, _worker_ref, url) = spawn_runtime_server().await;
|
let (_runtime, _worker_ref, url) = spawn_runtime_server().await;
|
||||||
let (mut malformed, _) = connect_async(format!("{url}?cursor=bad")).await.unwrap();
|
let malformed_url = format!("{url}?cursor=bad");
|
||||||
|
let (mut malformed, _) = connect_async(authed_ws_request(&malformed_url))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
next_frame(&mut malformed).await,
|
next_frame(&mut malformed).await,
|
||||||
protocol::Event::Error { .. }
|
protocol::Event::Error { .. }
|
||||||
));
|
));
|
||||||
|
|
||||||
let (mut stream, _) = connect_async(&url).await.unwrap();
|
let (mut stream, _) = connect_async(authed_ws_request(&url)).await.unwrap();
|
||||||
let _ = next_frame(&mut stream).await;
|
let _ = next_frame(&mut stream).await;
|
||||||
stream.send(Message::Text("{}".into())).await.unwrap();
|
stream.send(Message::Text("{}".into())).await.unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user