Files
tutti/services/tuttid/api/daemon_files_mutation_test.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

415 lines
13 KiB
Go

package api
import (
"context"
"net/http"
"net/http/httptest"
"testing"
workspacefiles "github.com/tutti-os/tutti/packages/workspace/files"
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
)
func TestDaemonAPIGeneratedRoutesCreateWorkspaceFileDirectoryMapsMissingParentTo404(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(
mux,
NewRoutes(DaemonAPI{
FileService: stubFileService{
createDirectoryFn: func(_ context.Context, workspaceID string, path string) (workspacefiles.FileEntry, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if path != "/workspace/missing/notes" {
t.Fatalf("path = %q, want /workspace/missing/notes", path)
}
return workspacefiles.FileEntry{}, workspacefiles.ErrEntryNotFound
},
},
}),
)
recorder := performGeneratedRouteRequest(
t,
mux,
http.MethodPut,
"/v1/workspaces/ws-1/files/directory",
map[string]any{"path": "/workspace/missing/notes"},
)
if recorder.Code != http.StatusNotFound {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusNotFound, recorder.Body.String())
}
assertGeneratedRouteError(
t,
recorder,
tuttigenerated.WorkspaceFileNotFound,
"workspace_file_not_found",
workspacefiles.ErrEntryNotFound.Error(),
)
}
func TestDaemonAPIGeneratedRoutesCreateWorkspaceFileMapsMissingParentTo404(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(
mux,
NewRoutes(DaemonAPI{
FileService: stubFileService{
createFileFn: func(_ context.Context, workspaceID string, path string) (workspacefiles.FileEntry, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if path != "/workspace/missing/todo.md" {
t.Fatalf("path = %q, want /workspace/missing/todo.md", path)
}
return workspacefiles.FileEntry{}, workspacefiles.ErrEntryNotFound
},
},
}),
)
recorder := performGeneratedRouteRequest(
t,
mux,
http.MethodPut,
"/v1/workspaces/ws-1/files/file",
map[string]any{"path": "/workspace/missing/todo.md"},
)
if recorder.Code != http.StatusNotFound {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusNotFound, recorder.Body.String())
}
assertGeneratedRouteError(
t,
recorder,
tuttigenerated.WorkspaceFileNotFound,
"workspace_file_not_found",
workspacefiles.ErrEntryNotFound.Error(),
)
}
func TestDaemonAPIGeneratedRoutesReadWorkspaceFilePreview(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(
mux,
NewRoutes(DaemonAPI{
FileService: stubFileService{
readFileFn: func(_ context.Context, workspaceID string, path string, maxBytes int64) (workspacefiles.FileContent, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if path != "/workspace/docs/todo.md" {
t.Fatalf("path = %q, want /workspace/docs/todo.md", path)
}
if maxBytes != workspacefiles.DefaultReadFileMaxBytes {
t.Fatalf("maxBytes = %d, want %d", maxBytes, workspacefiles.DefaultReadFileMaxBytes)
}
return workspacefiles.FileContent{
Bytes: []byte("hello"),
Name: "todo.md",
Path: "/workspace/docs/todo.md",
SizeBytes: 5,
}, nil
},
},
}),
)
request, err := http.NewRequest(
http.MethodGet,
"/v1/workspaces/ws-1/files/file/preview?path=%2Fworkspace%2Fdocs%2Ftodo.md",
nil,
)
if err != nil {
t.Fatalf("NewRequest() error = %v", err)
}
recorder := httptest.NewRecorder()
mux.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
}
var response tuttigenerated.WorkspaceFilePreviewResponse
decodeGeneratedRouteResponse(t, recorder, &response)
if response.Path != "/workspace/docs/todo.md" {
t.Fatalf("path = %q", response.Path)
}
if response.BytesBase64 != "aGVsbG8=" {
t.Fatalf("bytesBase64 = %q", response.BytesBase64)
}
if response.SizeBytes != 5 {
t.Fatalf("sizeBytes = %d", response.SizeBytes)
}
}
func TestDaemonAPIGeneratedRoutesReadWorkspaceFilePreviewUsesPathAwareRoot(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(
mux,
NewRoutes(DaemonAPI{
FileService: stubFileService{
readFileFn: func(_ context.Context, workspaceID string, path string, _ int64) (workspacefiles.FileContent, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if path != "/tmp/report.md" {
t.Fatalf("path = %q, want /tmp/report.md", path)
}
return workspacefiles.FileContent{
Bytes: []byte("hello"),
Name: "report.md",
Path: "/tmp/report.md",
SizeBytes: 5,
}, nil
},
resolveRootForPathFn: func(_ context.Context, workspaceID string, path string) (workspacefiles.WorkspaceRoot, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if path != "/tmp/report.md" {
t.Fatalf("root path = %q, want /tmp/report.md", path)
}
return workspacefiles.WorkspaceRoot{
WorkspaceID: workspaceID,
LogicalRoot: "/",
PhysicalRoot: "/",
}, nil
},
},
}),
)
request, err := http.NewRequest(
http.MethodGet,
"/v1/workspaces/ws-1/files/file/preview?path=%2Ftmp%2Freport.md",
nil,
)
if err != nil {
t.Fatalf("NewRequest() error = %v", err)
}
recorder := httptest.NewRecorder()
mux.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
}
var response tuttigenerated.WorkspaceFilePreviewResponse
decodeGeneratedRouteResponse(t, recorder, &response)
if response.Root != "/" {
t.Fatalf("root = %q, want /", response.Root)
}
if response.Path != "/tmp/report.md" {
t.Fatalf("path = %q, want /tmp/report.md", response.Path)
}
}
func TestDaemonAPIGeneratedRoutesWriteWorkspaceFileText(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(
mux,
NewRoutes(DaemonAPI{
FileService: stubFileService{
writeTextFileFn: func(_ context.Context, workspaceID string, path string, content string) (workspacefiles.FileEntry, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if path != "/workspace/docs/todo.md" {
t.Fatalf("path = %q, want /workspace/docs/todo.md", path)
}
if content != "updated" {
t.Fatalf("content = %q, want updated", content)
}
size := int64(len(content))
return workspacefiles.FileEntry{
Path: "/workspace/docs/todo.md",
Name: "todo.md",
Kind: workspacefiles.EntryKindFile,
SizeBytes: &size,
}, nil
},
},
}),
)
recorder := performGeneratedRouteRequest(
t,
mux,
http.MethodPut,
"/v1/workspaces/ws-1/files/file/text",
map[string]any{"content": "updated", "path": "/workspace/docs/todo.md"},
)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
}
var response tuttigenerated.WorkspaceFileEntryResponse
decodeGeneratedRouteResponse(t, recorder, &response)
if response.Entry.Path != "/workspace/docs/todo.md" {
t.Fatalf("entry path = %q", response.Entry.Path)
}
if response.Entry.SizeBytes == nil || *response.Entry.SizeBytes != int64(len("updated")) {
t.Fatalf("entry size = %#v", response.Entry.SizeBytes)
}
}
func TestDaemonAPIGeneratedRoutesRenameWorkspaceFileEntryUsesPathAwareRoot(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(
mux,
NewRoutes(DaemonAPI{
FileService: stubFileService{
renameEntryFn: func(_ context.Context, workspaceID string, path string, newName string) (workspacefiles.FileEntry, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if path != "/tmp/report.md" {
t.Fatalf("path = %q, want /tmp/report.md", path)
}
if newName != "renamed.md" {
t.Fatalf("newName = %q, want renamed.md", newName)
}
return workspacefiles.FileEntry{
Path: "/tmp/renamed.md",
Name: "renamed.md",
Kind: workspacefiles.EntryKindFile,
}, nil
},
resolveRootForPathFn: func(_ context.Context, workspaceID string, path string) (workspacefiles.WorkspaceRoot, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if path != "/tmp/renamed.md" {
t.Fatalf("root path = %q, want /tmp/renamed.md", path)
}
return workspacefiles.WorkspaceRoot{
WorkspaceID: workspaceID,
LogicalRoot: "/",
PhysicalRoot: "/",
}, nil
},
},
}),
)
recorder := performGeneratedRouteRequest(
t,
mux,
http.MethodPost,
"/v1/workspaces/ws-1/files/entry/rename",
map[string]any{"newName": "renamed.md", "path": "/tmp/report.md"},
)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
}
var response tuttigenerated.WorkspaceFileEntryResponse
decodeGeneratedRouteResponse(t, recorder, &response)
if response.Root != "/" {
t.Fatalf("root = %q, want /", response.Root)
}
if response.Entry.Path != "/tmp/renamed.md" {
t.Fatalf("entry path = %q, want /tmp/renamed.md", response.Entry.Path)
}
}
func TestDaemonAPIGeneratedRoutesUploadWorkspaceFiles(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(
mux,
NewRoutes(DaemonAPI{
FileService: stubFileService{
uploadFilesFn: func(_ context.Context, workspaceID string, input workspacefiles.UploadInput) (workspacefiles.UploadResult, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if !input.Overwrite {
t.Fatal("overwrite should be forwarded")
}
if input.TargetDirectoryPath != "/workspace/docs" {
t.Fatalf("targetDirectoryPath = %q, want /workspace/docs", input.TargetDirectoryPath)
}
if len(input.SourcePaths) != 2 || input.SourcePaths[0] != "/tmp/a.txt" || input.SourcePaths[1] != "/tmp/b.txt" {
t.Fatalf("sourcePaths = %#v", input.SourcePaths)
}
return workspacefiles.UploadResult{
WorkspaceID: workspaceID,
Root: "/workspace",
TargetDirectoryPath: "/workspace/docs",
Entries: []workspacefiles.FileEntry{
{Path: "/workspace/docs/a.txt", Name: "a.txt", Kind: workspacefiles.EntryKindFile},
{Path: "/workspace/docs/b.txt", Name: "b.txt", Kind: workspacefiles.EntryKindFile},
},
}, nil
},
},
}),
)
recorder := performGeneratedRouteRequest(t, mux, http.MethodPost, "/v1/workspaces/ws-1/files/upload", map[string]any{
"overwrite": true,
"sourcePaths": []string{"/tmp/a.txt", "/tmp/b.txt"},
"targetDirectoryPath": "/workspace/docs",
})
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
}
var response tuttigenerated.UploadWorkspaceFilesResponse
decodeGeneratedRouteResponse(t, recorder, &response)
if response.WorkspaceId != "ws-1" || response.TargetDirectoryPath != "/workspace/docs" || len(response.Entries) != 2 {
t.Fatalf("response = %#v", response)
}
}
func TestDaemonAPIGeneratedRoutesPreflightUploadWorkspaceFiles(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(
mux,
NewRoutes(DaemonAPI{
FileService: stubFileService{
preflightUploadFilesFn: func(_ context.Context, workspaceID string, input workspacefiles.PreflightUploadInput) (workspacefiles.PreflightUploadResult, error) {
if workspaceID != "ws-1" {
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
}
if input.TargetDirectoryPath != "/workspace/docs" {
t.Fatalf("targetDirectoryPath = %q, want /workspace/docs", input.TargetDirectoryPath)
}
if len(input.SourcePaths) != 1 || input.SourcePaths[0] != "/tmp/report.md" {
t.Fatalf("sourcePaths = %#v", input.SourcePaths)
}
return workspacefiles.PreflightUploadResult{
WorkspaceID: workspaceID,
Root: "/workspace",
TargetDirectoryPath: "/workspace/docs",
Conflicts: []workspacefiles.UploadConflict{
{
DestinationKind: workspacefiles.EntryKindFile,
DestinationPath: "/workspace/docs/report.md",
Name: "report.md",
SourcePath: "/tmp/report.md",
},
},
}, nil
},
},
}),
)
recorder := performGeneratedRouteRequest(t, mux, http.MethodPost, "/v1/workspaces/ws-1/files/upload/preflight", map[string]any{
"sourcePaths": []string{"/tmp/report.md"},
"targetDirectoryPath": "/workspace/docs",
})
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
}
var response tuttigenerated.PreflightUploadWorkspaceFilesResponse
decodeGeneratedRouteResponse(t, recorder, &response)
if len(response.Conflicts) != 1 {
t.Fatalf("conflicts len = %d, want 1", len(response.Conflicts))
}
if response.Conflicts[0].DestinationPath != "/workspace/docs/report.md" {
t.Fatalf("destinationPath = %q, want /workspace/docs/report.md", response.Conflicts[0].DestinationPath)
}
}