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
312 lines
9.9 KiB
Go
312 lines
9.9 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
workspacefiles "github.com/tutti-os/tutti/packages/workspace/files"
|
|
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
|
|
)
|
|
|
|
func TestDaemonAPIGeneratedRoutesSearchWorkspaceFilesRequiresQuery(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
RegisterRoutes(
|
|
mux,
|
|
NewRoutes(DaemonAPI{
|
|
FileService: stubFileService{
|
|
searchFn: func(context.Context, string, workspacefiles.SearchInput) (workspacefiles.SearchResult, error) {
|
|
t.Fatal("Search should not be called when query is missing")
|
|
return workspacefiles.SearchResult{}, nil
|
|
},
|
|
},
|
|
}),
|
|
)
|
|
|
|
recorder := performGeneratedRouteRequest(t, mux, http.MethodGet, "/v1/workspaces/ws-1/files/search", nil)
|
|
if recorder.Code != http.StatusBadRequest {
|
|
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusBadRequest, recorder.Body.String())
|
|
}
|
|
|
|
assertGeneratedRouteError(
|
|
t,
|
|
recorder,
|
|
tuttigenerated.InvalidRequest,
|
|
"malformed_request",
|
|
"Query argument query is required, but not found",
|
|
)
|
|
}
|
|
|
|
func TestDaemonAPIGeneratedRoutesSearchWorkspaceFilesRejectsInvalidLimit(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
RegisterRoutes(
|
|
mux,
|
|
NewRoutes(DaemonAPI{
|
|
FileService: stubFileService{
|
|
searchFn: func(context.Context, string, workspacefiles.SearchInput) (workspacefiles.SearchResult, error) {
|
|
t.Fatal("Search should not be called when limit is invalid")
|
|
return workspacefiles.SearchResult{}, nil
|
|
},
|
|
},
|
|
}),
|
|
)
|
|
|
|
recorder := performGeneratedRouteRequest(
|
|
t,
|
|
mux,
|
|
http.MethodGet,
|
|
"/v1/workspaces/ws-1/files/search?query=main&limit=nope",
|
|
nil,
|
|
)
|
|
if recorder.Code != http.StatusBadRequest {
|
|
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusBadRequest, recorder.Body.String())
|
|
}
|
|
|
|
assertGeneratedRouteError(
|
|
t,
|
|
recorder,
|
|
tuttigenerated.InvalidRequest,
|
|
"malformed_request",
|
|
"Invalid format for parameter limit: error binding string parameter: strconv.ParseInt: parsing \"nope\": invalid syntax",
|
|
)
|
|
}
|
|
|
|
func TestDaemonAPIGeneratedRoutesSearchWorkspaceFilesForwardsIncludeHidden(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
RegisterRoutes(
|
|
mux,
|
|
NewRoutes(DaemonAPI{
|
|
FileService: stubFileService{
|
|
searchFn: func(_ context.Context, workspaceID string, input workspacefiles.SearchInput) (workspacefiles.SearchResult, error) {
|
|
if workspaceID != "ws-1" {
|
|
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
|
|
}
|
|
if input.Query != "main" {
|
|
t.Fatalf("query = %q, want main", input.Query)
|
|
}
|
|
if !input.IncludeHidden {
|
|
t.Fatal("includeHidden = false, want true")
|
|
}
|
|
return workspacefiles.SearchResult{
|
|
Entries: []workspacefiles.SearchEntry{},
|
|
}, nil
|
|
},
|
|
},
|
|
}),
|
|
)
|
|
|
|
recorder := performGeneratedRouteRequest(
|
|
t,
|
|
mux,
|
|
http.MethodGet,
|
|
"/v1/workspaces/ws-1/files/search?query=main&includeHidden=true",
|
|
nil,
|
|
)
|
|
if recorder.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestDaemonAPIGeneratedRoutesListWorkspaceFileDirectory(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
RegisterRoutes(
|
|
mux,
|
|
NewRoutes(DaemonAPI{
|
|
FileService: stubFileService{
|
|
listDirectoryFn: func(_ context.Context, workspaceID string, input workspacefiles.DirectoryListInput) (workspacefiles.DirectoryListing, error) {
|
|
if workspaceID != "ws-1" {
|
|
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
|
|
}
|
|
if input.Path != "/workspace/src" {
|
|
t.Fatalf("path = %q, want /workspace/src", input.Path)
|
|
}
|
|
if input.IncludeHidden {
|
|
t.Fatal("includeHidden = true, want false")
|
|
}
|
|
size := int64(12)
|
|
return workspacefiles.DirectoryListing{
|
|
WorkspaceID: workspaceID,
|
|
Root: "/workspace",
|
|
DirectoryPath: "/workspace/src",
|
|
Entries: []workspacefiles.FileEntry{
|
|
{
|
|
Path: "/workspace/src/main.go",
|
|
Name: "main.go",
|
|
Kind: workspacefiles.EntryKindFile,
|
|
SizeBytes: &size,
|
|
},
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
}),
|
|
)
|
|
|
|
recorder := performGeneratedRouteRequest(t, mux, http.MethodGet, "/v1/workspaces/ws-1/files/directory?path=/workspace/src", nil)
|
|
if recorder.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
|
|
}
|
|
|
|
var response tuttigenerated.WorkspaceFileDirectoryResponse
|
|
decodeGeneratedRouteResponse(t, recorder, &response)
|
|
if response.WorkspaceId != "ws-1" || response.DirectoryPath != "/workspace/src" {
|
|
t.Fatalf("response = %#v", response)
|
|
}
|
|
if len(response.Entries) != 1 || response.Entries[0].Path != "/workspace/src/main.go" {
|
|
t.Fatalf("entries = %#v", response.Entries)
|
|
}
|
|
}
|
|
|
|
func TestDaemonAPIGeneratedRoutesListWorkspaceRecentFiles(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
RegisterRoutes(
|
|
mux,
|
|
NewRoutes(DaemonAPI{
|
|
FileService: stubFileService{
|
|
listRecentFn: func(_ context.Context, workspaceID string, input workspacefiles.RecentListInput) (workspacefiles.DirectoryListing, error) {
|
|
if workspaceID != "ws-1" {
|
|
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
|
|
}
|
|
if input.Limit != 5 {
|
|
t.Fatalf("limit = %d, want 5", input.Limit)
|
|
}
|
|
lastUsed := int64(1700)
|
|
return workspacefiles.DirectoryListing{
|
|
WorkspaceID: workspaceID,
|
|
Root: "/workspace",
|
|
DirectoryPath: "/workspace",
|
|
Entries: []workspacefiles.FileEntry{
|
|
{
|
|
Path: "/workspace/src/main.go",
|
|
Name: "main.go",
|
|
Kind: workspacefiles.EntryKindFile,
|
|
LastOpenedMs: &lastUsed,
|
|
},
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
}),
|
|
)
|
|
|
|
recorder := performGeneratedRouteRequest(t, mux, http.MethodGet, "/v1/workspaces/ws-1/files/recent?limit=5", nil)
|
|
if recorder.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
|
|
}
|
|
|
|
var response tuttigenerated.WorkspaceFileDirectoryResponse
|
|
decodeGeneratedRouteResponse(t, recorder, &response)
|
|
if response.WorkspaceId != "ws-1" {
|
|
t.Fatalf("response = %#v", response)
|
|
}
|
|
if len(response.Entries) != 1 || response.Entries[0].Path != "/workspace/src/main.go" {
|
|
t.Fatalf("entries = %#v", response.Entries)
|
|
}
|
|
}
|
|
|
|
func TestDaemonAPIGeneratedRoutesListWorkspaceFileDirectoryForwardsIncludeHidden(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
RegisterRoutes(
|
|
mux,
|
|
NewRoutes(DaemonAPI{
|
|
FileService: stubFileService{
|
|
listDirectoryFn: func(_ context.Context, workspaceID string, input workspacefiles.DirectoryListInput) (workspacefiles.DirectoryListing, error) {
|
|
if workspaceID != "ws-1" {
|
|
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
|
|
}
|
|
if input.Path != "" {
|
|
t.Fatalf("path = %q, want empty root path", input.Path)
|
|
}
|
|
if !input.IncludeHidden {
|
|
t.Fatal("includeHidden = false, want true")
|
|
}
|
|
return workspacefiles.DirectoryListing{
|
|
WorkspaceID: workspaceID,
|
|
Root: "/workspace",
|
|
DirectoryPath: "/workspace",
|
|
Entries: []workspacefiles.FileEntry{},
|
|
}, nil
|
|
},
|
|
},
|
|
}),
|
|
)
|
|
|
|
recorder := performGeneratedRouteRequest(t, mux, http.MethodGet, "/v1/workspaces/ws-1/files/directory?includeHidden=true", nil)
|
|
if recorder.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestDaemonAPIGeneratedRoutesGetWorkspaceFileTreeSnapshot(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
RegisterRoutes(
|
|
mux,
|
|
NewRoutes(DaemonAPI{
|
|
FileService: stubFileService{
|
|
getDirectoryTreeFn: func(_ context.Context, workspaceID string, input workspacefiles.DirectoryTreeSnapshotInput) (workspacefiles.DirectoryTreeSnapshot, error) {
|
|
if workspaceID != "ws-1" {
|
|
t.Fatalf("workspaceID = %q, want ws-1", workspaceID)
|
|
}
|
|
if input.Path != "/workspace/src" {
|
|
t.Fatalf("path = %q, want /workspace/src", input.Path)
|
|
}
|
|
if !input.IncludeHidden {
|
|
t.Fatal("includeHidden = false, want true")
|
|
}
|
|
if input.PrefetchDepth != 3 {
|
|
t.Fatalf("prefetchDepth = %d, want 3", input.PrefetchDepth)
|
|
}
|
|
if input.PrefetchBudget != 250*time.Millisecond {
|
|
t.Fatalf("prefetchBudget = %s, want 250ms", input.PrefetchBudget)
|
|
}
|
|
return workspacefiles.DirectoryTreeSnapshot{
|
|
WorkspaceID: workspaceID,
|
|
Root: "/workspace",
|
|
PrefetchBudgetMs: 250,
|
|
PrefetchDepth: 3,
|
|
BudgetExceeded: true,
|
|
Directory: workspacefiles.DirectoryTreeDirectory{
|
|
DirectoryPath: "/workspace/src",
|
|
PrefetchState: workspacefiles.DirectoryTreePrefetchStatePartial,
|
|
Entries: []workspacefiles.DirectoryTreeEntry{
|
|
{
|
|
Path: "/workspace/src/app",
|
|
Name: "app",
|
|
Kind: workspacefiles.EntryKindDirectory,
|
|
HasChildren: true,
|
|
PrefetchState: workspacefiles.DirectoryTreePrefetchStateNotLoaded,
|
|
PrefetchReason: workspacefiles.DirectoryTreePrefetchReasonBudgetExhausted,
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
}),
|
|
)
|
|
|
|
recorder := performGeneratedRouteRequest(
|
|
t,
|
|
mux,
|
|
http.MethodGet,
|
|
"/v1/workspaces/ws-1/files/tree-snapshot?path=/workspace/src&includeHidden=true&prefetchDepth=3&prefetchBudgetMs=250",
|
|
nil,
|
|
)
|
|
if recorder.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
|
|
}
|
|
|
|
var response tuttigenerated.WorkspaceFileTreeSnapshotResponse
|
|
decodeGeneratedRouteResponse(t, recorder, &response)
|
|
if response.Directory.DirectoryPath != "/workspace/src" {
|
|
t.Fatalf("directoryPath = %q, want /workspace/src", response.Directory.DirectoryPath)
|
|
}
|
|
if !response.BudgetExceeded || response.PrefetchDepth != 3 || response.PrefetchBudgetMs != 250 {
|
|
t.Fatalf("response = %#v", response)
|
|
}
|
|
if len(response.Directory.Entries) != 1 || response.Directory.Entries[0].Path != "/workspace/src/app" {
|
|
t.Fatalf("entries = %#v", response.Directory.Entries)
|
|
}
|
|
}
|