feat(clients): professional clients with tests, CI/CD, and examples

- Python: pyproject.toml, pytest suite (unit + integration), README, examples
- JavaScript: package.json, TypeScript definitions, node:test suite, README, examples
- Nim: integration tests, examples, README, improved nimble tasks
- Rust: fixed nodelay/localhost IPv6 bug, integration tests, examples, README
- Added GitHub Actions CI for all 4 clients against Docker server
- Added docker-compose.test.yml for local integration testing
- Added .gitignore for each client
This commit is contained in:
2026-05-08 00:18:34 +03:00
parent e9d3c7e9de
commit ae2e07e626
31 changed files with 2651 additions and 12 deletions
+152
View File
@@ -0,0 +1,152 @@
"""
Integration tests for BaraDB Python client.
Requires a running BaraDB server on localhost:9472.
These tests are skipped automatically if the server is unreachable.
"""
import socket
import pytest
from baradb import Client, WireValue, QueryBuilder
BARADB_HOST = "localhost"
BARADB_PORT = 9472
def _server_available() -> bool:
"""Check if BaraDB server is listening."""
try:
with socket.create_connection((BARADB_HOST, BARADB_PORT), timeout=2):
return True
except (socket.timeout, ConnectionRefusedError, OSError):
return False
pytestmark = pytest.mark.skipif(
not _server_available(),
reason=f"BaraDB server not available at {BARADB_HOST}:{BARADB_PORT}",
)
@pytest.fixture
def client():
c = Client(BARADB_HOST, BARADB_PORT)
c.connect()
yield c
c.close()
class TestConnection:
def test_connect_and_close(self):
c = Client(BARADB_HOST, BARADB_PORT)
assert not c.is_connected()
c.connect()
assert c.is_connected()
c.close()
assert not c.is_connected()
def test_context_manager(self):
with Client(BARADB_HOST, BARADB_PORT) as c:
assert c.is_connected()
assert not c.is_connected()
class TestPing:
def test_ping(self, client):
assert client.ping() is True
class TestQuery:
def test_simple_select(self, client):
result = client.query("SELECT 1 as one")
assert result.row_count >= 0 # server may return rows or empty
def test_query_with_params(self, client):
result = client.query_params(
"SELECT $1 as num, $2 as txt",
[WireValue.int64(42), WireValue.string("hello")],
)
assert result is not None
class TestExecute:
def test_create_table_and_insert(self, client):
# Clean up first (best-effort)
try:
client.execute("DROP TABLE IF EXISTS test_users")
except Exception:
pass
client.execute(
"CREATE TABLE test_users (id INT PRIMARY KEY, name STRING, age INT)"
)
affected = client.execute(
"INSERT INTO test_users (id, name, age) VALUES (1, 'Alice', 30)"
)
assert affected >= 0
result = client.query("SELECT name, age FROM test_users WHERE id = 1")
assert result.row_count == 1
row = result.rows[0]
# Server returns all columns; map by name via dict iteration
row_dict = dict(zip(result.columns, row))
assert row_dict["name"] == "Alice"
assert row_dict["age"] == 30
client.execute("DROP TABLE test_users")
class TestQueryBuilder:
def test_builder_exec(self, client):
try:
client.execute("DROP TABLE IF EXISTS test_products")
except Exception:
pass
client.execute(
"CREATE TABLE test_products (id INT PRIMARY KEY, name STRING, price FLOAT)"
)
client.execute(
"INSERT INTO test_products (id, name, price) VALUES (1, 'Widget', 9.99)"
)
qb = (
QueryBuilder(client)
.select("name", "price")
.from_("test_products")
.where("id = 1")
)
result = qb.exec()
assert result.row_count == 1
client.execute("DROP TABLE test_products")
class TestAuth:
def test_auth_with_dummy_token(self, client):
# 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.
try:
client.auth("dummy-token-for-testing")
except Exception as exc:
# Auth may fail with invalid token — that's acceptable for this test
assert "Auth" in str(exc) or "error" in str(exc).lower()
class TestTransactions:
def test_transaction_begin_commit(self, client):
try:
client.execute("DROP TABLE IF EXISTS test_txn")
except Exception:
pass
client.execute("CREATE TABLE test_txn (id INT PRIMARY KEY)")
client.execute("BEGIN")
client.execute("INSERT INTO test_txn (id) VALUES (1)")
client.execute("COMMIT")
result = client.query("SELECT COUNT(*) FROM test_txn")
assert result.row_count >= 0
client.execute("DROP TABLE test_txn")
+109
View File
@@ -0,0 +1,109 @@
"""
Unit tests for QueryBuilder — no running server required.
"""
import pytest
from baradb import Client, QueryBuilder
class TestQueryBuilder:
def test_simple_select(self):
client = Client()
qb = QueryBuilder(client)
sql = qb.select("name", "age").from_("users").build()
assert sql == "SELECT name, age FROM users"
def test_select_all(self):
client = Client()
qb = QueryBuilder(client)
sql = qb.from_("users").build()
assert sql == "SELECT * FROM users"
def test_where_single(self):
client = Client()
qb = QueryBuilder(client)
sql = qb.select("name").from_("users").where("age > 18").build()
assert sql == "SELECT name FROM users WHERE age > 18"
def test_where_multiple(self):
client = Client()
qb = QueryBuilder(client)
sql = (
qb.select("name")
.from_("users")
.where("age > 18")
.where("active = true")
.build()
)
assert sql == "SELECT name FROM users WHERE age > 18 AND active = true"
def test_join(self):
client = Client()
qb = QueryBuilder(client)
sql = (
qb.select("u.name", "o.total")
.from_("users u")
.join("orders o", "u.id = o.user_id")
.build()
)
assert "JOIN orders o ON u.id = o.user_id" in sql
def test_left_join(self):
client = Client()
qb = QueryBuilder(client)
sql = (
qb.select("u.name", "o.total")
.from_("users u")
.left_join("orders o", "u.id = o.user_id")
.build()
)
assert "LEFT JOIN orders o ON u.id = o.user_id" in sql
def test_group_by_having(self):
client = Client()
qb = QueryBuilder(client)
sql = (
qb.select("dept", "count(*)")
.from_("employees")
.group_by("dept")
.having("count(*) > 5")
.build()
)
assert "GROUP BY dept" in sql
assert "HAVING count(*) > 5" in sql
def test_order_by(self):
client = Client()
qb = QueryBuilder(client)
sql = qb.select("name").from_("users").order_by("name", "DESC").build()
assert "ORDER BY name DESC" in sql
def test_limit_offset(self):
client = Client()
qb = QueryBuilder(client)
sql = qb.select("name").from_("users").limit(10).offset(5).build()
assert "LIMIT 10" in sql
assert "OFFSET 5" in sql
def test_full_complex_query(self):
client = Client()
qb = QueryBuilder(client)
sql = (
qb.select("u.name", "count(*) as cnt")
.from_("users u")
.left_join("orders o", "u.id = o.user_id")
.where("u.age > 18")
.group_by("u.name")
.having("cnt > 3")
.order_by("cnt", "DESC")
.limit(50)
.build()
)
assert sql.startswith("SELECT")
assert "FROM users u" in sql
assert "LEFT JOIN orders o ON u.id = o.user_id" in sql
assert "WHERE u.age > 18" in sql
assert "GROUP BY u.name" in sql
assert "HAVING cnt > 3" in sql
assert "ORDER BY cnt DESC" in sql
assert "LIMIT 50" in sql
+142
View File
@@ -0,0 +1,142 @@
"""
Unit tests for BaraDB Python client wire protocol.
No running server required.
"""
import struct
import pytest
from baradb import WireValue, FieldKind, MsgKind, ResultFormat
class TestWireValue:
"""Tests for WireValue serialization / deserialization."""
def test_null(self):
wv = WireValue.null()
assert wv.kind == FieldKind.NULL
data = wv.serialize()
assert data == b"\x00"
def test_bool_true(self):
wv = WireValue.bool_val(True)
assert wv.serialize() == b"\x01\x01"
def test_bool_false(self):
wv = WireValue.bool_val(False)
assert wv.serialize() == b"\x01\x00"
def test_int8(self):
wv = WireValue.int8(-42)
data = wv.serialize()
assert data[0] == FieldKind.INT8
assert struct.unpack(">b", data[1:])[0] == -42
def test_int16(self):
wv = WireValue.int16(-1000)
data = wv.serialize()
assert data[0] == FieldKind.INT16
assert struct.unpack(">h", data[1:])[0] == -1000
def test_int32(self):
wv = WireValue.int32(123456)
data = wv.serialize()
assert data[0] == FieldKind.INT32
assert struct.unpack(">i", data[1:])[0] == 123456
def test_int64(self):
wv = WireValue.int64(9999999999)
data = wv.serialize()
assert data[0] == FieldKind.INT64
assert struct.unpack(">q", data[1:])[0] == 9999999999
def test_float32(self):
wv = WireValue.float32(3.14)
data = wv.serialize()
assert data[0] == FieldKind.FLOAT32
assert abs(struct.unpack(">f", data[1:])[0] - 3.14) < 0.01
def test_float64(self):
wv = WireValue.float64(2.718281828)
data = wv.serialize()
assert data[0] == FieldKind.FLOAT64
assert abs(struct.unpack(">d", data[1:])[0] - 2.718281828) < 1e-9
def test_string(self):
wv = WireValue.string("hello")
data = wv.serialize()
assert data[0] == FieldKind.STRING
length = struct.unpack(">I", data[1:5])[0]
assert length == 5
assert data[5:] == b"hello"
def test_bytes(self):
wv = WireValue.bytes_val(b"\xde\xad\xbe\xef")
data = wv.serialize()
assert data[0] == FieldKind.BYTES
length = struct.unpack(">I", data[1:5])[0]
assert length == 4
assert data[5:] == b"\xde\xad\xbe\xef"
def test_vector(self):
wv = WireValue.vector([1.0, 2.0, 3.0])
data = wv.serialize()
assert data[0] == FieldKind.VECTOR
dim = struct.unpack(">I", data[1:5])[0]
assert dim == 3
floats = [struct.unpack(">f", data[5 + i * 4 : 9 + i * 4])[0] for i in range(3)]
assert floats == [1.0, 2.0, 3.0]
def test_json(self):
wv = WireValue.json_val('{"key": "value"}')
data = wv.serialize()
assert data[0] == FieldKind.JSON
length = struct.unpack(">I", data[1:5])[0]
assert length == 16
def test_array(self):
inner = [WireValue.string("a"), WireValue.string("b")]
wv = WireValue.array_val(inner)
data = wv.serialize()
assert data[0] == FieldKind.ARRAY
count = struct.unpack(">I", data[1:5])[0]
assert count == 2
def test_object(self):
inner = {"name": WireValue.string("Bara"), "age": WireValue.int32(42)}
wv = WireValue.object_val(inner)
data = wv.serialize()
assert data[0] == FieldKind.OBJECT
count = struct.unpack(">I", data[1:5])[0]
assert count == 2
class TestMessageKinds:
"""Sanity checks for protocol constants."""
def test_client_kinds(self):
assert MsgKind.CLIENT_HANDSHAKE == 0x01
assert MsgKind.QUERY == 0x02
assert MsgKind.QUERY_PARAMS == 0x03
assert MsgKind.EXECUTE == 0x04
assert MsgKind.BATCH == 0x05
assert MsgKind.TRANSACTION == 0x06
assert MsgKind.CLOSE == 0x07
assert MsgKind.PING == 0x08
assert MsgKind.AUTH == 0x09
def test_server_kinds(self):
assert MsgKind.SERVER_HANDSHAKE == 0x80
assert MsgKind.READY == 0x81
assert MsgKind.DATA == 0x82
assert MsgKind.COMPLETE == 0x83
assert MsgKind.ERROR == 0x84
assert MsgKind.AUTH_CHALLENGE == 0x85
assert MsgKind.AUTH_OK == 0x86
assert MsgKind.SCHEMA_CHANGE == 0x87
assert MsgKind.PONG == 0x88
assert MsgKind.TRANSACTION_STATE == 0x89
def test_result_formats(self):
assert ResultFormat.BINARY == 0x00
assert ResultFormat.JSON == 0x01
assert ResultFormat.TEXT == 0x02