package cmd import ( "bytes" "context" "fmt" "os" "path/filepath" "reflect" "strings" "testing" "time" "github.com/open-policy-agent/opa/cmd/internal/exec" "github.com/open-policy-agent/opa/internal/file/archive" loggingtest "github.com/open-policy-agent/opa/logging/test" sdk_test "github.com/open-policy-agent/opa/sdk/test" "github.com/open-policy-agent/opa/util" "github.com/open-policy-agent/opa/util/test" ) func TestExecBasic(t *testing.T) { files := map[string]string{ "test.json": `{"foo": 7}`, "test2.yaml": `bar: 8`, "test3.yml": `baz: 9`, "ignore": `garbage`, // do not recognize this filetype } test.WithTempFS(files, func(dir string) { s := sdk_test.MustNewServer(sdk_test.MockBundle("/bundles/bundle.tar.gz", map[string]string{ "test.rego": ` package system main["hello"] `, })) defer s.Stop() var buf bytes.Buffer params := exec.NewParams(&buf) _ = params.OutputFormat.Set("json") params.ConfigOverrides = []string{ "services.test.url=" + s.URL(), "bundles.test.resource=/bundles/bundle.tar.gz", } params.Paths = append(params.Paths, dir) err := runExec(params) if err != nil { t.Fatal(err) } output := util.MustUnmarshalJSON(bytes.ReplaceAll(buf.Bytes(), []byte(dir), nil)) exp := util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/test.json", "result": ["hello"] }, { "path": "/test2.yaml", "result": ["hello"] }, { "path": "/test3.yml", "result": ["hello"] }]}`)) if !reflect.DeepEqual(output, exp) { t.Fatal("Expected:", exp, "Got:", output) } }) } func TestExecDecisionOption(t *testing.T) { files := map[string]string{ "test.json": `{"foo": 7}`, } test.WithTempFS(files, func(dir string) { s := sdk_test.MustNewServer(sdk_test.MockBundle("/bundles/bundle.tar.gz", map[string]string{ "test.rego": ` package foo main["hello"] `, })) defer s.Stop() var buf bytes.Buffer params := exec.NewParams(&buf) _ = params.OutputFormat.Set("json") params.Decision = "foo/main" params.ConfigOverrides = []string{ "services.test.url=" + s.URL(), "bundles.test.resource=/bundles/bundle.tar.gz", } params.Paths = append(params.Paths, dir) err := runExec(params) if err != nil { t.Fatal(err) } output := util.MustUnmarshalJSON(bytes.ReplaceAll(buf.Bytes(), []byte(dir), nil)) exp := util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/test.json", "result": ["hello"] }]}`)) if !reflect.DeepEqual(output, exp) { t.Fatal("Expected:", exp, "Got:", output) } }) } func TestExecBundleFlag(t *testing.T) { files := map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package system main["hello"]`, } test.WithTempFS(files, func(dir string) { var buf bytes.Buffer params := exec.NewParams(&buf) _ = params.OutputFormat.Set("json") params.BundlePaths = []string{dir + "/bundle/"} params.Paths = append(params.Paths, dir+"/files/") err := runExec(params) if err != nil { t.Fatal(err) } output := util.MustUnmarshalJSON(bytes.ReplaceAll(buf.Bytes(), []byte(dir), nil)) exp := util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": ["hello"] }]}`)) if !reflect.DeepEqual(output, exp) { t.Fatal("Expected:", exp, "Got:", output) } }) } func TestExecV1Compatible(t *testing.T) { tests := []struct { note string v1Compatible bool module string expErrs []string }{ { note: "v0.x, no keywords used", module: `package system main["hello"] { input.foo == "bar" }`, }, { note: "v0.x, no keywords imported", module: `package system main contains "hello" if { input.foo == "bar" }`, expErrs: []string{ "rego_parse_error: var cannot be used for rule name", "rego_parse_error: string cannot be used for rule name", }, }, { note: "v0.x, keywords imported", module: `package system import future.keywords main contains "hello" if { input.foo == "bar" }`, }, { note: "v0.x, rego.v1 imported", module: `package system import rego.v1 main contains "hello" if { input.foo == "bar" }`, }, { note: "v1.0, no keywords used", v1Compatible: true, module: `package system main["hello"] { input.foo == "bar" }`, expErrs: []string{ "rego_parse_error: `if` keyword is required before rule body", "rego_parse_error: `contains` keyword is required for partial set rules", }, }, { note: "v1.0, no keywords imported", v1Compatible: true, module: `package system main contains "hello" if { input.foo == "bar" }`, }, { note: "v1.0, keywords imported", v1Compatible: true, module: `package system import future.keywords main contains "hello" if { input.foo == "bar" }`, }, { note: "v1.0, rego.v1 imported", v1Compatible: true, module: `package system import rego.v1 main contains "hello" if { input.foo == "bar" }`, }, } for _, tc := range tests { t.Run(tc.note, func(t *testing.T) { files := map[string]string{ "test.json": `{"foo": "bar"}`, } test.WithTempFS(files, func(dir string) { s := sdk_test.MustNewServer( sdk_test.MockBundle("/bundles/bundle.tar.gz", map[string]string{"test.rego": tc.module}), sdk_test.RawBundles(true), ) defer s.Stop() var buf bytes.Buffer params := exec.NewParams(&buf) params.V1Compatible = tc.v1Compatible _ = params.OutputFormat.Set("json") params.ConfigOverrides = []string{ "services.test.url=" + s.URL(), "bundles.test.resource=/bundles/bundle.tar.gz", } params.Paths = append(params.Paths, dir) if len(tc.expErrs) > 0 { testLogger := loggingtest.New() params.Logger = testLogger ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { err := runExecWithContext(ctx, params) // Note(philipc): Catch the expected cancellation // errors, allowing unexpected test failures through. if err != context.Canceled { t.Error(err) return } }() if !test.Eventually(t, 5*time.Second, func() bool { for _, expErr := range tc.expErrs { found := false for _, e := range testLogger.Entries() { if strings.Contains(e.Message, expErr) { found = true break } } if !found { return false } } return true }) { t.Fatalf("timed out waiting for logged errors:\n\n%v\n\ngot\n\n%v:", tc.expErrs, testLogger.Entries()) } } else { err := runExec(params) if err != nil { t.Fatal(err) } output := util.MustUnmarshalJSON(bytes.ReplaceAll(buf.Bytes(), []byte(dir), nil)) exp := util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/test.json", "result": ["hello"] }]}`)) if !reflect.DeepEqual(output, exp) { t.Fatal("Expected:", exp, "Got:", output) } } }) }) } } func TestExecWithBundleRegoVersion(t *testing.T) { tests := []struct { note string files map[string]string expErrs []string }{ { note: "v0.x bundle, no keywords used", files: map[string]string{ ".manifest": `{"rego_version": 0}`, "policy.rego": `package system main["hello"] { input.foo == "bar" }`, }, }, { note: "v0.x bundle, no keywords imported", files: map[string]string{ ".manifest": `{"rego_version": 0}`, "policy.rego": `package system main contains "hello" if { input.foo == "bar" }`, }, expErrs: []string{ "rego_parse_error: var cannot be used for rule name", "rego_parse_error: string cannot be used for rule name", }, }, { note: "v0.x bundle, keywords imported", files: map[string]string{ ".manifest": `{"rego_version": 0}`, "policy.rego": `package system import future.keywords main contains "hello" if { input.foo == "bar" }`, }, }, { note: "v0.x bundle, rego.v1 imported", files: map[string]string{ ".manifest": `{"rego_version": 0}`, "policy.rego": `package system import rego.v1 main contains "hello" if { input.foo == "bar" }`, }, }, { note: "v0 bundle, v1 per-file override", files: map[string]string{ ".manifest": `{ "rego_version": 0, "file_rego_versions": { "/policy2.rego": 1 } }`, "policy1.rego": `package system p[42] { input.foo == "bar" }`, "policy2.rego": `package system main contains "hello" if { 42 in p }`, }, }, { note: "v0 bundle, v1 per-file override (glob)", files: map[string]string{ ".manifest": `{ "rego_version": 0, "file_rego_versions": { "*/policy2.rego": 1 } }`, "policy1.rego": `package system p[42] { input.foo == "bar" }`, "policy2.rego": `package system main contains "hello" if { 42 in p }`, }, }, { note: "v0 bundle, v1 per-file override, incompatible", files: map[string]string{ ".manifest": `{ "rego_version": 0, "file_rego_versions": { "/policy2.rego": 1 } }`, "policy1.rego": `package system p[42] { input.foo == "bar" }`, "policy2.rego": `package system main["hello"] { p[_] == 42 }`, }, expErrs: []string{ "rego_parse_error: `if` keyword is required before rule body", "rego_parse_error: `contains` keyword is required for partial set rules", }, }, { note: "v1.0 bundle, no keywords used", files: map[string]string{ ".manifest": `{"rego_version": 1}`, "policy.rego": `package system main["hello"] { input.foo == "bar" }`, }, expErrs: []string{ "rego_parse_error: `if` keyword is required before rule body", "rego_parse_error: `contains` keyword is required for partial set rules", }, }, { note: "v1.0 bundle, no keywords imported", files: map[string]string{ ".manifest": `{"rego_version": 1}`, "policy.rego": `package system main contains "hello" if { input.foo == "bar" }`, }, }, { note: "v1.0 bundle, keywords imported", files: map[string]string{ ".manifest": `{"rego_version": 1}`, "policy.rego": `package system import future.keywords main contains "hello" if { input.foo == "bar" }`, }, }, { note: "v1.0 bundle, rego.v1 imported", files: map[string]string{ ".manifest": `{"rego_version": 1}`, "policy.rego": `package system import rego.v1 main contains "hello" if { input.foo == "bar" }`, }, }, { note: "v1 bundle, v0 per-file override", files: map[string]string{ ".manifest": `{ "rego_version": 1, "file_rego_versions": { "/policy1.rego": 0 } }`, "policy1.rego": `package system p[42] { input.foo == "bar" }`, "policy2.rego": `package system main contains "hello" if { 42 in p }`, }, }, { note: "v1 bundle, v0 per-file override (glob)", files: map[string]string{ ".manifest": `{ "rego_version": 1, "file_rego_versions": { "*/policy1.rego": 0 } }`, "policy1.rego": `package system p[42] { input.foo == "bar" }`, "policy2.rego": `package system main contains "hello" if { 42 in p }`, }, }, { note: "v1 bundle, v0 per-file override, incompatible", files: map[string]string{ ".manifest": `{ "rego_version": 1, "file_rego_versions": { "/policy1.rego": 0 } }`, "policy1.rego": `package system p contains 42 { input.foo == "bar" }`, "policy2.rego": `package system main contains "hello" if { 42 in p }`, }, expErrs: []string{ "rego_parse_error: var cannot be used for rule name", "rego_parse_error: number cannot be used for rule name", "rego_parse_error: set cannot be used for rule name", }, }, } bundleTypeCases := []struct { note string tar bool }{ { "bundle dir", false, }, { "bundle tar", true, }, } v1CompatibleFlagCases := []struct { note string used bool }{ { "no --v1-compatible", false, }, { "--v1-compatible", true, }, } for _, bundleType := range bundleTypeCases { for _, v1CompatibleFlag := range v1CompatibleFlagCases { for _, tc := range tests { t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) { files := map[string]string{ "files/test.json": `{"foo": "bar"}`, } if bundleType.tar { files["bundle.tar.gz"] = "" } else { for k, v := range tc.files { files[k] = v } } test.WithTempFS(files, func(root string) { p := root if bundleType.tar { p = filepath.Join(root, "bundle.tar.gz") files := make([][2]string, 0, len(tc.files)) for k, v := range tc.files { files = append(files, [2]string{k, v}) } buf := archive.MustWriteTarGz(files) bf, err := os.Create(p) if err != nil { t.Fatalf("Unexpected error: %v", err) } _, err = bf.Write(buf.Bytes()) if err != nil { t.Fatalf("Unexpected error: %v", err) } } var buf bytes.Buffer params := exec.NewParams(&buf) params.Paths = append(params.Paths, root+"/files/") params.BundlePaths = []string{p} params.V1Compatible = v1CompatibleFlag.used _ = params.OutputFormat.Set("json") if len(tc.expErrs) > 0 { testLogger := loggingtest.New() params.Logger = testLogger ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { err := runExecWithContext(ctx, params) // we cancelled the context, so we expect that error if err != nil && err.Error() != "context canceled" { t.Error(err) return } }() if !test.Eventually(t, 5*time.Second, func() bool { for _, expErr := range tc.expErrs { found := false for _, e := range testLogger.Entries() { if strings.Contains(e.Message, expErr) { found = true break } } if !found { return false } } return true }) { t.Fatalf("timed out waiting for logged errors:\n\n%v\n\ngot\n\n%v:", tc.expErrs, testLogger.Entries()) } } else { err := runExec(params) if err != nil { t.Fatal(err) } output := util.MustUnmarshalJSON(bytes.ReplaceAll(buf.Bytes(), []byte(root), nil)) exp := util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": ["hello"] }]}`)) if !reflect.DeepEqual(output, exp) { t.Fatal("Expected:", exp, "Got:", output) } } }) }) } } } } func TestInvalidConfig(t *testing.T) { var buf bytes.Buffer params := exec.NewParams(&buf) params.Fail = true params.FailDefined = true err := exec.Exec(context.TODO(), nil, params) if err == nil || err.Error() != "specify --fail or --fail-defined but not both" { t.Fatalf("Expected error '%s' but got '%s'", "specify --fail or --fail-defined but not both", err.Error()) } } func TestInvalidConfigAllThree(t *testing.T) { var buf bytes.Buffer params := exec.NewParams(&buf) params.Fail = true params.FailDefined = true params.FailNonEmpty = true err := exec.Exec(context.TODO(), nil, params) if err == nil || err.Error() != "specify --fail or --fail-defined but not both" { t.Fatalf("Expected error '%s' but got '%s'", "specify --fail or --fail-defined but not both", err.Error()) } } func TestInvalidConfigNonEmptyAndFail(t *testing.T) { var buf bytes.Buffer params := exec.NewParams(&buf) params.FailNonEmpty = true params.Fail = true err := exec.Exec(context.TODO(), nil, params) if err == nil || err.Error() != "specify --fail-non-empty or --fail but not both" { t.Fatalf("Expected error '%s' but got '%s'", "specify --fail-non-empty or --fail but not both", err.Error()) } } func TestInvalidConfigNonEmptyAndFailDefined(t *testing.T) { var buf bytes.Buffer params := exec.NewParams(&buf) params.FailNonEmpty = true params.FailDefined = true err := exec.Exec(context.TODO(), nil, params) if err == nil || err.Error() != "specify --fail-non-empty or --fail-defined but not both" { t.Fatalf("Expected error '%s' but got '%s'", "specify --fail-non-empty or --fail-defined but not both", err.Error()) } } func TestFailFlagCases(t *testing.T) { var tests = []struct { description string files map[string]string decision string expectError bool expected interface{} fail bool failDefined bool failNonEmpty bool }{ { description: "--fail-defined with undefined result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package system test_fun := x { x = false x } undefined_test { test_fun }`, }, expectError: false, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "error": { "code": "opa_undefined_error", "message": "/system/main decision was undefined" } }]}`)), failDefined: true, }, { description: "--fail-defined with populated result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package system main["hello"]`, }, decision: "", expectError: true, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": ["hello"] }]}`)), failDefined: true, }, { description: "--fail-defined with true boolean result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package fail.defined.flag some_function { input.foo == 7 } default fail_test := false fail_test { some_function }`, }, decision: "fail/defined/flag/fail_test", expectError: true, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": true }]}`)), failDefined: true, }, { description: "--fail-defined with false boolean result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package fail.defined.flag default fail_test := false fail_test { false }`, }, decision: "fail/defined/flag/fail_test", expectError: true, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": false }]}`)), failDefined: true, }, { description: "--fail with undefined result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package system test_fun := x { x = false x } undefined_test { test_fun }`, }, expectError: true, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "error": { "code": "opa_undefined_error", "message": "/system/main decision was undefined" } }]}`)), fail: true, }, { description: "--fail with populated result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package system main["hello"]`, }, expectError: false, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": ["hello"] }]}`)), fail: true, }, { description: "--fail with true boolean result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package fail.defined.flag some_function { input.foo == 7 } default fail_test := false fail_test { some_function }`, }, decision: "fail/defined/flag/fail_test", expectError: false, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": true }]}`)), fail: true, }, { description: "--fail with false boolean result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package fail.defined.flag default fail_test := false fail_test { false }`, }, decision: "fail/defined/flag/fail_test", expectError: false, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": false }]}`)), fail: true, }, { description: "--fail-non-empty with undefined result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package system test_fun := x { x = false x } undefined_test { test_fun }`, }, expectError: false, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "error": { "code": "opa_undefined_error", "message": "/system/main decision was undefined" } }]}`)), failNonEmpty: true, }, { description: "--fail-non-empty with populated result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package system main["hello"]`, }, decision: "", expectError: true, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": ["hello"] }]}`)), failNonEmpty: true, }, { description: "--fail-non-empty with true boolean result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package fail.non.empty.flag some_function { input.foo == 7 } default fail_test := false fail_test { some_function }`, }, decision: "fail/non/empty/flag/fail_test", expectError: true, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": true }]}`)), failNonEmpty: true, }, { description: "--fail-non-empty with false boolean result", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package fail.non.empty.flag default fail_test := false fail_test { false }`, }, decision: "fail/non/empty/flag/fail_test", expectError: true, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": false }]}`)), failNonEmpty: true, }, { description: "--fail-non-empty with an empty array", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package fail.non.empty.flag default fail_test := ["something", "hello"] fail_test := [] if { input.foo == 7 }`, }, decision: "fail/non/empty/flag/fail_test", expectError: false, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": [] }]}`)), failNonEmpty: true, }, { description: "--fail-non-empty for an empty set coming from a partial rule", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package fail.non.empty.flag fail_test[message] { false message := "not gonna happen" }`, }, decision: "fail/non/empty/flag/fail_test", expectError: false, expected: util.MustUnmarshalJSON([]byte(`{"result": [{ "path": "/files/test.json", "result": [] }]}`)), failNonEmpty: true, }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { test.WithTempFS(tt.files, func(dir string) { var buf bytes.Buffer params := exec.NewParams(&buf) _ = params.OutputFormat.Set("json") params.BundlePaths = []string{dir + "/bundle/"} params.Paths = append(params.Paths, dir+"/files/") if tt.decision != "" { params.Decision = tt.decision } params.FailDefined = tt.failDefined params.Fail = tt.fail params.FailNonEmpty = tt.failNonEmpty err := runExec(params) if err != nil && !tt.expectError { t.Fatal("unexpected error in test") } if err == nil && tt.expectError { t.Fatal("expected error, but none occurred in test") } output := util.MustUnmarshalJSON(bytes.ReplaceAll(buf.Bytes(), []byte(dir), nil)) if !reflect.DeepEqual(output, tt.expected) { t.Errorf("Expected %v, got: %v", tt.expected, output) } }) }) } } func TestExecWithInvalidInputOptions(t *testing.T) { tests := []struct { description string files map[string]string stdIn bool input string expectError bool expected string }{ { description: "path passed in as arg should not raise error", files: map[string]string{ "files/test.json": `{"foo": 7}`, "bundle/x.rego": `package system test_fun := x { x = false x } undefined_test { test_fun }`, }, expectError: false, expected: "", }, { description: "no paths passed in as args should raise error if --stdin-input flag not set", files: map[string]string{ "bundle/x.rego": `package system test_fun := x { x = false x } undefined_test { test_fun }`, }, expectError: true, expected: "requires at least 1 path arg, or the --stdin-input flag", }, { description: "should not raise error if --stdin-input flag is set when no paths passed in as args", files: map[string]string{ "bundle/x.rego": `package system test_fun := x { x = false x } undefined_test { test_fun }`, }, stdIn: true, input: `{"foo": 7}`, expectError: false, expected: "", }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { test.WithTempFS(tt.files, func(dir string) { var buf bytes.Buffer params := exec.NewParams(&buf) _ = params.OutputFormat.Set("json") params.BundlePaths = []string{dir + "/bundle/"} if tt.stdIn { params.StdIn = true tempFile, err := os.CreateTemp("", "test") if err != nil { t.Fatalf("unexpected error creating temp file: %q", err.Error()) } if _, err := tempFile.Write([]byte(tt.input)); err != nil { t.Fatalf("unexpeced error when writing to temp file: %q", err.Error()) } if _, err := tempFile.Seek(0, 0); err != nil { t.Fatalf("unexpected error when rewinding temp file: %q", err.Error()) } oldStdin := os.Stdin defer func() { os.Stdin = oldStdin os.Remove(tempFile.Name()) }() os.Stdin = tempFile } else { if _, ok := tt.files["files/test.json"]; ok { params.Paths = append(params.Paths, dir+"/files/") } } err := runExec(params) if err != nil && !tt.expectError { t.Fatalf("unexpected error in test: %q", err.Error()) } if err == nil && tt.expectError { t.Fatalf("expected error %q, but none occurred in test", tt.expected) } if err != nil && err.Error() != tt.expected { t.Fatalf("expected error %q, but got %q", tt.expected, err.Error()) } }) }) } } func TestExecTimeoutWithMalformedRemoteBundle(t *testing.T) { test.WithTempFS(map[string]string{}, func(dir string) { // Note(philipc): We add the "raw bundles" flag so that we can stuff a // malformed bundle into the mock bundle server. Otherwise, the server // will just return 503 errors forever, because it won't be able to // build the bundle on its end. s := sdk_test.MustNewServer( sdk_test.RawBundles(true), sdk_test.MockBundle("/bundles/bundle.tar.gz", map[string]string{ "example.rego": ` package example p := bits.sand(42, 43) # typo of bits.and `, })) defer s.Stop() var buf bytes.Buffer params := exec.NewParams(&buf) _ = params.OutputFormat.Set("json") params.ConfigOverrides = []string{ "services.test.url=" + s.URL(), "bundles.test.resource=/bundles/bundle.tar.gz", } // Note(philipc): We can set this timeout almost arbitrarily high or // low-- the test will time out before it ever succeeds, due to the // faulty bundle. params.Timeout = time.Millisecond * 50 params.Paths = append(params.Paths, dir) err := runExec(params) if err == nil { t.Fatalf("Expected error, got nil instead.") } exp := "exec error: timed out before OPA was ready." if !strings.HasPrefix(err.Error(), exp) { t.Fatalf("Expected error: %s, got %s", exp, err.Error()) } }) }