diff --git a/Makefile b/Makefile index 6c6d580..de4010b 100644 --- a/Makefile +++ b/Makefile @@ -36,18 +36,18 @@ build-all: @echo "Binaries are in the '$(BUILD_DIR)' directory:" @ls -la $(BUILD_DIR)/ -# Run tests +# Run tests (needs sudo for E2E tests) .PHONY: test test: @echo "Running tests..." - go test -v -race ./... + sudo go test -v -race ./... @echo "✓ All tests passed!" -# Run tests with coverage +# Run tests with coverage (needs sudo for E2E tests) .PHONY: test-coverage test-coverage: @echo "Running tests with coverage..." - go test -v -race -coverprofile=coverage.out ./... + sudo go test -v -race -coverprofile=coverage.out ./... go tool cover -html=coverage.out -o coverage.html @echo "✓ Coverage report generated: coverage.html" diff --git a/cli/cli_test.go b/cli/cli_test.go new file mode 100644 index 0000000..df99d67 --- /dev/null +++ b/cli/cli_test.go @@ -0,0 +1,102 @@ +package cli + +import ( + "os" + "strings" + "testing" + + "github.com/coder/serpent" +) + +func ensureRoot(t *testing.T) { + t.Helper() + if os.Getgid() != 0 { + t.Skip("skipping test because no root privileges") + } +} + +// MockPTY provides a simple mock for PTY-like testing +// This is a simplified version inspired by coder/coder's ptytest. +type MockPTY struct { + t *testing.T + stdout strings.Builder + stderr strings.Builder +} + +// NewMockPTY creates a new mock PTY for testing +func NewMockPTY(t *testing.T) *MockPTY { + return &MockPTY{t: t} +} + +func (m *MockPTY) Attach(inv *serpent.Invocation) { + inv.Stdout = &m.stdout + inv.Stderr = &m.stderr +} + +func (m *MockPTY) Stdout() string { + return m.stdout.String() +} + +func (m *MockPTY) Stderr() string { + return m.stderr.String() +} + +func (m *MockPTY) Clear() { + m.stdout = strings.Builder{} + m.stderr = strings.Builder{} +} + +func (m *MockPTY) ExpectMatch(content string) { + if !strings.Contains(m.stdout.String(), content) { + m.t.Fatalf("expected \"%s\", got: %s", content, m.stdout.String()) + } +} + +func (m *MockPTY) ExpectError(content string) { + if !strings.Contains(m.stderr.String(), content) { + m.t.Fatalf("expected error with \"%s\", got: %s", content, m.stderr.String()) + } +} + +func (m *MockPTY) RequireError() { + if m.stderr.String() == "" { + m.t.Fatal("expected error") + } +} + +func (m *MockPTY) RequireNoError() { + if m.stderr.String() != "" { + m.t.Fatalf("expected nothing in stderr, but got: %s", m.stderr.String()) + } +} + +func TestPtySetupWorks(t *testing.T) { + cmd := NewCommand() + inv := cmd.Invoke("--help") + + pty := NewMockPTY(t) + pty.Attach(inv) + + if err := inv.Run(); err != nil { + t.Fatalf("could not run with simple --help arg: %v", err) + } + + pty.RequireNoError() + pty.ExpectMatch("Monitor and restrict HTTP/HTTPS requests from processes") +} + +func TestCurlGithub(t *testing.T) { + ensureRoot(t) + + cmd := NewCommand() + inv := cmd.Invoke("--allow", "\"github.com\"", "--", "curl", "https://github.com") + + pty := NewMockPTY(t) + pty.Attach(inv) + + if err := inv.Run(); err != nil { + t.Fatalf("error curling github: %v", err) + } + + pty.RequireNoError() +} diff --git a/tls/tls.go b/tls/tls.go index 4222147..51d1e9f 100644 --- a/tls/tls.go +++ b/tls/tls.go @@ -175,7 +175,7 @@ func (cm *CertificateManager) generateCA(keyPath, certPath string) error { // Create config directory if it doesn't exist err := os.MkdirAll(cm.configDir, 0700) if err != nil { - return fmt.Errorf("failed to create config directory: %v", err) + return fmt.Errorf("failed to create config directory at %s: %v", cm.configDir, err) } // When running under sudo, ensure the directory is owned by the original user