test.sh 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  4. cd "$SCRIPT_DIR"
  5. if [[ -f .env ]]; then
  6. set -a
  7. # shellcheck source=/dev/null
  8. source .env
  9. set +a
  10. fi
  11. PORT=8501
  12. BASE_URL="http://127.0.0.1:$PORT"
  13. TEST_GRAPH="http://world.eu.org/cannabis-breeding#test"
  14. if ! command -v jq >/dev/null 2>&1; then
  15. echo "ERROR: jq is required for test output parsing."
  16. exit 1
  17. fi
  18. pass_count=0
  19. fail_count=0
  20. section() {
  21. echo
  22. echo "============================================================"
  23. echo "$1"
  24. echo "============================================================"
  25. }
  26. pass() {
  27. pass_count=$((pass_count + 1))
  28. echo "✅ PASS: $1"
  29. }
  30. fail() {
  31. fail_count=$((fail_count + 1))
  32. echo "❌ FAIL: $1"
  33. echo " detail: $2"
  34. }
  35. call_mcp() {
  36. local payload="$1"
  37. curl -sS -X POST "$BASE_URL/mcp" \
  38. -H "Content-Type: application/json" \
  39. -d "$payload"
  40. }
  41. assert_tool_ok() {
  42. local label="$1"
  43. local payload="$2"
  44. local response
  45. response="$(call_mcp "$payload")" || {
  46. fail "$label" "HTTP request failed"
  47. return 1
  48. }
  49. if ! echo "$response" | jq -e . >/dev/null 2>&1; then
  50. fail "$label" "Non-JSON response: $response"
  51. return 1
  52. fi
  53. local status
  54. status="$(echo "$response" | jq -r '.status // empty')"
  55. if [[ "$status" != "ok" ]]; then
  56. local detail
  57. detail="$(echo "$response" | jq -r '.detail // .error // "unknown error"')"
  58. fail "$label" "$detail"
  59. return 1
  60. fi
  61. pass "$label"
  62. TOOL_LAST_RESPONSE="$response"
  63. return 0
  64. }
  65. section "Health check"
  66. root_json="$(curl -sS "$BASE_URL/")"
  67. echo "$root_json" | jq '{status, virtuoso, tools, guardrails}'
  68. if [[ "$(echo "$root_json" | jq -r '.status // empty')" == "MCP server running" ]]; then
  69. pass "root endpoint"
  70. else
  71. fail "root endpoint" "unexpected status"
  72. fi
  73. section "Read-only tools"
  74. TOOL_LAST_RESPONSE=""
  75. if assert_tool_ok "list_graphs" '{"tool":"list_graphs","input":{}}'; then
  76. echo "Graphs returned:"
  77. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].g.value' | sed 's/^/ - /' || true
  78. fi
  79. section "Update path (single triple + small Turtle)"
  80. if [[ "${MCP_ALLOW_EXAMPLE_LOAD:-false}" == "true" ]]; then
  81. TOOL_LAST_RESPONSE=""
  82. if assert_tool_ok "insert_triple" '{"tool":"insert_triple","input":{"subject":"http://world.eu.org/example1#TestPlant1","predicate":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","object":"http://world.eu.org/cannabis-breeding#IndividualPlant","object_type":"uri","graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
  83. echo "Inserted query preview:"
  84. echo "$TOOL_LAST_RESPONSE" | jq -r '.query' | sed 's/^/ /'
  85. fi
  86. TOOL_LAST_RESPONSE=""
  87. if assert_tool_ok "load_examples (productioncycle_export.ttl)" '{"tool":"load_examples","input":{"files":["productioncycle_export.ttl"],"graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
  88. echo "Loaded files:"
  89. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.loaded[] | " - \(.file) -> \(.graph)"'
  90. fi
  91. TOOL_LAST_RESPONSE=""
  92. if assert_tool_ok "load_examples (export large TTL)" '{"tool":"load_examples","input":{"files":["export_2026-03-26T14_10_20.ttl"],"graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
  93. echo "Loaded files:"
  94. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.loaded[] | " - \(.file) -> \(.graph)"'
  95. fi
  96. else
  97. echo "ℹ️ Skipped update tests (MCP_ALLOW_EXAMPLE_LOAD != true)"
  98. fi
  99. section "Helper retrieval checks"
  100. TOOL_LAST_RESPONSE=""
  101. if assert_tool_ok "get_entities_by_type(Strain)" '{"tool":"get_entities_by_type","input":{"type_uri":"http://world.eu.org/cannabis-breeding#Strain","limit":5}}'; then
  102. echo "Entity IRIs:"
  103. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].s.value' | sed 's/^/ - /' || true
  104. fi
  105. TOOL_LAST_RESPONSE=""
  106. if assert_tool_ok "search_label(term=King)" '{"tool":"search_label","input":{"term":"King","limit":5}}'; then
  107. echo "Label hits:"
  108. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - \(.label.value) (\(.s.value))"' || true
  109. fi
  110. TOOL_LAST_RESPONSE=""
  111. if assert_tool_ok "get_predicates_for_subject(Strain_king_kong)" '{"tool":"get_predicates_for_subject","input":{"subject_uri":"http://world.eu.org/example1#Strain_king_kong","limit":10}}'; then
  112. echo "Predicates found:"
  113. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].p.value' | sed 's/^/ - /' || true
  114. fi
  115. TOOL_LAST_RESPONSE=""
  116. if assert_tool_ok "get_labels_for_subject(Strain_king_kong)" '{"tool":"get_labels_for_subject","input":{"subject_uri":"http://world.eu.org/example1#Strain_king_kong"}}'; then
  117. echo "Subject labels:"
  118. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].label.value' | sed 's/^/ - /' || true
  119. fi
  120. section "Clone inspection"
  121. KEROSENE_ROOT="http://world.eu.org/example1#Plant_90d53925-bb5"
  122. TOOL_LAST_RESPONSE=""
  123. if assert_tool_ok "traverse_property(cloneOf incoming)" '{"tool":"traverse_property","input":{"subject_uri":"http://world.eu.org/example1#Plant_90d53925-bb5","property_uri":"http://world.eu.org/cannabis-breeding#cloneOf","direction":"incoming","limit":20}}'; then
  124. echo "Clones of Kerosene Krash root:"
  125. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - \(.neighbor.value) (label: \(.label.value // "<no label>"))"'
  126. fi
  127. section "Ontology discovery"
  128. TOOL_LAST_RESPONSE=""
  129. if assert_tool_ok "list_classes(term=cycle)" '{"tool":"list_classes","input":{"term":"cycle","limit":10}}'; then
  130. echo "Class hits:"
  131. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - \(.class.value) (\(.label.value // "<no label>"))"' || true
  132. fi
  133. TOOL_LAST_RESPONSE=""
  134. if assert_tool_ok "list_properties(term=clone)" '{"tool":"list_properties","input":{"term":"clone","limit":10}}'; then
  135. echo "Property hits:"
  136. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - \(.property.value) (\(.label.value // "<no label>"))"' || true
  137. fi
  138. TOOL_LAST_RESPONSE=""
  139. if assert_tool_ok "describe_property(cb:cloneOf)" '{"tool":"describe_property","input":{"property_uri":"http://world.eu.org/cannabis-breeding#cloneOf","usage_limit":5}}'; then
  140. echo "cloneOf metadata rows:"
  141. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.metadata.results.bindings | length | tostring | " - rows: " + .'
  142. fi
  143. section "Subject/path helpers"
  144. PATH_SUBJECT="http://world.eu.org/example1#Plant_cookie_kerosene_2026_3"
  145. TOOL_LAST_RESPONSE=""
  146. if assert_tool_ok "describe_subject(cookie path)" "{\"tool\":\"describe_subject\",\"input\":{\"subject_uri\":\"${PATH_SUBJECT}\",\"limit\":10}}"; then
  147. echo "Subject triples:"
  148. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[] | " - \(.predicate.value) -> \(.objectLabel.value // .object.value)"' || true
  149. fi
  150. TOOL_LAST_RESPONSE=""
  151. if assert_tool_ok "path_traverse(seed lineage)" "{\"tool\":\"path_traverse\",\"input\":{\"subject_uri\":\"${PATH_SUBJECT}\",\"property_path\":[\"http://world.eu.org/cannabis-breeding#grownFromSeedProduct\",\"http://world.eu.org/cannabis-breeding#seedProductFromPollination\"],\"limit\":5}}"; then
  152. echo "Path traverse results:"
  153. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.result.results.bindings[] | " - " + (.n1Label.value // .n1.value) + " -> " + (.n2Label.value // .n2.value)' || true
  154. fi
  155. TOOL_LAST_RESPONSE=""
  156. if assert_tool_ok "property_usage_statistics" '{"tool":"property_usage_statistics","input":{"property_uri":"http://world.eu.org/cannabis-breeding#cloneOf","examples_limit":3}}'; then
  157. echo "cloneOf usage count:"
  158. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.count.results.bindings[0].usageCount.value // "0" | " - " + .' || true
  159. echo "Sample bindings:"
  160. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.examples.results.bindings[] | " - " + (.subjectLabel.value // .subject.value) + " -> " + (.objectLabel.value // .object.value)' || true
  161. fi
  162. TOOL_LAST_RESPONSE=""
  163. if assert_tool_ok "batch_insert(test)" '{"tool":"batch_insert","input":{"ttl":"<http://world.eu.org/example1#batch_test_subject> <http://www.w3.org/2000/01/rdf-schema#label> \"batch helper\" .","graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
  164. echo "Batch insert query:"
  165. echo "$TOOL_LAST_RESPONSE" | jq -r '.result.query' | sed 's/^/ /'
  166. fi
  167. section "Summary"
  168. echo "Passed: $pass_count"
  169. echo "Failed: $fail_count"
  170. if [[ "$fail_count" -gt 0 ]]; then
  171. exit 1
  172. fi
  173. echo "All checks passed."