- Added an initialization step in the Load function to register attachment processes, improving the setup for file management. - Expanded the README to include detailed documentation on the new Process API, outlining available processes, permission models, and usage examples for better developer guidance.
1044 lines
29 KiB
Go
1044 lines
29 KiB
Go
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")
|
|
}
|
|
})
|
|
}
|
|
|
|
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 base64 (no data URI header)
|
|
t.Run("PlainBase64", func(t *testing.T) {
|
|
content := "Plain base64"
|
|
base64Content := base64.StdEncoding.EncodeToString([]byte(content))
|
|
|
|
contentType, data, err := parseDataURI(base64Content)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse plain base64: %v", err)
|
|
}
|
|
|
|
if contentType != "application/octet-stream" {
|
|
t.Errorf("Expected content type 'application/octet-stream', 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
|
|
// So we just verify the filename has a proper extension
|
|
testCases := []struct {
|
|
contentType string
|
|
expectedPrefix string
|
|
validExts []string // Multiple valid extensions
|
|
}{
|
|
{"image/png", "file", []string{".png"}},
|
|
{"image/jpeg", "file", []string{".jpg", ".jpeg", ".jpe"}},
|
|
{"image/gif", "file", []string{".gif"}},
|
|
{"image/webp", "file", []string{".webp"}},
|
|
{"text/plain", "file", []string{".txt", ".conf", ".text"}},
|
|
{"application/pdf", "file", []string{".pdf"}},
|
|
{"application/json", "file", []string{".json"}},
|
|
{"application/octet-stream", "file", []string{".bin"}},
|
|
{"unknown/type", "file", []string{".bin"}},
|
|
}
|
|
|
|
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 extension is one of the valid ones
|
|
valid := false
|
|
for _, ext := range tc.validExts {
|
|
if strings.HasSuffix(filename, ext) {
|
|
valid = true
|
|
break
|
|
}
|
|
}
|
|
if !valid {
|
|
t.Errorf("For content type '%s', got '%s', expected one of extensions: %v", tc.contentType, filename, tc.validExts)
|
|
}
|
|
})
|
|
}
|
|
}
|