mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 23:41:12 +08:00
### What problem does this PR solve? As title ### Type of change - [x] New Feature (non-breaking change which adds functionality)
180 lines
5.9 KiB
Go
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)
|
|
}
|
|
}
|