Files
ragflow/internal/service/dataset_delete_index_test.go
Haruko386 97718ec779 feat[Go] implement datasets/<dataset_id>/<index_type> DELETE (#16257)
### What problem does this PR solve?

As title

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2026-06-24 14:47:55 +08:00

268 lines
8.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 service
import (
"context"
"errors"
"testing"
"time"
"gorm.io/gorm"
"ragflow/internal/common"
"ragflow/internal/dao"
"ragflow/internal/entity"
)
type deleteIndexDocEngine struct {
fakeChatDocEngine
deleteCalls []deleteIndexDocEngineCall
}
type deleteIndexDocEngineCall struct {
condition map[string]interface{}
indexName string
datasetID string
}
func (e *deleteIndexDocEngine) DeleteChunks(_ context.Context, condition map[string]interface{}, indexName string, datasetID string) (int64, error) {
e.deleteCalls = append(e.deleteCalls, deleteIndexDocEngineCall{
condition: condition,
indexName: indexName,
datasetID: datasetID,
})
return 1, nil
}
func testDatasetServiceForDeleteIndex(docEngine *deleteIndexDocEngine) *DatasetService {
return &DatasetService{
kbDAO: dao.NewKnowledgebaseDAO(),
taskDAO: dao.NewTaskDAO(),
docEngine: docEngine,
}
}
func insertDeleteIndexKB(t *testing.T, indexType string, taskID string) {
t.Helper()
finishAt := time.Date(2026, 6, 23, 10, 0, 0, 0, time.UTC)
kb := &entity.Knowledgebase{
ID: "kb-1",
TenantID: "user-1",
Name: "test-kb",
EmbdID: "embedding@OpenAI",
CreatedBy: "user-1",
Permission: string(entity.TenantPermissionMe),
ParserID: "naive",
ParserConfig: entity.JSONMap{},
Status: sptr("1"),
}
switch indexType {
case "graph":
kb.GraphragTaskID = &taskID
kb.GraphragTaskFinishAt = &finishAt
case "raptor":
kb.RaptorTaskID = &taskID
kb.RaptorTaskFinishAt = &finishAt
case "mindmap":
kb.MindmapTaskID = &taskID
kb.MindmapTaskFinishAt = &finishAt
}
if err := dao.DB.Create(kb).Error; err != nil {
t.Fatalf("insert kb: %v", err)
}
if taskID != "" {
if err := dao.DB.Create(&entity.Task{ID: taskID, DocID: "doc-1", TaskType: indexTypeToTaskType[indexType]}).Error; err != nil {
t.Fatalf("insert task: %v", err)
}
}
}
func TestDatasetServiceDeleteIndexGraphWipeFalseOnlyCancelsTask(t *testing.T) {
db := setupServiceTestDB(t)
pushServiceDB(t, db)
insertDeleteIndexKB(t, "graph", "graph-task")
docEngine := &deleteIndexDocEngine{}
code, err := testDatasetServiceForDeleteIndex(docEngine).DeleteIndex("user-1", "kb-1", "graph", false)
if err != nil {
t.Fatalf("DeleteIndex failed: %v", err)
}
if code != common.CodeSuccess {
t.Fatalf("expected success code, got %d", code)
}
if len(docEngine.deleteCalls) != 0 {
t.Fatalf("wipe=false should not delete doc-store artefacts, got %#v", docEngine.deleteCalls)
}
assertDeleteIndexTaskDeleted(t, "graph-task")
kb := getDeleteIndexKB(t)
if kb.GraphragTaskID == nil || *kb.GraphragTaskID != "" {
t.Fatalf("expected graphrag_task_id to be cleared to empty string, got %#v", kb.GraphragTaskID)
}
if kb.GraphragTaskFinishAt != nil {
t.Fatalf("expected graphrag_task_finish_at to be cleared, got %#v", kb.GraphragTaskFinishAt)
}
}
func TestDatasetServiceDeleteIndexGraphWipeTrueDeletesArtefacts(t *testing.T) {
db := setupServiceTestDB(t)
pushServiceDB(t, db)
insertDeleteIndexKB(t, "graph", "graph-task")
docEngine := &deleteIndexDocEngine{}
code, err := testDatasetServiceForDeleteIndex(docEngine).DeleteIndex("user-1", "kb-1", "graph", true)
if err != nil {
t.Fatalf("DeleteIndex failed: %v", err)
}
if code != common.CodeSuccess {
t.Fatalf("expected success code, got %d", code)
}
if len(docEngine.deleteCalls) != 1 {
t.Fatalf("expected one doc-store delete call, got %#v", docEngine.deleteCalls)
}
call := docEngine.deleteCalls[0]
if call.indexName != "ragflow_user-1" || call.datasetID != "kb-1" {
t.Fatalf("unexpected delete target: %#v", call)
}
if call.condition["kb_id"] != "kb-1" {
t.Fatalf("delete condition must include kb_id, got %#v", call.condition)
}
assertStringSet(t, call.condition["knowledge_graph_kwd"], []string{"graph", "subgraph", "entity", "relation", "community_report"})
assertDeleteIndexTaskDeleted(t, "graph-task")
}
func TestDatasetServiceDeleteIndexRaptorWipeTrueDeletesRaptorArtefacts(t *testing.T) {
db := setupServiceTestDB(t)
pushServiceDB(t, db)
insertDeleteIndexKB(t, "raptor", "raptor-task")
docEngine := &deleteIndexDocEngine{}
code, err := testDatasetServiceForDeleteIndex(docEngine).DeleteIndex("user-1", "kb-1", "raptor", true)
if err != nil {
t.Fatalf("DeleteIndex failed: %v", err)
}
if code != common.CodeSuccess {
t.Fatalf("expected success code, got %d", code)
}
if len(docEngine.deleteCalls) != 1 {
t.Fatalf("expected one doc-store delete call, got %#v", docEngine.deleteCalls)
}
call := docEngine.deleteCalls[0]
if call.condition["kb_id"] != "kb-1" {
t.Fatalf("delete condition must include kb_id, got %#v", call.condition)
}
assertStringSet(t, call.condition["raptor_kwd"], []string{"raptor"})
assertDeleteIndexTaskDeleted(t, "raptor-task")
kb := getDeleteIndexKB(t)
if kb.RaptorTaskID == nil || *kb.RaptorTaskID != "" {
t.Fatalf("expected raptor_task_id to be cleared to empty string, got %#v", kb.RaptorTaskID)
}
if kb.RaptorTaskFinishAt != nil {
t.Fatalf("expected raptor_task_finish_at to be cleared, got %#v", kb.RaptorTaskFinishAt)
}
}
func TestDatasetServiceDeleteIndexMindmapDoesNotDeleteDocStore(t *testing.T) {
db := setupServiceTestDB(t)
pushServiceDB(t, db)
insertDeleteIndexKB(t, "mindmap", "mindmap-task")
docEngine := &deleteIndexDocEngine{}
code, err := testDatasetServiceForDeleteIndex(docEngine).DeleteIndex("user-1", "kb-1", "mindmap", true)
if err != nil {
t.Fatalf("DeleteIndex failed: %v", err)
}
if code != common.CodeSuccess {
t.Fatalf("expected success code, got %d", code)
}
if len(docEngine.deleteCalls) != 0 {
t.Fatalf("mindmap delete should not delete doc-store artefacts, got %#v", docEngine.deleteCalls)
}
assertDeleteIndexTaskDeleted(t, "mindmap-task")
kb := getDeleteIndexKB(t)
if kb.MindmapTaskID == nil || *kb.MindmapTaskID != "" {
t.Fatalf("expected mindmap_task_id to be cleared to empty string, got %#v", kb.MindmapTaskID)
}
if kb.MindmapTaskFinishAt != nil {
t.Fatalf("expected mindmap_task_finish_at to be cleared, got %#v", kb.MindmapTaskFinishAt)
}
}
func TestDatasetServiceDeleteIndexRejectsInvalidType(t *testing.T) {
db := setupServiceTestDB(t)
pushServiceDB(t, db)
code, err := testDatasetServiceForDeleteIndex(&deleteIndexDocEngine{}).DeleteIndex("user-1", "kb-1", "invalid", true)
if err == nil {
t.Fatal("expected invalid index type error")
}
if code != common.CodeArgumentError {
t.Fatalf("expected argument error code, got %d", code)
}
}
func assertDeleteIndexTaskDeleted(t *testing.T, taskID string) {
t.Helper()
var task entity.Task
err := dao.DB.Where("id = ?", taskID).First(&task).Error
if !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected task %s to be deleted, got err=%v task=%#v", taskID, err, task)
}
}
func getDeleteIndexKB(t *testing.T) entity.Knowledgebase {
t.Helper()
var kb entity.Knowledgebase
if err := dao.DB.Where("id = ?", "kb-1").First(&kb).Error; err != nil {
t.Fatalf("fetch kb: %v", err)
}
return kb
}
func assertStringSet(t *testing.T, actual interface{}, expected []string) {
t.Helper()
items, ok := actual.([]interface{})
if !ok {
t.Fatalf("expected []interface{}, got %#v", actual)
}
if len(items) != len(expected) {
t.Fatalf("expected %d items, got %#v", len(expected), items)
}
seen := make(map[string]bool, len(items))
for _, item := range items {
value, ok := item.(string)
if !ok {
t.Fatalf("expected string item, got %#v", item)
}
seen[value] = true
}
for _, item := range expected {
if !seen[item] {
t.Fatalf("missing %q in %#v", item, items)
}
}
}