Files
OpenShell/crates/openshell-server/tests/multiplex_integration.rs
Eric Curtin 10af3e609f refactor: deduplicate shared test helpers (#1399)
Extract four categories of copy-pasted test code into single
canonical locations:

- POLICY_OBJECT_TYPE / DRAFT_CHUNK_OBJECT_TYPE constants that were
  defined identically in both persistence/sqlite.rs and
  persistence/postgres.rs are now owned by persistence/mod.rs.

- test_server_state() was duplicated across grpc/provider.rs,
  grpc/policy.rs, and grpc/sandbox.rs. A shared grpc::test_support
  module in grpc/mod.rs now owns the single implementation; each
  submodule imports it.

- The TestOpenShell stub (full OpenShell trait impl), install_rustls_provider(),
  PkiBundle, generate_pki(), and start_test_server() were copied across
  up to five openshell-server integration test files. A new
  tests/common/mod.rs module owns them; each test file uses mod common.

- StubResponse, unique_socket_path(), and spawn_podman_stub() were
  duplicated between openshell-driver-podman/src/driver.rs and
  openshell-driver-podman/src/grpc.rs. A new src/test_utils.rs
  (cfg(test)-gated) owns the shared helpers.
2026-05-19 12:26:12 -07:00

142 lines
4.8 KiB
Rust

// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
mod common;
use bytes::Bytes;
use common::TestOpenShell;
use http_body_util::Empty;
use hyper::{Request, StatusCode};
use hyper_util::{
rt::{TokioExecutor, TokioIo},
server::conn::auto::Builder,
};
use openshell_core::proto::{
HealthRequest, ServiceStatus, open_shell_client::OpenShellClient,
open_shell_server::OpenShellServer,
};
use openshell_server::{MultiplexedService, health_router};
use tokio::net::TcpListener;
#[tokio::test]
async fn serves_grpc_and_http_on_same_port() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let grpc_service = OpenShellServer::new(TestOpenShell);
let http_service = health_router();
let service = MultiplexedService::new(grpc_service, http_service);
let server = tokio::spawn(async move {
loop {
let Ok((stream, _)) = listener.accept().await else {
continue;
};
let svc = service.clone();
tokio::spawn(async move {
let _ = Builder::new(TokioExecutor::new())
.serve_connection(TokioIo::new(stream), svc)
.await;
});
}
});
let mut client = OpenShellClient::connect(format!("http://{addr}"))
.await
.unwrap();
let response = client.health(HealthRequest {}).await.unwrap();
assert_eq!(response.get_ref().status, ServiceStatus::Healthy as i32);
let stream = tokio::net::TcpStream::connect(addr).await.unwrap();
let (mut sender, conn) = hyper::client::conn::http1::Builder::new()
.handshake(TokioIo::new(stream))
.await
.unwrap();
tokio::spawn(async move {
let _ = conn.await;
});
let req = Request::builder()
.method("GET")
.uri(format!("http://{addr}/healthz"))
.body(Empty::<Bytes>::new())
.unwrap();
let resp = sender.send_request(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
server.abort();
}
/// Verify tonic metadata ↔ HTTP header roundtrip for `x-request-id`.
///
/// This intentionally constructs its own request-ID layers from
/// `tower-http`'s public API rather than reusing the production macro
/// (which is crate-private). Production middleware composition and
/// layer ordering are covered by the unit tests in `multiplex::tests`.
#[tokio::test]
async fn grpc_response_propagates_request_id() {
use tower::ServiceBuilder;
use tower_http::request_id::{
MakeRequestId, PropagateRequestIdLayer, RequestId, SetRequestIdLayer,
};
#[derive(Clone)]
struct TestUuidRequestId;
impl MakeRequestId for TestUuidRequestId {
fn make_request_id<B>(&mut self, _req: &Request<B>) -> Option<RequestId> {
let id = uuid::Uuid::new_v4().to_string();
Some(RequestId::new(http::HeaderValue::from_str(&id).unwrap()))
}
}
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let x_request_id = http::HeaderName::from_static("x-request-id");
let grpc_service = ServiceBuilder::new()
.layer(SetRequestIdLayer::new(
x_request_id.clone(),
TestUuidRequestId,
))
.layer(PropagateRequestIdLayer::new(x_request_id))
.service(OpenShellServer::new(TestOpenShell));
let http_service = health_router();
let service = MultiplexedService::new(grpc_service, http_service);
tokio::spawn(async move {
loop {
let Ok((stream, _)) = listener.accept().await else {
continue;
};
let svc = service.clone();
tokio::spawn(async move {
let _ = Builder::new(TokioExecutor::new())
.serve_connection(TokioIo::new(stream), svc)
.await;
});
}
});
let mut client = OpenShellClient::connect(format!("http://{addr}"))
.await
.unwrap();
// Server generates a UUID when client omits x-request-id.
let response = client.health(HealthRequest {}).await.unwrap();
let generated = response
.metadata()
.get("x-request-id")
.expect("gRPC response should include server-generated x-request-id");
uuid::Uuid::parse_str(generated.to_str().unwrap()).expect("should be a valid UUID");
// Server preserves a client-supplied x-request-id.
let mut request = tonic::Request::new(HealthRequest {});
request
.metadata_mut()
.insert("x-request-id", "grpc-corr-id".parse().unwrap());
let response = client.health(request).await.unwrap();
let echoed = response.metadata().get("x-request-id").unwrap();
assert_eq!(echoed.to_str().unwrap(), "grpc-corr-id");
}