Files
ragflow/internal/service/memory_message_service_test.go
Haruko386 a1f1dd5007 feat[Go]: implement Add messages for Go (#16375)
### What problem does this PR solve?

As title

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2026-06-26 19:21:52 +08:00

180 lines
5.9 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// memory_message_service_test.go — Phase 8b real MemorySaver port
// tests.
//
// Coverage focuses on the synchronous parts (memory lookup + raw
// message construction + result aggregation). The embedder call
// is exercised only to verify it loud-fails with
// ErrEmbedderNotWired — the embedder port itself is a separate
// follow-up.
package service
import (
"context"
"errors"
"strings"
"testing"
)
// TestQueueSaveToMemoryTask_NilService: a nil receiver surfaces
// a clear error rather than panicking.
func TestQueueSaveToMemoryTask_NilService(t *testing.T) {
var s *MemoryMessageService
_, err := s.QueueSaveToMemoryTask(context.Background(), []string{"m1"}, MemoryMessage{AgentID: "a1"})
if err == nil {
t.Fatal("expected error from nil service")
}
if !strings.Contains(err.Error(), "nil") {
t.Errorf("error = %v, want nil-service error", err)
}
}
// TestQueueSaveToMemoryTask_EmptyMemoryList: an empty input
// short-circuits to an empty result with no error.
func TestQueueSaveToMemoryTask_EmptyMemoryList(t *testing.T) {
s := &MemoryMessageService{memories: nil} // no lookups happen
res, err := s.QueueSaveToMemoryTask(context.Background(), nil, MemoryMessage{AgentID: "a1"})
if err != nil {
t.Fatalf("QueueSaveToMemoryTask: %v", err)
}
if len(res.NotFound) != 0 || len(res.Failed) != 0 {
t.Errorf("expected empty result, got %+v", res)
}
}
// TestQueueSaveToMemoryTask_MissingAgentID: AgentID is required
// for the row envelope; an empty AgentID surfaces a clear error
// up front.
func TestQueueSaveToMemoryTask_MissingAgentID(t *testing.T) {
s := &MemoryMessageService{}
_, err := s.QueueSaveToMemoryTask(context.Background(), []string{"m1"}, MemoryMessage{})
if err == nil {
t.Fatal("expected error for missing AgentID")
}
if !strings.Contains(err.Error(), "AgentID") {
t.Errorf("error = %v, want AgentID-required error", err)
}
}
// TestBuildRawMessage_EnvelopeShape: the row envelope carries
// every field the Python extractor reads.
func TestBuildRawMessage_EnvelopeShape(t *testing.T) {
mem := &CreateMemoryResponse{}
mem.EmbdID = "embd-1"
raw := buildRawMessage(42, "mem-1", mem, MemoryMessage{
UserID: "u1",
AgentID: "a1",
SessionID: "s1",
UserInput: "hi",
AgentResponse: "hello",
})
want := map[string]any{
"message_id": int64(42),
"message_type": "raw",
"memory_id": "mem-1",
"user_id": "u1",
"agent_id": "a1",
"session_id": "s1",
"_memory_embd_id": "embd-1",
}
for k, want := range want {
if got := raw[k]; got != want {
t.Errorf("raw[%q] = %v (%T), want %v (%T)", k, got, got, want, want)
}
}
content, _ := raw["content"].(string)
if !strings.Contains(content, "User Input: hi") {
t.Errorf("content missing user input: %q", content)
}
if !strings.Contains(content, "Agent Response: hello") {
t.Errorf("content missing agent response: %q", content)
}
if !strings.Contains(content, "\n") {
t.Errorf("content should have user/agent on separate lines: %q", content)
}
}
// TestBuildRawMessage_NilMemory: when the lookup returned nil
// (e.g. caller already filtered NotFound), buildRawMessage
// doesn't panic and omits _memory_embd_id.
func TestBuildRawMessage_NilMemory(t *testing.T) {
raw := buildRawMessage(1, "m", nil, MemoryMessage{AgentID: "a"})
if _, ok := raw["_memory_embd_id"]; ok {
t.Errorf("_memory_embd_id should be absent when mem is nil")
}
// The other fields still land.
if raw["agent_id"] != "a" {
t.Errorf("agent_id missing or wrong: %v", raw["agent_id"])
}
}
// TestBuildTaskRow_Shape: the task row mirrors the Python Task
// entity's memory-task shape.
func TestBuildTaskRow_Shape(t *testing.T) {
row := buildTaskRow(99, "mem-1")
if row["task_type"] != "memory" {
t.Errorf("task_type = %v, want \"memory\"", row["task_type"])
}
if row["doc_id"] != "mem-1" {
t.Errorf("doc_id = %v, want \"mem-1\"", row["doc_id"])
}
if row["progress"] != 0.0 {
t.Errorf("progress = %v, want 0.0", row["progress"])
}
if row["digest"] != "99" {
t.Errorf("digest = %v, want \"99\"", row["digest"])
}
if id, _ := row["id"].(string); len(id) != 32 {
t.Errorf("id = %q, want 32-char uuid", id)
}
}
// TestEmbedAndSave_LoudFails: until the embedder port lands,
// the call must return ErrEmbedderNotWired — not panic, not
// silently succeed. A successful embed_and_save would mask the
// deferred state and let callers persist data that never gets
// embedded.
func TestEmbedAndSave_LoudFails(t *testing.T) {
err := embedAndSave(context.Background(), nil, nil)
if !errors.Is(err, ErrEmbedderNotWired) {
t.Errorf("err = %v, want ErrEmbedderNotWired", err)
}
}
// TestGenerateRawMessageID_Unique: two calls produce different
// values. (Wall-clock based today; the Redis-backed counter will
// be added when the project's Redis client lands.)
func TestGenerateRawMessageID_Unique(t *testing.T) {
a := generateRawMessageID()
b := generateRawMessageID()
if a == b {
// Allow a 1-second tie when the clock hasn't ticked.
// GenerateRawMessageID uses Unix seconds; two calls
// within the same second will collide. The Redis
// counter is the eventual fix; this test only
// verifies monotonic-ish behaviour.
t.Logf("note: raw_message_ids collided (%d) — within the same second; the Redis counter will fix this", a)
}
if a <= 0 || b <= 0 {
t.Errorf("expected positive ids, got a=%d b=%d", a, b)
}
}