package memllm import ( "testing" adapters "github.com/memohai/memoh/internal/memory/adapters" ) func TestParseJSONStringArray_Valid(t *testing.T) { t.Parallel() result := parseJSONStringArray(`["fact one", "fact two"]`) if len(result) != 2 { t.Fatalf("expected 2 facts, got %d", len(result)) } if result[0] != "fact one" || result[1] != "fact two" { t.Fatalf("unexpected facts: %v", result) } } func TestParseJSONStringArray_Empty(t *testing.T) { t.Parallel() result := parseJSONStringArray(`[]`) if len(result) != 0 { t.Fatalf("expected 0 facts, got %d", len(result)) } } func TestParseJSONStringArray_CodeFence(t *testing.T) { t.Parallel() input := "```json\n[\"hello\", \"world\"]\n```" result := parseJSONStringArray(input) if len(result) != 2 { t.Fatalf("expected 2 facts from code fence, got %d", len(result)) } } func TestParseJSONStringArray_PrefixText(t *testing.T) { t.Parallel() input := "Here are the facts:\n[\"a\", \"b\"]" result := parseJSONStringArray(input) if len(result) != 2 { t.Fatalf("expected 2 facts with prefix text, got %d", len(result)) } } func TestParseJSONStringArray_Garbage(t *testing.T) { t.Parallel() result := parseJSONStringArray("this is not json at all") if result != nil { t.Fatalf("expected nil for garbage input, got %v", result) } } func TestParseJSONStringArray_FiltersBlanks(t *testing.T) { t.Parallel() result := parseJSONStringArray(`["fact one", "", " ", "fact two"]`) if len(result) != 2 { t.Fatalf("expected 2 non-empty facts, got %d", len(result)) } } func TestParseExtractResponse_FactsWrapper(t *testing.T) { t.Parallel() input := `{"facts": ["Name is John", "Is a Software engineer"]}` result := parseExtractResponse(input) if len(result) != 2 { t.Fatalf("expected 2 facts, got %d", len(result)) } if result[0] != "Name is John" { t.Fatalf("unexpected first fact: %q", result[0]) } } func TestParseExtractResponse_BareArray(t *testing.T) { t.Parallel() input := `["fact one", "fact two"]` result := parseExtractResponse(input) if len(result) != 2 { t.Fatalf("expected 2 facts from bare array, got %d", len(result)) } } func TestParseExtractResponse_EmptyFacts(t *testing.T) { t.Parallel() result := parseExtractResponse(`{"facts": []}`) if len(result) != 0 { t.Fatalf("expected 0 facts, got %d", len(result)) } } func TestParseUpdateResponse_Mem0Format(t *testing.T) { t.Parallel() input := `{"memory": [ {"id": "0", "text": "User is a software engineer", "event": "NONE"}, {"id": "1", "text": "Name is John", "event": "ADD"}, {"id": "2", "text": "Loves cheese pizza", "event": "DELETE"}, {"id": "3", "text": "Moved to Berlin", "event": "UPDATE", "old_memory": "Lives in Tokyo"} ]}` result := parseUpdateResponse(input) if len(result) != 4 { t.Fatalf("expected 4 actions, got %d", len(result)) } if result[0].Event != "NOOP" { t.Fatalf("expected NONE mapped to NOOP, got %q", result[0].Event) } if result[1].Event != "ADD" || result[1].Text != "Name is John" { t.Fatalf("unexpected ADD action: %+v", result[1]) } if result[2].Event != "DELETE" || result[2].ID != "2" { t.Fatalf("unexpected DELETE action: %+v", result[2]) } if result[3].Event != "UPDATE" || result[3].OldMemory != "Lives in Tokyo" { t.Fatalf("unexpected UPDATE action: %+v", result[3]) } } func TestParseUpdateResponse_FlatArrayFallback(t *testing.T) { t.Parallel() input := `[{"event":"ADD","text":"User likes tea"},{"event":"NOOP"},{"event":"DELETE","id":"bot-1:mem_123"}]` result := parseUpdateResponse(input) if len(result) != 3 { t.Fatalf("expected 3 actions, got %d", len(result)) } } func TestParseUpdateResponse_WithCodeFence(t *testing.T) { t.Parallel() input := "```json\n{\"memory\": [{\"event\":\"ADD\",\"text\":\"foo\",\"id\":\"1\"}]}\n```" result := parseUpdateResponse(input) if len(result) != 1 { t.Fatalf("expected 1 action from code fence, got %d", len(result)) } } func TestParseUpdateResponse_EmptyMemory(t *testing.T) { t.Parallel() result := parseUpdateResponse(`{"memory": []}`) if result != nil { t.Fatalf("expected nil for empty memory array, got %v", result) } } func TestParseUpdateResponse_Garbage(t *testing.T) { t.Parallel() result := parseUpdateResponse("not json") if result != nil { t.Fatalf("expected nil for garbage, got %v", result) } } func TestExtractJSONBlock_NoFence(t *testing.T) { t.Parallel() got := extractJSONBlock(`["a"]`) if got != `["a"]` { t.Fatalf("expected raw pass-through, got %q", got) } } func TestExtractJSONBlock_JSONFence(t *testing.T) { t.Parallel() got := extractJSONBlock("```json\n[\"a\"]\n```") if got != `["a"]` { t.Fatalf("expected extracted content, got %q", got) } } func TestExtractJSONBlock_PlainFence(t *testing.T) { t.Parallel() got := extractJSONBlock("```\n[\"a\"]\n```") if got != `["a"]` { t.Fatalf("expected extracted content, got %q", got) } } var _ adapters.LLM = (*Client)(nil)