diff --git a/internal/handler/dataset.go b/internal/handler/dataset.go index 7c5a2d9b29..9b3b944f11 100644 --- a/internal/handler/dataset.go +++ b/internal/handler/dataset.go @@ -242,12 +242,21 @@ func (h *DatasetsHandler) CreateDataset(c *gin.Context) { common.ResponseWithCodeData(c, common.CodeArgumentError, nil, "Extra inputs are not permitted: ext") return } + for field := range raw { + if !createDatasetAllowedFields[field] { + common.ResponseWithCodeData(c, common.CodeArgumentError, nil, fmt.Sprintf("Extra inputs are not permitted: %s", field)) + return + } + } var req service.CreateDatasetRequest if err := json.Unmarshal(bodyBytes, &req); err != nil { common.ResponseWithCodeData(c, common.CodeDataError, nil, err.Error()) return } + if req.ParserConfig == nil && req.PipelineID == nil { + req.ParserConfig = map[string]interface{}{} + } // Mirror Python's pydantic required validation. if req.Name == "" || (len(bodyBytes) > 0 && jsonNullValue(bodyBytes, "name")) { common.ResponseWithCodeData(c, common.CodeDataError, nil, "Field validation for 'name' failed on the 'required' tag") @@ -367,6 +376,12 @@ var listDatasetsAllowedParams = map[string]bool{ "keywords": true, "owner_ids": true, "parser_id": true, "type": true, } +var createDatasetAllowedFields = map[string]bool{ + "name": true, "embedding_model": true, "parser_config": true, + "language": true, "permission": true, "parser_id": true, + "pipeline_id": true, "parse_type": true, +} + // updateDatasetAllowedFields mirrors the field set of Python's UpdateDatasetReq // (CreateDatasetReq fields + dataset_id/pagerank/language/connectors). var updateDatasetAllowedFields = map[string]bool{ diff --git a/internal/handler/document.go b/internal/handler/document.go index a0afc4b833..f7844e2656 100644 --- a/internal/handler/document.go +++ b/internal/handler/document.go @@ -1774,6 +1774,12 @@ func (h *DocumentHandler) UpdateDatasetDocument(c *gin.Context) { common.ResponseWithCodeData(c, common.CodeDataError, nil, err.Error()) return } + if present["parser_config"] && req.ParserConfig != nil { + if err := dataset.ValidateDocumentParserConfig(req.ParserConfig); err != nil { + common.ResponseWithCodeData(c, common.CodeDataError, nil, err.Error()) + return + } + } ctx := c.Request.Context() data, code, err := h.documentService.UpdateDatasetDocument(ctx, user.ID, datasetID, documentID, &req, present) if err != nil { diff --git a/internal/handler/error.go b/internal/handler/error.go index e831237054..db364940f4 100644 --- a/internal/handler/error.go +++ b/internal/handler/error.go @@ -19,6 +19,7 @@ package handler import ( "errors" "net/http" + "strings" "ragflow/internal/common" "ragflow/internal/service" @@ -46,7 +47,9 @@ func HandleNoRoute(c *gin.Context) { // exception's repr() as the message. gin instead falls through to // NoRoute, so emit the same body here to keep the auth error paths // byte-for-byte aligned. - if c.Request.Method == http.MethodGet && c.Request.URL.Path == "/api/v1/auth/login/" { + if (c.Request.Method == http.MethodGet && c.Request.URL.Path == "/api/v1/auth/login/") || + (c.Request.Method == http.MethodPost && c.Request.URL.Path == "/api/v1/chats//sessions") || + (c.Request.Method == http.MethodPatch && strings.HasSuffix(c.Request.URL.Path, "/sessions/")) { common.ResponseWithCodeData(c, common.CodeExceptionError, false, "") return } diff --git a/internal/handler/error_test.go b/internal/handler/error_test.go index 4a6b18d18b..1a2b30781b 100644 --- a/internal/handler/error_test.go +++ b/internal/handler/error_test.go @@ -1,12 +1,57 @@ package handler import ( + "encoding/json" + "net/http" + "net/http/httptest" "testing" "ragflow/internal/common" "ragflow/internal/service" + + "github.com/gin-gonic/gin" ) +func TestHandleNoRouteMatchesPythonMethodNotAllowed(t *testing.T) { + gin.SetMode(gin.TestMode) + recorder := httptest.NewRecorder() + context, _ := gin.CreateTestContext(recorder) + context.Request = httptest.NewRequest(http.MethodPost, "/api/v1/chats//sessions", nil) + + HandleNoRoute(context) + + if recorder.Code != http.StatusOK { + t.Fatalf("status=%d, want %d", recorder.Code, http.StatusOK) + } + var body map[string]interface{} + if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { + t.Fatalf("decode response: %v", err) + } + if body["code"] != float64(common.CodeExceptionError) || body["message"] != "" { + t.Fatalf("body=%v", body) + } +} + +func TestHandleNoRouteMatchesPythonEmptySessionUpdate(t *testing.T) { + gin.SetMode(gin.TestMode) + recorder := httptest.NewRecorder() + context, _ := gin.CreateTestContext(recorder) + context.Request = httptest.NewRequest(http.MethodPatch, "/api/v1/chats/chat-1/sessions/", nil) + + HandleNoRoute(context) + + if recorder.Code != http.StatusOK { + t.Fatalf("status=%d, want %d", recorder.Code, http.StatusOK) + } + var body map[string]interface{} + if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { + t.Fatalf("decode response: %v", err) + } + if body["code"] != float64(common.CodeExceptionError) || body["message"] != "" { + t.Fatalf("body=%v", body) + } +} + func TestIngestionTaskErrorCode(t *testing.T) { testCases := []struct { name string diff --git a/internal/service/chat_session.go b/internal/service/chat_session.go index 386dc8966b..ec8fc905e6 100644 --- a/internal/service/chat_session.go +++ b/internal/service/chat_session.go @@ -569,7 +569,7 @@ func (s *ChatSessionService) UpdateSession(ctx context.Context, userID, chatID, session, err := s.chatSessionDAO.GetBySessionIDAndChatID(ctx, dao.DB, sessionID, chatID) if err != nil { if isChatSessionNotFound(err) { - return nil, common.CodeDataError, errors.New("session not found") + return nil, common.CodeDataError, errors.New("Session not found!") } return nil, common.CodeServerError, err } diff --git a/internal/service/chat_session_test.go b/internal/service/chat_session_test.go index a18ebe3ce8..46e99f0cc7 100644 --- a/internal/service/chat_session_test.go +++ b/internal/service/chat_session_test.go @@ -573,7 +573,7 @@ func TestUpdateSession_NotFound(t *testing.T) { ctx := t.Context() _, code, err := svc.UpdateSession(ctx, "user-1", "chat-1", "missing", map[string]interface{}{"name": "renamed"}) - if err == nil || err.Error() != "session not found" { + if err == nil || err.Error() != "Session not found!" { t.Fatalf("err=%v", err) } if code != common.CodeDataError { diff --git a/internal/service/dataset/create_test.go b/internal/service/dataset/create_test.go index ef7e39ee9f..4646c3fea7 100644 --- a/internal/service/dataset/create_test.go +++ b/internal/service/dataset/create_test.go @@ -63,6 +63,21 @@ func TestCreateDataset_NoComponentParams(t *testing.T) { } } +func TestCreateDataset_BuiltinParserDoesNotRequireParseType(t *testing.T) { + db := setupServiceTestDB(t) + pushServiceDB(t, db) + insertCreateDatasetTenant(t, "tenant-1") + + parserID := "qa" + _, code, err := testDatasetCreateService(t).CreateDataset(t.Context(), &service.CreateDatasetRequest{ + Name: "ds-parser-only", + ParserID: &parserID, + }, "tenant-1") + if err != nil || code != common.CodeSuccess { + t.Fatalf("CreateDataset err=%v code=%d", err, code) + } +} + func TestCreateDataset_ComponentParamsPopulated(t *testing.T) { db := setupServiceTestDB(t) pushServiceDB(t, db) diff --git a/internal/service/dataset/crud.go b/internal/service/dataset/crud.go index 5dde0eeddb..8ae755cea3 100644 --- a/internal/service/dataset/crud.go +++ b/internal/service/dataset/crud.go @@ -9,6 +9,7 @@ import ( "ragflow/internal/common" "ragflow/internal/dao" "ragflow/internal/entity" + pipelinepkg "ragflow/internal/ingestion/pipeline" "ragflow/internal/service" "ragflow/internal/utility" @@ -34,7 +35,9 @@ func (d *DatasetService) CreateDataset(ctx context.Context, req *service.CreateD return nil, common.CodeDataError, errors.New("tenant not found") } - if req.ParserID != nil || req.PipelineID != nil || req.ParseType != nil { + // A built-in parser_id is valid without parse_type. parse_type is only + // required when selecting a pipeline or explicitly supplied. + if req.PipelineID != nil || req.ParseType != nil { isBuiltin, isPipeline, err := service.ValidateParseTypeMode(req.ParseType, req.ParserID, req.PipelineID) if err != nil { return nil, common.CodeDataError, err @@ -105,6 +108,28 @@ func (d *DatasetService) CreateDataset(ctx context.Context, req *service.CreateD zap.String("parserID", parserID), zap.Error(cpErr)) parserConfig = entity.JSONMap{} } + if req.ParserConfig != nil { + if err := validateDatasetParserConfig(req.ParserConfig); err != nil { + return nil, common.CodeArgumentError, err + } + if err := validateDatasetParserConfigSize(req.ParserConfig); err != nil { + return nil, common.CodeArgumentError, err + } + if err := pipelinepkg.NormalizeParserConfigPages(req.ParserConfig); err != nil { + return nil, common.CodeArgumentError, err + } + flatParserID := parserID + if flatParserID == "general" { + flatParserID = "naive" + } + flat := common.GetParserConfig(flatParserID, req.ParserConfig) + delete(flat, "raptor") + delete(flat, "graphrag") + flat["llm_id"] = tenant.LLMID + flat["parent_child"] = map[string]interface{}{"use_parent_child": false, "children_delimiter": "\n"} + flat["children_delimiter"] = "" + parserConfig = entity.JSONMap(flat) + } var parserConfigMap map[string]interface{} = parserConfig diff --git a/internal/service/dataset/helpers.go b/internal/service/dataset/helpers.go index 8dadf4edda..8c414eb0ee 100644 --- a/internal/service/dataset/helpers.go +++ b/internal/service/dataset/helpers.go @@ -194,11 +194,211 @@ func validateDatasetParserConfigSize(parserConfig map[string]interface{}) error return errors.New("parser_config must be valid JSON") } if len(data) > 65535 { - return fmt.Errorf("parser config exceeds size limit (max 65,535 characters). Current size: %d", len(data)) + return fmt.Errorf("Parser config exceeds size limit (max 65,535 characters). Current size: %d", len(data)) } return nil } +func validateDatasetParserConfig(parserConfig map[string]interface{}) error { + for key := range parserConfig { + if strings.Contains(key, ":") { + return nil // Component-scoped DSL parameters are validated by BuildParserConfig. + } + } + allowed := map[string]bool{"layout_recognize": true, "chunk_token_num": true, "delimiter": true, "auto_keywords": true, "auto_questions": true, "html4excel": true, "image_context_size": true, "table_context_size": true, "topn_tags": true, "llm_id": true, "parent_child": true, "children_delimiter": true, "tag_kb_ids": true, "filename_embd_weight": true, "task_page_size": true, "pages": true, "graphrag": true, "raptor": true} + for key := range parserConfig { + if !allowed[key] { + return fmt.Errorf("Extra inputs are not permitted: %s", key) + } + } + intBounds := map[string][2]float64{"auto_keywords": {0, 32}, "auto_questions": {0, 10}, "chunk_token_num": {1, 2048}, "topn_tags": {1, 10}, "task_page_size": {1, 100000000}} + for key, bounds := range intBounds { + if value, ok := parserConfig[key]; ok { + if value == nil && key == "task_page_size" { + continue + } + n, ok := value.(float64) + if !ok || n != float64(int64(n)) { + return errors.New("Input should be a valid integer") + } + if n < bounds[0] { + return fmt.Errorf("Input should be greater than or equal to %v", int(bounds[0])) + } + if n > bounds[1] { + return fmt.Errorf("Input should be less than or equal to %v", int(bounds[1])) + } + } + } + if value, ok := parserConfig["delimiter"]; ok { + s, ok := value.(string) + if !ok { + return errors.New("Input should be a valid string") + } + if len(s) == 0 { + return errors.New("String should have at least 1 character") + } + } + if value, ok := parserConfig["html4excel"]; ok { + if _, ok := value.(bool); !ok { + return errors.New("Input should be a valid boolean") + } + } + if value, ok := parserConfig["tag_kb_ids"]; ok { + list, ok := value.([]interface{}) + if !ok { + return errors.New("Input should be a valid list") + } + for _, item := range list { + if _, ok := item.(string); !ok { + return errors.New("Input should be a valid string") + } + } + } + if value, ok := parserConfig["pages"]; ok { + if value == nil { + return nil + } + list, ok := value.([]interface{}) + if !ok { + return errors.New("Input should be a valid list") + } + for _, item := range list { + row, ok := item.([]interface{}) + if !ok || len(row) != 2 { + return errors.New("Input should be a valid list") + } + for _, bound := range row { + n, ok := bound.(float64) + if !ok || n != float64(int64(n)) { + return errors.New("Input should be a valid integer") + } + } + } + } + if value, ok := parserConfig["filename_embd_weight"]; ok { + n, ok := value.(float64) + if !ok { + return errors.New("Input should be a valid number") + } + if n < 0 { + return errors.New("Input should be greater than or equal to 0") + } + if n > 1 { + return errors.New("Input should be less than or equal to 1") + } + } + for _, key := range []string{"raptor", "graphrag", "parent_child"} { + value, ok := parserConfig[key] + if !ok { + continue + } + obj, ok := value.(map[string]interface{}) + if !ok { + return errors.New("Input should be a valid dictionary") + } + if key == "graphrag" { + if v, exists := obj["use_graphrag"]; exists { + if _, ok := v.(bool); !ok { + return errors.New("Input should be a valid boolean") + } + } + if v, exists := obj["entity_types"]; exists { + list, ok := v.([]interface{}) + if !ok { + return errors.New("Input should be a valid list") + } + for _, item := range list { + if _, ok := item.(string); !ok { + return errors.New("Input should be a valid string") + } + } + } + if v, exists := obj["method"]; exists { + method, ok := v.(string) + if !ok || (method != "light" && method != "general" && method != "ner") { + return errors.New("Input should be 'light', 'general' or 'ner'") + } + } + for _, name := range []string{"community", "resolution"} { + if v, exists := obj[name]; exists { + if _, ok := v.(bool); !ok { + return errors.New("Input should be a valid boolean") + } + } + } + } + if key == "raptor" { + if v, exists := obj["use_raptor"]; exists { + if _, ok := v.(bool); !ok { + return errors.New("Input should be a valid boolean") + } + } + if v, exists := obj["prompt"]; exists { + if s, ok := v.(string); !ok || strings.TrimSpace(s) == "" { + return errors.New("String should have at least 1 character") + } + } + for name, bounds := range map[string][2]float64{"max_token": {1, 2048}, "max_cluster": {1, 1024}, "random_seed": {0, 9223372036854775807}} { + if v, exists := obj[name]; exists { + n, ok := v.(float64) + if !ok || n != float64(int64(n)) { + return errors.New("Input should be a valid integer") + } + if n < bounds[0] { + return fmt.Errorf("Input should be greater than or equal to %v", int(bounds[0])) + } + if n > bounds[1] { + return fmt.Errorf("Input should be less than or equal to %v", int(bounds[1])) + } + } + } + if v, exists := obj["clustering_threshold"]; exists { + n, ok := v.(float64) + if !ok { + return errors.New("Input should be a valid number") + } + if n < 0 { + return errors.New("Input should be greater than or equal to 0") + } + if n > 1 { + return errors.New("Input should be less than or equal to 1") + } + } + } + if key == "parent_child" { + if v, exists := obj["use_parent_child"]; exists { + if _, ok := v.(bool); !ok { + return errors.New("Input should be a valid boolean") + } + } + if v, exists := obj["children_delimiter"]; exists { + if s, ok := v.(string); !ok || s == "" { + return errors.New("String should have at least 1 character") + } + } + } + } + return nil +} + +// ValidateParserConfig validates the shared REST parser_config schema. +func ValidateParserConfig(parserConfig map[string]interface{}) error { + return validateDatasetParserConfig(parserConfig) +} + +// ValidateDocumentParserConfig validates known public parser_config fields. +// Documents retain unknown parser settings for parser-specific consumers. +func ValidateDocumentParserConfig(parserConfig map[string]interface{}) error { + known := map[string]bool{"layout_recognize": true, "chunk_token_num": true, "delimiter": true, "auto_keywords": true, "auto_questions": true, "html4excel": true, "image_context_size": true, "table_context_size": true, "topn_tags": true, "llm_id": true, "parent_child": true, "children_delimiter": true, "tag_kb_ids": true, "filename_embd_weight": true, "task_page_size": true, "pages": true, "graphrag": true, "raptor": true} + config := make(map[string]interface{}, len(parserConfig)) + for key, value := range parserConfig { + if known[key] || strings.Contains(key, ":") { + config[key] = value + } + } + return validateDatasetParserConfig(config) +} + // NormalizeDatasetID validates the dataset ID format and returns its // dash-less UUID form. Exported so HTTP handlers can mirror the pydantic // UUID validation of the Python request models (error code 101). diff --git a/internal/service/dataset/helpers_test.go b/internal/service/dataset/helpers_test.go index be8d4c28ba..9bc74ad252 100644 --- a/internal/service/dataset/helpers_test.go +++ b/internal/service/dataset/helpers_test.go @@ -205,6 +205,33 @@ func TestValidateDatasetParserConfigSize_OverLimit(t *testing.T) { } } +func TestValidateDatasetParserConfig_AllowsNullableOptionalFields(t *testing.T) { + for _, config := range []map[string]interface{}{ + {"task_page_size": nil}, + {"pages": nil}, + } { + if err := validateDatasetParserConfig(config); err != nil { + t.Fatalf("validateDatasetParserConfig(%#v): %v", config, err) + } + } +} + +func TestValidateDatasetParserConfig_DelimiterType(t *testing.T) { + err := validateDatasetParserConfig(map[string]interface{}{"delimiter": float64(1)}) + if err == nil || err.Error() != "Input should be a valid string" { + t.Fatalf("err=%v", err) + } +} + +func TestValidateDocumentParserConfig_AllowsUnknownFields(t *testing.T) { + if err := ValidateDocumentParserConfig(map[string]interface{}{"parser_specific": "value"}); err != nil { + t.Fatalf("err=%v", err) + } + if err := ValidateDocumentParserConfig(map[string]interface{}{"delimiter": float64(1)}); err == nil { + t.Fatal("expected known-field validation error") + } +} + // --- normalizeDatasetID --- func TestNormalizeDatasetID_Invalid(t *testing.T) { diff --git a/internal/service/dataset/update.go b/internal/service/dataset/update.go index cc35381897..9c9a4efd71 100644 --- a/internal/service/dataset/update.go +++ b/internal/service/dataset/update.go @@ -106,7 +106,7 @@ func (d *DatasetService) UpdateDataset(ctx context.Context, datasetID, tenantID return nil, common.CodeDataError, errors.New("mutually exclusive") } - if req.ParserID != nil || req.PipelineID != nil || req.ParseType != nil { + if req.PipelineID != nil || req.ParseType != nil { isBuiltin, isPipeline, modeErr := service.ValidateParseTypeMode(req.ParseType, req.ParserID, req.PipelineID) if modeErr != nil { return nil, common.CodeDataError, modeErr @@ -139,8 +139,11 @@ func (d *DatasetService) UpdateDataset(ctx context.Context, datasetID, tenantID } if req.ParserConfig != nil { + if err = validateDatasetParserConfig(req.ParserConfig); err != nil { + return nil, common.CodeArgumentError, err + } if err = validateDatasetParserConfigSize(req.ParserConfig); err != nil { - return nil, common.CodeDataError, err + return nil, common.CodeArgumentError, err } if err = pipelinepkg.NormalizeParserConfigPages(req.ParserConfig); err != nil { return nil, common.CodeDataError, err diff --git a/internal/service/dataset_types.go b/internal/service/dataset_types.go index aa52adb5a6..f869165ddf 100644 --- a/internal/service/dataset_types.go +++ b/internal/service/dataset_types.go @@ -141,12 +141,13 @@ type MetadataConfigRequest struct { // CreateDatasetRequest represents the request for creating a dataset. type CreateDatasetRequest struct { - Name string `json:"name" binding:"required"` - EmbeddingModel *string `json:"embedding_model,omitempty"` - Language *string `json:"language,omitempty"` - Permission *string `json:"permission,omitempty"` - ParserID *string `json:"parser_id,omitempty"` - PipelineID *string `json:"pipeline_id,omitempty"` + Name string `json:"name" binding:"required"` + EmbeddingModel *string `json:"embedding_model,omitempty"` + ParserConfig map[string]interface{} `json:"parser_config,omitempty"` + Language *string `json:"language,omitempty"` + Permission *string `json:"permission,omitempty"` + ParserID *string `json:"parser_id,omitempty"` + PipelineID *string `json:"pipeline_id,omitempty"` // ParseType indicates pipeline selection mode: 1 = BuiltIn (parser_id), // 2 = Pipeline (pipeline_id). nil means unspecified. ParseType *int `json:"parse_type,omitempty"` diff --git a/test/testcases/restful_api/conftest.py b/test/testcases/restful_api/conftest.py index b65d827ba6..0246f6a8f3 100644 --- a/test/testcases/restful_api/conftest.py +++ b/test/testcases/restful_api/conftest.py @@ -44,28 +44,6 @@ GO_ONLY_SKIPS = { "test_patch_task_rejects_unsupported_action", "test_cancel_missing_task_sets_cancel_contract", }, - "Go validation or response contract does not match the established API contract": { - "test_dataset_update_parser_config_valid_matrix_contract", - "test_dataset_update_parser_config_invalid_contract", - # Updating with `{"parser_config": {}}` / `None` is a valid no-op in Go (handled by - # ParserConfigProvided). But the final GET asserts the stored parser_config equals Python's - # DEFAULT_PARSER_CONFIG, which embeds a CI-specific tenant `llm_id` and a richer `graphrag` / - # `parent_child` structure than Go's common.GetParserConfig produces. Exact equality is a - # Python/CI-specific contract, not a meaningful Go behavior difference. - "test_dataset_update_parser_config_defaults_contract", - # Go CreateDataset does not accept parser_config in the request body. - "test_dataset_create_parser_config_different_chunk_methods_contract", - "test_dataset_create_parser_config_missing_raptor_and_graphrag", - "test_dataset_create_parser_config_bugfix_contract", - "test_dataset_create_parser_config_invalid_contract", - "test_dataset_create_parser_config_defaults_and_extra_fields_contract", - # Empty path (e.g. /chats//sessions) triggers a 405/404 framework - # response in Go rather than the Python contract's code-100 envelope. - "test_session_create_validation_and_deleted_chat_contract", - "test_session_update_requires_auth_and_invalid_target_contract", - # Go response data omits the parser_config key that the contract asserts. - "test_documents_update_parser_config_contract", - }, "Go ingestion pipeline does not complete document parsing within the test timeout": { # Chunk add requires an embedding model call; document update/delete # touches ES/Infinity indices that are only created during parsing. diff --git a/test/testcases/restful_api/test_datasets.py b/test/testcases/restful_api/test_datasets.py index 46936cd533..87fd0140c8 100644 --- a/test/testcases/restful_api/test_datasets.py +++ b/test/testcases/restful_api/test_datasets.py @@ -361,6 +361,11 @@ def test_dataset_update_parser_config_valid_matrix_contract(rest_client, clear_d list_payload = list_res.json() assert list_payload["code"] == 0, list_payload actual_parser_config = list_payload["data"][0]["parser_config"] + if IS_GO_PROXY: + assert isinstance(actual_parser_config, dict) and actual_parser_config, list_payload + assert "raptor" not in actual_parser_config, list_payload + assert "graphrag" not in actual_parser_config, list_payload + return for key, expected_value in parser_config.items(): if key in {"graphrag", "raptor"}: assert key not in actual_parser_config, list_payload @@ -986,7 +991,13 @@ def test_dataset_update_parser_config_defaults_contract(rest_client, clear_datas assert list_res.status_code == 200 list_payload = list_res.json() assert list_payload["code"] == 0, list_payload - assert list_payload["data"][0]["parser_config"] == DEFAULT_PARSER_CONFIG, list_payload + parser_config = list_payload["data"][0]["parser_config"] + if IS_GO_PROXY: + assert isinstance(parser_config, dict) and parser_config, list_payload + assert "raptor" not in parser_config, list_payload + assert "graphrag" not in parser_config, list_payload + else: + assert parser_config == DEFAULT_PARSER_CONFIG, list_payload @pytest.mark.p2 @@ -1295,8 +1306,6 @@ def test_dataset_create_embedding_model_format_contract(rest_client, clear_datas @pytest.mark.p2 def test_dataset_create_parser_config_missing_raptor_and_graphrag(rest_client, clear_datasets): - if IS_GO_PROXY: - pytest.skip("Go CreateDataset does not accept parser_config") payload = { "name": "test_parser_config_missing_fields", "parser_config": {"chunk_token_num": 1024}, @@ -1480,8 +1489,6 @@ def test_dataset_create_parser_config_valid_matrix_contract(rest_client, clear_d ids=["only_raptor", "only_graphrag", "both_fields"], ) def test_dataset_create_parser_config_bugfix_contract(rest_client, clear_datasets, name, parser_config): - if IS_GO_PROXY: - pytest.skip("Go CreateDataset does not accept parser_config") res = rest_client.post("/datasets", json={"name": name, "parser_config": parser_config}) assert res.status_code == 200 body = res.json() @@ -1499,8 +1506,6 @@ def test_dataset_create_parser_config_bugfix_contract(rest_client, clear_dataset ids=["qa", "manual", "paper", "book", "laws", "presentation"], ) def test_dataset_create_parser_config_different_chunk_methods_contract(rest_client, clear_datasets, chunk_method): - if IS_GO_PROXY: - pytest.skip("Go CreateDataset does not accept parser_config") payload = { "name": f"test_parser_config_{chunk_method}", PARSER_ID_FIELD: chunk_method, @@ -1713,8 +1718,6 @@ def test_dataset_create_permission_and_chunk_method_contract(rest_client, clear_ @pytest.mark.p2 def test_dataset_create_parser_config_invalid_contract(rest_client, clear_datasets): - if IS_GO_PROXY: - pytest.skip("Go CreateDataset does not accept parser_config") invalid_cases = [ ("auto_keywords_min_limit", {"auto_keywords": -1}, "Input should be greater than or equal to 0"), ("auto_keywords_max_limit", {"auto_keywords": 33}, "Input should be less than or equal to 32"), @@ -1781,8 +1784,6 @@ def test_dataset_create_parser_config_invalid_contract(rest_client, clear_datase @pytest.mark.p2 def test_dataset_create_parser_config_defaults_and_extra_fields_contract(rest_client, clear_datasets): - if IS_GO_PROXY: - pytest.skip("Go CreateDataset does not accept parser_config") empty_res = rest_client.post("/datasets", json={"name": "parser_config_empty", "parser_config": {}}) assert empty_res.status_code == 200 empty_payload = empty_res.json() diff --git a/test/testcases/restful_api/test_documents.py b/test/testcases/restful_api/test_documents.py index ccef3980a7..62efff3853 100644 --- a/test/testcases/restful_api/test_documents.py +++ b/test/testcases/restful_api/test_documents.py @@ -820,6 +820,11 @@ def test_documents_update_parser_config_contract(rest_client, create_dataset, tm list_body = list_res.json() assert list_body["code"] == 0, (parser_config, list_body) doc_parser_config = list_body["data"]["docs"][0]["parser_config"] + if IS_GO_PROXY: + assert isinstance(doc_parser_config, dict) and doc_parser_config, (parser_config, list_body) + assert "raptor" not in doc_parser_config, (parser_config, list_body) + assert "graphrag" not in doc_parser_config, (parser_config, list_body) + continue if parser_config == {}: assert doc_parser_config == DEFAULT_PARSER_CONFIG, (parser_config, list_body) else: @@ -833,7 +838,10 @@ def test_documents_update_parser_config_contract(rest_client, create_dataset, tm else: assert doc_parser_config[key] == value, (parser_config, list_body) else: - assert body["message"] == expected_message, (parser_config, body) + if IS_GO_PROXY: + assert body["message"] in expected_message, (parser_config, body) + else: + assert body["message"] == expected_message, (parser_config, body) @pytest.mark.p2