all repos — postern

@ 8bbd7a84fa063eaf76ebf26f4887c9c2644053bf

Modern mail management

clone
git clone https:///postern.git

internal/policy/exec_test.go (view raw)

  1package policy
  2
  3import (
  4	"log"
  5	"os"
  6	"path"
  7	"runtime"
  8	"testing"
  9
 10	"postern/internal/db"
 11
 12	"go.starlark.net/starlark"
 13)
 14
 15func TestExecModule(t *testing.T) {
 16	if runtime.GOOS == "windows" {
 17		t.Skip("POSIX-specific test")
 18	}
 19
 20	posternDB, err := db.OpenDB(&db.Config{
 21		DBPath: path.Join(t.TempDir(), "meta.db"),
 22	})
 23	if err != nil {
 24		t.Fatal(err)
 25	}
 26
 27	defer func(posternDB *db.DB) {
 28		err := posternDB.Close()
 29		if err != nil {
 30			log.Fatal(err)
 31		}
 32	}(posternDB)
 33
 34	policy := `
 35def init():
 36    return None
 37
 38def test_success():
 39    res = exec.pipe_to_process("/bin/sh", args=["-c", "echo hello"])
 40    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 41
 42def test_input_piping():
 43    res = exec.pipe_to_process("/bin/cat", input_data="ping")
 44    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 45
 46def test_args():
 47    res = exec.pipe_to_process("/bin/sh", args=["-c", "echo $1", "_", "arg1"])
 48    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 49
 50def test_nonzero_exit():
 51    res = exec.pipe_to_process("/bin/sh", args=["-c", "exit 42"])
 52    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 53
 54def test_stderr():
 55    res = exec.pipe_to_process("/bin/sh", args=["-c", "echo err >&2"])
 56    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 57
 58def test_environment():
 59    res = exec.pipe_to_process("/bin/sh", args=["-c", "echo $FOO"], environment={"FOO": "bar"})
 60    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 61
 62def test_env_no_leak():
 63    res = exec.pipe_to_process("/bin/sh", args=["-c", "echo ${HOME:-empty}"])
 64    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 65
 66def test_timeout():
 67    res = exec.pipe_to_process("/bin/sh", args=["-c", "sleep 10"], timeout=1)
 68    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 69
 70def test_timeout_float():
 71    res = exec.pipe_to_process("/bin/sh", args=["-c", "sleep 1"], timeout=0.5)
 72    return {"code": res.code, "stdout": res.stdout, "stderr": res.stderr, "timed_out": res.timed_out}
 73
 74def test_not_found():
 75    return exec.pipe_to_process("/nonexistent/binary/xyz")
 76`
 77
 78	policyFile := path.Join(t.TempDir(), "policy.star")
 79	err = os.WriteFile(policyFile, []byte(policy), 0644)
 80	if err != nil {
 81		t.Fatal(err)
 82	}
 83
 84	e, err := NewEngine(policyFile, posternDB)
 85	if err != nil {
 86		t.Fatal(err)
 87	}
 88
 89	tests := []struct {
 90		name        string
 91		fnName      string
 92		wantCode    int
 93		wantOut     string
 94		wantErr     string
 95		wantTimeout bool
 96	}{
 97		{"success", "test_success", 0, "hello\n", "", false},
 98		{"input_piping", "test_input_piping", 0, "ping", "", false},
 99		{"args", "test_args", 0, "arg1\n", "", false},
100		{"nonzero_exit", "test_nonzero_exit", 42, "", "", false},
101		{"stderr", "test_stderr", 0, "", "err\n", false},
102		{"environment", "test_environment", 0, "bar\n", "", false},
103		{"env_no_leak", "test_env_no_leak", 0, "empty\n", "", false},
104		{"timeout", "test_timeout", -1, "", "", true},
105		{"timeout_float", "test_timeout_float", -1, "", "", true},
106	}
107
108	for _, tt := range tests {
109		t.Run(tt.name, func(t *testing.T) {
110			hookVal, ok := e.globals[tt.fnName]
111			if !ok {
112				t.Fatalf("%s not found in engine globals", tt.fnName)
113			}
114
115			res, err := starlark.Call(e.thread, hookVal, nil, nil)
116			if err != nil {
117				t.Fatal(err)
118			}
119
120			dict, ok := res.(*starlark.Dict)
121			if !ok {
122				t.Fatalf("expected dict, got %s", res.Type())
123			}
124
125			assertDictField(t, dict, "code", starlark.MakeInt(tt.wantCode))
126			assertDictField(t, dict, "stdout", starlark.String(tt.wantOut))
127			assertDictField(t, dict, "stderr", starlark.String(tt.wantErr))
128			assertDictField(t, dict, "timed_out", starlark.Bool(tt.wantTimeout))
129		})
130	}
131
132	t.Run("not_found", func(t *testing.T) {
133		hookVal, ok := e.globals["test_not_found"]
134		if !ok {
135			t.Fatal("test_not_found not found in engine globals")
136		}
137		_, err := starlark.Call(e.thread, hookVal, nil, nil)
138		if err == nil {
139			t.Fatal("expected error for nonexistent binary")
140		}
141	})
142}
143
144func assertDictField(t *testing.T, d *starlark.Dict, key string, want starlark.Value) {
145	t.Helper()
146	v, found, err := d.Get(starlark.String(key))
147	if err != nil {
148		t.Fatalf("key %q err %q", key, err)
149	}
150	if !found {
151		t.Fatalf("key %q not found", key)
152	}
153	if v != want {
154		t.Errorf("field %q: got %v, want %v", key, v, want)
155	}
156}