package core import ( "strings" "testing" "time" ) func TestExecute(t *testing.T) { assertLen := func(t *testing.T, items []Item, length int) { t.Helper() if len(items) != length { t.Fatalf("Expected %d items, got %d", length, len(items)) } } assertNil := func(t *testing.T, err error) { t.Helper() if err != nil { t.Fatal(err) } } assertNotNil := func(t *testing.T, err error) { t.Helper() if err == nil { t.Fatal("expected err") } } execute := func(argv []string) ([]Item, error) { item, _, _, err := Execute("_", argv, nil, nil, "", time.Minute, nil) return item, err } t.Run("Noop", func(t *testing.T) { res, err := execute([]string{"true"}) assertNil(t, err) assertLen(t, res, 0) }) t.Run("ExitWithErrorCode", func(t *testing.T) { res, err := execute([]string{"false"}) assertNotNil(t, err) assertLen(t, res, 0) }) t.Run("ExitWithSpecificErrorCode", func(t *testing.T) { res, err := execute([]string{"sh", "-c", "exit 22"}) assertNotNil(t, err) assertLen(t, res, 0) }) t.Run("Timeout", func(t *testing.T) { res, _, _, err := Execute("_", []string{"sleep", "10"}, nil, nil, "", time.Millisecond, nil) assertNotNil(t, err) assertLen(t, res, 0) }) t.Run("ReturningItems", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: "foo"}`}) assertNil(t, err) assertLen(t, res, 1) if res[0].Id != "foo" { t.Fatal("jq -cn test failed") } }) t.Run("ReadFromStdin", func(t *testing.T) { res, _, _, err := Execute("_", []string{"jq", "-cR", `{id: .}`}, nil, nil, "bar", time.Minute, nil) assertNil(t, err) assertLen(t, res, 1) if res[0].Id != "bar" { t.Fatal("jq -cR test failed") } }) t.Run("SetEnv", func(t *testing.T) { res, _, _, err := Execute("_", []string{"jq", "-cn", `{id: env.HELLO}`}, []string{"HELLO=baz"}, nil, "", time.Minute, nil) assertNil(t, err) assertLen(t, res, 1) if res[0].Id != "baz" { t.Fatal("jq -cn env test failed") } }) t.Run("StderrLogging", func(t *testing.T) { res, err := execute([]string{"sh", "-c", `echo 1>&2 Hello; jq -cn '{id: "box"}'; echo 1>&2 World`}) assertNil(t, err) assertLen(t, res, 1) if res[0].Id != "box" { t.Fatal("stderr test failed") } }) t.Run("UnknownFieldIgnored", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: "test", unknownField: "what is this"}`}) assertNil(t, err) assertLen(t, res, 1) }) t.Run("IncorrectIdType", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: ["list"]}`}) assertNotNil(t, err) assertLen(t, res, 0) }) t.Run("IncorrectTimeType", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: "test", time: "0"}`}) assertNotNil(t, err) assertLen(t, res, 0) }) t.Run("NullId", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: null}`}) assertNotNil(t, err) assertLen(t, res, 0) }) // TODO maybe this *should* be an Execute error, via a map[string]bool t.Run("DuplicateItemIds", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `["a", "a"] | .[] | {id: .}`}) assertNil(t, err) assertLen(t, res, 2) }) t.Run("ActionNullValueOk", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: "test", action: {"hello": null}}`}) assertNil(t, err) assertLen(t, res, 1) if res[0].Action["hello"] == nil { t.Fatal("missing hello action") } if res[0].Action["goodbye"] != nil { t.Fatal("nonexistent action should key to nil in Action") } }) t.Run("ActionEmptyStringOk", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: "test", action: {"hello": ""}}`}) assertNil(t, err) assertLen(t, res, 1) if res[0].Action["hello"] == nil { t.Fatal("missing hello action") } }) t.Run("ActionEmptyArrayOk", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: "test", action: {"hello": []}}`}) assertNil(t, err) assertLen(t, res, 1) if res[0].Action["hello"] == nil { t.Fatal("missing hello action") } }) t.Run("ActionEmptyObjectOk", func(t *testing.T) { res, err := execute([]string{"jq", "-cn", `{id: "test", action: {"hello": {}}}`}) assertNil(t, err) assertLen(t, res, 1) if res[0].Action["hello"] == nil { t.Fatal("missing hello action") } }) t.Run("ReadState", func(t *testing.T) { argv := []string{"sh", "-c", `cat $STATE_PATH | jq -cR '{id: "greeting", title: .} | .title = "Hello " + .title'`} res, _, _, err := Execute("_", argv, nil, []byte("world"), "", time.Minute, nil) assertNil(t, err) assertLen(t, res, 1) if res[0].Title != "Hello world" { t.Fatalf("expected 'Hello world' from read state, got '%s'", res[0].Title) } }) t.Run("WriteState", func(t *testing.T) { argv := []string{"sh", "-c", `printf "Hello world" > $STATE_PATH; jq -cn '{id: "test"}'`} res, newState, _, err := Execute("_", argv, nil, nil, "", time.Minute, nil) assertNil(t, err) assertLen(t, res, 1) if string(newState) != "Hello world" { t.Fatalf("expected 'Hello world' from write state, got %s", string(newState)) } }) t.Run("PostprocessSetTtl", func(t *testing.T) { argv := []string{"jq", "-cn", `{id: "foo"}`} res, _, _, err := Execute("_", argv, nil, nil, "", time.Minute, func(item Item) Item { item.Ttl = 123456 return item }) assertNil(t, err) assertLen(t, res, 1) if res[0].Ttl != 123456 { t.Fatalf("expected ttl to be set to 123456, got %d", res[0].Ttl) } }) t.Run("ErrorItem", func(t *testing.T) { argv := []string{"sh", "-c", `echo 1>&2 Hello; jq -cn '{id: "box"}'; echo 1>&2 World; printf '{"whoops": "my bad"}'`} _, _, errItem, err := Execute("test", argv, nil, nil, "", time.Minute, nil) assertNotNil(t, err) if errItem.Id == "" { t.Fatal("missing erritem id") } if errItem.Source != "default" { t.Fatalf("unexpected erritem source: expected default, got %s", errItem.Source) } if !strings.Contains(errItem.Body, "Hello") || !strings.Contains(errItem.Body, "World") { t.Fatal("missing stderr from erritem") } if !strings.Contains(errItem.Body, "whoops") { t.Fatal("missing stdout from erritem") } }) }