forked from ryyst/kalzu-value-store
Trying to add _ls and _tree subcalls to item paths..
This commit is contained in:
@@ -53,7 +53,7 @@ wait_for_service() {
|
||||
local port=$1
|
||||
local timeout=${2:-30}
|
||||
local count=0
|
||||
|
||||
|
||||
while [ $count -lt $timeout ]; do
|
||||
if curl -s "http://localhost:$port/health" >/dev/null 2>&1; then
|
||||
return 0
|
||||
@@ -67,7 +67,7 @@ wait_for_service() {
|
||||
# Test 1: Build verification
|
||||
test_build() {
|
||||
test_start "Binary build verification"
|
||||
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
if go build -o kvs . >/dev/null 2>&1; then
|
||||
log_success "Binary builds successfully"
|
||||
@@ -82,7 +82,7 @@ test_build() {
|
||||
# Test 2: Basic functionality
|
||||
test_basic_functionality() {
|
||||
test_start "Basic functionality test"
|
||||
|
||||
|
||||
# Create basic config
|
||||
cat > basic.yaml <<EOF
|
||||
node_id: "basic-test"
|
||||
@@ -94,20 +94,20 @@ log_level: "error"
|
||||
allow_anonymous_read: true
|
||||
allow_anonymous_write: true
|
||||
EOF
|
||||
|
||||
|
||||
# Start node
|
||||
$BINARY basic.yaml >/dev/null 2>&1 &
|
||||
local pid=$!
|
||||
|
||||
|
||||
if wait_for_service 8090; then
|
||||
# Test basic CRUD
|
||||
local put_result=$(curl -s -X PUT http://localhost:8090/kv/test/basic \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"message":"hello world"}')
|
||||
|
||||
|
||||
local get_result=$(curl -s http://localhost:8090/kv/test/basic)
|
||||
local message=$(echo "$get_result" | jq -r '.data.message' 2>/dev/null) # Adjusted jq path
|
||||
|
||||
|
||||
if [ "$message" = "hello world" ]; then
|
||||
log_success "Basic CRUD operations work"
|
||||
else
|
||||
@@ -116,15 +116,38 @@ EOF
|
||||
else
|
||||
log_error "Basic test node failed to start"
|
||||
fi
|
||||
|
||||
|
||||
kill $pid 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# Test _ls endpoint
|
||||
echo "Testing _ls endpoint..."
|
||||
curl -X PUT http://localhost:8080/kv/home/room/closet/socks -H "Content-Type: application/json" -d '{"data":"socks"}'
|
||||
curl -X PUT http://localhost:8080/kv/home/room/bed/sheets -H "Content-Type: application/json" -d '{"data":"sheets"}'
|
||||
sleep 2 # Allow indexing
|
||||
|
||||
ls_response=$(curl -s http://localhost:8080/kv/home/room/_ls)
|
||||
if echo "$ls_response" | jq -e '.children | length == 2' >/dev/null; then
|
||||
echo "✓ _ls returns correct number of children"
|
||||
else
|
||||
echo "✗ _ls failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test _tree endpoint
|
||||
tree_response=$(curl -s http://localhost:8080/kv/home/_tree?depth=2)
|
||||
if echo "$tree_response" | jq -e '.total > 0' >/dev/null; then
|
||||
echo "✓ _tree returns tree structure"
|
||||
else
|
||||
echo "✗ _tree failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 3: Cluster formation
|
||||
test_cluster_formation() {
|
||||
test_start "2-node cluster formation and Merkle Tree replication"
|
||||
|
||||
|
||||
# Node 1 config
|
||||
cat > cluster1.yaml <<EOF
|
||||
node_id: "cluster-1"
|
||||
@@ -139,7 +162,7 @@ sync_interval: 10
|
||||
allow_anonymous_read: true
|
||||
allow_anonymous_write: true
|
||||
EOF
|
||||
|
||||
|
||||
# Node 2 config
|
||||
cat > cluster2.yaml <<EOF
|
||||
node_id: "cluster-2"
|
||||
@@ -154,51 +177,51 @@ sync_interval: 10
|
||||
allow_anonymous_read: true
|
||||
allow_anonymous_write: true
|
||||
EOF
|
||||
|
||||
|
||||
# Start nodes
|
||||
$BINARY cluster1.yaml >/dev/null 2>&1 &
|
||||
local pid1=$!
|
||||
|
||||
|
||||
if ! wait_for_service 8101; then
|
||||
log_error "Cluster node 1 failed to start"
|
||||
kill $pid1 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
sleep 2 # Give node 1 a moment to fully initialize
|
||||
$BINARY cluster2.yaml >/dev/null 2>&1 &
|
||||
local pid2=$!
|
||||
|
||||
|
||||
if ! wait_for_service 8102; then
|
||||
log_error "Cluster node 2 failed to start"
|
||||
kill $pid1 $pid2 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
# Wait for cluster formation and initial Merkle sync
|
||||
sleep 15
|
||||
|
||||
|
||||
# Check if nodes see each other
|
||||
local node1_members=$(curl -s http://localhost:8101/members/ | jq length 2>/dev/null || echo 0)
|
||||
local node2_members=$(curl -s http://localhost:8102/members/ | jq length 2>/dev/null || echo 0)
|
||||
|
||||
|
||||
if [ "$node1_members" -ge 1 ] && [ "$node2_members" -ge 1 ]; then
|
||||
log_success "2-node cluster formed successfully (N1 members: $node1_members, N2 members: $node2_members)"
|
||||
|
||||
|
||||
# Test data replication
|
||||
log_info "Putting data on Node 1, waiting for Merkle sync..."
|
||||
curl -s -X PUT http://localhost:8101/kv/cluster/test \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"source":"node1", "value": 1}' >/dev/null
|
||||
|
||||
|
||||
# Wait for Merkle sync cycle to complete
|
||||
sleep 12
|
||||
|
||||
|
||||
local node2_data_full=$(curl -s http://localhost:8102/kv/cluster/test)
|
||||
local node2_data_source=$(echo "$node2_data_full" | jq -r '.data.source' 2>/dev/null)
|
||||
local node2_data_value=$(echo "$node2_data_full" | jq -r '.data.value' 2>/dev/null)
|
||||
local node1_data_full=$(curl -s http://localhost:8101/kv/cluster/test)
|
||||
|
||||
|
||||
if [ "$node2_data_source" = "node1" ] && [ "$node2_data_value" = "1" ]; then
|
||||
log_success "Data replication works correctly (Node 2 has data from Node 1)"
|
||||
|
||||
@@ -219,7 +242,7 @@ EOF
|
||||
else
|
||||
log_error "Cluster formation failed (N1 members: $node1_members, N2 members: $node2_members)"
|
||||
fi
|
||||
|
||||
|
||||
kill $pid1 $pid2 2>/dev/null || true
|
||||
sleep 2
|
||||
}
|
||||
@@ -230,15 +253,15 @@ EOF
|
||||
# but same path. The Merkle tree sync should then trigger conflict resolution.
|
||||
test_conflict_resolution() {
|
||||
test_start "Conflict resolution test (Merkle Tree based)"
|
||||
|
||||
|
||||
# Create conflicting data using our utility
|
||||
rm -rf conflict1_data conflict2_data 2>/dev/null || true
|
||||
mkdir -p conflict1_data conflict2_data
|
||||
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
if go run test_conflict.go "$TEST_DIR/conflict1_data" "$TEST_DIR/conflict2_data"; then
|
||||
cd "$TEST_DIR"
|
||||
|
||||
|
||||
# Create configs
|
||||
cat > conflict1.yaml <<EOF
|
||||
node_id: "conflict-1"
|
||||
@@ -251,7 +274,7 @@ sync_interval: 3
|
||||
allow_anonymous_read: true
|
||||
allow_anonymous_write: true
|
||||
EOF
|
||||
|
||||
|
||||
cat > conflict2.yaml <<EOF
|
||||
node_id: "conflict-2"
|
||||
bind_address: "127.0.0.1"
|
||||
@@ -263,31 +286,31 @@ sync_interval: 3
|
||||
allow_anonymous_read: true
|
||||
allow_anonymous_write: true
|
||||
EOF
|
||||
|
||||
|
||||
# Start nodes
|
||||
# Node 1 started first, making it "older" for tie-breaker if timestamps are equal
|
||||
"$BINARY" conflict1.yaml >conflict1.log 2>&1 &
|
||||
local pid1=$!
|
||||
|
||||
|
||||
if wait_for_service 8111; then
|
||||
sleep 2
|
||||
$BINARY conflict2.yaml >conflict2.log 2>&1 &
|
||||
local pid2=$!
|
||||
|
||||
|
||||
if wait_for_service 8112; then
|
||||
# Get initial data (full StoredValue)
|
||||
local node1_initial_full=$(curl -s http://localhost:8111/kv/test/conflict/data)
|
||||
local node2_initial_full=$(curl -s http://localhost:8112/kv/test/conflict/data)
|
||||
|
||||
|
||||
local node1_initial_msg=$(echo "$node1_initial_full" | jq -r '.data.message' 2>/dev/null)
|
||||
local node2_initial_msg=$(echo "$node2_initial_full" | jq -r '.data.message' 2>/dev/null)
|
||||
|
||||
|
||||
log_info "Initial conflict state: Node1='$node1_initial_msg', Node2='$node2_initial_msg'"
|
||||
|
||||
# Allow time for cluster formation and gossip protocol to stabilize
|
||||
log_info "Waiting for cluster formation and gossip stabilization..."
|
||||
sleep 20
|
||||
|
||||
|
||||
# Wait for conflict resolution with retry logic (up to 60 seconds)
|
||||
local max_attempts=20
|
||||
local attempt=1
|
||||
@@ -295,33 +318,33 @@ EOF
|
||||
local node2_final_msg=""
|
||||
local node1_final_full=""
|
||||
local node2_final_full=""
|
||||
|
||||
|
||||
log_info "Waiting for conflict resolution (checking every 3 seconds, max 60 seconds)..."
|
||||
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
sleep 3
|
||||
|
||||
|
||||
# Get current data from both nodes
|
||||
node1_final_full=$(curl -s http://localhost:8111/kv/test/conflict/data)
|
||||
node2_final_full=$(curl -s http://localhost:8112/kv/test/conflict/data)
|
||||
|
||||
|
||||
node1_final_msg=$(echo "$node1_final_full" | jq -r '.data.message' 2>/dev/null)
|
||||
node2_final_msg=$(echo "$node2_final_full" | jq -r '.data.message' 2>/dev/null)
|
||||
|
||||
|
||||
# Check if they've converged
|
||||
if [ "$node1_final_msg" = "$node2_final_msg" ] && [ -n "$node1_final_msg" ] && [ "$node1_final_msg" != "null" ]; then
|
||||
log_info "Conflict resolution achieved after $((attempt * 3)) seconds"
|
||||
break
|
||||
fi
|
||||
|
||||
|
||||
log_info "Attempt $attempt/$max_attempts: Node1='$node1_final_msg', Node2='$node2_final_msg' (not converged yet)"
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
|
||||
# Check if they converged
|
||||
if [ "$node1_final_msg" = "$node2_final_msg" ] && [ -n "$node1_final_msg" ]; then
|
||||
log_success "Conflict resolution converged to: '$node1_final_msg'"
|
||||
|
||||
|
||||
# Verify UUIDs and Timestamps are identical after resolution
|
||||
local node1_final_uuid=$(echo "$node1_final_full" | jq -r '.uuid' 2>/dev/null)
|
||||
local node1_final_timestamp=$(echo "$node1_final_full" | jq -r '.timestamp' 2>/dev/null)
|
||||
@@ -347,12 +370,12 @@ EOF
|
||||
else
|
||||
log_error "Conflict node 2 failed to start"
|
||||
fi
|
||||
|
||||
|
||||
kill $pid2 2>/dev/null || true
|
||||
else
|
||||
log_error "Conflict node 1 failed to start"
|
||||
fi
|
||||
|
||||
|
||||
kill $pid1 2>/dev/null || true
|
||||
sleep 2
|
||||
else
|
||||
@@ -364,7 +387,7 @@ EOF
|
||||
# Test 5: Authentication middleware (Issue #4)
|
||||
test_authentication_middleware() {
|
||||
test_start "Authentication middleware test (Issue #4)"
|
||||
|
||||
|
||||
# Create auth test config
|
||||
cat > auth_test.yaml <<EOF
|
||||
node_id: "auth-test"
|
||||
@@ -377,23 +400,23 @@ auth_enabled: true
|
||||
allow_anonymous_read: false
|
||||
allow_anonymous_write: false
|
||||
EOF
|
||||
|
||||
|
||||
# Start node
|
||||
$BINARY auth_test.yaml >auth_test.log 2>&1 &
|
||||
local pid=$!
|
||||
|
||||
|
||||
if wait_for_service 8095; then
|
||||
sleep 2 # Allow root account creation
|
||||
|
||||
|
||||
# Extract the token from logs
|
||||
local token=$(grep "Token:" auth_test.log | sed 's/.*Token: //' | tr -d '\n\r')
|
||||
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
log_error "Failed to extract authentication token from logs"
|
||||
kill $pid 2>/dev/null || true
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
# Test 1: Admin endpoints should fail without authentication
|
||||
local no_auth_response=$(curl -s -X POST http://localhost:8095/api/users -H "Content-Type: application/json" -d '{"nickname":"test","password":"test"}')
|
||||
if echo "$no_auth_response" | grep -q "Unauthorized"; then
|
||||
@@ -401,7 +424,7 @@ EOF
|
||||
else
|
||||
log_error "Admin endpoints should reject unauthenticated requests, got: $no_auth_response"
|
||||
fi
|
||||
|
||||
|
||||
# Test 2: Admin endpoints should work with valid authentication
|
||||
local auth_response=$(curl -s -X POST http://localhost:8095/api/users -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d '{"nickname":"authtest","password":"authtest"}')
|
||||
if echo "$auth_response" | grep -q "uuid"; then
|
||||
@@ -409,7 +432,7 @@ EOF
|
||||
else
|
||||
log_error "Admin endpoints should work with authentication, got: $auth_response"
|
||||
fi
|
||||
|
||||
|
||||
# Test 3: KV endpoints should require auth when anonymous access is disabled
|
||||
local kv_no_auth=$(curl -s -X PUT http://localhost:8095/kv/test/auth -H "Content-Type: application/json" -d '{"test":"auth"}')
|
||||
if echo "$kv_no_auth" | grep -q "Unauthorized"; then
|
||||
@@ -417,7 +440,7 @@ EOF
|
||||
else
|
||||
log_error "KV endpoints should require auth when anonymous access disabled, got: $kv_no_auth"
|
||||
fi
|
||||
|
||||
|
||||
# Test 4: KV endpoints should work with valid authentication
|
||||
local kv_auth=$(curl -s -X PUT http://localhost:8095/kv/test/auth -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d '{"test":"auth"}')
|
||||
if echo "$kv_auth" | grep -q "uuid\|timestamp" || [ -z "$kv_auth" ]; then
|
||||
@@ -425,7 +448,7 @@ EOF
|
||||
else
|
||||
log_error "KV endpoints should work with authentication, got: $kv_auth"
|
||||
fi
|
||||
|
||||
|
||||
kill $pid 2>/dev/null || true
|
||||
sleep 2
|
||||
else
|
||||
@@ -439,20 +462,20 @@ main() {
|
||||
echo "=================================================="
|
||||
echo " KVS Integration Test Suite (Merkle Tree)"
|
||||
echo "=================================================="
|
||||
|
||||
|
||||
# Setup
|
||||
log_info "Setting up test environment..."
|
||||
cleanup
|
||||
mkdir -p "$TEST_DIR"
|
||||
cd "$TEST_DIR"
|
||||
|
||||
|
||||
# Run core tests
|
||||
test_build
|
||||
test_basic_functionality
|
||||
test_cluster_formation
|
||||
test_conflict_resolution
|
||||
test_authentication_middleware
|
||||
|
||||
|
||||
# Results
|
||||
echo "=================================================="
|
||||
echo " Test Results"
|
||||
@@ -461,7 +484,7 @@ main() {
|
||||
echo -e "${GREEN}Passed: $TESTS_PASSED${NC}"
|
||||
echo -e "${RED}Failed: $TESTS_FAILED${NC}"
|
||||
echo "=================================================="
|
||||
|
||||
|
||||
if [ $TESTS_FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 All tests passed! KVS with Merkle Tree sync is working correctly.${NC}"
|
||||
cleanup
|
||||
|
Reference in New Issue
Block a user