mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Add Sandbox Inspection and Debugging Commands (#261)
* feat: add sandbox DX commands * fix: Linter errors * fix: Sandbox deny log parsing * fix: Sandbox docs * refactor: Maintain SSOT across pkg dependencies * fix: Linter errors
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||
"github.com/safedep/pmg/usefulerror"
|
||||
)
|
||||
|
||||
func writeUserProfileLint(t *testing.T, dir, name, body string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(dir, name+".yml")
|
||||
require.NoError(t, os.WriteFile(path, []byte(body), 0o644))
|
||||
return path
|
||||
}
|
||||
|
||||
func TestProfileLint_BuiltinClean(t *testing.T) {
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, ""))
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs([]string{"npm-restrictive"})
|
||||
|
||||
require.NoError(t, cmd.Execute())
|
||||
assert.Contains(t, stdout.String(), "Profile Lint")
|
||||
}
|
||||
|
||||
func TestProfileLint_JSONOutput(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeUserProfileLint(t, dir, "broad", `name: broad
|
||||
description: broad profile
|
||||
package_managers:
|
||||
- npm
|
||||
filesystem:
|
||||
allow_read:
|
||||
- /**
|
||||
allow_write:
|
||||
- ${HOME}/**
|
||||
`)
|
||||
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, dir))
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs([]string{"broad", "--json"})
|
||||
|
||||
err := cmd.Execute()
|
||||
// Lint surfaces warnings but no errors; exit should be zero without --strict.
|
||||
require.NoError(t, err)
|
||||
|
||||
var report struct {
|
||||
Profile string `json:"profile"`
|
||||
Issues []pmgsandbox.LintIssue `json:"issues"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(stdout.Bytes(), &report))
|
||||
assert.Equal(t, filepath.Join(dir, "broad.yml"), report.Profile)
|
||||
codes := map[string]int{}
|
||||
for _, i := range report.Issues {
|
||||
codes[i.Code]++
|
||||
}
|
||||
assert.Equal(t, 1, codes["broad.root_glob"])
|
||||
assert.Equal(t, 1, codes["broad.home_glob"])
|
||||
}
|
||||
|
||||
func TestProfileLint_StrictFailsOnWarn(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeUserProfileLint(t, dir, "warn", `name: warn
|
||||
description: warn profile
|
||||
package_managers:
|
||||
- npm
|
||||
filesystem:
|
||||
allow_read:
|
||||
- /**
|
||||
`)
|
||||
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, dir))
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs([]string{"warn", "--strict"})
|
||||
|
||||
err := cmd.Execute()
|
||||
require.Error(t, err)
|
||||
fail, ok := err.(*lintFailError)
|
||||
require.True(t, ok, "expected *lintFailError, got %T", err)
|
||||
assert.Equal(t, ExitCodeLintFail, fail.ExitCode())
|
||||
}
|
||||
|
||||
func TestProfileLint_NoStrictWarnSucceeds(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeUserProfileLint(t, dir, "warn", `name: warn
|
||||
description: warn profile
|
||||
package_managers:
|
||||
- npm
|
||||
filesystem:
|
||||
allow_read:
|
||||
- /**
|
||||
`)
|
||||
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, dir))
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs([]string{"warn"})
|
||||
|
||||
require.NoError(t, cmd.Execute())
|
||||
assert.Contains(t, stdout.String(), "WARN")
|
||||
}
|
||||
|
||||
func TestProfileLint_VerboseShowsInfo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeUserProfileLint(t, dir, "dead", `name: dead
|
||||
description: dead rule profile
|
||||
package_managers:
|
||||
- npm
|
||||
filesystem:
|
||||
allow_read:
|
||||
- ${HOME}/.cache/npm/**
|
||||
- ${HOME}/.cache/npm/foo
|
||||
`)
|
||||
|
||||
t.Run("default hides info", func(t *testing.T) {
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, dir))
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"dead", "--json"})
|
||||
require.NoError(t, cmd.Execute())
|
||||
|
||||
var report struct {
|
||||
Issues []pmgsandbox.LintIssue `json:"issues"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(stdout.Bytes(), &report))
|
||||
for _, i := range report.Issues {
|
||||
assert.NotEqual(t, pmgsandbox.LintLevelInfo, i.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("verbose shows info", func(t *testing.T) {
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, dir))
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"dead", "--json", "--verbose"})
|
||||
require.NoError(t, cmd.Execute())
|
||||
|
||||
var report struct {
|
||||
Issues []pmgsandbox.LintIssue `json:"issues"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(stdout.Bytes(), &report))
|
||||
foundInfo := false
|
||||
for _, i := range report.Issues {
|
||||
if i.Level == pmgsandbox.LintLevelInfo {
|
||||
foundInfo = true
|
||||
}
|
||||
}
|
||||
assert.True(t, foundInfo, "expected at least one info-level issue with --verbose")
|
||||
})
|
||||
}
|
||||
|
||||
func TestProfileLint_UnknownProfile(t *testing.T) {
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, ""))
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs([]string{"does-not-exist"})
|
||||
|
||||
err := cmd.Execute()
|
||||
require.Error(t, err)
|
||||
assert.Empty(t, stderr.String())
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, usefulerror.ErrCodeNotFound, usefulErr.Code())
|
||||
}
|
||||
|
||||
func TestProfileLintMissingTargetShowsUsage(t *testing.T) {
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, ""))
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs([]string{})
|
||||
|
||||
err := cmd.Execute()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, stderr.String(), "accepts 1 arg(s), received 0")
|
||||
assert.Contains(t, stdout.String(), "Usage:")
|
||||
assert.Contains(t, stdout.String(), "lint <path|name> [flags]")
|
||||
assert.Contains(t, stdout.String(), "pmg sandbox profile lint npm-restrictive")
|
||||
}
|
||||
|
||||
func TestProfileLint_LiteralPath(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := writeUserProfileLint(t, dir, "literal", `name: literal
|
||||
description: literal
|
||||
package_managers:
|
||||
- npm
|
||||
filesystem:
|
||||
allow_read:
|
||||
- /tmp
|
||||
`)
|
||||
|
||||
// Use a registry without user dir — must fall through to the literal path branch.
|
||||
cmd := newProfileLintCommand(newTestRegistry(t, ""))
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{path, "--json"})
|
||||
|
||||
require.NoError(t, cmd.Execute())
|
||||
var report struct {
|
||||
Profile string `json:"profile"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(stdout.Bytes(), &report))
|
||||
assert.Equal(t, path, report.Profile)
|
||||
}
|
||||
Reference in New Issue
Block a user