yao/workspace/fileio_test.go
Max df420978f0 feat(workspace): implement JSAPI with archive support and full test coverage
- 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
2026-03-08 14:17:39 +08:00

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)
})
}
}