36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
180 lines
4.5 KiB
Go
180 lines
4.5 KiB
Go
package output
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/pranshuparmar/witr/pkg/model"
|
|
)
|
|
|
|
func jsonFixture() model.Result {
|
|
target := model.Process{
|
|
PID: 1234,
|
|
Command: "nginx",
|
|
Cmdline: "/usr/sbin/nginx -g daemon off;",
|
|
Env: []string{"FOO=bar", "PATH=/usr/bin"},
|
|
}
|
|
return model.Result{
|
|
Target: model.Target{Type: model.TargetName, Value: "nginx"},
|
|
Process: target,
|
|
Ancestry: []model.Process{{PID: 1, Command: "systemd"}, target},
|
|
Children: []model.Process{{PID: 5678, Command: "worker"}},
|
|
Source: model.Source{Type: model.SourceSystemd, Name: "nginx.service"},
|
|
Warnings: []string{"Process is listening on a public interface"},
|
|
}
|
|
}
|
|
|
|
// TestToJSONIsValidAndIncludesKeyFields verifies the full result serializes
|
|
// cleanly and includes the top-level fields downstream consumers depend on.
|
|
func TestToJSONIsValidAndIncludesKeyFields(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s, err := ToJSON(jsonFixture())
|
|
if err != nil {
|
|
t.Fatalf("ToJSON returned error: %v", err)
|
|
}
|
|
|
|
var decoded map[string]any
|
|
if err := json.Unmarshal([]byte(s), &decoded); err != nil {
|
|
t.Fatalf("ToJSON output is not valid JSON: %v\n%s", err, s)
|
|
}
|
|
|
|
for _, key := range []string{"Target", "Process", "Ancestry", "Source", "Warnings"} {
|
|
if _, ok := decoded[key]; !ok {
|
|
t.Errorf("ToJSON output missing top-level field %q", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToShortJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s, err := ToShortJSON(jsonFixture())
|
|
if err != nil {
|
|
t.Fatalf("ToShortJSON: %v", err)
|
|
}
|
|
|
|
var got []struct {
|
|
PID int
|
|
Command string
|
|
}
|
|
if err := json.Unmarshal([]byte(s), &got); err != nil {
|
|
t.Fatalf("ToShortJSON not parseable as []shortProcess: %v\n%s", err, s)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("ToShortJSON length = %d, want 2", len(got))
|
|
}
|
|
if got[0].Command != "systemd" || got[1].Command != "nginx" {
|
|
t.Errorf("ToShortJSON unexpected ancestry: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestToTreeJSONIncludesChildren(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s, err := ToTreeJSON(jsonFixture())
|
|
if err != nil {
|
|
t.Fatalf("ToTreeJSON: %v", err)
|
|
}
|
|
|
|
var got struct {
|
|
Ancestry []struct{ PID int }
|
|
Children []struct {
|
|
PID int
|
|
Command string
|
|
}
|
|
}
|
|
if err := json.Unmarshal([]byte(s), &got); err != nil {
|
|
t.Fatalf("ToTreeJSON not parseable: %v\n%s", err, s)
|
|
}
|
|
if len(got.Children) != 1 || got.Children[0].PID != 5678 || got.Children[0].Command != "worker" {
|
|
t.Errorf("ToTreeJSON children = %+v, want one worker pid 5678", got.Children)
|
|
}
|
|
}
|
|
|
|
// TestToTreeJSONOmitsEmptyChildren pins the `omitempty` contract — callers
|
|
// scripting against the JSON shouldn't need to special-case "Children":[].
|
|
func TestToTreeJSONOmitsEmptyChildren(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fx := jsonFixture()
|
|
fx.Children = nil
|
|
|
|
s, err := ToTreeJSON(fx)
|
|
if err != nil {
|
|
t.Fatalf("ToTreeJSON: %v", err)
|
|
}
|
|
if strings.Contains(s, `"Children"`) {
|
|
t.Errorf("ToTreeJSON should omit Children when empty; got:\n%s", s)
|
|
}
|
|
}
|
|
|
|
func TestToWarningsJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s, err := ToWarningsJSON(jsonFixture())
|
|
if err != nil {
|
|
t.Fatalf("ToWarningsJSON: %v", err)
|
|
}
|
|
|
|
var got struct {
|
|
PID int
|
|
Process string
|
|
Command string
|
|
Warnings []string
|
|
}
|
|
if err := json.Unmarshal([]byte(s), &got); err != nil {
|
|
t.Fatalf("ToWarningsJSON not parseable: %v\n%s", err, s)
|
|
}
|
|
if got.PID != 1234 || got.Process != "nginx" {
|
|
t.Errorf("ToWarningsJSON identity wrong: %+v", got)
|
|
}
|
|
if len(got.Warnings) != 1 || !strings.Contains(got.Warnings[0], "public interface") {
|
|
t.Errorf("ToWarningsJSON warnings = %v", got.Warnings)
|
|
}
|
|
}
|
|
|
|
// TestToWarningsJSONNilWarningsBecomesEmptyArray ensures downstream
|
|
// consumers always see [] and never null — important for jq scripts that do
|
|
// `.Warnings | length`.
|
|
func TestToWarningsJSONNilWarningsBecomesEmptyArray(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fx := jsonFixture()
|
|
fx.Warnings = nil
|
|
|
|
s, err := ToWarningsJSON(fx)
|
|
if err != nil {
|
|
t.Fatalf("ToWarningsJSON: %v", err)
|
|
}
|
|
if !strings.Contains(s, `"Warnings": []`) {
|
|
t.Errorf("ToWarningsJSON should emit Warnings:[] when nil; got:\n%s", s)
|
|
}
|
|
}
|
|
|
|
func TestToEnvJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s, err := ToEnvJSON(jsonFixture())
|
|
if err != nil {
|
|
t.Fatalf("ToEnvJSON: %v", err)
|
|
}
|
|
|
|
var got struct {
|
|
PID int
|
|
Process string
|
|
Command string
|
|
Env []string
|
|
}
|
|
if err := json.Unmarshal([]byte(s), &got); err != nil {
|
|
t.Fatalf("ToEnvJSON not parseable: %v\n%s", err, s)
|
|
}
|
|
if got.PID != 1234 {
|
|
t.Errorf("ToEnvJSON PID = %d, want 1234", got.PID)
|
|
}
|
|
if len(got.Env) != 2 || got.Env[0] != "FOO=bar" {
|
|
t.Errorf("ToEnvJSON env = %v", got.Env)
|
|
}
|
|
}
|