// ── test_sessions_routes.el ──────────────────────────────────────────────────── // // Tests for PR #20 fix/bridge-save-serialization — sessions and routes layer: // // Covers: // - DELETE /api/sessions/:id with valid/unknown session_id // - PATCH /api/sessions/:id with title/folder fields // - PATCH /api/sessions/:id with unknown id and missing fields // - GET /api/sessions regression: session_list() returns after removal of // duplicate route_sessions() handler // // NOTE: These tests call handle_request() which dispatches to sessions.el // functions that use engram_search_json. Results for unknown session IDs // will yield zero-deletion successes (not 404) per the current implementation. // // To run: // elc routes.el && ./soul --test tests/test_sessions_routes.el // // ────────────────────────────────────────────────────────────────────────────── import "../routes.el" // ── Test harness ────────────────────────────────────────────────────────────── let pass_count: Int = 0 let fail_count: Int = 0 fn assert_eq(label: String, got: String, expected: String) -> Void { if str_eq(got, expected) { let pass_count = pass_count + 1 println(" PASS: " + label) } else { let fail_count = fail_count + 1 println(" FAIL: " + label) println(" got: " + got) println(" expected: " + expected) } } fn assert_contains(label: String, haystack: String, needle: String) -> Void { if str_contains(haystack, needle) { let pass_count = pass_count + 1 println(" PASS: " + label) } else { let fail_count = fail_count + 1 println(" FAIL: " + label) println(" missing '" + needle + "' in: " + haystack) } } fn assert_not_contains(label: String, haystack: String, needle: String) -> Void { if str_contains(haystack, needle) { let fail_count = fail_count + 1 println(" FAIL: " + label) println(" unexpected '" + needle + "' found in: " + haystack) } else { let pass_count = pass_count + 1 println(" PASS: " + label) } } fn assert_true(label: String, cond: Bool) -> Void { if cond { let pass_count = pass_count + 1 println(" PASS: " + label) } else { let fail_count = fail_count + 1 println(" FAIL: " + label) } } // ── Section 1: DELETE /api/sessions/:id — unknown id ───────────────────────── // // session_delete does not return 404 for unknown ids; it returns ok:true with // zero-count deletions. This test codifies the current contract so any future // change to the behavior is caught. println("") println("1. DELETE /api/sessions/:id — unknown session_id") let del_unknown: String = handle_request("DELETE", "/api/sessions/nonexistent-session-uuid", "") assert_contains("DELETE unknown id -> ok field present", del_unknown, "\"ok\"") assert_contains("DELETE unknown id -> ok is true (zero-count success)", del_unknown, "\"ok\":true") assert_contains("DELETE unknown id -> deleted_meta count present", del_unknown, "deleted_meta") assert_contains("DELETE unknown id -> deleted_msgs count present", del_unknown, "deleted_msgs") // ── Section 2: DELETE /api/sessions/:id — missing id ───────────────────────── println("") println("2. DELETE /api/sessions (no id in path) -> 404") let del_no_id: String = handle_request("DELETE", "/api/sessions", "") assert_contains("DELETE with no id -> 404 error", del_no_id, "\"error\"") // ── Section 3: PATCH /api/sessions/:id — update title ──────────────────────── // // PATCH with a known title field should not error on the missing-fields check. // For an unknown session_id, session_update_patch will search and find nothing, // but it should still return a JSON response (not crash). println("") println("3. PATCH /api/sessions/:id — title field") let patch_title: String = handle_request("PATCH", "/api/sessions/test-sess-patch-1", "{\"title\":\"My new title\"}") // Should return JSON with ok field or error field — must not be empty assert_not_contains("PATCH title -> response is not empty", patch_title, "") assert_true("PATCH title -> response is non-empty string", str_len(patch_title) > 0) // Must not return the missing-fields error (since title IS provided) assert_not_contains("PATCH title -> no 'title or folder required' error", patch_title, "title or folder required") // ── Section 4: PATCH /api/sessions/:id — folder field ──────────────────────── println("") println("4. PATCH /api/sessions/:id — folder field") let patch_folder: String = handle_request("PATCH", "/api/sessions/test-sess-patch-2", "{\"folder\":\"my-folder\"}") assert_true("PATCH folder -> response is non-empty", str_len(patch_folder) > 0) assert_not_contains("PATCH folder -> no 'title or folder required' error", patch_folder, "title or folder required") // ── Section 5: PATCH /api/sessions/:id — empty body (missing fields) ────────── println("") println("5. PATCH /api/sessions/:id — empty body returns field-required error") let patch_empty: String = handle_request("PATCH", "/api/sessions/test-sess-patch-3", "{}") assert_contains("PATCH empty body -> error field present", patch_empty, "\"error\"") assert_contains("PATCH empty body -> missing fields message", patch_empty, "title or folder required") // ── Section 6: PATCH /api/sessions (no id in path) -> 404 ──────────────────── println("") println("6. PATCH /api/sessions (no id) -> 404") let patch_no_id: String = handle_request("PATCH", "/api/sessions", "{\"title\":\"x\"}") assert_contains("PATCH no id -> 404 error", patch_no_id, "\"error\"") // ── Section 7: GET /api/sessions — session_list regression ─────────────────── // // After removal of the duplicate route_sessions() GET handler in routes.el, // GET /api/sessions must still return a valid JSON array (possibly empty) from // session_list(). Verifies the deduplication fix does not break the endpoint. println("") println("7. GET /api/sessions — session_list() returns valid JSON array") let get_sessions: String = handle_request("GET", "/api/sessions", "") assert_true("GET /api/sessions -> response is non-empty", str_len(get_sessions) > 0) // Result must be a JSON array (starts with '[') let first_char: String = str_slice(get_sessions, 0, 1) assert_eq("GET /api/sessions -> response is a JSON array", first_char, "[") // ── Section 8: DELETE then GET — session_index cache invalidation ───────────── // // After a DELETE, session_list() must not return the deleted session. // Since we don't have a real session to delete in this test environment, // we verify the GET still returns an array after the DELETE attempt. println("") println("8. GET /api/sessions after DELETE attempt -> still returns valid array") let del_first: String = handle_request("DELETE", "/api/sessions/test-cache-inval-sess", "") assert_contains("pre-DELETE: ok field present", del_first, "\"ok\"") let get_after_del: String = handle_request("GET", "/api/sessions", "") let first_char2: String = str_slice(get_after_del, 0, 1) assert_eq("GET after DELETE -> still returns JSON array", first_char2, "[") // ── Summary ──────────────────────────────────────────────────────────────────── println("") println("test_sessions_routes.el: " + int_to_str(pass_count) + " passed, " + int_to_str(fail_count) + " failed")