go-io/workspace/service_test.go
Snider df9c443657
Some checks failed
CI / test (push) Failing after 3s
CI / auto-fix (push) Failing after 0s
CI / auto-merge (push) Failing after 0s
feat(workspace): encrypt workspace files using ChaChaPolySigil
ReadWorkspaceFile and WriteWorkspaceFile now encrypt/decrypt file
content using XChaCha20-Poly1305 via the existing sigil pipeline.
A 32-byte symmetric key is derived by SHA-256-hashing the workspace's
stored private.key material so no new dependencies are required.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-31 16:14:43 +01:00

214 lines
6.2 KiB
Go

package workspace
import (
"io/fs"
"testing"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testKeyPairProvider struct {
privateKey string
err error
}
func (provider testKeyPairProvider) CreateKeyPair(identifier, passphrase string) (string, error) {
if provider.err != nil {
return "", provider.err
}
return provider.privateKey, nil
}
func newWorkspaceService(t *testing.T) (*Service, string) {
t.Helper()
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
service, err := New(Options{KeyPairProvider: testKeyPairProvider{privateKey: "private-key"}})
require.NoError(t, err)
return service, tempHome
}
func TestService_New_MissingKeyPairProvider_Bad(t *testing.T) {
_, err := New(Options{})
require.Error(t, err)
}
func TestService_New_CustomRootPathAndMedium_Good(t *testing.T) {
medium := coreio.NewMemoryMedium()
rootPath := core.Path(t.TempDir(), "custom", "workspaces")
service, err := New(Options{
KeyPairProvider: testKeyPairProvider{privateKey: "private-key"},
RootPath: rootPath,
Medium: medium,
})
require.NoError(t, err)
assert.Equal(t, rootPath, service.rootPath)
assert.Same(t, medium, service.medium)
workspaceID, err := service.CreateWorkspace("custom-user", "pass123")
require.NoError(t, err)
assert.NotEmpty(t, workspaceID)
expectedWorkspacePath := core.Path(rootPath, workspaceID)
assert.True(t, medium.IsDir(rootPath))
assert.True(t, medium.IsDir(core.Path(expectedWorkspacePath, "keys")))
assert.True(t, medium.Exists(core.Path(expectedWorkspacePath, "keys", "private.key")))
}
func TestService_WorkspaceFileRoundTrip_Good(t *testing.T) {
service, tempHome := newWorkspaceService(t)
workspaceID, err := service.CreateWorkspace("test-user", "pass123")
require.NoError(t, err)
assert.NotEmpty(t, workspaceID)
workspacePath := core.Path(tempHome, ".core", "workspaces", workspaceID)
assert.DirExists(t, workspacePath)
assert.DirExists(t, core.Path(workspacePath, "keys"))
assert.FileExists(t, core.Path(workspacePath, "keys", "private.key"))
err = service.SwitchWorkspace(workspaceID)
require.NoError(t, err)
assert.Equal(t, workspaceID, service.activeWorkspaceID)
err = service.WriteWorkspaceFile("secret.txt", "top secret info")
require.NoError(t, err)
got, err := service.ReadWorkspaceFile("secret.txt")
require.NoError(t, err)
assert.Equal(t, "top secret info", got)
}
func TestService_SwitchWorkspace_TraversalBlocked_Bad(t *testing.T) {
service, tempHome := newWorkspaceService(t)
outside := core.Path(tempHome, ".core", "escaped")
require.NoError(t, service.medium.EnsureDir(outside))
err := service.SwitchWorkspace("../escaped")
require.Error(t, err)
assert.Empty(t, service.activeWorkspaceID)
}
func TestService_WriteWorkspaceFile_TraversalBlocked_Bad(t *testing.T) {
service, tempHome := newWorkspaceService(t)
workspaceID, err := service.CreateWorkspace("test-user", "pass123")
require.NoError(t, err)
require.NoError(t, service.SwitchWorkspace(workspaceID))
keyPath := core.Path(tempHome, ".core", "workspaces", workspaceID, "keys", "private.key")
before, err := service.medium.Read(keyPath)
require.NoError(t, err)
err = service.WriteWorkspaceFile("../keys/private.key", "hijack")
require.Error(t, err)
after, err := service.medium.Read(keyPath)
require.NoError(t, err)
assert.Equal(t, before, after)
_, err = service.ReadWorkspaceFile("../keys/private.key")
require.Error(t, err)
}
func TestService_JoinPathWithinRoot_DefaultSeparator_Good(t *testing.T) {
t.Setenv("CORE_PATH_SEPARATOR", "")
path, err := joinPathWithinRoot("/tmp/workspaces", "../workspaces2")
require.Error(t, err)
assert.ErrorIs(t, err, fs.ErrPermission)
assert.Empty(t, path)
}
func TestService_New_IPCAutoRegistration_Good(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
c := core.New()
service, err := New(Options{
KeyPairProvider: testKeyPairProvider{privateKey: "private-key"},
Core: c,
})
require.NoError(t, err)
// Create a workspace directly, then switch via the Core IPC bus.
workspaceID, err := service.CreateWorkspace("ipc-bus-user", "pass789")
require.NoError(t, err)
// Dispatching workspace.switch via ACTION must reach the auto-registered handler.
c.ACTION(WorkspaceCommand{
Action: WorkspaceSwitchAction,
WorkspaceID: workspaceID,
})
assert.Equal(t, workspaceID, service.activeWorkspaceID)
}
func TestService_New_IPCCreate_Good(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
c := core.New()
service, err := New(Options{
KeyPairProvider: testKeyPairProvider{privateKey: "private-key"},
Core: c,
})
require.NoError(t, err)
// workspace.create dispatched via the bus must create the workspace on the medium.
c.ACTION(WorkspaceCommand{
Action: WorkspaceCreateAction,
Identifier: "ipc-create-user",
Password: "pass123",
})
// A duplicate create must fail — proves the first create succeeded.
_, err = service.CreateWorkspace("ipc-create-user", "pass123")
require.Error(t, err)
}
func TestService_New_NoCoreOption_NoRegistration_Good(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
// Without Core in Options, New must succeed and no IPC handler is registered.
service, err := New(Options{
KeyPairProvider: testKeyPairProvider{privateKey: "private-key"},
})
require.NoError(t, err)
assert.NotNil(t, service)
}
func TestService_HandleWorkspaceMessage_Command_Good(t *testing.T) {
service, _ := newWorkspaceService(t)
create := service.HandleWorkspaceMessage(core.New(), WorkspaceCommand{
Action: WorkspaceCreateAction,
Identifier: "ipc-user",
Password: "pass123",
})
assert.True(t, create.OK)
workspaceID, ok := create.Value.(string)
require.True(t, ok)
require.NotEmpty(t, workspaceID)
switchResult := service.HandleWorkspaceMessage(core.New(), WorkspaceCommand{
Action: WorkspaceSwitchAction,
WorkspaceID: workspaceID,
})
assert.True(t, switchResult.OK)
assert.Equal(t, workspaceID, service.activeWorkspaceID)
unknownAction := service.HandleWorkspaceCommand(WorkspaceCommand{Action: "noop"})
assert.False(t, unknownAction.OK)
unknown := service.HandleWorkspaceMessage(core.New(), "noop")
assert.False(t, unknown.OK)
}