#![allow(warnings)]
#![allow(clippy::unwrap_used, clippy::expect_used)]
#![allow(clippy::absurd_extreme_comparisons, clippy::nonminimal_bool)]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::sync::Arc;
use thunder::Value;
use thunder::server::{
AuthError, Credentials, Dispatch, ListenerConfig, ListenerHandle, Principal, ServerInfo,
Session, spawn_listener,
};
use vectorizer_sdk::rpc::{HelloPayload, RpcClient, RpcClientError, protocol_config};
const GOOD_TOKEN: &str = "good-token";
struct FakeVectorizer;
impl Dispatch for FakeVectorizer {
type Identity = ();
async fn dispatch(
&self,
_session: &Session<()>,
command: &str,
args: Vec<Value>,
) -> Result<Value, String> {
match command {
"HELLO" => Ok(Value::Map(vec![
(
Value::Str("server_version".into()),
Value::Str("test-fixture/0.0.0".into()),
),
(Value::Str("protocol_version".into()), Value::Int(1)),
(Value::Str("authenticated".into()), Value::Bool(true)),
(Value::Str("admin".into()), Value::Bool(true)),
(
Value::Str("capabilities".into()),
Value::Array(vec![
Value::Str("PING".into()),
Value::Str("collections.list".into()),
Value::Str("collections.get_info".into()),
Value::Str("vectors.get".into()),
Value::Str("search.basic".into()),
]),
),
])),
"PING" => Ok(Value::Str("PONG".into())),
"collections.list" => Ok(Value::Array(vec![
Value::Str("alpha-docs".into()),
Value::Str("beta-source".into()),
])),
"collections.get_info" => {
let name = args.first().and_then(|v| v.as_str()).unwrap_or("unknown");
Ok(Value::Map(vec![
(Value::Str("name".into()), Value::Str(name.to_owned())),
(Value::Str("vector_count".into()), Value::Int(42)),
(Value::Str("document_count".into()), Value::Int(10)),
(Value::Str("dimension".into()), Value::Int(384)),
(Value::Str("metric".into()), Value::Str("Cosine".into())),
(
Value::Str("created_at".into()),
Value::Str("2026-04-19T00:00:00Z".into()),
),
(
Value::Str("updated_at".into()),
Value::Str("2026-04-19T00:00:00Z".into()),
),
]))
}
"search.basic" => Ok(Value::Array(vec![
Value::Map(vec![
(Value::Str("id".into()), Value::Str("vec-0".into())),
(Value::Str("score".into()), Value::Float(0.95)),
(
Value::Str("payload".into()),
Value::Str(r#"{"title":"hit one"}"#.into()),
),
]),
Value::Map(vec![
(Value::Str("id".into()), Value::Str("vec-1".into())),
(Value::Str("score".into()), Value::Float(0.81)),
]),
])),
other => Err(format!("unknown command '{other}'")),
}
}
async fn authenticate(&self, creds: Credentials) -> Result<Principal<()>, AuthError> {
let secret = match creds {
Credentials::Token(t) | Credentials::ApiKey(t) => t,
Credentials::UserPass(_, pass) => pass,
Credentials::None => return Err(AuthError::InvalidCredentials),
};
if secret == GOOD_TOKEN {
Ok(Principal::new("fake-admin"))
} else {
Err(AuthError::InvalidCredentials)
}
}
}
async fn spawn_fake_server(auth_required: bool) -> (ListenerHandle, String) {
let mut config = ListenerConfig::new("127.0.0.1:0".parse().unwrap());
if !auth_required {
config = config.open();
}
let info = ServerInfo {
name: "vectorizer-test-fixture".to_owned(),
version: "0.0.0".to_owned(),
};
let handle = spawn_listener(Arc::new(FakeVectorizer), protocol_config(), info, config)
.await
.unwrap();
let endpoint = format!("vectorizer://{}", handle.local_addr());
(handle, endpoint)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn hello_then_ping_then_typed_commands() {
let (_handle, endpoint) = spawn_fake_server(false).await;
let client = RpcClient::connect(&endpoint).await.unwrap();
let pong = client.ping().await.unwrap();
assert_eq!(pong, "PONG");
let hello = client
.hello(HelloPayload::new("rpc-integration-test"))
.await
.unwrap();
assert!(hello.authenticated);
assert!(hello.admin);
assert_eq!(hello.protocol_version, 1);
assert_eq!(hello.server_version, "test-fixture/0.0.0");
assert!(hello.capabilities.contains(&"collections.list".to_owned()));
let cols = client.list_collections().await.unwrap();
assert_eq!(
cols,
vec!["alpha-docs".to_owned(), "beta-source".to_owned()]
);
let info = client.get_collection_info("alpha-docs").await.unwrap();
assert_eq!(info.name, "alpha-docs");
assert_eq!(info.vector_count, 42);
assert_eq!(info.dimension, 384);
assert_eq!(info.metric, "Cosine");
let hits = client
.search_basic("alpha-docs", "anything", 10)
.await
.unwrap();
assert_eq!(hits.len(), 2);
assert_eq!(hits[0].id, "vec-0");
assert!((hits[0].score - 0.95).abs() < 1e-9);
assert_eq!(hits[0].payload.as_deref(), Some(r#"{"title":"hit one"}"#));
assert_eq!(hits[1].id, "vec-1");
assert!(hits[1].payload.is_none());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn data_plane_call_without_credentials_is_rejected() {
let (_handle, endpoint) = spawn_fake_server(true).await;
let client = RpcClient::connect(&endpoint).await.unwrap();
assert!(!client.is_authenticated());
let err = client.list_collections().await.unwrap_err();
match err {
RpcClientError::NotAuthenticated(_) => {}
other => panic!("expected NotAuthenticated, got {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn hello_with_token_authenticates_the_session() {
let (_handle, endpoint) = spawn_fake_server(true).await;
let client = RpcClient::connect(&endpoint).await.unwrap();
let hello = client
.hello(HelloPayload::new("rpc-integration-test").with_token(GOOD_TOKEN))
.await
.unwrap();
assert!(hello.authenticated);
assert!(client.is_authenticated());
let cols = client.list_collections().await.unwrap();
assert_eq!(cols.len(), 2);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn bad_credentials_fail_the_handshake() {
let (_handle, endpoint) = spawn_fake_server(true).await;
let client = RpcClient::connect(&endpoint).await.unwrap();
let err = client
.hello(HelloPayload::new("rpc-integration-test").with_token("wrong-token"))
.await
.unwrap_err();
match err {
RpcClientError::NotAuthenticated(_) => {}
other => panic!("expected NotAuthenticated, got {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn concurrent_calls_on_one_connection_are_demultiplexed_by_id() {
let (_handle, endpoint) = spawn_fake_server(false).await;
let client = Arc::new(RpcClient::connect(&endpoint).await.unwrap());
client
.hello(HelloPayload::new("concurrent-test"))
.await
.unwrap();
let mut handles = Vec::new();
for _ in 0..16 {
let c = Arc::clone(&client);
handles.push(tokio::spawn(async move { c.list_collections().await }));
}
for h in handles {
let cols = h.await.unwrap().unwrap();
assert_eq!(cols.len(), 2);
assert_eq!(cols[0], "alpha-docs");
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn connect_url_accepts_canonical_vectorizer_scheme() {
let (_handle, endpoint) = spawn_fake_server(false).await;
let client = RpcClient::connect_url(&endpoint).await.unwrap();
let pong = client.ping().await.unwrap();
assert_eq!(pong, "PONG");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn connect_url_rejects_http_scheme_with_clear_error() {
let result = RpcClient::connect_url("http://localhost:15002").await;
let err = match result {
Err(e) => e,
Ok(_) => panic!("connect_url with http scheme must fail"),
};
let msg = err.to_string();
assert!(
msg.contains("REST URL") && msg.contains("HTTP client"),
"expected the error to point the caller at the HTTP client; got: {msg}"
);
}