mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-15 01:18:26 +08:00
### Summary As title <img width="3165" height="2011" alt="image" src="https://github.com/user-attachments/assets/6947aa4f-5dfd-4d8c-b6af-82015b1f1537" /> --- <img width="3735" height="1721" alt="image" src="https://github.com/user-attachments/assets/3a7ec8e8-d5b9-4195-bba9-c435218f6142" />
230 lines
7.1 KiB
Go
230 lines
7.1 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.
|
|
//
|
|
|
|
package component
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"ragflow/internal/agent/canvas"
|
|
"ragflow/internal/storage"
|
|
)
|
|
|
|
// TestDocsGenerator_Registered: the component is registered under
|
|
// its canonical name. The param check requires output_format and
|
|
// content; we provide a minimal valid params map.
|
|
func TestDocsGenerator_Registered(t *testing.T) {
|
|
c, err := New("DocsGenerator", map[string]any{
|
|
"output_format": "pdf",
|
|
"content": "Hello world",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(DocsGenerator): %v", err)
|
|
}
|
|
if c.Name() != "DocsGenerator" {
|
|
t.Errorf("Name=%q, want DocsGenerator", c.Name())
|
|
}
|
|
if c.Inputs() == nil {
|
|
t.Error("Inputs() should be non-nil")
|
|
}
|
|
if c.Outputs() == nil {
|
|
t.Error("Outputs() should be non-nil")
|
|
}
|
|
}
|
|
|
|
func TestDocGeneratorAlias_Registered(t *testing.T) {
|
|
c, err := New("DocGenerator", map[string]any{
|
|
"output_format": "pdf",
|
|
"content": "Hello world",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(DocGenerator): %v", err)
|
|
}
|
|
if c.Name() != "DocsGenerator" {
|
|
t.Errorf("Name=%q, want DocsGenerator", c.Name())
|
|
}
|
|
}
|
|
|
|
func TestDocGeneratorAlias_InputForm(t *testing.T) {
|
|
c, err := New("DocGenerator", map[string]any{
|
|
"output_format": "pdf",
|
|
"content": "Hello world",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(DocGenerator): %v", err)
|
|
}
|
|
formGetter, ok := c.(interface{ GetInputForm() map[string]any })
|
|
if !ok {
|
|
t.Fatal("DocGenerator component does not expose GetInputForm")
|
|
}
|
|
content, ok := formGetter.GetInputForm()["content"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("GetInputForm()[content] has type %T, want map", formGetter.GetInputForm()["content"])
|
|
}
|
|
if content["type"] != "line" {
|
|
t.Fatalf("GetInputForm()[content][type] = %v, want line", content["type"])
|
|
}
|
|
}
|
|
|
|
// TestDocsGenerator_Invoke_HappyPath: with valid params, the
|
|
// component runs without error and produces a non-nil output map.
|
|
// Real PDF/DOCX generation needs an actual font file on disk
|
|
// (see docs_generator.go's font initialization); when that's
|
|
// missing the generator returns a soft error.
|
|
func TestDocsGenerator_Invoke_HappyPath(t *testing.T) {
|
|
c, err := New("DocsGenerator", map[string]any{
|
|
"output_format": "txt",
|
|
"content": "Hello world",
|
|
"filename": "test",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(DocsGenerator): %v", err)
|
|
}
|
|
_, _ = c.Invoke(context.Background(), map[string]any{})
|
|
// We do not assert err == nil here because txt output requires
|
|
// the internal writer (which may not be available in this
|
|
// checkout). The test pins that the call doesn't panic.
|
|
}
|
|
|
|
func TestDocsGenerator_Invoke_UsesQueryWhenContentEmpty(t *testing.T) {
|
|
c, err := New("DocsGenerator", map[string]any{
|
|
"output_format": "txt",
|
|
"filename": "query-body",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(DocsGenerator): %v", err)
|
|
}
|
|
out, err := c.Invoke(context.Background(), map[string]any{
|
|
"query": "Hello from begin query",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Invoke: %v", err)
|
|
}
|
|
payload, ok := out["bytes"].([]byte)
|
|
if !ok {
|
|
t.Fatalf("bytes has type %T, want []byte", out["bytes"])
|
|
}
|
|
if !strings.Contains(string(payload), "Hello from begin query") {
|
|
t.Fatalf("payload = %q, want query content", string(payload))
|
|
}
|
|
}
|
|
|
|
func TestDocsGenerator_Invoke_AcceptsDecorationParamNames(t *testing.T) {
|
|
c, err := New("DocsGenerator", map[string]any{
|
|
"output_format": "txt",
|
|
"content": "body",
|
|
"filename": "decorated",
|
|
"header_text": "Canonical Header",
|
|
"footer_text": "Canonical Footer",
|
|
"watermark_text": "Canonical Watermark",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(DocsGenerator): %v", err)
|
|
}
|
|
out, err := c.Invoke(context.Background(), map[string]any{})
|
|
if err != nil {
|
|
t.Fatalf("Invoke canonical params: %v", err)
|
|
}
|
|
payload, ok := out["bytes"].([]byte)
|
|
if !ok {
|
|
t.Fatalf("bytes has type %T, want []byte", out["bytes"])
|
|
}
|
|
text := string(payload)
|
|
if !strings.Contains(text, "Canonical Header") || !strings.Contains(text, "Canonical Footer") {
|
|
t.Fatalf("payload = %q, want canonical header and footer", text)
|
|
}
|
|
|
|
c, err = New("DocsGenerator", map[string]any{
|
|
"output_format": "txt",
|
|
"content": "body",
|
|
"filename": "decorated-alias",
|
|
"header": "Alias Header",
|
|
"footer": "Alias Footer",
|
|
"watermark": "Alias Watermark",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(DocsGenerator alias params): %v", err)
|
|
}
|
|
out, err = c.Invoke(context.Background(), map[string]any{})
|
|
if err != nil {
|
|
t.Fatalf("Invoke alias params: %v", err)
|
|
}
|
|
payload, ok = out["bytes"].([]byte)
|
|
if !ok {
|
|
t.Fatalf("bytes has type %T, want []byte", out["bytes"])
|
|
}
|
|
text = string(payload)
|
|
if !strings.Contains(text, "Alias Header") || !strings.Contains(text, "Alias Footer") {
|
|
t.Fatalf("payload = %q, want alias header and footer", text)
|
|
}
|
|
}
|
|
|
|
func TestDocsGenerator_Invoke_StoresAgentAttachment(t *testing.T) {
|
|
storageFactory := storage.GetStorageFactory()
|
|
prevStorage := storageFactory.GetStorage()
|
|
memStorage := storage.NewMemoryStorage()
|
|
storageFactory.SetStorage(memStorage)
|
|
t.Cleanup(func() { storageFactory.SetStorage(prevStorage) })
|
|
|
|
c, err := New("DocGenerator", map[string]any{
|
|
"output_format": "txt",
|
|
"content": "Hello storage",
|
|
"filename": "stored",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(DocGenerator): %v", err)
|
|
}
|
|
state := canvas.NewCanvasState("run-1", "task-1")
|
|
state.Sys["tenant_id"] = "tenant-1"
|
|
ctx := canvas.WithState(context.Background(), state)
|
|
|
|
out, err := c.Invoke(ctx, map[string]any{})
|
|
if err != nil {
|
|
t.Fatalf("Invoke: %v", err)
|
|
}
|
|
if out["stored"] != true {
|
|
t.Fatalf("stored = %v, want true", out["stored"])
|
|
}
|
|
docID, ok := out["doc_id"].(string)
|
|
if !ok || docID == "" {
|
|
t.Fatalf("doc_id = %v, want non-empty string", out["doc_id"])
|
|
}
|
|
blob, err := memStorage.Get("tenant-1-downloads", docID)
|
|
if err != nil {
|
|
t.Fatalf("stored blob missing: %v", err)
|
|
}
|
|
if string(blob) == "" || !strings.Contains(string(blob), "Hello storage") {
|
|
t.Fatalf("stored blob = %q, want generated content", string(blob))
|
|
}
|
|
download, _ := out["download"].(string)
|
|
var info map[string]any
|
|
if err := json.Unmarshal([]byte(download), &info); err != nil {
|
|
t.Fatalf("download is not JSON: %v", err)
|
|
}
|
|
if info["doc_id"] != docID {
|
|
t.Fatalf("download doc_id = %v, want %s", info["doc_id"], docID)
|
|
}
|
|
if path, _ := info["url"].(string); !strings.HasPrefix(path, "/api/v1/agents/attachments/"+docID+"/download") {
|
|
t.Fatalf("download url = %q, want attachment download path", path)
|
|
}
|
|
if _, ok := out["download_info"].(map[string]any); !ok {
|
|
t.Fatalf("download_info has type %T, want map", out["download_info"])
|
|
}
|
|
}
|