mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* feat: Add support for sandbox edit command * docs: Update sandbox docs * fix(editor): address review — neutral failure wording, skip sh-script tests on Windows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(test): avoid pnpm init in pm-e2e — devEngines.packageManager crashes subsequent pnpm add Same workaround and rationale as the PNPM job in pmg-e2e.yml. Latest pnpm (unpinned pnpm/action-setup) writes devEngines.packageManager with onFail:download on init, and the next add fails with "Cannot use 'in' operator to search for 'integrity' in undefined". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
191 lines
5.0 KiB
Go
191 lines
5.0 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/safedep/dry/usefulerror"
|
|
"github.com/safedep/pmg/errcodes"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const editTestProfile = `name: myprof
|
|
description: test profile
|
|
inherits: npm-restrictive
|
|
package_managers:
|
|
- npm
|
|
filesystem:
|
|
allow_write:
|
|
- ${CWD}/extra
|
|
`
|
|
|
|
func writeEditTestProfile(t *testing.T, dir, name string) string {
|
|
t.Helper()
|
|
require.NoError(t, os.MkdirAll(dir, 0o755))
|
|
path := filepath.Join(dir, name+".yml")
|
|
require.NoError(t, os.WriteFile(path, []byte(editTestProfile), 0o644))
|
|
return path
|
|
}
|
|
|
|
func writeEditorScript(t *testing.T, body string) string {
|
|
t.Helper()
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("editor scripts require /bin/sh")
|
|
}
|
|
path := filepath.Join(t.TempDir(), "editor.sh")
|
|
require.NoError(t, os.WriteFile(path, []byte("#!/bin/sh\n"+body+"\n"), 0o755))
|
|
return path
|
|
}
|
|
|
|
func runEditCmd(t *testing.T, dir string, args ...string) (string, string, error) {
|
|
t.Helper()
|
|
cmd := newProfileEditCommand(newTestRegistryFactory(t, dir))
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.SetOut(&stdout)
|
|
cmd.SetErr(&stderr)
|
|
cmd.SetArgs(args)
|
|
err := cmd.Execute()
|
|
return stdout.String(), stderr.String(), err
|
|
}
|
|
|
|
func TestProfileEdit_HappyPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := writeEditTestProfile(t, dir, "myprof")
|
|
|
|
editor := writeEditorScript(t, `printf ' - ${CWD}/more\n' >> "$1"`)
|
|
t.Setenv("VISUAL", "")
|
|
t.Setenv("EDITOR", editor)
|
|
|
|
stdout, stderr, err := runEditCmd(t, dir, "myprof")
|
|
require.NoError(t, err, "stderr: %s", stderr)
|
|
|
|
data, err := os.ReadFile(path)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, string(data), "${CWD}/more")
|
|
assert.Contains(t, stdout, "Profile Lint")
|
|
}
|
|
|
|
func TestProfileEdit_VisualTakesPrecedenceOverEditor(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := writeEditTestProfile(t, dir, "myprof")
|
|
|
|
visual := writeEditorScript(t, `printf ' - ${CWD}/from-visual\n' >> "$1"`)
|
|
editor := writeEditorScript(t, `printf ' - ${CWD}/from-editor\n' >> "$1"`)
|
|
t.Setenv("VISUAL", visual)
|
|
t.Setenv("EDITOR", editor)
|
|
|
|
_, _, err := runEditCmd(t, dir, "myprof")
|
|
require.NoError(t, err)
|
|
|
|
data, err := os.ReadFile(path)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, string(data), "from-visual")
|
|
assert.NotContains(t, string(data), "from-editor")
|
|
}
|
|
|
|
func TestProfileEdit_BuiltinRefused(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("VISUAL", "")
|
|
t.Setenv("EDITOR", writeEditorScript(t, `exit 0`))
|
|
|
|
_, _, err := runEditCmd(t, dir, "npm-restrictive")
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "built-in")
|
|
|
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
|
assert.Contains(t, usefulErr.Help(), "pmg sandbox profile init")
|
|
}
|
|
|
|
func TestProfileEdit_UnknownProfile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("VISUAL", "")
|
|
t.Setenv("EDITOR", writeEditorScript(t, `exit 0`))
|
|
|
|
_, _, err := runEditCmd(t, dir, "no-such-profile")
|
|
require.Error(t, err)
|
|
|
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errcodes.NotFound, usefulErr.Code())
|
|
}
|
|
|
|
func TestProfileEdit_EditorFailure(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := writeEditTestProfile(t, dir, "myprof")
|
|
|
|
t.Setenv("VISUAL", "")
|
|
t.Setenv("EDITOR", writeEditorScript(t, `exit 7`))
|
|
|
|
_, _, err := runEditCmd(t, dir, "myprof")
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "editor")
|
|
|
|
data, readErr := os.ReadFile(path)
|
|
require.NoError(t, readErr)
|
|
assert.Equal(t, editTestProfile, string(data))
|
|
}
|
|
|
|
func TestProfileEdit_InvalidAfterEditKeepsFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := writeEditTestProfile(t, dir, "myprof")
|
|
|
|
t.Setenv("VISUAL", "")
|
|
t.Setenv("EDITOR", writeEditorScript(t, `printf 'name: [broken\n' > "$1"`))
|
|
|
|
_, _, err := runEditCmd(t, dir, "myprof")
|
|
require.Error(t, err)
|
|
|
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
|
|
|
data, readErr := os.ReadFile(path)
|
|
require.NoError(t, readErr)
|
|
assert.Contains(t, string(data), "name: [broken")
|
|
}
|
|
|
|
func TestProfileEdit_LintWarningsShown(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeEditTestProfile(t, dir, "myprof")
|
|
|
|
editor := writeEditorScript(t, `printf ' - /**\n' >> "$1"`)
|
|
t.Setenv("VISUAL", "")
|
|
t.Setenv("EDITOR", editor)
|
|
|
|
stdout, _, err := runEditCmd(t, dir, "myprof")
|
|
require.NoError(t, err)
|
|
assert.Contains(t, stdout, "WARN")
|
|
}
|
|
|
|
func TestProfileEdit_ShadowedProfileWarns(t *testing.T) {
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.MkdirAll(dir, 0o755))
|
|
shadow := `name: npm
|
|
package_managers:
|
|
- npm
|
|
filesystem:
|
|
allow_write:
|
|
- ${CWD}/extra
|
|
`
|
|
path := filepath.Join(dir, "npm.yml")
|
|
require.NoError(t, os.WriteFile(path, []byte(shadow), 0o644))
|
|
|
|
editor := writeEditorScript(t, `printf ' - ${CWD}/more\n' >> "$1"`)
|
|
t.Setenv("VISUAL", "")
|
|
t.Setenv("EDITOR", editor)
|
|
|
|
_, stderr, err := runEditCmd(t, dir, "npm")
|
|
require.NoError(t, err)
|
|
assert.Contains(t, stderr, "shadowed")
|
|
|
|
data, readErr := os.ReadFile(path)
|
|
require.NoError(t, readErr)
|
|
assert.Contains(t, string(data), "${CWD}/more")
|
|
}
|