diff --git a/core/execute_test.go b/core/execute_test.go index 6a25b43..3bee29c 100644 --- a/core/execute_test.go +++ b/core/execute_test.go @@ -6,19 +6,19 @@ import ( ) func TestExecute(t *testing.T) { - assertLen := func(items []Item, length int) { + 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(err error) { + assertNil := func(t *testing.T, err error) { t.Helper() if err != nil { t.Fatal(err) } } - assertNotNil := func(err error) { + assertNotNil := func(t *testing.T, err error) { t.Helper() if err == nil { t.Fatal("expected err") @@ -29,138 +29,165 @@ func TestExecute(t *testing.T) { return item, err } - res, err := execute([]string{"true"}) - assertNil(err) - assertLen(res, 0) - - // Exit with error code - res, err = execute([]string{"false"}) - assertNotNil(err) - assertLen(res, 0) - - res, err = execute([]string{"sh", "-c", "exit 22"}) - assertNotNil(err) - assertLen(res, 0) - - // Timeout - res, _, err = Execute("_", []string{"sleep", "10"}, nil, nil, "", time.Millisecond, nil) - assertNotNil(err) - assertLen(res, 0) - - // Returning items - res, err = execute([]string{"jq", "-cn", `{id: "foo"}`}) - assertNil(err) - assertLen(res, 1) - if res[0].Id != "foo" { - t.Fatal("jq -cn test failed") - } - - // Read from stdin - res, _, err = Execute("_", []string{"jq", "-cR", `{id: .}`}, nil, nil, "bar", time.Minute, nil) - assertNil(err) - assertLen(res, 1) - if res[0].Id != "bar" { - t.Fatal("jq -cR test failed") - } - - // Set env - res, _, err = Execute("_", []string{"jq", "-cn", `{id: env.HELLO}`}, []string{"HELLO=baz"}, nil, "", time.Minute, nil) - assertNil(err) - assertLen(res, 1) - if res[0].Id != "baz" { - t.Fatal("jq -cn env test failed") - } - - // With logging on stderr - res, err = execute([]string{"sh", "-c", `echo 1>&2 Hello; jq -cn '{id: "box"}'; echo 1>&2 World`}) - assertNil(err) - assertLen(res, 1) - if res[0].Id != "box" { - t.Fatal("stderr test failed") - } - - // Unsupported item field is silently discarded - res, err = execute([]string{"jq", "-cn", `{id: "test", unknownField: "what is this"}`}) - assertNil(err) - assertLen(res, 1) - - // Field with incorrect type fails - res, err = execute([]string{"jq", "-cn", `{id: ["list"]}`}) - assertNotNil(err) - assertLen(res, 0) - - res, err = execute([]string{"jq", "-cn", `{id: "test", time: "0"}`}) - assertNotNil(err) - assertLen(res, 0) - - res, err = execute([]string{"jq", "-cn", `{id: null}`}) - assertNotNil(err) - assertLen(res, 0) - - // Items with duplicate ids is not a fetch error, but it will fail to update - res, err = execute([]string{"jq", "-cn", `["a", "a"] | .[] | {id: .}`}) - assertNil(err) - assertLen(res, 2) - - // Action keys are detected even with empty values - res, err = execute([]string{"jq", "-cn", `{id: "test", action: {"hello": null}}`}) - assertNil(err) - assertLen(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") - } - - res, err = execute([]string{"jq", "-cn", `{id: "test", action: {"hello": ""}}`}) - assertNil(err) - assertLen(res, 1) - if res[0].Action["hello"] == nil { - t.Fatal("missing hello action") - } - - res, err = execute([]string{"jq", "-cn", `{id: "test", action: {"hello": []}}`}) - assertNil(err) - assertLen(res, 1) - if res[0].Action["hello"] == nil { - t.Fatal("missing hello action") - } - - res, err = execute([]string{"jq", "-cn", `{id: "test", action: {"hello": {}}}`}) - assertNil(err) - assertLen(res, 1) - if res[0].Action["hello"] == nil { - t.Fatal("missing hello action") - } - - // Read state - 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(err) - assertLen(res, 1) - if res[0].Title != "Hello world" { - t.Fatalf("expected 'Hello world' from read state, got '%s'", res[0].Title) - } - - // Write state - argv = []string{"sh", "-c", `printf "Hello world" > $STATE_PATH; jq -cn '{id: "test"}'`} - res, newState, err := Execute("_", argv, nil, nil, "", time.Minute, nil) - assertNil(err) - assertLen(res, 1) - if string(newState) != "Hello world" { - t.Fatalf("expected 'Hello world' from write state, got %s", string(newState)) - } - - // Postprocessing function - argv = []string{"jq", "-cn", `{id: "foo"}`} - res, _, err = Execute("_", argv, nil, nil, "", time.Minute, func(item Item) Item { - item.Ttl = 123456 - return item + 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) + } }) - assertNil(err) - assertLen(res, 1) - if res[0].Ttl != 123456 { - t.Fatalf("expected ttl to be set to 123456, got %d", res[0].Ttl) - } }