yao/attachment/process_test.go
Max 02e813af0b Update Dependencies and Enhance Agent Context with Forked Context Support
- Updated `logrus` dependency from v1.9.3 to v1.9.4 and `golang.org/x/sys` from v0.38.0 to v0.40.0 for improved functionality and security.
- Introduced a `Fork` method in the agent context to create child contexts for concurrent agent and LLM calls, preventing race conditions on shared state during batch operations.
- Enhanced the `Orchestrator` methods to utilize forked contexts, ensuring independent execution of agent calls without interference.
2026-01-25 20:32:02 +08:00

1132 lines
31 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package attachment
import (
"encoding/base64"
"fmt"
"strings"
"testing"
"time"
"github.com/yaoapp/gou/process"
"github.com/yaoapp/yao/config"
"github.com/yaoapp/yao/test"
)
func TestProcessSave(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Register default uploader for testing
manager, err := RegisterDefault("data.local")
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
_ = manager
// Test 1: Save with data URI format
t.Run("SaveWithDataURI", func(t *testing.T) {
content := "Hello, World!"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
p := process.New("attachment.Save", "data.local", dataURI, "hello.txt")
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save file: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
if file.ID == "" {
t.Error("File ID should not be empty")
}
if file.Filename != "hello.txt" {
t.Errorf("Expected filename 'hello.txt', got '%s'", file.Filename)
}
if !strings.HasPrefix(file.ContentType, "text/plain") {
t.Errorf("Expected content type 'text/plain', got '%s'", file.ContentType)
}
t.Logf("Saved file - ID: %s, Filename: %s, ContentType: %s", file.ID, file.Filename, file.ContentType)
})
// Test 2: Save with plain base64 (no data URI header) - use text/plain to pass allowed types
t.Run("SaveWithPlainBase64", func(t *testing.T) {
content := "Plain base64 content"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
// Without data URI header, we need to provide a filename with allowed extension
// or use data URI format. Let's test with text file extension.
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
p := process.New("attachment.Save", "data.local", dataURI, "plain.txt")
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save file: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
if file.ID == "" {
t.Error("File ID should not be empty")
}
// With data URI, content type should be text/plain
if !strings.HasPrefix(file.ContentType, "text/plain") {
t.Errorf("Expected content type 'text/plain', got '%s'", file.ContentType)
}
})
// Test 3: Save image with data URI
t.Run("SaveImageDataURI", func(t *testing.T) {
// Minimal valid PNG (1x1 pixel transparent PNG)
pngBase64 := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
dataURI := fmt.Sprintf("data:image/png;base64,%s", pngBase64)
p := process.New("attachment.Save", "data.local", dataURI, "pixel.png")
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save image: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
if file.ContentType != "image/png" {
t.Errorf("Expected content type 'image/png', got '%s'", file.ContentType)
}
})
// Test 4: Save with options - verify via Info since File struct may not have all fields
t.Run("SaveWithOptions", func(t *testing.T) {
content := "Content with options"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
options := map[string]interface{}{
"groups": []interface{}{"test", "unit"},
"public": true,
"share": "team",
}
p := process.New("attachment.Save", "data.local", dataURI, "options.txt", options)
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save file with options: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
// File should be saved successfully
if file.ID == "" {
t.Error("File ID should not be empty")
}
// Get info to verify public and share fields
infoP := process.New("attachment.Info", "data.local", file.ID)
infoResult := processInfo(infoP)
info, ok := infoResult.(*File)
if !ok {
t.Fatalf("Failed to get file info: %v", infoResult)
}
if !info.Public {
t.Error("Expected file to be public")
}
if info.Share != "team" {
t.Errorf("Expected share 'team', got '%s'", info.Share)
}
t.Logf("Saved file with options - ID: %s, Public: %v, Share: %s", file.ID, info.Public, info.Share)
})
// Test 5: Save without filename (auto-generate)
t.Run("SaveWithoutFilename", func(t *testing.T) {
content := "Auto filename content"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:application/json;base64,%s", base64Content)
p := process.New("attachment.Save", "data.local", dataURI)
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save file: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
// Should auto-generate a filename
if file.Filename == "" {
t.Error("Filename should not be empty")
}
t.Logf("Auto-generated filename: %s", file.Filename)
})
// Test 6: Save with invalid uploader
t.Run("SaveWithInvalidUploader", func(t *testing.T) {
content := "Test content"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
p := process.New("attachment.Save", "non-existent-uploader", dataURI, "test.txt")
result := processSave(p)
err, ok := result.(error)
if !ok {
t.Fatal("Expected error for non-existent uploader")
}
if !strings.Contains(err.Error(), "uploader not found") {
t.Errorf("Expected 'uploader not found' error, got: %s", err.Error())
}
})
// Test 7: Save with invalid base64
t.Run("SaveWithInvalidBase64", func(t *testing.T) {
invalidDataURI := "data:text/plain;base64,not-valid-base64!!!"
p := process.New("attachment.Save", "data.local", invalidDataURI, "invalid.txt")
result := processSave(p)
_, ok := result.(error)
if !ok {
t.Fatal("Expected error for invalid base64")
}
})
// Test 8: Save plain text directly (no data URI)
t.Run("SavePlainText", func(t *testing.T) {
content := "This is plain text content without data URI encoding."
p := process.New("attachment.Save", "data.local", content, "plain-text.txt")
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save plain text: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
if file.ID == "" {
t.Error("File ID should not be empty")
}
// Content type should be text/plain for plain text
if !strings.HasPrefix(file.ContentType, "text/plain") {
t.Errorf("Expected content type 'text/plain', got '%s'", file.ContentType)
}
// Read back and verify content
readP := process.New("attachment.Read", "data.local", file.ID)
readResult := processRead(readP)
dataURI, ok := readResult.(string)
if !ok {
t.Fatalf("Expected string, got %T: %v", readResult, readResult)
}
// Decode from data URI
parts := strings.SplitN(dataURI, ",", 2)
if len(parts) != 2 {
t.Fatalf("Invalid data URI format")
}
decoded, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
t.Fatalf("Failed to decode: %v", err)
}
if string(decoded) != content {
t.Errorf("Content mismatch: expected %q, got %q", content, string(decoded))
}
})
// Test 9: Save Chinese text directly (UTF-8)
t.Run("SaveChineseText", func(t *testing.T) {
content := "这是一段中文内容测试UTF-8编码。\n第二行内容。"
p := process.New("attachment.Save", "data.local", content, "chinese.txt")
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save Chinese text: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
// Read back and verify content
readP := process.New("attachment.Read", "data.local", file.ID)
readResult := processRead(readP)
dataURI, ok := readResult.(string)
if !ok {
t.Fatalf("Expected string, got %T: %v", readResult, readResult)
}
// Decode from data URI
parts := strings.SplitN(dataURI, ",", 2)
if len(parts) != 2 {
t.Fatalf("Invalid data URI format")
}
decoded, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
t.Fatalf("Failed to decode: %v", err)
}
if string(decoded) != content {
t.Errorf("Chinese content mismatch: expected %q, got %q", content, string(decoded))
}
})
}
func TestProcessRead(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Register default uploader for testing
_, err := RegisterDefault("data.local")
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
// First, save a file to read
content := "Content to read"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
saveP := process.New("attachment.Save", "data.local", dataURI, "read-test.txt")
saveResult := processSave(saveP)
file, ok := saveResult.(*File)
if !ok {
t.Fatalf("Failed to save test file: %v", saveResult)
}
// Test 1: Read file as data URI
t.Run("ReadAsDataURI", func(t *testing.T) {
p := process.New("attachment.Read", "data.local", file.ID)
result := processRead(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to read file: %v", err)
}
resultDataURI, ok := result.(string)
if !ok {
t.Fatalf("Expected string, got %T", result)
}
// Should return data URI format
if !strings.HasPrefix(resultDataURI, "data:text/plain") {
t.Errorf("Expected data URI starting with 'data:text/plain', got: %s", resultDataURI[:50])
}
if !strings.Contains(resultDataURI, ";base64,") {
t.Error("Expected data URI to contain ';base64,'")
}
// Decode and verify content
parts := strings.SplitN(resultDataURI, ",", 2)
if len(parts) != 2 {
t.Fatal("Invalid data URI format")
}
decodedContent, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
t.Fatalf("Failed to decode base64: %v", err)
}
if string(decodedContent) != content {
t.Errorf("Content mismatch. Expected: %s, Got: %s", content, string(decodedContent))
}
t.Logf("Read file successfully - Data URI length: %d", len(resultDataURI))
})
// Test 2: Read non-existent file
t.Run("ReadNonExistent", func(t *testing.T) {
p := process.New("attachment.Read", "data.local", "non-existent-file-id")
result := processRead(p)
err, ok := result.(error)
if !ok {
t.Fatal("Expected error for non-existent file")
}
if !strings.Contains(err.Error(), "file not found") {
t.Errorf("Expected 'file not found' error, got: %s", err.Error())
}
})
// Test 3: Read with invalid uploader
t.Run("ReadWithInvalidUploader", func(t *testing.T) {
p := process.New("attachment.Read", "non-existent-uploader", file.ID)
result := processRead(p)
err, ok := result.(error)
if !ok {
t.Fatal("Expected error for non-existent uploader")
}
if !strings.Contains(err.Error(), "uploader not found") {
t.Errorf("Expected 'uploader not found' error, got: %s", err.Error())
}
})
}
func TestProcessInfo(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Register default uploader for testing
_, err := RegisterDefault("data.local")
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
// Save a file with options
content := "Info test content"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
options := map[string]interface{}{
"groups": []interface{}{"info", "test"},
"public": true,
"share": "team",
}
saveP := process.New("attachment.Save", "data.local", dataURI, "info-test.txt", options)
saveResult := processSave(saveP)
file, ok := saveResult.(*File)
if !ok {
t.Fatalf("Failed to save test file: %v", saveResult)
}
// Test 1: Get file info
t.Run("GetFileInfo", func(t *testing.T) {
p := process.New("attachment.Info", "data.local", file.ID)
result := processInfo(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to get file info: %v", err)
}
info, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
if info.ID != file.ID {
t.Errorf("Expected ID %s, got %s", file.ID, info.ID)
}
if info.Filename != file.Filename {
t.Errorf("Expected filename %s, got %s", file.Filename, info.Filename)
}
if !strings.HasPrefix(info.ContentType, "text/plain") {
t.Errorf("Expected content type 'text/plain', got %s", info.ContentType)
}
if !info.Public {
t.Error("Expected file to be public")
}
if info.Share != "team" {
t.Errorf("Expected share 'team', got %s", info.Share)
}
t.Logf("File info - ID: %s, Filename: %s, Bytes: %d, Public: %v, Share: %s",
info.ID, info.Filename, info.Bytes, info.Public, info.Share)
})
// Test 2: Get info for non-existent file
t.Run("GetInfoNonExistent", func(t *testing.T) {
p := process.New("attachment.Info", "data.local", "non-existent-file-id")
result := processInfo(p)
err, ok := result.(error)
if !ok {
t.Fatal("Expected error for non-existent file")
}
if !strings.Contains(err.Error(), "file not found") {
t.Errorf("Expected 'file not found' error, got: %s", err.Error())
}
})
}
func TestProcessList(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Use unique manager name for test isolation
managerName := fmt.Sprintf("data.local.list.%d", time.Now().UnixNano())
_, err := RegisterDefault(managerName)
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
// Upload multiple test files
testFiles := []struct {
content string
filename string
contentType string
}{
{"File 1 content", "file1.txt", "text/plain"},
{"File 2 content", "file2.txt", "text/plain"},
{"File 3 content", "file3.txt", "text/plain"},
{`{"key": "value"}`, "data.json", "application/json"},
{"CSV,Data\n1,2", "data.csv", "text/csv"},
}
uploadedIDs := make([]string, 0, len(testFiles))
for _, tf := range testFiles {
base64Content := base64.StdEncoding.EncodeToString([]byte(tf.content))
dataURI := fmt.Sprintf("data:%s;base64,%s", tf.contentType, base64Content)
p := process.New("attachment.Save", managerName, dataURI, tf.filename)
result := processSave(p)
file, ok := result.(*File)
if !ok {
t.Fatalf("Failed to save file %s: %v", tf.filename, result)
}
uploadedIDs = append(uploadedIDs, file.ID)
}
// Test 1: Basic list
t.Run("BasicList", func(t *testing.T) {
p := process.New("attachment.List", managerName)
result := processList(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to list files: %v", err)
}
listResult, ok := result.(*ListResult)
if !ok {
t.Fatalf("Expected *ListResult, got %T", result)
}
if len(listResult.Files) != len(testFiles) {
t.Errorf("Expected %d files, got %d", len(testFiles), len(listResult.Files))
}
if listResult.Total != int64(len(testFiles)) {
t.Errorf("Expected total %d, got %d", len(testFiles), listResult.Total)
}
t.Logf("List result - Total: %d, Page: %d, PageSize: %d", listResult.Total, listResult.Page, listResult.PageSize)
})
// Test 2: List with pagination
t.Run("ListWithPagination", func(t *testing.T) {
options := map[string]interface{}{
"page": 1,
"page_size": 2,
}
p := process.New("attachment.List", managerName, options)
result := processList(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to list files with pagination: %v", err)
}
listResult, ok := result.(*ListResult)
if !ok {
t.Fatalf("Expected *ListResult, got %T", result)
}
if len(listResult.Files) != 2 {
t.Errorf("Expected 2 files, got %d", len(listResult.Files))
}
if listResult.PageSize != 2 {
t.Errorf("Expected page size 2, got %d", listResult.PageSize)
}
if listResult.TotalPages != 3 { // 5 files / 2 per page = 3 pages
t.Errorf("Expected 3 total pages, got %d", listResult.TotalPages)
}
})
// Test 3: List with filters - use content_type wildcard
t.Run("ListWithFilters", func(t *testing.T) {
options := map[string]interface{}{
"filters": map[string]interface{}{
"content_type": "text/*",
},
}
p := process.New("attachment.List", managerName, options)
result := processList(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to list files with filters: %v", err)
}
listResult, ok := result.(*ListResult)
if !ok {
t.Fatalf("Expected *ListResult, got %T", result)
}
// Should find text/plain and text/csv files
// Note: The filter implementation may vary, so we just check the call succeeds
t.Logf("List with content_type filter - Total: %d files", listResult.Total)
})
// Test 4: List with ordering
t.Run("ListWithOrdering", func(t *testing.T) {
options := map[string]interface{}{
"order_by": "name asc",
}
p := process.New("attachment.List", managerName, options)
result := processList(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to list files with ordering: %v", err)
}
listResult, ok := result.(*ListResult)
if !ok {
t.Fatalf("Expected *ListResult, got %T", result)
}
// Verify files are sorted
for i := 1; i < len(listResult.Files); i++ {
if listResult.Files[i-1].Filename > listResult.Files[i].Filename {
t.Errorf("Files are not sorted ascending by name")
break
}
}
})
}
func TestProcessDelete(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Register default uploader for testing
_, err := RegisterDefault("data.local")
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
// Save a file to delete
content := "Content to delete"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
saveP := process.New("attachment.Save", "data.local", dataURI, "delete-test.txt")
saveResult := processSave(saveP)
file, ok := saveResult.(*File)
if !ok {
t.Fatalf("Failed to save test file: %v", saveResult)
}
// Test 1: Delete existing file
t.Run("DeleteExistingFile", func(t *testing.T) {
// Verify file exists first
existsP := process.New("attachment.Exists", "data.local", file.ID)
existsResult := processExists(existsP)
if exists, ok := existsResult.(bool); !ok || !exists {
t.Fatal("File should exist before deletion")
}
// Delete the file
p := process.New("attachment.Delete", "data.local", file.ID)
result := processDelete(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to delete file: %v", err)
}
success, ok := result.(bool)
if !ok || !success {
t.Errorf("Expected true, got %v", result)
}
// Verify file no longer exists
existsP2 := process.New("attachment.Exists", "data.local", file.ID)
existsResult2 := processExists(existsP2)
if exists, ok := existsResult2.(bool); ok && exists {
t.Error("File should not exist after deletion")
}
})
// Test 2: Delete non-existent file
t.Run("DeleteNonExistent", func(t *testing.T) {
p := process.New("attachment.Delete", "data.local", "non-existent-file-id")
result := processDelete(p)
err, ok := result.(error)
if !ok {
t.Fatal("Expected error for non-existent file")
}
if !strings.Contains(err.Error(), "file not found") {
t.Errorf("Expected 'file not found' error, got: %s", err.Error())
}
})
}
func TestProcessExists(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Register default uploader for testing
_, err := RegisterDefault("data.local")
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
// Save a file
content := "Exists test content"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
saveP := process.New("attachment.Save", "data.local", dataURI, "exists-test.txt")
saveResult := processSave(saveP)
file, ok := saveResult.(*File)
if !ok {
t.Fatalf("Failed to save test file: %v", saveResult)
}
// Test 1: Existing file
t.Run("FileExists", func(t *testing.T) {
p := process.New("attachment.Exists", "data.local", file.ID)
result := processExists(p)
exists, ok := result.(bool)
if !ok {
t.Fatalf("Expected bool, got %T", result)
}
if !exists {
t.Error("File should exist")
}
})
// Test 2: Non-existent file
t.Run("FileNotExists", func(t *testing.T) {
p := process.New("attachment.Exists", "data.local", "non-existent-file-id")
result := processExists(p)
exists, ok := result.(bool)
if !ok {
t.Fatalf("Expected bool, got %T", result)
}
if exists {
t.Error("File should not exist")
}
})
}
func TestProcessURL(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Register default uploader for testing
_, err := RegisterDefault("data.local")
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
// Save a file
content := "URL test content"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
saveP := process.New("attachment.Save", "data.local", dataURI, "url-test.txt")
saveResult := processSave(saveP)
file, ok := saveResult.(*File)
if !ok {
t.Fatalf("Failed to save test file: %v", saveResult)
}
// Test 1: Get URL
t.Run("GetURL", func(t *testing.T) {
p := process.New("attachment.URL", "data.local", file.ID)
result := processURL(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to get URL: %v", err)
}
url, ok := result.(string)
if !ok {
t.Fatalf("Expected string, got %T", result)
}
if url == "" {
t.Error("URL should not be empty")
}
t.Logf("File URL: %s", url)
})
// Test 2: Get URL for non-existent file
t.Run("GetURLNonExistent", func(t *testing.T) {
p := process.New("attachment.URL", "data.local", "non-existent-file-id")
result := processURL(p)
err, ok := result.(error)
if !ok {
t.Fatal("Expected error for non-existent file")
}
if !strings.Contains(err.Error(), "file not found") {
t.Errorf("Expected 'file not found' error, got: %s", err.Error())
}
})
}
func TestProcessSaveTextAndGetText(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Register default uploader for testing
_, err := RegisterDefault("data.local")
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
// Save a file
content := "Original file content"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
saveP := process.New("attachment.Save", "data.local", dataURI, "text-test.txt")
saveResult := processSave(saveP)
file, ok := saveResult.(*File)
if !ok {
t.Fatalf("Failed to save test file: %v", saveResult)
}
// Test 1: Get text from file without saved text (should be empty)
t.Run("GetTextEmpty", func(t *testing.T) {
p := process.New("attachment.GetText", "data.local", file.ID)
result := processGetText(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to get text: %v", err)
}
text, ok := result.(string)
if !ok {
t.Fatalf("Expected string, got %T", result)
}
if text != "" {
t.Errorf("Expected empty text, got: %s", text)
}
})
// Test 2: Save text and retrieve
t.Run("SaveTextAndRetrieve", func(t *testing.T) {
parsedText := "This is the parsed/extracted text content from the file."
// Save text
saveTextP := process.New("attachment.SaveText", "data.local", file.ID, parsedText)
saveTextResult := processSaveText(saveTextP)
if err, ok := saveTextResult.(error); ok {
t.Fatalf("Failed to save text: %v", err)
}
success, ok := saveTextResult.(bool)
if !ok || !success {
t.Errorf("Expected true, got %v", saveTextResult)
}
// Retrieve text
getTextP := process.New("attachment.GetText", "data.local", file.ID)
getTextResult := processGetText(getTextP)
if err, ok := getTextResult.(error); ok {
t.Fatalf("Failed to get text: %v", err)
}
retrievedText, ok := getTextResult.(string)
if !ok {
t.Fatalf("Expected string, got %T", getTextResult)
}
if retrievedText != parsedText {
t.Errorf("Text mismatch. Expected: %s, Got: %s", parsedText, retrievedText)
}
t.Logf("Saved and retrieved text: %s", retrievedText)
})
// Test 3: Get full text vs preview
t.Run("GetTextFullVsPreview", func(t *testing.T) {
// Save a long text
longText := strings.Repeat("This is a long text content. ", 200) // > 2000 chars
saveTextP := process.New("attachment.SaveText", "data.local", file.ID, longText)
saveTextResult := processSaveText(saveTextP)
if err, ok := saveTextResult.(error); ok {
t.Fatalf("Failed to save long text: %v", err)
}
// Get preview (default)
previewP := process.New("attachment.GetText", "data.local", file.ID)
previewResult := processGetText(previewP)
previewText, _ := previewResult.(string)
// Preview should be 2000 runes
if len([]rune(previewText)) != 2000 {
t.Errorf("Preview should be 2000 runes, got %d", len([]rune(previewText)))
}
// Get full content
fullP := process.New("attachment.GetText", "data.local", file.ID, true)
fullResult := processGetText(fullP)
fullText, _ := fullResult.(string)
if fullText != longText {
t.Errorf("Full text length mismatch. Expected: %d, Got: %d", len(longText), len(fullText))
}
})
// Test 4: Save/Get text for non-existent file
t.Run("SaveTextNonExistent", func(t *testing.T) {
p := process.New("attachment.SaveText", "data.local", "non-existent-id", "some text")
result := processSaveText(p)
err, ok := result.(error)
if !ok {
t.Fatal("Expected error for non-existent file")
}
if !strings.Contains(err.Error(), "file not found") {
t.Errorf("Expected 'file not found' error, got: %s", err.Error())
}
})
}
func TestProcessWithAuthorizedPermission(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Register default uploader for testing
_, err := RegisterDefault("data.local")
if err != nil {
t.Fatalf("Failed to register manager: %v", err)
}
// Test 1: Save with Authorized info - verify via database query since File struct
// does not expose these fields in JSON (they are marked with json:"-")
t.Run("SaveWithAuthorizedInfo", func(t *testing.T) {
content := "Content with permission"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
p := process.New("attachment.Save", "data.local", dataURI, "perm-test.txt")
// Set authorized info
p.WithAuthorized(process.AuthorizedInfo{
UserID: "user123",
TeamID: "team456",
TenantID: "tenant789",
})
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save file: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
// File should be saved successfully
if file.ID == "" {
t.Error("File ID should not be empty")
}
// Note: The YaoCreatedBy, YaoTeamID, YaoTenantID fields in File struct
// are marked with json:"-" and may not be populated in the returned struct.
// The permission fields are stored in the database during upload via UploadOption.
// To verify, we would need to query the database directly.
t.Logf("File saved with authorized info - ID: %s", file.ID)
})
// Test 2: Save without Authorized (should still work)
t.Run("SaveWithoutAuthorized", func(t *testing.T) {
content := "Content without permission"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
p := process.New("attachment.Save", "data.local", dataURI, "no-perm-test.txt")
// Don't set authorized info
result := processSave(p)
if err, ok := result.(error); ok {
t.Fatalf("Failed to save file: %v", err)
}
file, ok := result.(*File)
if !ok {
t.Fatalf("Expected *File, got %T", result)
}
// File should be saved successfully without permission fields
if file.ID == "" {
t.Error("File ID should not be empty")
}
t.Logf("File saved without permissions - ID: %s", file.ID)
})
}
func TestParseDataURI(t *testing.T) {
// Test 1: Valid data URI with content type
t.Run("ValidDataURI", func(t *testing.T) {
content := "Hello, World!"
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
dataURI := fmt.Sprintf("data:text/plain;base64,%s", base64Content)
contentType, data, err := parseDataURI(dataURI)
if err != nil {
t.Fatalf("Failed to parse data URI: %v", err)
}
if contentType != "text/plain" {
t.Errorf("Expected content type 'text/plain', got '%s'", contentType)
}
if string(data) != content {
t.Errorf("Expected content '%s', got '%s'", content, string(data))
}
})
// Test 2: Plain text (no data URI header) - treated as plain text, not base64
t.Run("PlainText", func(t *testing.T) {
content := "Plain text content"
contentType, data, err := parseDataURI(content)
if err != nil {
t.Fatalf("Failed to parse plain text: %v", err)
}
if contentType != "text/plain" {
t.Errorf("Expected content type 'text/plain', got '%s'", contentType)
}
if string(data) != content {
t.Errorf("Expected content '%s', got '%s'", content, string(data))
}
})
// Test 3: Data URI with image
t.Run("ImageDataURI", func(t *testing.T) {
pngBase64 := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
dataURI := fmt.Sprintf("data:image/png;base64,%s", pngBase64)
contentType, _, err := parseDataURI(dataURI)
if err != nil {
t.Fatalf("Failed to parse image data URI: %v", err)
}
if contentType != "image/png" {
t.Errorf("Expected content type 'image/png', got '%s'", contentType)
}
})
// Test 4: Invalid base64
t.Run("InvalidBase64", func(t *testing.T) {
dataURI := "data:text/plain;base64,not-valid!!!"
_, _, err := parseDataURI(dataURI)
if err == nil {
t.Fatal("Expected error for invalid base64")
}
})
// Test 5: Invalid data URI format
t.Run("InvalidDataURIFormat", func(t *testing.T) {
dataURI := "data:text/plain" // Missing base64 part
_, _, err := parseDataURI(dataURI)
if err == nil {
t.Fatal("Expected error for invalid data URI format")
}
})
}
func TestGenerateFilename(t *testing.T) {
// Note: mime.ExtensionsByType may return different extensions on different systems
// (e.g., Linux may return .jfif for image/jpeg, .asc for text/plain)
// So we verify the filename has a proper extension format and is not empty
testCases := []struct {
contentType string
expectedPrefix string
}{
{"image/png", "file"},
{"image/jpeg", "file"},
{"image/gif", "file"},
{"image/webp", "file"},
{"text/plain", "file"},
{"application/pdf", "file"},
{"application/json", "file"},
{"application/octet-stream", "file"},
{"unknown/type", "file"},
}
for _, tc := range testCases {
t.Run(tc.contentType, func(t *testing.T) {
filename := generateFilename(tc.contentType)
// Check prefix
if !strings.HasPrefix(filename, tc.expectedPrefix) {
t.Errorf("For content type '%s', expected prefix '%s', got '%s'", tc.contentType, tc.expectedPrefix, filename)
}
// Check filename has an extension (starts with dot and has at least one character)
dotIndex := strings.LastIndex(filename, ".")
if dotIndex == -1 || dotIndex == len(filename)-1 {
t.Errorf("For content type '%s', expected filename with extension, got '%s'", tc.contentType, filename)
}
// Extension should not be empty
ext := filename[dotIndex:]
if len(ext) < 2 {
t.Errorf("For content type '%s', expected non-empty extension, got '%s'", tc.contentType, ext)
}
})
}
}