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
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:
@@ -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
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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]"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
"
|
||||||
Executable
+58
@@ -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
|
||||||
Reference in New Issue
Block a user