// // 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 testutil import ( "fmt" "strings" "testing" "ragflow/internal/common" "ragflow/internal/dao" "ragflow/internal/entity" "github.com/glebarez/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) // ────────────────────────────────────────────────────────── // Simple Helper Functions // ────────────────────────────────────────────────────────── // StrPtr returns a pointer to the given string. func StrPtr(s string) *string { return &s } // ────────────────────────────────────────────────────────── // Database Helpers // ────────────────────────────────────────────────────────── // SetupTestDB sets up an in-memory SQLite database for testing. // It auto-migrates the given tables (or all common tables if none provided). func SetupTestDB(t *testing.T, tables ...any) *gorm.DB { t.Helper() dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("open sqlite: %v", err) } sqlDB, err := db.DB() if err != nil { t.Fatalf("failed to get sql DB: %v", err) } sqlDB.SetMaxOpenConns(1) if len(tables) == 0 { tables = []any{ &entity.IngestionTask{}, &entity.IngestionTaskLog{}, &entity.Task{}, &entity.Document{}, &entity.Knowledgebase{}, &entity.Tenant{}, &entity.File{}, &entity.File2Document{}, } } if err := db.AutoMigrate(tables...); err != nil { t.Fatalf("auto-migrate: %v", err) } return db } // ReplaceDBForTest replaces dao.DB with the given test database // and returns a cleanup function to restore the original. func ReplaceDBForTest(t *testing.T, db *gorm.DB) func() { t.Helper() origDB := dao.DB dao.DB = db return func() { dao.DB = origDB } } // ────────────────────────────────────────────────────────── // Test Data Configuration (Options Pattern) // ────────────────────────────────────────────────────────── // TestDataOption configures the test data setup. type TestDataOption func(*testDataConfig) type testDataConfig struct { tenantID string kbID string docID string taskID string pipelineID *string docName string docType string } // WithTenantID sets the tenant ID for test data. func WithTenantID(id string) TestDataOption { return func(c *testDataConfig) { c.tenantID = id } } // WithKBID sets the knowledgebase ID for test data. func WithKBID(id string) TestDataOption { return func(c *testDataConfig) { c.kbID = id } } // WithDocID sets the document ID for test data. func WithDocID(id string) TestDataOption { return func(c *testDataConfig) { c.docID = id } } // WithTaskID sets the task ID for test data. func WithTaskID(id string) TestDataOption { return func(c *testDataConfig) { c.taskID = id } } // WithPipelineID sets the pipeline ID for test data. func WithPipelineID(id string) TestDataOption { return func(c *testDataConfig) { pid := id c.pipelineID = &pid } } // WithDocName sets the document name for test data. func WithDocName(name string) TestDataOption { return func(c *testDataConfig) { c.docName = name } } // WithDocType sets the document type for test data. func WithDocType(docType string) TestDataOption { return func(c *testDataConfig) { c.docType = docType } } // SeedTestData seeds the database with test data. // Returns: tenantID, kbID, docID, taskID func SeedTestData(t *testing.T, db *gorm.DB, opts ...TestDataOption) (string, string, string, string) { t.Helper() cfg := &testDataConfig{ tenantID: "tenant-1", kbID: "kb-1", docID: "doc-1", taskID: "task-1", docName: "test.pdf", docType: "pdf", } for _, opt := range opts { opt(cfg) } // Create Tenant if err := db.Create(&entity.Tenant{ ID: cfg.tenantID, LLMID: "gpt-4", Status: StrPtr("1"), }).Error; err != nil { t.Fatalf("create tenant: %v", err) } // Create Knowledgebase if err := db.Create(&entity.Knowledgebase{ ID: cfg.kbID, TenantID: cfg.tenantID, EmbdID: "embd-1", Status: StrPtr("1"), ParserConfig: entity.JSONMap{}, }).Error; err != nil { t.Fatalf("create kb: %v", err) } // Create Document doc := &entity.Document{ ID: cfg.docID, KbID: cfg.kbID, Name: &cfg.docName, ParserID: "naive", ParserConfig: entity.JSONMap{}, PipelineID: cfg.pipelineID, Status: StrPtr("1"), Type: cfg.docType, } loc := "doc_store/" + cfg.docID doc.Location = &loc if err := db.Create(doc).Error; err != nil { t.Fatalf("create doc: %v", err) } // Create Task if err := db.Create(&entity.Task{ ID: cfg.taskID, DocID: cfg.docID, TaskType: "dataflow", FromPage: 0, ToPage: 100000, }).Error; err != nil { t.Fatalf("create task: %v", err) } // Create IngestionTask if err := db.Create(&entity.IngestionTask{ ID: cfg.taskID, UserID: "u1", DocumentID: cfg.docID, DatasetID: cfg.kbID, Status: common.RUNNING, }).Error; err != nil { t.Fatalf("create ingestion task: %v", err) } return cfg.tenantID, cfg.kbID, cfg.docID, cfg.taskID } // ────────────────────────────────────────────────────────── // Test Entity Helpers // ────────────────────────────────────────────────────────── // TestTask creates a test IngestionTask. func TestTask(docID string) *entity.IngestionTask { return &entity.IngestionTask{ ID: "task-1", DocumentID: docID, DatasetID: "kb-1", } } // TestDoc creates a test Document. func TestDoc(id string, docType string, suffix string, kbID ...string) *entity.Document { name := "test." + suffix loc := "doc_store/" + id kb := "kb-1" if len(kbID) > 0 { kb = kbID[0] } return &entity.Document{ ID: id, KbID: kb, Type: docType, Suffix: suffix, Name: &name, Location: &loc, ParserID: "naive", } }