fix(clients): repair Python & Rust tests, add container test orchestration
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled

- Python: fix wire protocol test method names (bool_val -> bool, etc.)
- Python: make integration tests and example fully async with pytest-asyncio
- Rust: add tokio dev-dependency and convert integration tests to async/await
- Rust: update ping_test example to async
- Nim: remove committed ELF build artifact
- Docker: add BARADB_HOST/BARADB_PORT env vars to test containers
- Docker: fix docker-compose.test.yml usage (remove --abort-on-container-exit)
- Add scripts/test-clients.sh for sequential client test runs
- Remove docker-compose.test.yml from .gitignore so it is tracked
- Fix repository URLs in Python and Rust READMEs
This commit is contained in:
2026-05-14 23:35:45 +03:00
parent c55d3080cf
commit 359f945170
14 changed files with 311 additions and 129 deletions
-1
View File
@@ -37,6 +37,5 @@ clients/rust/target/
# Nim compiled binaries # Nim compiled binaries
clients/nim/src/baradb/client clients/nim/src/baradb/client
src/barabadb/storage/compaction src/barabadb/storage/compaction
docker-compose.test.yml
src/barabadb/query/executor src/barabadb/query/executor
tests/join_tests tests/join_tests
+2 -2
View File
@@ -8,8 +8,8 @@ const assert = require('node:assert');
const net = require('net'); const net = require('net');
const { Client, WireValue, QueryBuilder } = require('../baradb'); const { Client, WireValue, QueryBuilder } = require('../baradb');
const HOST = 'localhost'; const HOST = process.env.BARADB_HOST || 'localhost';
const PORT = 9472; const PORT = parseInt(process.env.BARADB_PORT || '9472', 10);
function serverAvailable() { function serverAvailable() {
return new Promise((resolve) => { return new Promise((resolve) => {
+5 -3
View File
@@ -1,15 +1,17 @@
## BaraDB Nim Client — Integration Tests ## BaraDB Nim Client — Integration Tests
## Requires a running BaraDB server on localhost:9472. ## Requires a running BaraDB server.
## Set BARADB_HOST / BARADB_PORT env vars to override defaults.
import std/unittest import std/unittest
import std/asyncdispatch import std/asyncdispatch
import std/asyncnet import std/asyncnet
import std/strutils import std/strutils
import std/os
import baradb/client import baradb/client
const const
TestHost = "127.0.0.1" TestHost = getEnv("BARADB_HOST", "127.0.0.1")
TestPort = 9472 TestPort = parseInt(getEnv("BARADB_PORT", "9472"))
proc serverAvailable(): bool = proc serverAvailable(): bool =
try: try:
+1 -1
View File
@@ -21,7 +21,7 @@ pip install baradb
Or from source: Or from source:
```bash ```bash
git clone https://github.com/katehonz/barabaDB.git git clone https://github.com/barabadb/baradadb.git
cd clients/python cd clients/python
pip install -e ".[dev]" pip install -e ".[dev]"
``` ```
+30 -29
View File
@@ -6,35 +6,36 @@ This script demonstrates common operations with the BaraDB Python client.
Run it while a BaraDB server is listening on localhost:9472. Run it while a BaraDB server is listening on localhost:9472.
""" """
import asyncio
import sys import sys
from baradb import Client, QueryBuilder, WireValue from baradb import Client, QueryBuilder, WireValue
def example_connection(): async def example_connection():
print("=== Connection ===") print("=== Connection ===")
client = Client("localhost", 9472) client = Client("localhost", 9472)
client.connect() await client.connect()
print(f"Connected: {client.is_connected()}") print(f"Connected: {client.is_connected()}")
print(f"Ping: {client.ping()}") print(f"Ping: {await client.ping()}")
client.close() await client.close()
print(f"Connected after close: {client.is_connected()}") print(f"Connected after close: {client.is_connected()}")
print() print()
def example_context_manager(): async def example_context_manager():
print("=== Context Manager ===") print("=== Context Manager ===")
with Client("localhost", 9472) as client: async with Client("localhost", 9472) as client:
print(f"Inside context: {client.is_connected()}") print(f"Inside context: {client.is_connected()}")
print(f"Ping: {client.ping()}") print(f"Ping: {await client.ping()}")
print("Outside context: closed automatically") print("Outside context: closed automatically")
print() print()
def example_simple_query(): async def example_simple_query():
print("=== Simple Query ===") print("=== Simple Query ===")
with Client("localhost", 9472) as client: async with Client("localhost", 9472) as client:
result = client.query("SELECT 42 as answer, 'BaraDB' as db") result = await client.query("SELECT 42 as answer, 'BaraDB' as db")
print(f"Columns: {result.columns}") print(f"Columns: {result.columns}")
print(f"Rows: {result.rows}") print(f"Rows: {result.rows}")
print(f"Row count: {result.row_count}") print(f"Row count: {result.row_count}")
@@ -43,15 +44,15 @@ def example_simple_query():
print() print()
def example_parameterized_query(): async def example_parameterized_query():
print("=== Parameterized Query ===") print("=== Parameterized Query ===")
with Client("localhost", 9472) as client: async with Client("localhost", 9472) as client:
result = client.query_params( result = await client.query_params(
"SELECT $1 as num, $2 as txt, $3 as flag", "SELECT $1 as num, $2 as txt, $3 as flag",
[ [
WireValue.int64(123), WireValue.int64(123),
WireValue.string("hello world"), WireValue.string("hello world"),
WireValue.bool_val(True), WireValue.bool(True),
], ],
) )
for row in result: for row in result:
@@ -59,9 +60,9 @@ def example_parameterized_query():
print() print()
def example_query_builder(): async def example_query_builder():
print("=== Query Builder ===") print("=== Query Builder ===")
with Client("localhost", 9472) as client: async with Client("localhost", 9472) as client:
sql = ( sql = (
QueryBuilder(client) QueryBuilder(client)
.select("id", "name") .select("id", "name")
@@ -75,10 +76,10 @@ def example_query_builder():
print() print()
def example_vector(): async def example_vector():
print("=== Vector Value ===") print("=== Vector Value ===")
with Client("localhost", 9472) as client: async with Client("localhost", 9472) as client:
result = client.query_params( result = await client.query_params(
"SELECT $1 as embedding", "SELECT $1 as embedding",
[WireValue.vector([0.1, 0.2, 0.3, 0.4])], [WireValue.vector([0.1, 0.2, 0.3, 0.4])],
) )
@@ -87,34 +88,34 @@ def example_vector():
print() print()
def example_ddl_dml(): async def example_ddl_dml():
print("=== DDL & DML ===") print("=== DDL & DML ===")
with Client("localhost", 9472) as client: async with Client("localhost", 9472) as client:
# Clean up # Clean up
try: try:
client.execute("DROP TABLE IF EXISTS demo_products") await client.execute("DROP TABLE IF EXISTS demo_products")
except Exception as exc: except Exception as exc:
print(f"Cleanup warning: {exc}") print(f"Cleanup warning: {exc}")
client.execute( await client.execute(
"CREATE TABLE demo_products (id INT PRIMARY KEY, name STRING, price FLOAT)" "CREATE TABLE demo_products (id INT PRIMARY KEY, name STRING, price FLOAT)"
) )
affected = client.execute( affected = await client.execute(
"INSERT INTO demo_products (id, name, price) VALUES (1, 'Widget', 9.99)" "INSERT INTO demo_products (id, name, price) VALUES (1, 'Widget', 9.99)"
) )
print(f"Insert affected rows: {affected}") print(f"Insert affected rows: {affected}")
result = client.query("SELECT * FROM demo_products") result = await client.query("SELECT * FROM demo_products")
print(f"Select returned {result.row_count} row(s)") print(f"Select returned {result.row_count} row(s)")
for row in result: for row in result:
print(f" {row}") print(f" {row}")
client.execute("DROP TABLE demo_products") await client.execute("DROP TABLE demo_products")
print("Table dropped") print("Table dropped")
print() print()
def main(): async def main():
print("BaraDB Python Client Examples") print("BaraDB Python Client Examples")
print("Make sure BaraDB is running on localhost:9472") print("Make sure BaraDB is running on localhost:9472")
print() print()
@@ -131,10 +132,10 @@ def main():
for fn in examples: for fn in examples:
try: try:
fn() await fn()
except Exception as exc: except Exception as exc:
print(f"ERROR in {fn.__name__}: {exc}", file=sys.stderr) print(f"ERROR in {fn.__name__}: {exc}", file=sys.stderr)
if __name__ == "__main__": if __name__ == "__main__":
main() asyncio.run(main())
+1
View File
@@ -48,6 +48,7 @@ packages = ["baradb"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
testpaths = ["tests"] testpaths = ["tests"]
pythonpath = ["."] pythonpath = ["."]
asyncio_mode = "auto"
[tool.mypy] [tool.mypy]
strict = true strict = true
+42 -39
View File
@@ -5,13 +5,16 @@ Requires a running BaraDB server on localhost:9472.
These tests are skipped automatically if the server is unreachable. These tests are skipped automatically if the server is unreachable.
""" """
import asyncio
import os
import socket import socket
import pytest import pytest
import pytest_asyncio
from baradb import Client, WireValue, QueryBuilder from baradb import Client, WireValue, QueryBuilder
BARADB_HOST = "localhost" BARADB_HOST = os.environ.get("BARADB_HOST", "localhost")
BARADB_PORT = 9472 BARADB_PORT = int(os.environ.get("BARADB_PORT", "9472"))
def _server_available() -> bool: def _server_available() -> bool:
@@ -29,41 +32,41 @@ pytestmark = pytest.mark.skipif(
) )
@pytest.fixture @pytest_asyncio.fixture
def client(): async def client():
c = Client(BARADB_HOST, BARADB_PORT) c = Client(BARADB_HOST, BARADB_PORT)
c.connect() await c.connect()
yield c yield c
c.close() await c.close()
class TestConnection: class TestConnection:
def test_connect_and_close(self): async def test_connect_and_close(self):
c = Client(BARADB_HOST, BARADB_PORT) c = Client(BARADB_HOST, BARADB_PORT)
assert not c.is_connected() assert not c.is_connected()
c.connect() await c.connect()
assert c.is_connected() assert c.is_connected()
c.close() await c.close()
assert not c.is_connected() assert not c.is_connected()
def test_context_manager(self): async def test_context_manager(self):
with Client(BARADB_HOST, BARADB_PORT) as c: async with Client(BARADB_HOST, BARADB_PORT) as c:
assert c.is_connected() assert c.is_connected()
assert not c.is_connected() assert not c.is_connected()
class TestPing: class TestPing:
def test_ping(self, client): async def test_ping(self, client):
assert client.ping() is True assert await client.ping() is True
class TestQuery: class TestQuery:
def test_simple_select(self, client): async def test_simple_select(self, client):
result = client.query("SELECT 1 as one") result = await client.query("SELECT 1 as one")
assert result.row_count >= 0 # server may return rows or empty assert result.row_count >= 0 # server may return rows or empty
def test_query_with_params(self, client): async def test_query_with_params(self, client):
result = client.query_params( result = await client.query_params(
"SELECT $1 as num, $2 as txt", "SELECT $1 as num, $2 as txt",
[WireValue.int64(42), WireValue.string("hello")], [WireValue.int64(42), WireValue.string("hello")],
) )
@@ -71,22 +74,22 @@ class TestQuery:
class TestExecute: class TestExecute:
def test_create_table_and_insert(self, client): async def test_create_table_and_insert(self, client):
# Clean up first (best-effort) # Clean up first (best-effort)
try: try:
client.execute("DROP TABLE IF EXISTS test_users") await client.execute("DROP TABLE IF EXISTS test_users")
except Exception: except Exception:
pass pass
client.execute( await client.execute(
"CREATE TABLE test_users (id INT PRIMARY KEY, name STRING, age INT)" "CREATE TABLE test_users (id INT PRIMARY KEY, name STRING, age INT)"
) )
affected = client.execute( affected = await client.execute(
"INSERT INTO test_users (id, name, age) VALUES (1, 'Alice', 30)" "INSERT INTO test_users (id, name, age) VALUES (1, 'Alice', 30)"
) )
assert affected >= 0 assert affected >= 0
result = client.query("SELECT name, age FROM test_users WHERE id = 1") result = await client.query("SELECT name, age FROM test_users WHERE id = 1")
assert result.row_count == 1 assert result.row_count == 1
row = result.rows[0] row = result.rows[0]
# Server returns all columns; map by name via dict iteration # Server returns all columns; map by name via dict iteration
@@ -94,20 +97,20 @@ class TestExecute:
assert row_dict["name"] == "Alice" assert row_dict["name"] == "Alice"
assert row_dict["age"] == 30 assert row_dict["age"] == 30
client.execute("DROP TABLE test_users") await client.execute("DROP TABLE test_users")
class TestQueryBuilder: class TestQueryBuilder:
def test_builder_exec(self, client): async def test_builder_exec(self, client):
try: try:
client.execute("DROP TABLE IF EXISTS test_products") await client.execute("DROP TABLE IF EXISTS test_products")
except Exception: except Exception:
pass pass
client.execute( await client.execute(
"CREATE TABLE test_products (id INT PRIMARY KEY, name STRING, price FLOAT)" "CREATE TABLE test_products (id INT PRIMARY KEY, name STRING, price FLOAT)"
) )
client.execute( await client.execute(
"INSERT INTO test_products (id, name, price) VALUES (1, 'Widget', 9.99)" "INSERT INTO test_products (id, name, price) VALUES (1, 'Widget', 9.99)"
) )
@@ -117,36 +120,36 @@ class TestQueryBuilder:
.from_("test_products") .from_("test_products")
.where("id = 1") .where("id = 1")
) )
result = qb.exec() result = await qb.exec()
assert result.row_count == 1 assert result.row_count == 1
client.execute("DROP TABLE test_products") await client.execute("DROP TABLE test_products")
class TestAuth: class TestAuth:
def test_auth_with_dummy_token(self, client): async def test_auth_with_dummy_token(self, client):
# Server uses default JWT secret in dev mode, any token format is accepted # Server uses default JWT secret in dev mode, any token format is accepted
# depending on server config. We just verify the method does not crash. # depending on server config. We just verify the method does not crash.
try: try:
client.auth("dummy-token-for-testing") await client.auth("dummy-token-for-testing")
except Exception as exc: except Exception as exc:
# Auth may fail with invalid token — that's acceptable for this test # Auth may fail with invalid token — that's acceptable for this test
assert "Auth" in str(exc) or "error" in str(exc).lower() assert "Auth" in str(exc) or "error" in str(exc).lower()
class TestTransactions: class TestTransactions:
def test_transaction_begin_commit(self, client): async def test_transaction_begin_commit(self, client):
try: try:
client.execute("DROP TABLE IF EXISTS test_txn") await client.execute("DROP TABLE IF EXISTS test_txn")
except Exception: except Exception:
pass pass
client.execute("CREATE TABLE test_txn (id INT PRIMARY KEY)") await client.execute("CREATE TABLE test_txn (id INT PRIMARY KEY)")
client.execute("BEGIN") await client.execute("BEGIN")
client.execute("INSERT INTO test_txn (id) VALUES (1)") await client.execute("INSERT INTO test_txn (id) VALUES (1)")
client.execute("COMMIT") await client.execute("COMMIT")
result = client.query("SELECT COUNT(*) FROM test_txn") result = await client.query("SELECT COUNT(*) FROM test_txn")
assert result.row_count >= 0 assert result.row_count >= 0
client.execute("DROP TABLE test_txn") await client.execute("DROP TABLE test_txn")
+6 -6
View File
@@ -18,11 +18,11 @@ class TestWireValue:
assert data == b"\x00" assert data == b"\x00"
def test_bool_true(self): def test_bool_true(self):
wv = WireValue.bool_val(True) wv = WireValue.bool(True)
assert wv.serialize() == b"\x01\x01" assert wv.serialize() == b"\x01\x01"
def test_bool_false(self): def test_bool_false(self):
wv = WireValue.bool_val(False) wv = WireValue.bool(False)
assert wv.serialize() == b"\x01\x00" assert wv.serialize() == b"\x01\x00"
def test_int8(self): def test_int8(self):
@@ -70,7 +70,7 @@ class TestWireValue:
assert data[5:] == b"hello" assert data[5:] == b"hello"
def test_bytes(self): def test_bytes(self):
wv = WireValue.bytes_val(b"\xde\xad\xbe\xef") wv = WireValue.bytes(b"\xde\xad\xbe\xef")
data = wv.serialize() data = wv.serialize()
assert data[0] == FieldKind.BYTES assert data[0] == FieldKind.BYTES
length = struct.unpack(">I", data[1:5])[0] length = struct.unpack(">I", data[1:5])[0]
@@ -87,7 +87,7 @@ class TestWireValue:
assert floats == [1.0, 2.0, 3.0] assert floats == [1.0, 2.0, 3.0]
def test_json(self): def test_json(self):
wv = WireValue.json_val('{"key": "value"}') wv = WireValue.json('{"key": "value"}')
data = wv.serialize() data = wv.serialize()
assert data[0] == FieldKind.JSON assert data[0] == FieldKind.JSON
length = struct.unpack(">I", data[1:5])[0] length = struct.unpack(">I", data[1:5])[0]
@@ -95,7 +95,7 @@ class TestWireValue:
def test_array(self): def test_array(self):
inner = [WireValue.string("a"), WireValue.string("b")] inner = [WireValue.string("a"), WireValue.string("b")]
wv = WireValue.array_val(inner) wv = WireValue.array(inner)
data = wv.serialize() data = wv.serialize()
assert data[0] == FieldKind.ARRAY assert data[0] == FieldKind.ARRAY
count = struct.unpack(">I", data[1:5])[0] count = struct.unpack(">I", data[1:5])[0]
@@ -103,7 +103,7 @@ class TestWireValue:
def test_object(self): def test_object(self):
inner = {"name": WireValue.string("Bara"), "age": WireValue.int32(42)} inner = {"name": WireValue.string("Bara"), "age": WireValue.int32(42)}
wv = WireValue.object_val(inner) wv = WireValue.object(inner)
data = wv.serialize() data = wv.serialize()
assert data[0] == FieldKind.OBJECT assert data[0] == FieldKind.OBJECT
count = struct.unpack(">I", data[1:5])[0] count = struct.unpack(">I", data[1:5])[0]
+2 -1
View File
@@ -5,7 +5,7 @@ edition = "2021"
authors = ["BaraDB Team <team@baradb.dev>"] authors = ["BaraDB Team <team@baradb.dev>"]
description = "Official async Rust client for BaraDB — binary protocol client" description = "Official async Rust client for BaraDB — binary protocol client"
license = "Apache-2.0" license = "Apache-2.0"
repository = "https://github.com/katehonz/barabaDB" repository = "https://github.com/barabadb/baradadb.git"
documentation = "https://docs.baradb.dev" documentation = "https://docs.baradb.dev"
readme = "README.md" readme = "README.md"
keywords = ["database", "baradb", "multimodal", "client", "wire-protocol"] keywords = ["database", "baradb", "multimodal", "client", "wire-protocol"]
@@ -16,6 +16,7 @@ rust-version = "1.70"
tokio = { version = "1.35", features = ["full"] } tokio = { version = "1.35", features = ["full"] }
[dev-dependencies] [dev-dependencies]
tokio = { version = "1.35", features = ["full"] }
[[example]] [[example]]
name = "basic" name = "basic"
+1 -1
View File
@@ -23,7 +23,7 @@ tokio = { version = "1.35", features = ["full"] }
Or from source: Or from source:
```bash ```bash
git clone https://github.com/katehonz/barabaDB.git git clone https://github.com/barabadb/baradadb.git
cd clients/rust cd clients/rust
cargo build cargo build
``` ```
+5 -4
View File
@@ -1,11 +1,12 @@
use baradb::Client; use baradb::Client;
fn main() { #[tokio::main]
let mut client = Client::connect("localhost", 9472).unwrap(); async fn main() {
let mut client = Client::connect("localhost", 9472).await.unwrap();
println!("Connected: {}", client.is_connected()); println!("Connected: {}", client.is_connected());
match client.ping() { match client.ping().await {
Ok(v) => println!("Ping: {}", v), Ok(v) => println!("Ping: {}", v),
Err(e) => println!("Ping error: {}", e), Err(e) => println!("Ping error: {}", e),
} }
client.close(); client.close().await;
} }
+57 -42
View File
@@ -1,104 +1,118 @@
// BaraDB Rust Client — Integration Tests // BaraDB Rust Client — Integration Tests
// Requires a running BaraDB server on localhost:9472. // Requires a running BaraDB server.
// Set BARADB_HOST / BARADB_PORT env vars to override defaults.
use std::net::TcpStream; use std::net::TcpStream;
use std::time::Duration;
use baradb::{Client, QueryBuilder, WireValue}; use baradb::{Client, QueryBuilder, WireValue};
const HOST: &str = "127.0.0.1"; fn host() -> String {
const PORT: u16 = 9472; std::env::var("BARADB_HOST").unwrap_or_else(|_| "127.0.0.1".to_string())
fn server_available() -> bool {
TcpStream::connect((HOST, PORT)).is_ok()
} }
#[test] fn port() -> u16 {
fn test_connect_and_close() { std::env::var("BARADB_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(9472)
}
fn server_available() -> bool {
TcpStream::connect((host().as_str(), port())).is_ok()
}
#[tokio::test]
async fn test_connect_and_close() {
if !server_available() { if !server_available() {
return; return;
} }
let mut client = Client::connect(HOST, PORT).unwrap(); let mut client = Client::connect(&host(), port()).await.unwrap();
assert!(client.is_connected()); assert!(client.is_connected());
client.close(); client.close().await;
assert!(!client.is_connected()); assert!(!client.is_connected());
} }
#[test] #[tokio::test]
fn test_ping() { async fn test_ping() {
if !server_available() { if !server_available() {
return; return;
} }
let mut client = Client::connect(HOST, PORT).unwrap(); let mut client = Client::connect(&host(), port()).await.unwrap();
assert!(client.ping().unwrap()); assert!(client.ping().await.unwrap());
client.close(); client.close().await;
} }
#[test] #[tokio::test]
fn test_simple_select() { async fn test_simple_select() {
if !server_available() { if !server_available() {
return; return;
} }
let mut client = Client::connect(HOST, PORT).unwrap(); let mut client = Client::connect(&host(), port()).await.unwrap();
let result = client.query("SELECT 1 as one").unwrap(); let result = client.query("SELECT 1 as one").await.unwrap();
assert!(result.row_count() >= 0); assert!(result.row_count() >= 0);
client.close(); client.close().await;
} }
#[test] #[tokio::test]
fn test_parameterized_query() { async fn test_parameterized_query() {
if !server_available() { if !server_available() {
return; return;
} }
let mut client = Client::connect(HOST, PORT).unwrap(); let mut client = Client::connect(&host(), port()).await.unwrap();
let result = client let result = client
.query_params( .query_params(
"SELECT $1 as num, $2 as txt", "SELECT $1 as num, $2 as txt",
&[WireValue::Int64(42), WireValue::String("hello".to_string())], &[WireValue::Int64(42), WireValue::String("hello".to_string())],
) )
.await
.unwrap(); .unwrap();
assert!(result.row_count() >= 0); assert!(result.row_count() >= 0);
client.close(); client.close().await;
} }
#[test] #[tokio::test]
fn test_create_table_insert_select_drop() { async fn test_create_table_insert_select_drop() {
if !server_available() { if !server_available() {
return; return;
} }
let mut client = Client::connect(HOST, PORT).unwrap(); let mut client = Client::connect(&host(), port()).await.unwrap();
let _ = client.execute("DROP TABLE IF EXISTS rust_test_users"); let _ = client.execute("DROP TABLE IF EXISTS rust_test_users").await;
client client
.execute("CREATE TABLE rust_test_users (id INT PRIMARY KEY, name STRING, age INT)") .execute("CREATE TABLE rust_test_users (id INT PRIMARY KEY, name STRING, age INT)")
.await
.unwrap(); .unwrap();
let affected = client let affected = client
.execute("INSERT INTO rust_test_users (id, name, age) VALUES (1, 'Alice', 30)") .execute("INSERT INTO rust_test_users (id, name, age) VALUES (1, 'Alice', 30)")
.await
.unwrap(); .unwrap();
assert!(affected >= 0); assert!(affected >= 0);
let result = client let result = client
.query("SELECT name, age FROM rust_test_users WHERE id = 1") .query("SELECT name, age FROM rust_test_users WHERE id = 1")
.await
.unwrap(); .unwrap();
assert_eq!(result.row_count(), 1); assert_eq!(result.row_count(), 1);
client.execute("DROP TABLE rust_test_users").unwrap(); client.execute("DROP TABLE rust_test_users").await.unwrap();
client.close(); client.close().await;
} }
#[test] #[tokio::test]
fn test_query_builder_exec() { async fn test_query_builder_exec() {
if !server_available() { if !server_available() {
return; return;
} }
let mut client = Client::connect(HOST, PORT).unwrap(); let mut client = Client::connect(&host(), port()).await.unwrap();
let _ = client.execute("DROP TABLE IF EXISTS rust_test_products"); let _ = client.execute("DROP TABLE IF EXISTS rust_test_products").await;
client client
.execute("CREATE TABLE rust_test_products (id INT PRIMARY KEY, name STRING, price FLOAT)") .execute("CREATE TABLE rust_test_products (id INT PRIMARY KEY, name STRING, price FLOAT)")
.await
.unwrap(); .unwrap();
client client
.execute("INSERT INTO rust_test_products (id, name, price) VALUES (1, 'Widget', 9.99)") .execute("INSERT INTO rust_test_products (id, name, price) VALUES (1, 'Widget', 9.99)")
.await
.unwrap(); .unwrap();
let result = QueryBuilder::new(&mut client) let result = QueryBuilder::new(&mut client)
@@ -106,21 +120,22 @@ fn test_query_builder_exec() {
.from("rust_test_products") .from("rust_test_products")
.where_clause("id = 1") .where_clause("id = 1")
.exec() .exec()
.await
.unwrap(); .unwrap();
assert_eq!(result.row_count(), 1); assert_eq!(result.row_count(), 1);
client.execute("DROP TABLE rust_test_products").unwrap(); client.execute("DROP TABLE rust_test_products").await.unwrap();
client.close(); client.close().await;
} }
#[test] #[tokio::test]
fn test_auth_dummy_token() { async fn test_auth_dummy_token() {
if !server_available() { if !server_available() {
return; return;
} }
let mut client = Client::connect(HOST, PORT).unwrap(); let mut client = Client::connect(&host(), port()).await.unwrap();
let res = client.auth("dummy-token-for-testing"); let res = client.auth("dummy-token-for-testing").await;
// Dev server may accept or reject — both are fine // Dev server may accept or reject — both are fine
match res { match res {
Ok(()) => {} Ok(()) => {}
@@ -129,5 +144,5 @@ fn test_auth_dummy_token() {
assert!(msg.contains("Auth") || msg.to_lowercase().contains("error")); assert!(msg.contains("Auth") || msg.to_lowercase().contains("error"));
} }
} }
client.close(); client.close().await;
} }
+101
View File
@@ -0,0 +1,101 @@
# BaraDB — Client Integration Test Stack
#
# This compose file starts BaraDB server and runs all client test suites.
# Because test containers run in parallel, do NOT use --abort-on-container-exit
# (the first finished container would kill the others).
#
# Recommended usage:
# ./scripts/test-clients.sh
#
# Or manually run each suite:
# docker compose -f docker-compose.test.yml up -d baradb
# docker compose -f docker-compose.test.yml run --rm test-python
# docker compose -f docker-compose.test.yml run --rm test-javascript
# docker compose -f docker-compose.test.yml run --rm test-nim
# docker compose -f docker-compose.test.yml run --rm test-rust
# docker compose -f docker-compose.test.yml down
services:
baradb:
build:
context: .
dockerfile: Dockerfile
image: baradb:latest
ports:
- "9472:9472"
healthcheck:
test: ["CMD", "sh", "-c", "wget -qO- http://localhost:9912/health >/dev/null 2>&1"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
test-python:
image: python:3.12-slim
depends_on:
baradb:
condition: service_healthy
environment:
- BARADB_HOST=baradb
- BARADB_PORT=9472
volumes:
- ./clients/python:/workspace
working_dir: /workspace
command: >
sh -c "
pip install pytest pytest-asyncio -q &&
pip install -e ".[dev]" -q &&
pytest tests/test_wire_protocol.py tests/test_query_builder.py -v &&
pytest tests/test_integration.py -v
"
test-javascript:
image: node:20-slim
depends_on:
baradb:
condition: service_healthy
environment:
- BARADB_HOST=baradb
- BARADB_PORT=9472
volumes:
- ./clients/javascript:/workspace
working_dir: /workspace
command: >
sh -c "
node --test tests/unit.test.js &&
node --test tests/integration.test.js
"
test-nim:
image: nimlang/nim:2.2.10
depends_on:
baradb:
condition: service_healthy
environment:
- BARADB_HOST=baradb
- BARADB_PORT=9472
volumes:
- ./clients/nim:/workspace
- ./clients/nim/src:/workspace/src
working_dir: /workspace
command: >
sh -c "
nim c --path:src -r tests/test_client.nim &&
nim c --path:src -r tests/test_integration.nim
"
test-rust:
image: rust:1.78
depends_on:
baradb:
condition: service_healthy
environment:
- BARADB_HOST=baradb
- BARADB_PORT=9472
volumes:
- ./clients/rust:/workspace
working_dir: /workspace
command: >
sh -c "
cargo test -- --test-threads=1
"
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# BaraDB — Client Integration Test Runner
# Spins up the BaraDB server in Docker and runs all client test suites sequentially.
#
# Usage:
# ./scripts/test-clients.sh
set -euo pipefail
COMPOSE="docker compose -f docker-compose.test.yml"
echo "=== Building BaraDB server image ==="
$COMPOSE build baradb
echo "=== Starting BaraDB server ==="
$COMPOSE up -d baradb
# Wait for server healthcheck
echo "=== Waiting for server to be healthy ==="
$COMPOSE ps baradb --format json 2>/dev/null | grep -q '"Health":"healthy"' || sleep 10
# Give a little extra time for the wire protocol port to be ready
sleep 3
run_test() {
local service=$1
echo ""
echo "========================================"
echo "=== Running $service tests ==="
echo "========================================"
if $COMPOSE run --rm "$service"; then
echo "$service tests PASSED"
else
echo "$service tests FAILED"
EXIT_CODE=1
fi
}
EXIT_CODE=0
run_test test-python
run_test test-javascript
run_test test-nim
run_test test-rust
echo ""
echo "========================================"
if [ "$EXIT_CODE" -eq 0 ]; then
echo "🎉 All client tests passed!"
else
echo "⚠️ Some client tests failed."
fi
echo "========================================"
echo "=== Stopping BaraDB server ==="
$COMPOSE down -v
exit $EXIT_CODE