8db3c8c7f7
Neuron Soul CI / build (pull_request) Failing after 13m18s
BLOCKER 1: use untyped reassignment (let x = ...) for the fallback bindings in agentic_resume instead of re-declaring typed let bindings (let x: Type = ...) for the same variable in the same scope. The typed form risks shadowing semantics that differ from the established pattern used everywhere else in the loop (e.g. agentic_loop line 720). BLOCKER 2: add empty-string guards in both bridge_save and agentic_resume. bridge_save now returns false without writing state if messages or tools_json is empty — preventing syntactically invalid JSON blobs. agentic_resume now returns an error envelope after the fallback resolution if either field is still empty, rather than passing empty strings into agentic_loop which would silently start a fresh turn with no context. Also add tests: - test_bridge_serialization.el: covers bridge_save empty-guard, golden-path raw-JSON round-trip, agentic_resume unknown/corrupt/missing-fields paths, and legacy string-escaped fallback path - test_sessions_routes.el: covers DELETE and PATCH /api/sessions/:id routes (valid args, unknown id, empty body) and GET /api/sessions regression after removal of the duplicate route_sessions() handler
172 lines
8.0 KiB
EmacsLisp
172 lines
8.0 KiB
EmacsLisp
// ── 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")
|