Skip to content

Commit 1e45e86

Browse files
committed
Add comprehensive unit tests, integration tests, and Docker environment
- Add 161 unit tests across all modules (types, info, utils, client, http, sirix, database, resource) using mockito for behavioral HTTP testing - Add 20 integration tests in tests/sirix.rs exercising the full API against a running SirixDB server (database CRUD, resource CRUD, read, update, history, diff, query, metadata, etag, delete_all) - Modernize Docker environment to match sirix-python-client: - Upgrade Keycloak from 7.0.1 to 25.0.1 with custom Dockerfile - Add Docker bridge network, healthchecks, and service dependencies - Add wait-for-keycloak.sh entrypoint and kcadm.sh user setup - Add logback-test.xml for SirixDB logging - Update CI workflow with separate unit test and integration test steps - Add test.sh for local Docker-based test orchestration - Make InfoResultWithResources fields public for integration test access https://claude.ai/code/session_01LgXiks4zqYLRVbkgmCNoGc
1 parent 4520f23 commit 1e45e86

24 files changed

Lines changed: 3846 additions & 46 deletions

.github/workflows/rust.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@ name: Rust
22

33
on: [push]
44

5+
env:
6+
GITHUB_ACTIONS: "true"
7+
58
jobs:
69
build:
710

811
runs-on: ubuntu-latest
912

1013
steps:
11-
- uses: actions/checkout@v2
12-
- name: setup SirixDB server
13-
run: docker-compose -f ./tests/resources/docker-compose.yml up -d keycloak
14+
- uses: actions/checkout@v4
1415
- name: Build crate
1516
run: cargo build --all-features --verbose
16-
- name: Run tests
17-
run: bash ./prepare-test.sh && cargo test --all-features --verbose
17+
- name: Run unit tests
18+
run: cargo test --all-features --verbose --lib
19+
- name: Setup SirixDB environment
20+
run: bash ./prepare-test.sh
21+
- name: Run all tests (unit + integration)
22+
run: cargo test --all-features --verbose
23+
- name: Teardown
24+
if: always()
25+
run: docker-compose -f ./tests/resources/docker-compose.yml down -v --remove-orphans 2>/dev/null || true

prepare-test.sh

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,43 @@
1-
echo "starting the docker environment"
2-
bash ./tests/resources/wait.sh
3-
docker-compose -f ./tests/resources/docker-compose.yml up -d server
4-
sleep 5
1+
#!/bin/bash
2+
set -e
3+
4+
COMPOSE_FILE="./tests/resources/docker-compose.yml"
5+
6+
echo "Starting Docker environment..."
7+
docker-compose -f "$COMPOSE_FILE" up -d --build
8+
9+
echo "Waiting for Keycloak to become healthy..."
10+
timeout=300
11+
elapsed=0
12+
while [ $elapsed -lt $timeout ]; do
13+
if docker-compose -f "$COMPOSE_FILE" ps keycloak | grep -q "healthy"; then
14+
echo "Keycloak is healthy."
15+
break
16+
fi
17+
sleep 5
18+
elapsed=$((elapsed + 5))
19+
done
20+
21+
if [ $elapsed -ge $timeout ]; then
22+
echo "ERROR: Keycloak did not become healthy within ${timeout}s"
23+
docker-compose -f "$COMPOSE_FILE" logs keycloak
24+
exit 1
25+
fi
26+
27+
echo "Waiting for SirixDB to be ready..."
28+
timeout=120
29+
elapsed=0
30+
while [ $elapsed -lt $timeout ]; do
31+
if curl -sf http://localhost:9443 > /dev/null 2>&1; then
32+
echo "SirixDB is ready!"
33+
break
34+
fi
35+
sleep 5
36+
elapsed=$((elapsed + 5))
37+
done
38+
39+
if [ $elapsed -ge $timeout ]; then
40+
echo "ERROR: SirixDB did not become ready within ${timeout}s"
41+
docker-compose -f "$COMPOSE_FILE" logs server
42+
exit 1
43+
fi

src/asynchronous/client.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,51 @@ pub async fn request_impl<T: DeserializeOwned>(
9595
}
9696
}
9797

98+
pub async fn request_impl_string(
99+
channel: Sender<Message>,
100+
scheme: Scheme,
101+
authority: Authority,
102+
path_and_query: PathAndQuery,
103+
method: Method,
104+
headers: HeaderMap,
105+
body: Body,
106+
) -> SirixResult<SirixResponse<String>> {
107+
let uri = Uri::builder()
108+
.scheme(scheme)
109+
.authority(authority)
110+
.path_and_query(path_and_query)
111+
.build()
112+
.unwrap();
113+
// create request
114+
let mut request_builder = Request::builder().uri(uri).method(method);
115+
for header in headers {
116+
request_builder = request_builder.header(header.0.unwrap(), header.1);
117+
}
118+
let request = request_builder.body(body).unwrap();
119+
// create response channel
120+
let (tx, rx) = oneshot::channel::<ResultResponse>();
121+
// Perform request
122+
let _ = channel
123+
.send(Message {
124+
request: request,
125+
responder: tx,
126+
})
127+
.await;
128+
let response = rx.await.unwrap().unwrap();
129+
let status = response.status().clone();
130+
let headers = response.headers().clone();
131+
// Aggregate body
132+
let body = body::aggregate(response).await?;
133+
let mut buf: Vec<u8> = vec![];
134+
std::io::Read::read_to_end(&mut body.reader(), &mut buf).unwrap();
135+
136+
Ok(SirixResponse {
137+
headers: headers.to_owned(),
138+
status: status,
139+
body: String::from_utf8_lossy(&buf).into_owned(),
140+
})
141+
}
142+
98143
pub async fn request_impl_fire_no_response(
99144
channel: Sender<Message>,
100145
scheme: Scheme,

src/asynchronous/database.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
use super::super::info::TokenData;
44
use super::super::types::{DbInfo, DbType, Json, Xml};
55
use super::client::{Message, SirixResponse};
6-
use super::http::{create_database, delete_database, get_database_info};
6+
use super::http::{create_database, delete_database, get_database_info, get_database_info_string};
77
use super::resource::Resource;
88
use super::SirixResult;
99
use hyper::http::uri::{Authority, Scheme};
10+
use serde::de::DeserializeOwned;
1011
use tokio::sync::mpsc::Sender;
1112
use tokio::sync::watch::Receiver;
1213

@@ -30,6 +31,10 @@ pub struct Database<T> {
3031

3132
impl<T> Database<T> {
3233
pub async fn info(&self) -> SirixResult<SirixResponse<DbInfo>> {
34+
self.info_raw().await
35+
}
36+
37+
pub async fn info_raw<U: DeserializeOwned>(&self) -> SirixResult<SirixResponse<U>> {
3338
match self.auth_channel.clone() {
3439
Some(watcher) => {
3540
let token_data = watcher.borrow().as_ref().unwrap().clone();
@@ -56,6 +61,33 @@ impl<T> Database<T> {
5661
}
5762
}
5863

64+
pub async fn info_string(&self) -> SirixResult<SirixResponse<String>> {
65+
match self.auth_channel.clone() {
66+
Some(watcher) => {
67+
let token_data = watcher.borrow().as_ref().unwrap().clone();
68+
let token = token_data.token_type + " " + &token_data.access_token;
69+
get_database_info_string(
70+
self.scheme.clone(),
71+
self.authority.clone(),
72+
&self.db_name,
73+
Some(&token),
74+
self.channel.clone(),
75+
)
76+
.await
77+
}
78+
None => {
79+
get_database_info_string(
80+
self.scheme.clone(),
81+
self.authority.clone(),
82+
&self.db_name,
83+
None,
84+
self.channel.clone(),
85+
)
86+
.await
87+
}
88+
}
89+
}
90+
5991
pub async fn delete(&self) -> SirixResult<SirixResponse<()>> {
6092
match self.auth_channel.clone() {
6193
Some(watcher) => {

0 commit comments

Comments
 (0)