| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- #!/usr/bin/env bash
- set -euo pipefail
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- cd "$SCRIPT_DIR"
- if [[ -f .env ]]; then
- set -a
- # shellcheck source=/dev/null
- source .env
- set +a
- fi
- PORT=8501
- BASE_URL="http://127.0.0.1:$PORT"
- TEST_GRAPH="${EXAMPLE_GRAPH:-http://example.org/catalog#test}"
- DERIVED_PROPERTY="http://example.org/relations#derivedFrom"
- SOURCE_BATCH_PROPERTY="http://example.org/relations#sourceBatch"
- ORIGIN_BATCH_PROPERTY="http://example.org/relations#originBatch"
- CATEGORY_TYPE="http://example.org/catalog#Category"
- ROOT_ITEM="http://example.org/catalog#Item_Prototype"
- PATH_SUBJECT="http://example.org/catalog#Item_Prime"
- SAMPLE_FIXTURE="catalog_fixture.ttl"
- : "${MCP_ALLOW_EXAMPLE_LOAD:=true}"
- if ! command -v jq >/dev/null 2>&1; then
- echo "ERROR: jq is required for test output parsing."
- exit 1
- fi
- pass_count=0
- fail_count=0
- section() {
- echo
- echo "============================================================"
- echo "$1"
- echo "============================================================"
- }
- pass() {
- pass_count=$((pass_count + 1))
- echo "✅ PASS: $1"
- }
- fail() {
- fail_count=$((fail_count + 1))
- echo "❌ FAIL: $1"
- echo " detail: $2"
- }
- call_mcp() {
- local payload="$1"
- curl -sS -X POST "$BASE_URL/mcp" \
- -H "Content-Type: application/json" \
- -d "$payload"
- }
- assert_tool_ok() {
- local label="$1"
- local payload="$2"
- local response
- response="$(call_mcp "$payload")" || {
- fail "$label" "HTTP request failed"
- return 1
- }
- if ! echo "$response" | jq -e . >/dev/null 2>&1; then
- fail "$label" "Non-JSON response: $response"
- return 1
- fi
- local status
- status="$(echo "$response" | jq -r '.status // empty')"
- if [[ "$status" != "ok" ]]; then
- local detail
- detail="$(echo "$response" | jq -r '.detail // .error // "unknown error"')"
- fail "$label" "$detail"
- return 1
- fi
- pass "$label"
- TOOL_LAST_RESPONSE="$response"
- return 0
- }
- section "Load sample dataset"
- if [[ "${MCP_ALLOW_EXAMPLE_LOAD:-false}" == "true" ]]; then
- payload=$(jq -n --arg file "$SAMPLE_FIXTURE" --arg graph "$TEST_GRAPH" '{"tool":"load_examples","input":{"files":[$file],"graph":$graph}}')
- TOOL_LAST_RESPONSE=""
- if assert_tool_ok "load_examples" "$payload"; then
- echo "Loaded fixtures:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.loaded[] | " - " + (.file + " → " + .graph)' || true
- fi
- else
- echo "MCP_ALLOW_EXAMPLE_LOAD not true; skipping sample dataset load."
- fi
- section "Health check"
- root_json="$(curl -sS "$BASE_URL/")"
- echo "$root_json" | jq '{status, virtuoso, tools, guardrails}'
- if [[ "$(echo "$root_json" | jq -r '.status // empty')" == "MCP server running" ]]; then
- pass "root endpoint"
- else
- fail "root endpoint" "unexpected status"
- fi
- section "Read-only tools"
- TOOL_LAST_RESPONSE=""
- if assert_tool_ok "list_graphs" '{"tool":"list_graphs","input":{}}'; then
- echo "Graphs returned:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].g.value' | sed 's/^/ - /' || true
- fi
- section "Update path (fabricated triples)"
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg subject "http://example.org/plain#TestItem1" --arg predicate "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" --arg object "http://example.org/plain#Specimen" --arg graph "$TEST_GRAPH" '{"tool":"insert_triple","input":{"subject":$subject,"predicate":$predicate,"object":$object,"object_type":"uri","graph":$graph}}')
- if assert_tool_ok "insert_triple" "$payload"; then
- echo "Inserted query preview:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.query' | sed 's/^/ /'
- fi
- EXAMPLE_TTL=$(cat <<'EOF'
- @prefix ex: <http://example.org/plain#> .
- @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
- ex:PlainItem a ex:Specimen ;
- rdfs:label "Plain test item" .
- EOF
- )
- payload=$(jq -n --arg ttl "$EXAMPLE_TTL" --arg graph "$TEST_GRAPH" '{"tool":"batch_insert","input":{"ttl":$ttl,"graph":$graph}}')
- TOOL_LAST_RESPONSE=""
- if assert_tool_ok "batch_insert (fabricated TTL)" "$payload"; then
- echo "Batch insert query:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.query' | sed 's/^/ /'
- fi
- section "Helper retrieval checks"
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg type_uri "$CATEGORY_TYPE" --argjson limit 5 '{"tool":"get_entities_by_type","input":{"type_uri":$type_uri,"limit":$limit}}')
- if assert_tool_ok "get_entities_by_type(Category)" "$payload"; then
- echo "Entity IRIs:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].s.value' | sed 's/^/ - /' || true
- fi
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg term "Variant" --argjson limit 5 '{"tool":"search_label","input":{"term":$term,"limit":$limit}}')
- if assert_tool_ok "search_label(term=Variant)" "$payload"; then
- echo "Label hits:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - " + (.label.value // "<no label>") + " (" + .s.value + ")"' || true
- fi
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg subject_uri "$PATH_SUBJECT" --argjson limit 10 '{"tool":"get_predicates_for_subject","input":{"subject_uri":$subject_uri,"limit":$limit}}')
- if assert_tool_ok "get_predicates_for_subject(Prime Item)" "$payload"; then
- echo "Predicates found:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].p.value' | sed 's/^/ - /' || true
- fi
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg subject_uri "$PATH_SUBJECT" '{"tool":"get_labels_for_subject","input":{"subject_uri":$subject_uri}}')
- if assert_tool_ok "get_labels_for_subject(Prime Item)" "$payload"; then
- echo "Subject labels:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].label.value' | sed 's/^/ - /' || true
- fi
- section "Relationship inspection"
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg subject_uri "$ROOT_ITEM" --arg property_uri "$DERIVED_PROPERTY" --arg direction "incoming" --argjson limit 20 '{"tool":"traverse_property","input":{"subject_uri":$subject_uri,"property_uri":$property_uri,"direction":$direction,"limit":$limit}}')
- if assert_tool_ok "traverse_property(derivedFrom incoming)" "$payload"; then
- echo "Items derived from the prototype:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - " + (.neighbor.value) + " (label: " + (.label.value // "<no label>") + ")"'
- fi
- section "Ontology discovery"
- TOOL_LAST_RESPONSE=""
- if assert_tool_ok "list_classes(term=cycle)" '{"tool":"list_classes","input":{"term":"cycle","limit":10}}'; then
- echo "Class hits:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - " + .class.value + " (" + (.label.value // "<no label>") + ")"' || true
- fi
- TOOL_LAST_RESPONSE=""
- if assert_tool_ok "list_properties(term=derived)" '{"tool":"list_properties","input":{"term":"derived","limit":10}}'; then
- echo "Property hits:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - " + .property.value + " (" + (.label.value // "<no label>") + ")"' || true
- fi
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg property_uri "$DERIVED_PROPERTY" --argjson limit 5 '{"tool":"describe_property","input":{"property_uri":$property_uri,"usage_limit":$limit}}')
- if assert_tool_ok "describe_property(derivedFrom)" "$payload"; then
- echo "derivedFrom metadata rows:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.metadata.results.bindings | length | tostring | " - rows: " + .'
- fi
- section "Subject/path helpers"
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg subject_uri "$PATH_SUBJECT" --argjson limit 10 '{"tool":"describe_subject","input":{"subject_uri":$subject_uri,"limit":$limit}}')
- if assert_tool_ok "describe_subject(Prime Item)" "$payload"; then
- echo "Subject triples:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - " + .predicate.value + " -> " + (.objectLabel.value // .object.value)' || true
- fi
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg subject_uri "$PATH_SUBJECT" --arg property1 "$SOURCE_BATCH_PROPERTY" --arg property2 "$ORIGIN_BATCH_PROPERTY" --argjson limit 5 '{"tool":"path_traverse","input":{"subject_uri":$subject_uri,"property_path":[$property1,$property2],"limit":$limit}}')
- if assert_tool_ok "path_traverse(batch lineage)" "$payload"; then
- echo "Path traverse results:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.result.results.bindings[] | " - " + (.n1Label.value // .n1.value) + " -> " + (.n2Label.value // .n2.value)' || true
- fi
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg property_uri "$DERIVED_PROPERTY" --argjson examples_limit 3 '{"tool":"property_usage_statistics","input":{"property_uri":$property_uri,"examples_limit":$examples_limit}}')
- if assert_tool_ok "property_usage_statistics" "$payload"; then
- echo "derivedFrom usage count:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.count.results.bindings[0].usageCount.value // "0" | " - " + .'
- echo "Sample bindings:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.examples.results.bindings[] | " - " + (.subjectLabel.value // .subject.value) + " -> " + (.objectLabel.value // .object.value)' || true
- fi
- TOOL_LAST_RESPONSE=""
- payload=$(jq -n --arg ttl "<http://example.org/plain#BatchItem> <http://www.w3.org/2000/01/rdf-schema#label> \"batch helper\" ." --arg graph "$TEST_GRAPH" '{"tool":"batch_insert","input":{"ttl":$ttl,"graph":$graph}}')
- if assert_tool_ok "batch_insert(test)" "$payload"; then
- echo "Batch insert query:"
- echo "$TOOL_LAST_RESPONSE" | jq -r '.result.query' | sed 's/^/ /'
- fi
- section "Summary"
- echo "Passed: $pass_count"
- echo "Failed: $fail_count"
- if [[ "$fail_count" -gt 0 ]]; then
- exit 1
- fi
- echo "All checks passed."
|