diff --git a/openapi/tests/kb/addfile_test.go b/openapi/tests/kb/addfile_test.go new file mode 100644 index 00000000..1273065d --- /dev/null +++ b/openapi/tests/kb/addfile_test.go @@ -0,0 +1,368 @@ +package openapi_test + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/openapi" + "github.com/yaoapp/yao/openapi/tests/testutils" +) + +// TestAddFile tests the add file endpoint (sync) +func TestAddFile(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB AddFile Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + // Create a test collection first + testCollectionID := fmt.Sprintf("test_addfile_collection_%d", time.Now().UnixNano()) + testutils.RegisterTestCollection(testCollectionID) + + createData := map[string]interface{}{ + "id": testCollectionID, + "metadata": map[string]interface{}{ + "name": "Test Collection for AddFile", + "category": "test", + }, + "config": map[string]interface{}{ + "embedding_provider_id": "__yao.openai", + "embedding_option_id": "text-embedding-3-small", + "locale": "en", + "index_type": "hnsw", + "distance": "cosine", + }, + } + + body, _ := json.Marshal(createData) + req, _ := http.NewRequest("POST", serverURL+baseURL+"/kb/collections", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Failed to create test collection: %v", err) + } + resp.Body.Close() + + t.Run("AddFileInvalidRequest", func(t *testing.T) { + // Test with missing required fields + invalidData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing file_id, chunking, embedding + } + + body, err := json.Marshal(invalidData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/file", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("AddFileMissingFileID", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing file_id + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/file", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains FileID (case insensitive check) + assert.Contains(t, response["error_description"], "FileID") + }) + + t.Run("AddFileNonExistentCollection", func(t *testing.T) { + // Test with a non-existent collection + addData := map[string]interface{}{ + "collection_id": "non_existent_collection_12345", + "file_id": "test_file_123", + "uploader": "local", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/non_existent_collection_12345/documents/file", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 403 Forbidden, 404 Not Found, or 500 Internal Server Error for non-existent collection + // (depends on whether permission check or collection lookup happens first) + assert.True(t, resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusInternalServerError, + "Expected 403, 404, or 500, got %d", resp.StatusCode) + }) + + t.Run("AddFileUnauthorized", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "file_id": "test_file_123", + "uploader": "local", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/file", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) +} + +// TestAddFileAsync tests the add file async endpoint +func TestAddFileAsync(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB AddFileAsync Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + // Create a test collection first + testCollectionID := fmt.Sprintf("test_addfile_async_collection_%d", time.Now().UnixNano()) + testutils.RegisterTestCollection(testCollectionID) + + createData := map[string]interface{}{ + "id": testCollectionID, + "metadata": map[string]interface{}{ + "name": "Test Collection for AddFileAsync", + "category": "test", + }, + "config": map[string]interface{}{ + "embedding_provider_id": "__yao.openai", + "embedding_option_id": "text-embedding-3-small", + "locale": "en", + "index_type": "hnsw", + "distance": "cosine", + }, + } + + body, _ := json.Marshal(createData) + req, _ := http.NewRequest("POST", serverURL+baseURL+"/kb/collections", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Failed to create test collection: %v", err) + } + resp.Body.Close() + + t.Run("AddFileAsyncInvalidRequest", func(t *testing.T) { + // Test with missing required fields + invalidData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing file_id, chunking, embedding + } + + body, err := json.Marshal(invalidData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/file/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("AddFileAsyncMissingFileID", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing file_id + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/file/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains FileID (case insensitive check) + assert.Contains(t, response["error_description"], "FileID") + }) + + t.Run("AddFileAsyncUnauthorized", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "file_id": "test_file_123", + "uploader": "local", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/file/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) + + t.Run("AddFileAsyncFileNotFound", func(t *testing.T) { + // Test with a file_id that doesn't exist + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "file_id": "non_existent_file_12345", + "uploader": "local", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/file/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 404 Not Found for non-existent file + assert.Equal(t, http.StatusNotFound, resp.StatusCode) + }) +} diff --git a/openapi/tests/kb/addtext_test.go b/openapi/tests/kb/addtext_test.go new file mode 100644 index 00000000..f93e250a --- /dev/null +++ b/openapi/tests/kb/addtext_test.go @@ -0,0 +1,401 @@ +package openapi_test + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/openapi" + "github.com/yaoapp/yao/openapi/tests/testutils" +) + +// TestAddText tests the add text endpoint (sync) +func TestAddText(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB AddText Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + // Create a test collection first + testCollectionID := fmt.Sprintf("test_addtext_collection_%d", time.Now().UnixNano()) + testutils.RegisterTestCollection(testCollectionID) + + createData := map[string]interface{}{ + "id": testCollectionID, + "metadata": map[string]interface{}{ + "name": "Test Collection for AddText", + "category": "test", + }, + "config": map[string]interface{}{ + "embedding_provider_id": "__yao.openai", + "embedding_option_id": "text-embedding-3-small", + "locale": "en", + "index_type": "hnsw", + "distance": "cosine", + }, + } + + body, _ := json.Marshal(createData) + req, _ := http.NewRequest("POST", serverURL+baseURL+"/kb/collections", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Failed to create test collection: %v", err) + } + resp.Body.Close() + + t.Run("AddTextInvalidRequest", func(t *testing.T) { + // Test with missing required fields + invalidData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing text, chunking, embedding + } + + body, err := json.Marshal(invalidData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/text", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("AddTextMissingText", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing text + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/text", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains Text (case insensitive check) + assert.Contains(t, response["error_description"], "Text") + }) + + t.Run("AddTextNonExistentCollection", func(t *testing.T) { + // Test with a non-existent collection + addData := map[string]interface{}{ + "collection_id": "non_existent_collection_12345", + "text": "This is a test text content for the knowledge base.", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/non_existent_collection_12345/documents/text", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 403 Forbidden, 404 Not Found, or 500 Internal Server Error for non-existent collection + // (depends on whether permission check or collection lookup happens first) + assert.True(t, resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusInternalServerError, + "Expected 403, 404, or 500, got %d", resp.StatusCode) + }) + + t.Run("AddTextMissingChunking", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "text": "This is a test text content.", + // Missing chunking + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/text", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains Chunking (case insensitive check) + assert.Contains(t, response["error_description"], "Chunking") + }) + + t.Run("AddTextMissingEmbedding", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "text": "This is a test text content.", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + // Missing embedding + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/text", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains Embedding (case insensitive check) + assert.Contains(t, response["error_description"], "Embedding") + }) + + t.Run("AddTextUnauthorized", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "text": "This is a test text content.", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/text", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) +} + +// TestAddTextAsync tests the add text async endpoint +func TestAddTextAsync(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB AddTextAsync Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + // Create a test collection first + testCollectionID := fmt.Sprintf("test_addtext_async_collection_%d", time.Now().UnixNano()) + testutils.RegisterTestCollection(testCollectionID) + + createData := map[string]interface{}{ + "id": testCollectionID, + "metadata": map[string]interface{}{ + "name": "Test Collection for AddTextAsync", + "category": "test", + }, + "config": map[string]interface{}{ + "embedding_provider_id": "__yao.openai", + "embedding_option_id": "text-embedding-3-small", + "locale": "en", + "index_type": "hnsw", + "distance": "cosine", + }, + } + + body, _ := json.Marshal(createData) + req, _ := http.NewRequest("POST", serverURL+baseURL+"/kb/collections", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Failed to create test collection: %v", err) + } + resp.Body.Close() + + t.Run("AddTextAsyncInvalidRequest", func(t *testing.T) { + // Test with missing required fields + invalidData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing text, chunking, embedding + } + + body, err := json.Marshal(invalidData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/text/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("AddTextAsyncMissingText", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing text + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/text/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains Text (case insensitive check) + assert.Contains(t, response["error_description"], "Text") + }) + + t.Run("AddTextAsyncUnauthorized", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "text": "This is a test text content for async processing.", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/text/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) +} diff --git a/openapi/tests/kb/addurl_test.go b/openapi/tests/kb/addurl_test.go new file mode 100644 index 00000000..2c6ca6ab --- /dev/null +++ b/openapi/tests/kb/addurl_test.go @@ -0,0 +1,401 @@ +package openapi_test + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/openapi" + "github.com/yaoapp/yao/openapi/tests/testutils" +) + +// TestAddURL tests the add URL endpoint (sync) +func TestAddURL(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB AddURL Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + // Create a test collection first + testCollectionID := fmt.Sprintf("test_addurl_collection_%d", time.Now().UnixNano()) + testutils.RegisterTestCollection(testCollectionID) + + createData := map[string]interface{}{ + "id": testCollectionID, + "metadata": map[string]interface{}{ + "name": "Test Collection for AddURL", + "category": "test", + }, + "config": map[string]interface{}{ + "embedding_provider_id": "__yao.openai", + "embedding_option_id": "text-embedding-3-small", + "locale": "en", + "index_type": "hnsw", + "distance": "cosine", + }, + } + + body, _ := json.Marshal(createData) + req, _ := http.NewRequest("POST", serverURL+baseURL+"/kb/collections", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Failed to create test collection: %v", err) + } + resp.Body.Close() + + t.Run("AddURLInvalidRequest", func(t *testing.T) { + // Test with missing required fields + invalidData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing url, chunking, embedding + } + + body, err := json.Marshal(invalidData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/url", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("AddURLMissingURL", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing url + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/url", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains URL (case insensitive check) + assert.Contains(t, response["error_description"], "URL") + }) + + t.Run("AddURLNonExistentCollection", func(t *testing.T) { + // Test with a non-existent collection + addData := map[string]interface{}{ + "collection_id": "non_existent_collection_12345", + "url": "https://example.com/test-page", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/non_existent_collection_12345/documents/url", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 403 Forbidden, 404 Not Found, or 500 Internal Server Error for non-existent collection + // (depends on whether permission check or collection lookup happens first) + assert.True(t, resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusInternalServerError, + "Expected 403, 404, or 500, got %d", resp.StatusCode) + }) + + t.Run("AddURLMissingChunking", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "url": "https://example.com/test-page", + // Missing chunking + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/url", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains Chunking (case insensitive check) + assert.Contains(t, response["error_description"], "Chunking") + }) + + t.Run("AddURLMissingEmbedding", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "url": "https://example.com/test-page", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + // Missing embedding + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/url", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains Embedding (case insensitive check) + assert.Contains(t, response["error_description"], "Embedding") + }) + + t.Run("AddURLUnauthorized", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "url": "https://example.com/test-page", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/url", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) +} + +// TestAddURLAsync tests the add URL async endpoint +func TestAddURLAsync(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB AddURLAsync Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + // Create a test collection first + testCollectionID := fmt.Sprintf("test_addurl_async_collection_%d", time.Now().UnixNano()) + testutils.RegisterTestCollection(testCollectionID) + + createData := map[string]interface{}{ + "id": testCollectionID, + "metadata": map[string]interface{}{ + "name": "Test Collection for AddURLAsync", + "category": "test", + }, + "config": map[string]interface{}{ + "embedding_provider_id": "__yao.openai", + "embedding_option_id": "text-embedding-3-small", + "locale": "en", + "index_type": "hnsw", + "distance": "cosine", + }, + } + + body, _ := json.Marshal(createData) + req, _ := http.NewRequest("POST", serverURL+baseURL+"/kb/collections", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Failed to create test collection: %v", err) + } + resp.Body.Close() + + t.Run("AddURLAsyncInvalidRequest", func(t *testing.T) { + // Test with missing required fields + invalidData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing url, chunking, embedding + } + + body, err := json.Marshal(invalidData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/url/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("AddURLAsyncMissingURL", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + // Missing url + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/url/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + // Error message contains URL (case insensitive check) + assert.Contains(t, response["error_description"], "URL") + }) + + t.Run("AddURLAsyncUnauthorized", func(t *testing.T) { + addData := map[string]interface{}{ + "collection_id": testCollectionID, + "url": "https://example.com/async-test-page", + "chunking": map[string]interface{}{ + "provider_id": "__yao.structured", + "option_id": "standard", + }, + "embedding": map[string]interface{}{ + "provider_id": "__yao.openai", + "option_id": "text-embedding-3-small", + }, + } + + body, err := json.Marshal(addData) + assert.NoError(t, err) + + req, err := http.NewRequest("POST", serverURL+baseURL+"/kb/collections/"+testCollectionID+"/documents/url/async", bytes.NewBuffer(body)) + assert.NoError(t, err) + + req.Header.Set("Content-Type", "application/json") + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) +} diff --git a/openapi/tests/kb/document_test.go b/openapi/tests/kb/document_test.go new file mode 100644 index 00000000..01fe937a --- /dev/null +++ b/openapi/tests/kb/document_test.go @@ -0,0 +1,295 @@ +package openapi_test + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/openapi" + "github.com/yaoapp/yao/openapi/tests/testutils" +) + +// TestListDocuments tests the document listing endpoint +func TestListDocuments(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB Document List Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + // Create a test collection first + testCollectionID := fmt.Sprintf("test_doc_list_collection_%d", time.Now().UnixNano()) + testutils.RegisterTestCollection(testCollectionID) + + createData := map[string]interface{}{ + "id": testCollectionID, + "metadata": map[string]interface{}{ + "name": "Test Collection for Document List", + "category": "test", + }, + "config": map[string]interface{}{ + "embedding_provider_id": "__yao.openai", + "embedding_option_id": "text-embedding-3-small", + "locale": "en", + "index_type": "hnsw", + "distance": "cosine", + }, + } + + body, _ := json.Marshal(createData) + req, _ := http.NewRequest("POST", serverURL+baseURL+"/kb/collections", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Failed to create test collection: %v", err) + } + resp.Body.Close() + + t.Run("ListDocumentsSuccess", func(t *testing.T) { + req, err := http.NewRequest("GET", serverURL+baseURL+"/kb/documents?collection_id="+testCollectionID, nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + + // Verify pagination fields exist + assert.Contains(t, response, "data") + assert.Contains(t, response, "page") + assert.Contains(t, response, "pagesize") + assert.Contains(t, response, "total") + }) + + t.Run("ListDocumentsWithPagination", func(t *testing.T) { + req, err := http.NewRequest("GET", serverURL+baseURL+"/kb/documents?collection_id="+testCollectionID+"&page=1&pagesize=10", nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + + // Verify pagination values + assert.Equal(t, float64(1), response["page"]) + assert.Equal(t, float64(10), response["pagesize"]) + }) + + t.Run("ListDocumentsWithStatusFilter", func(t *testing.T) { + req, err := http.NewRequest("GET", serverURL+baseURL+"/kb/documents?collection_id="+testCollectionID+"&status=completed", nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + }) + + t.Run("ListDocumentsWithSort", func(t *testing.T) { + req, err := http.NewRequest("GET", serverURL+baseURL+"/kb/documents?collection_id="+testCollectionID+"&sort=created_at+desc", nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + }) + + t.Run("ListDocumentsUnauthorized", func(t *testing.T) { + req, err := http.NewRequest("GET", serverURL+baseURL+"/kb/documents?collection_id="+testCollectionID, nil) + assert.NoError(t, err) + + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) +} + +// TestGetDocument tests the get document endpoint +func TestGetDocument(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB Document Get Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + t.Run("GetDocumentNotFound", func(t *testing.T) { + req, err := http.NewRequest("GET", serverURL+baseURL+"/kb/documents/non_existent_doc_id", nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 404 Not Found + assert.Equal(t, http.StatusNotFound, resp.StatusCode) + }) + + t.Run("GetDocumentWithSelectFields", func(t *testing.T) { + // This test verifies the select parameter works + req, err := http.NewRequest("GET", serverURL+baseURL+"/kb/documents/test_doc_id?select=id,name,status", nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 404 since doc doesn't exist, but the request format is valid + assert.Equal(t, http.StatusNotFound, resp.StatusCode) + }) + + t.Run("GetDocumentUnauthorized", func(t *testing.T) { + req, err := http.NewRequest("GET", serverURL+baseURL+"/kb/documents/test_doc_id", nil) + assert.NoError(t, err) + + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) +} + +// TestRemoveDocuments tests the remove documents endpoint +func TestRemoveDocuments(t *testing.T) { + serverURL := testutils.Prepare(t) + defer testutils.Clean() + + // Get base URL from server config + baseURL := "" + if openapi.Server != nil && openapi.Server.Config != nil { + baseURL = openapi.Server.Config.BaseURL + } + + // Register test client and get token + client := testutils.RegisterTestClient(t, "KB Document Remove Test Client", []string{"https://localhost/callback"}) + defer testutils.CleanupTestClient(t, client.ClientID) + tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile") + + t.Run("RemoveDocumentsMissingIDs", func(t *testing.T) { + req, err := http.NewRequest("DELETE", serverURL+baseURL+"/kb/documents", nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request for missing document_ids + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + assert.NoError(t, err) + assert.Contains(t, response["error_description"], "document_ids") + }) + + t.Run("RemoveDocumentsEmptyIDs", func(t *testing.T) { + req, err := http.NewRequest("DELETE", serverURL+baseURL+"/kb/documents?document_ids=", nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 400 Bad Request for empty document_ids + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("RemoveDocumentsNonExistent", func(t *testing.T) { + req, err := http.NewRequest("DELETE", serverURL+baseURL+"/kb/documents?document_ids=non_existent_doc_1,non_existent_doc_2", nil) + assert.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // The behavior depends on implementation - could be 200 with 0 removed or 404 + // Accept either as valid + assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusForbidden) + }) + + t.Run("RemoveDocumentsUnauthorized", func(t *testing.T) { + req, err := http.NewRequest("DELETE", serverURL+baseURL+"/kb/documents?document_ids=doc1,doc2", nil) + assert.NoError(t, err) + + // No Authorization header + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.NotNil(t, resp) + defer resp.Body.Close() + + // Should return 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + }) +}