- tai/volume: add WithRemotePath sync option, 8 archive/compression methods (Zip/Unzip/Gzip/Gunzip/Tar/Untar/Tgz/Untgz) with local+remote impls - workspace/jsapi: implement 24-method WorkspaceFS (file I/O, Copy with local:///tmp:// URI dispatch, archive ops) and 4 static methods - workspace/manager: add Rename, MkdirAll, Volume() accessors via singleton - Tests: comprehensive Go tests for tai/volume, workspace, workspace/jsapi covering both local and remote modes via SANDBOX_TEST_REMOTE_ADDR - CI: update tai Docker image to latest in pr-test.yml - sandbox/v2: add JSAPI docs and host/node module stubs Made-with: Cursor
319 lines
8.1 KiB
Go
319 lines
8.1 KiB
Go
package workspace_test
|
|
|
|
import (
|
|
"context"
|
|
"io/fs"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/yaoapp/yao/workspace"
|
|
)
|
|
|
|
func TestReadWriteFile(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
err := m.WriteFile(ctx, ws.ID, "hello.txt", []byte("hello world"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
data, err := m.ReadFile(ctx, ws.ID, "hello.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "hello world", string(data))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWriteFile_NestedPath(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
err := m.WriteFile(ctx, ws.ID, "src/main.go", []byte("package main"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
data, err := m.ReadFile(ctx, ws.ID, "src/main.go")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "package main", string(data))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListDir(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
require.NoError(t, m.WriteFile(ctx, ws.ID, "a.txt", []byte("a"), 0644))
|
|
require.NoError(t, m.WriteFile(ctx, ws.ID, "b.txt", []byte("b"), 0644))
|
|
|
|
entries, err := m.ListDir(ctx, ws.ID, ".")
|
|
require.NoError(t, err)
|
|
names := make(map[string]bool)
|
|
for _, e := range entries {
|
|
names[e.Name] = true
|
|
}
|
|
assert.True(t, names["a.txt"])
|
|
assert.True(t, names["b.txt"])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemoveFile(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
require.NoError(t, m.WriteFile(ctx, ws.ID, "tmp.txt", []byte("temp"), 0644))
|
|
|
|
err := m.Remove(ctx, ws.ID, "tmp.txt")
|
|
require.NoError(t, err)
|
|
|
|
_, err = m.ReadFile(ctx, ws.ID, "tmp.txt")
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFS_ReadFile(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
require.NoError(t, m.WriteFile(ctx, ws.ID, "test.txt", []byte("via fs"), 0644))
|
|
|
|
wfs, err := m.FS(ctx, ws.ID)
|
|
require.NoError(t, err)
|
|
|
|
data, err := fs.ReadFile(wfs, "test.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "via fs", string(data))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFS_WriteFile(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
wfs, err := m.FS(ctx, ws.ID)
|
|
require.NoError(t, err)
|
|
|
|
err = wfs.WriteFile("from-fs.txt", []byte("written via fs"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
data, err := m.ReadFile(ctx, ws.ID, "from-fs.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "written via fs", string(data))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFS_MkdirAll(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
wfs, err := m.FS(ctx, ws.ID)
|
|
require.NoError(t, err)
|
|
|
|
err = wfs.MkdirAll("a/b/c", 0755)
|
|
require.NoError(t, err)
|
|
|
|
info, err := fs.Stat(wfs, "a/b/c")
|
|
require.NoError(t, err)
|
|
assert.True(t, info.IsDir())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFS_Rename(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
wfs, err := m.FS(ctx, ws.ID)
|
|
require.NoError(t, err)
|
|
|
|
err = wfs.WriteFile("old.txt", []byte("content"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
err = wfs.Rename("old.txt", "new.txt")
|
|
require.NoError(t, err)
|
|
|
|
data, err := fs.ReadFile(wfs, "new.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "content", string(data))
|
|
|
|
_, err = fs.ReadFile(wfs, "old.txt")
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFS_WalkDir(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
wfs, err := m.FS(ctx, ws.ID)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, wfs.MkdirAll("src", 0755))
|
|
require.NoError(t, wfs.WriteFile("src/main.go", []byte("package main"), 0644))
|
|
require.NoError(t, wfs.WriteFile("src/util.go", []byte("package main"), 0644))
|
|
|
|
var files []string
|
|
err = fs.WalkDir(wfs, "src", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !d.IsDir() {
|
|
files = append(files, path)
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Len(t, files, 2)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFS_Remove(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
|
|
ctx := context.Background()
|
|
wfs, err := m.FS(ctx, ws.ID)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, wfs.WriteFile("removeme.txt", []byte("bye"), 0644))
|
|
|
|
err = wfs.Remove("removeme.txt")
|
|
require.NoError(t, err)
|
|
|
|
_, err = fs.ReadFile(wfs, "removeme.txt")
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFS_NotFound(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
_, err := m.FS(context.Background(), "nonexistent")
|
|
assert.ErrorIs(t, err, workspace.ErrNotFound)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagerRename(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
ctx := context.Background()
|
|
|
|
require.NoError(t, m.WriteFile(ctx, ws.ID, "old.txt", []byte("rename me"), 0644))
|
|
require.NoError(t, m.Rename(ctx, ws.ID, "old.txt", "new.txt"))
|
|
|
|
data, err := m.ReadFile(ctx, ws.ID, "new.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "rename me", string(data))
|
|
|
|
_, err = m.ReadFile(ctx, ws.ID, "old.txt")
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagerRename_NotFound(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
err := m.Rename(context.Background(), "nonexistent", "a", "b")
|
|
assert.ErrorIs(t, err, workspace.ErrNotFound)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagerMkdirAll(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
ctx := context.Background()
|
|
|
|
require.NoError(t, m.MkdirAll(ctx, ws.ID, "a/b/c"))
|
|
require.NoError(t, m.WriteFile(ctx, ws.ID, "a/b/c/test.txt", []byte("deep"), 0644))
|
|
|
|
data, err := m.ReadFile(ctx, ws.ID, "a/b/c/test.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "deep", string(data))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagerMkdirAll_NotFound(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
err := m.MkdirAll(context.Background(), "nonexistent", "a/b")
|
|
assert.ErrorIs(t, err, workspace.ErrNotFound)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagerVolume(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
ws := createWorkspace(t, m, pc.Name)
|
|
ctx := context.Background()
|
|
|
|
vol, wsID, err := m.Volume(ctx, ws.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ws.ID, wsID)
|
|
assert.NotNil(t, vol)
|
|
|
|
require.NoError(t, vol.WriteFile(ctx, wsID, "via-vol.txt", []byte("volume direct"), 0o644))
|
|
data, _, err := vol.ReadFile(ctx, wsID, "via-vol.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "volume direct", string(data))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagerVolume_NotFound(t *testing.T) {
|
|
for _, pc := range testPools() {
|
|
t.Run(pc.Name, func(t *testing.T) {
|
|
m := setupManagerForPool(t, pc)
|
|
_, _, err := m.Volume(context.Background(), "nonexistent")
|
|
assert.ErrorIs(t, err, workspace.ErrNotFound)
|
|
})
|
|
}
|
|
}
|