tests.sh 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #!/usr/bin/env bash
  2. # tests.sh — mem0server endpoint tests
  3. # Usage: bash tests.sh [BASE_URL]
  4. # Default base: http://192.168.0.200:8420
  5. BASE="${1:-http://192.168.0.200:8420}"
  6. PASS=0
  7. FAIL=0
  8. LAST_JSON=""
  9. LAST_STATUS=""
  10. MEMORY_MARKER="mem0-test-case"
  11. # Colours
  12. GREEN='\033[0;32m'
  13. RED='\033[0;31m'
  14. CYAN='\033[0;36m'
  15. BOLD='\033[1m'
  16. RESET='\033[0m'
  17. sep() { echo -e "\n${CYAN}${BOLD}━━━ $* ━━━${RESET}"; }
  18. ok() { echo -e "${GREEN}✓ $*${RESET}"; ((PASS++)); }
  19. fail() { echo -e "${RED}✗ $*${RESET}"; ((FAIL++)); }
  20. run() {
  21. local label="$1"; shift
  22. local expected="200"
  23. if [[ "$1" =~ ^[0-9]{3}$ ]]; then
  24. expected="$1"
  25. shift
  26. fi
  27. echo -e "\n${BOLD}▶ $label${RESET}"
  28. local body
  29. body=$(curl -s -w "\n__STATUS__%{http_code}" "$@")
  30. local status
  31. status=$(echo "$body" | tail -1 | sed 's/__STATUS__//')
  32. local json
  33. json=$(echo "$body" | sed '$d')
  34. echo "$json" | jq . 2>/dev/null || echo "$json"
  35. LAST_STATUS="$status"
  36. LAST_JSON="$json"
  37. if [[ "$status" == "$expected" ]]; then
  38. ok "HTTP $status"
  39. else
  40. fail "HTTP $status (expected $expected)"
  41. fi
  42. }
  43. assert_json() {
  44. local label="$1" expr="$2"
  45. if echo "$LAST_JSON" | jq -e "$expr" >/dev/null 2>&1; then
  46. ok "$label"
  47. else
  48. fail "$label (jq: $expr)"
  49. fi
  50. }
  51. assert_contains() {
  52. local label="$1" substring="$2"
  53. if echo "$LAST_JSON" | grep -qF "$substring"; then
  54. ok "$label"
  55. else
  56. fail "$label (missing '$substring')"
  57. fi
  58. }
  59. sep "HEALTH"
  60. run "GET /health" "$BASE/health"
  61. assert_json "health -> collections object" '.collections | type == "object"'
  62. assert_json "health -> prompts present" '.prompts | type == "object"'
  63. sep "DASHBOARD"
  64. run "GET /dashboard" "$BASE/dashboard"
  65. assert_contains "dashboard contains HTML" "<html"
  66. sep "/memories (conversational)"
  67. run "POST /memories — plain text" \
  68. -X POST "$BASE/memories" \
  69. -H "Content-Type: application/json" \
  70. -d "{\"text\": \"${MEMORY_MARKER} plain text entry for testing\", \"user_id\": \"testuser\"}"
  71. run "POST /memories — messages array" \
  72. -X POST "$BASE/memories" \
  73. -H "Content-Type: application/json" \
  74. -d "{\"messages\": [{\"role\": \"user\", \"content\": \"${MEMORY_MARKER} message entry for testing\"}], \"user_id\": \"testuser\"}"
  75. run "POST /memories/all" \
  76. -X POST "$BASE/memories/all" \
  77. -H "Content-Type: application/json" \
  78. -d '{"user_id": "testuser"}'
  79. assert_json "memories/all returns array" '.results | type == "array"'
  80. RAW_MARKER="mem0-raw-created-at"
  81. RAW_TS="2020-01-02T03:04:05Z"
  82. run "POST /memories/raw — created_at override" \
  83. -X POST "$BASE/memories/raw" \
  84. -H "Content-Type: application/json" \
  85. -d "{\"text\": \"${RAW_MARKER} override timestamp check\", \"user_id\": \"testuser\", \"metadata\": {\"created_at\": \"${RAW_TS}\", \"source\": \"tests\"}}"
  86. run "POST /memories/all — after raw insert" \
  87. -X POST "$BASE/memories/all" \
  88. -H "Content-Type: application/json" \
  89. -d '{"user_id": "testuser"}'
  90. assert_json "memories/all after raw returns array" '.results | type == "array"'
  91. RAW_ID=$(echo "$LAST_JSON" | jq -r --arg marker "$RAW_MARKER" '.results[] | select(.memory | contains($marker)) | .id' | head -n1)
  92. RAW_CREATED=$(echo "$LAST_JSON" | jq -r --arg marker "$RAW_MARKER" '.results[] | select(.memory | contains($marker)) | .created_at' | head -n1)
  93. if [[ -n "$RAW_ID" ]]; then
  94. ok "captured raw memory ID"
  95. else
  96. fail "raw memory ID missing"
  97. fi
  98. if [[ "$RAW_CREATED" == "$RAW_TS" ]]; then
  99. ok "raw memory created_at override respected"
  100. else
  101. fail "raw memory created_at override missing (got '$RAW_CREATED')"
  102. fi
  103. run "POST /memories/search — capture marker" \
  104. -X POST "$BASE/memories/search" \
  105. -H "Content-Type: application/json" \
  106. -d "{\"query\": \"${MEMORY_MARKER}\", \"user_id\": \"testuser\", \"limit\": 5}"
  107. assert_json "memories marker search returns array" '.results | type == "array"'
  108. MEMORY_IDS=$(echo "$LAST_JSON" | jq -r '.results[] | .id')
  109. if [[ -z "$MEMORY_IDS" ]]; then
  110. fail "no test memory IDs captured"
  111. else
  112. ok "captured conversational memory IDs"
  113. fi
  114. run "POST /memories/search" \
  115. -X POST "$BASE/memories/search" \
  116. -H "Content-Type: application/json" \
  117. -d '{"query": "programming preferences", "user_id": "testuser", "limit": 5 }'
  118. assert_json "memories search returns array" '.results | type == "array"'
  119. run "POST /memories/recent" \
  120. -X POST "$BASE/memories/recent" \
  121. -H "Content-Type: application/json" \
  122. -d '{"user_id": "testuser", "limit": 5}'
  123. assert_json "memories recent returns array" '.results | type == "array"'
  124. if [[ -n "$MEMORY_IDS" || -n "$RAW_ID" ]]; then
  125. for id in $MEMORY_IDS $RAW_ID; do
  126. if [[ -z "$id" ]]; then
  127. continue
  128. fi
  129. run "DELETE /memory/$id" \
  130. -X DELETE "$BASE/memory/$id" \
  131. -H "Content-Type: application/json" \
  132. -d '{"collection": "conversational"}'
  133. done
  134. else
  135. fail "conversational memory IDs missing before cleanup"
  136. fi
  137. sep "/knowledge (knowledge base)"
  138. run "POST /knowledge — Gesell metadata" \
  139. -X POST "$BASE/knowledge" \
  140. -H "Content-Type: application/json" \
  141. -d '{"text": "Silvio Gesell proposed demurrage to discourage hoarding of currency.", "user_id": "knowledge_base", "metadata": {"source_file": "gesell_neo.pdf", "chapter": 3, "page": 47}}'
  142. run "POST /knowledge — MIDI verbatim" \
  143. -X POST "$BASE/knowledge" \
  144. -H "Content-Type: application/json" \
  145. -d '{"text": "MIDI SysEx uses a 7-bit checksum.", "user_id": "knowledge_base", "infer": false, "metadata": {"source_file": "midi_spec.pdf", "chapter": 9, "page": 112}}'
  146. run "POST /knowledge/search — Gesell demurrage" \
  147. -X POST "$BASE/knowledge/search" \
  148. -H "Content-Type: application/json" \
  149. -d '{"query": "Gesell demurrage", "user_id": "knowledge_base", "limit": 5}'
  150. assert_json "knowledge search results array" '.results | type == "array"'
  151. run "POST /knowledge/recent" \
  152. -X POST "$BASE/knowledge/recent" \
  153. -H "Content-Type: application/json" \
  154. -d '{"user_id": "knowledge_base", "limit": 5}'
  155. assert_json "knowledge recent returns array" '.results | type == "array"'
  156. run "POST /knowledge/sources" \
  157. -X POST "$BASE/knowledge/sources" \
  158. -H "Content-Type: application/json" \
  159. -d '{"user_id": "knowledge_base"}'
  160. assert_json "knowledge sources array" '.sources | type == "array"'
  161. assert_json "knowledge sources include Gesell" '.sources | any(.source_file == "gesell_neo.pdf")'
  162. run "DELETE /knowledge/by-source — gesell_neo.pdf" \
  163. -X DELETE "$BASE/knowledge/by-source" \
  164. -H "Content-Type: application/json" \
  165. -d '{"source_file": "gesell_neo.pdf", "user_id": "knowledge_base"}'
  166. assert_json "knowledge by-source removed entries" '.deleted | tonumber > 0'
  167. run "DELETE /knowledge/by-source — midi_spec.pdf" \
  168. -X DELETE "$BASE/knowledge/by-source" \
  169. -H "Content-Type: application/json" \
  170. -d '{"source_file": "midi_spec.pdf", "user_id": "knowledge_base"}'
  171. assert_json "knowledge by-source removed MIDI entries" '.deleted | tonumber > 0'
  172. sep "/search (merged)"
  173. run "POST /search — Python programming" \
  174. -X POST "$BASE/search" \
  175. -H "Content-Type: application/json" \
  176. -d '{"query": "Python programming", "user_id": "testuser", "limit": 8}'
  177. assert_json "merged search results array" '.results | type == "array"'
  178. assert_json "merged search items have _source" 'if (.results | length) == 0 then true else (.results[] | has("_source")) end'
  179. run "POST /search — Gesell economic theory" \
  180. -X POST "$BASE/search" \
  181. -H "Content-Type: application/json" \
  182. -d '{"query": "Gesell economic theory", "user_id": "knowledge_base", "limit": 5}'
  183. assert_json "merged search results array (Gesell)" '.results | type == "array"'
  184. assert_json "merged Gesell results tag _source" 'if (.results | length) == 0 then true else (.results[] | has("_source")) end'
  185. sep "ERROR HANDLING"
  186. run "POST /memories — missing text" 400 \
  187. -X POST "$BASE/memories" \
  188. -H "Content-Type: application/json" \
  189. -d '{"user_id": "testuser"}'
  190. run "POST /memories — infer:false ignored" \
  191. -X POST "$BASE/memories" \
  192. -H "Content-Type: application/json" \
  193. -d '{"text": "This should still go through LLM extraction", "user_id": "testuser", "infer": false}'
  194. run "POST /search — empty query" \
  195. -X POST "$BASE/search" \
  196. -H "Content-Type: application/json" \
  197. -d '{"query": "", "user_id": "testuser"}'
  198. assert_json "empty search returns results array" '.results | type == "array"'
  199. assert_json "empty search returns zero results" '.results | length == 0'
  200. sep "RESULTS"
  201. echo -e "\n${BOLD}Passed: ${GREEN}$PASS${RESET} ${BOLD}Failed: ${RED}$FAIL${RESET}\n"