Refactor Job System for Enhanced Function Execution and Error Handling
- Improved the `Job` struct to support dynamic function execution with the `AddFunc` method, allowing for flexible job management. - Enhanced the `Goroutine` struct with the `ExecuteFunc` method to manage function execution, including robust error handling and context management. - Updated unit tests for `AddFunc` to ensure proper function registration and execution, including memory cleanup verification. - Revised documentation to reflect the new function execution capabilities within the job system.
This commit is contained in:
parent
01820d9fe2
commit
324ee7dc92
4 changed files with 1465 additions and 0 deletions
368
openapi/tests/kb/addfile_test.go
Normal file
368
openapi/tests/kb/addfile_test.go
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
401
openapi/tests/kb/addtext_test.go
Normal file
401
openapi/tests/kb/addtext_test.go
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
401
openapi/tests/kb/addurl_test.go
Normal file
401
openapi/tests/kb/addurl_test.go
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
295
openapi/tests/kb/document_test.go
Normal file
295
openapi/tests/kb/document_test.go
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue