- Move non-NIP-17 admin functions from dm_admin.c to api.c for better architecture - Add NIP-44 encryption to send_admin_response() for secure admin responses - Implement SQL query validation and execution with safety limits - Add unified SQL query handler for admin API - Fix buffer size for encrypted content to handle larger responses - Update function declarations and includes across files - Successfully test frontend query execution through web interface
448 lines
13 KiB
Bash
Executable File
448 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# SQL Query Admin API Test Script
|
|
# Tests the sql_query command functionality
|
|
|
|
set -e
|
|
|
|
# Configuration
|
|
RELAY_URL="ws://localhost:8888"
|
|
ADMIN_PRIVKEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
ADMIN_PUBKEY="6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"
|
|
RELAY_PUBKEY="4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Test counters
|
|
TOTAL_TESTS=0
|
|
PASSED_TESTS=0
|
|
FAILED_TESTS=0
|
|
|
|
# Helper functions
|
|
print_test() {
|
|
echo -e "${YELLOW}TEST: $1${NC}"
|
|
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
|
}
|
|
|
|
print_pass() {
|
|
echo -e "${GREEN}✓ PASS: $1${NC}"
|
|
PASSED_TESTS=$((PASSED_TESTS + 1))
|
|
}
|
|
|
|
print_fail() {
|
|
echo -e "${RED}✗ FAIL: $1${NC}"
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
}
|
|
|
|
# Check if nak is installed
|
|
check_nak() {
|
|
if ! command -v nak &> /dev/null; then
|
|
echo -e "${RED}ERROR: nak command not found. Please install nak first.${NC}"
|
|
echo -e "${RED}Visit: https://github.com/fiatjaf/nak${NC}"
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}✓ nak is available${NC}"
|
|
}
|
|
|
|
# Send SQL query command via WebSocket using nak
|
|
send_sql_query() {
|
|
local query="$1"
|
|
local description="$2"
|
|
|
|
echo -n "Testing $description... "
|
|
|
|
# Create the admin command
|
|
COMMAND="[\"sql_query\", \"$query\"]"
|
|
|
|
# Encrypt the command using NIP-44
|
|
ENCRYPTED_COMMAND=$(nak encrypt "$COMMAND" \
|
|
--sec "$ADMIN_PRIVKEY" \
|
|
--recipient-pubkey "$RELAY_PUBKEY" 2>/dev/null)
|
|
|
|
if [ -z "$ENCRYPTED_COMMAND" ]; then
|
|
echo -e "${RED}FAILED${NC} - Failed to encrypt admin command"
|
|
return 1
|
|
fi
|
|
|
|
# Create admin event
|
|
ADMIN_EVENT=$(nak event \
|
|
--kind 23456 \
|
|
--content "$ENCRYPTED_COMMAND" \
|
|
--sec "$ADMIN_PRIVKEY" \
|
|
--tag "p=$RELAY_PUBKEY" 2>/dev/null)
|
|
|
|
if [ -z "$ADMIN_EVENT" ]; then
|
|
echo -e "${RED}FAILED${NC} - Failed to create admin event"
|
|
return 1
|
|
fi
|
|
|
|
echo "=== SENT EVENT ==="
|
|
echo "$ADMIN_EVENT"
|
|
echo "==================="
|
|
|
|
# Send SQL query event via WebSocket
|
|
local response
|
|
response=$(echo "$ADMIN_EVENT" | timeout 10 websocat -B 1048576 "$RELAY_URL" 2>/dev/null | head -3 || echo 'TIMEOUT')
|
|
|
|
echo "=== RECEIVED RESPONSE ==="
|
|
echo "$response"
|
|
echo "=========================="
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
echo -e "${RED}FAILED${NC} - Connection timeout"
|
|
return 1
|
|
fi
|
|
|
|
echo "$response" # Return the response for further processing
|
|
}
|
|
|
|
# Test functions
|
|
test_valid_select() {
|
|
print_test "Valid SELECT query"
|
|
local response=$(send_sql_query "SELECT * FROM events LIMIT 1" "valid SELECT query")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"query_type":"sql_query"' && echo "$response" | grep -q '"row_count"'; then
|
|
print_pass "Valid SELECT accepted and executed"
|
|
else
|
|
print_fail "Valid SELECT failed: $response"
|
|
fi
|
|
}
|
|
|
|
test_select_count() {
|
|
print_test "SELECT COUNT(*) query"
|
|
local response=$(send_sql_query "SELECT COUNT(*) FROM events" "COUNT query")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"query_type":"sql_query"' && echo "$response" | grep -q '"row_count"'; then
|
|
print_pass "COUNT query executed successfully"
|
|
else
|
|
print_fail "COUNT query failed: $response"
|
|
fi
|
|
}
|
|
|
|
test_blocked_insert() {
|
|
print_test "INSERT statement blocked"
|
|
local response=$(send_sql_query "INSERT INTO events VALUES ('id', 'pubkey', 1234567890, 1, 'content', 'sig')" "INSERT blocking")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"' && echo "$response" | grep -q '"error_type":"blocked_statement"'; then
|
|
print_pass "INSERT correctly blocked"
|
|
else
|
|
print_fail "INSERT not blocked: $response"
|
|
fi
|
|
}
|
|
|
|
test_blocked_update() {
|
|
print_test "UPDATE statement blocked"
|
|
local response=$(send_sql_query "UPDATE events SET content = 'test' WHERE id = 'abc123'" "UPDATE blocking")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"' && echo "$response" | grep -q '"error_type":"blocked_statement"'; then
|
|
print_pass "UPDATE correctly blocked"
|
|
else
|
|
print_fail "UPDATE not blocked: $response"
|
|
fi
|
|
}
|
|
|
|
test_blocked_delete() {
|
|
print_test "DELETE statement blocked"
|
|
local response=$(send_sql_query "DELETE FROM events WHERE id = 'abc123'" "DELETE blocking")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"' && echo "$response" | grep -q '"error_type":"blocked_statement"'; then
|
|
print_pass "DELETE correctly blocked"
|
|
else
|
|
print_fail "DELETE not blocked: $response"
|
|
fi
|
|
}
|
|
|
|
test_blocked_drop() {
|
|
print_test "DROP statement blocked"
|
|
local response=$(send_sql_query "DROP TABLE events" "DROP blocking")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"' && echo "$response" | grep -q '"error_type":"blocked_statement"'; then
|
|
print_pass "DROP correctly blocked"
|
|
else
|
|
print_fail "DROP not blocked: $response"
|
|
fi
|
|
}
|
|
|
|
test_blocked_create() {
|
|
print_test "CREATE statement blocked"
|
|
local response=$(send_sql_query "CREATE TABLE test (id TEXT)" "CREATE blocking")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"' && echo "$response" | grep -q '"error_type":"blocked_statement"'; then
|
|
print_pass "CREATE correctly blocked"
|
|
else
|
|
print_fail "CREATE not blocked: $response"
|
|
fi
|
|
}
|
|
|
|
test_blocked_alter() {
|
|
print_test "ALTER statement blocked"
|
|
local response=$(send_sql_query "ALTER TABLE events ADD COLUMN test TEXT" "ALTER blocking")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"' && echo "$response" | grep -q '"error_type":"blocked_statement"'; then
|
|
print_pass "ALTER correctly blocked"
|
|
else
|
|
print_fail "ALTER not blocked: $response"
|
|
fi
|
|
}
|
|
|
|
test_blocked_pragma() {
|
|
print_test "PRAGMA statement blocked"
|
|
local response=$(send_sql_query "PRAGMA table_info(events)" "PRAGMA blocking")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"' && echo "$response" | grep -q '"error_type":"blocked_statement"'; then
|
|
print_pass "PRAGMA correctly blocked"
|
|
else
|
|
print_fail "PRAGMA not blocked: $response"
|
|
fi
|
|
}
|
|
|
|
test_select_with_where() {
|
|
print_test "SELECT with WHERE clause"
|
|
local response=$(send_sql_query "SELECT id, kind FROM events WHERE kind = 1 LIMIT 5" "WHERE clause query")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"query_type":"sql_query"'; then
|
|
print_pass "WHERE clause query executed"
|
|
else
|
|
print_fail "WHERE clause query failed: $response"
|
|
fi
|
|
}
|
|
|
|
test_select_with_join() {
|
|
print_test "SELECT with JOIN"
|
|
local response=$(send_sql_query "SELECT e.id, e.kind, s.events_sent FROM events e LEFT JOIN active_subscriptions_log s ON e.id = s.subscription_id LIMIT 3" "JOIN query")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"query_type":"sql_query"'; then
|
|
print_pass "JOIN query executed"
|
|
else
|
|
print_fail "JOIN query failed: $response"
|
|
fi
|
|
}
|
|
|
|
test_select_views() {
|
|
print_test "SELECT from views"
|
|
local response=$(send_sql_query "SELECT * FROM event_kinds_view LIMIT 5" "view query")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"query_type":"sql_query"'; then
|
|
print_pass "View query executed"
|
|
else
|
|
print_fail "View query failed: $response"
|
|
fi
|
|
}
|
|
|
|
test_nonexistent_table() {
|
|
print_test "Query nonexistent table"
|
|
local response=$(send_sql_query "SELECT * FROM nonexistent_table" "nonexistent table")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"'; then
|
|
print_pass "Nonexistent table error handled correctly"
|
|
else
|
|
print_fail "Nonexistent table error not handled: $response"
|
|
fi
|
|
}
|
|
|
|
test_invalid_syntax() {
|
|
print_test "Invalid SQL syntax"
|
|
local response=$(send_sql_query "SELECT * FROM events WHERE" "invalid syntax")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"status":"error"'; then
|
|
print_pass "Invalid syntax error handled"
|
|
else
|
|
print_fail "Invalid syntax not handled: $response"
|
|
fi
|
|
}
|
|
|
|
test_request_id_correlation() {
|
|
print_test "Request ID correlation"
|
|
local response=$(send_sql_query "SELECT * FROM events LIMIT 1" "request ID correlation")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"request_id"'; then
|
|
print_pass "Request ID included in response"
|
|
else
|
|
print_fail "Request ID missing from response: $response"
|
|
fi
|
|
}
|
|
|
|
test_response_format() {
|
|
print_test "Response format validation"
|
|
local response=$(send_sql_query "SELECT * FROM events LIMIT 1" "response format")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"query_type":"sql_query"' &&
|
|
echo "$response" | grep -q '"timestamp"' &&
|
|
echo "$response" | grep -q '"execution_time_ms"' &&
|
|
echo "$response" | grep -q '"row_count"' &&
|
|
echo "$response" | grep -q '"columns"' &&
|
|
echo "$response" | grep -q '"rows"'; then
|
|
print_pass "Response format is valid"
|
|
else
|
|
print_fail "Response format invalid: $response"
|
|
fi
|
|
}
|
|
|
|
test_empty_result() {
|
|
print_test "Empty result set"
|
|
local response=$(send_sql_query "SELECT * FROM events WHERE kind = 99999" "empty result")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
FAILED_TESTS=$((FAILED_TESTS + 1))
|
|
return 1
|
|
fi
|
|
|
|
if echo "$response" | grep -q '"query_type":"sql_query"'; then
|
|
print_pass "Empty result handled correctly"
|
|
else
|
|
print_fail "Empty result not handled: $response"
|
|
fi
|
|
}
|
|
|
|
echo "=========================================="
|
|
echo "C-Relay SQL Query Admin API Testing Suite"
|
|
echo "=========================================="
|
|
echo "Testing SQL query functionality at $RELAY_URL"
|
|
echo ""
|
|
|
|
# Check prerequisites
|
|
check_nak
|
|
|
|
# Test basic connectivity first
|
|
echo "=== Basic Connectivity Test ==="
|
|
print_test "Basic connectivity"
|
|
response=$(send_sql_query "SELECT 1" "basic connectivity")
|
|
|
|
if [[ "$response" == *"TIMEOUT"* ]]; then
|
|
echo -e "${RED}FAILED${NC} - Cannot connect to relay at $RELAY_URL"
|
|
echo "Make sure the relay is running and accessible."
|
|
exit 1
|
|
else
|
|
print_pass "Relay connection established"
|
|
fi
|
|
echo ""
|
|
|
|
# Run test suites
|
|
echo "=== Query Validation Tests ==="
|
|
test_valid_select
|
|
test_select_count
|
|
test_blocked_insert
|
|
test_blocked_update
|
|
test_blocked_delete
|
|
test_blocked_drop
|
|
test_blocked_create
|
|
test_blocked_alter
|
|
test_blocked_pragma
|
|
echo ""
|
|
|
|
echo "=== Query Execution Tests ==="
|
|
test_select_with_where
|
|
test_select_with_join
|
|
test_select_views
|
|
test_empty_result
|
|
echo ""
|
|
|
|
echo "=== Error Handling Tests ==="
|
|
test_nonexistent_table
|
|
test_invalid_syntax
|
|
echo ""
|
|
|
|
echo "=== Response Format Tests ==="
|
|
test_request_id_correlation
|
|
test_response_format
|
|
echo ""
|
|
|
|
echo "=== Test Results ==="
|
|
echo "Total tests: $TOTAL_TESTS"
|
|
echo -e "Passed: ${GREEN}$PASSED_TESTS${NC}"
|
|
echo -e "Failed: ${RED}$FAILED_TESTS${NC}"
|
|
|
|
if [[ $FAILED_TESTS -eq 0 ]]; then
|
|
echo -e "${GREEN}✓ All SQL query tests passed!${NC}"
|
|
echo "SQL query admin API is working correctly."
|
|
exit 0
|
|
else
|
|
echo -e "${RED}✗ Some SQL query tests failed!${NC}"
|
|
echo "SQL query admin API may have issues."
|
|
exit 1
|
|
fi |