mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-27 18:55:54 -06:00
91d758bbaf
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
391 lines
7.1 KiB
Go
391 lines
7.1 KiB
Go
// Copyright 2019 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package presentation
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/open-policy-agent/opa/ast"
|
|
"github.com/open-policy-agent/opa/loader"
|
|
"github.com/open-policy-agent/opa/rego"
|
|
"github.com/open-policy-agent/opa/storage"
|
|
"github.com/open-policy-agent/opa/storage/inmem"
|
|
"github.com/open-policy-agent/opa/util/test"
|
|
)
|
|
|
|
type testErrorWithMarshaller struct {
|
|
msg string
|
|
}
|
|
|
|
func (t *testErrorWithMarshaller) Error() string {
|
|
return t.msg
|
|
}
|
|
|
|
func (t *testErrorWithMarshaller) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(struct {
|
|
Text string `json:"text"`
|
|
}{
|
|
Text: t.msg,
|
|
})
|
|
}
|
|
|
|
func validateJSONOutput(t *testing.T, testErr error, expected string) {
|
|
t.Helper()
|
|
output := Output{Errors: NewOutputErrors(testErr)}
|
|
var buf bytes.Buffer
|
|
err := JSON(&buf, output)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
if buf.String() != expected {
|
|
t.Fatalf("Unexpected marshalled error value.\n Expected (len=%d):\n>>>\n%s\n<<<\n\nActual (len=%d):\n>>>>\n%s\n<<<<\n",
|
|
len(expected), expected, len(buf.String()), buf.String())
|
|
}
|
|
}
|
|
|
|
func TestOutputJSONErrorUnstructured(t *testing.T) {
|
|
err := errors.New("some text")
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "some text"
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorCustomMarshaller(t *testing.T) {
|
|
err := &testErrorWithMarshaller{
|
|
msg: "custom message",
|
|
}
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "custom message"
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorStructuredASTErr(t *testing.T) {
|
|
err := &ast.Error{
|
|
Code: "1",
|
|
Message: "error message",
|
|
}
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "error message",
|
|
"code": "1"
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorStructuredStorageErr(t *testing.T) {
|
|
store := inmem.New()
|
|
txn := storage.NewTransactionOrDie(context.Background(), store)
|
|
err := store.Write(context.Background(), txn, storage.AddOp, storage.Path{}, map[string]interface{}{"foo": 1})
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "data write during read transaction",
|
|
"code": "storage_invalid_txn_error"
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorStructuredTopdownErr(t *testing.T) {
|
|
mod := `
|
|
package test
|
|
|
|
p(x) = y {
|
|
y = x[_]
|
|
}
|
|
|
|
z := p([1, 2, 3])
|
|
`
|
|
|
|
_, err := rego.New(
|
|
rego.Module("test.rego", mod),
|
|
rego.Query("data.test.z"),
|
|
).Eval(context.Background())
|
|
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "functions must not produce multiple outputs for same inputs",
|
|
"code": "eval_conflict_error",
|
|
"location": {
|
|
"file": "test.rego",
|
|
"row": 4,
|
|
"col": 3
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorStructuredAstErr(t *testing.T) {
|
|
_, err := rego.New(rego.Query("count(0)")).Eval(context.Background())
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "count: invalid argument(s)",
|
|
"code": "rego_type_error",
|
|
"location": {
|
|
"file": "",
|
|
"row": 1,
|
|
"col": 1
|
|
},
|
|
"details": {
|
|
"have": [
|
|
{
|
|
"type": "number"
|
|
},
|
|
null
|
|
],
|
|
"want": [
|
|
{
|
|
"of": [
|
|
{
|
|
"of": {
|
|
"type": "any"
|
|
},
|
|
"type": "set"
|
|
},
|
|
{
|
|
"dynamic": {
|
|
"type": "any"
|
|
},
|
|
"type": "array"
|
|
},
|
|
{
|
|
"dynamic": {
|
|
"key": {
|
|
"type": "any"
|
|
},
|
|
"value": {
|
|
"type": "any"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
{
|
|
"type": "string"
|
|
}
|
|
],
|
|
"type": "any"
|
|
},
|
|
{
|
|
"type": "number"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorStructuredAstParseErr(t *testing.T) {
|
|
_, err := rego.New(
|
|
rego.Module("parse-err.rego", "!!!"),
|
|
rego.Query("!!!"),
|
|
).Eval(context.Background())
|
|
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "illegal ! character",
|
|
"code": "rego_parse_error",
|
|
"location": {
|
|
"file": "parse-err.rego",
|
|
"row": 1,
|
|
"col": 1
|
|
},
|
|
"details": {
|
|
"line": "!!!",
|
|
"idx": 0
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorStructuredASTErrList(t *testing.T) {
|
|
c := ast.NewCompiler()
|
|
c.Compile(map[string]*ast.Module{
|
|
"error.rego": ast.MustParseModule(`
|
|
package test
|
|
|
|
q {
|
|
bad[reference]
|
|
}
|
|
`)})
|
|
c.Errors.Sort()
|
|
err := c.Errors
|
|
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "var bad is unsafe",
|
|
"code": "rego_unsafe_var_error",
|
|
"location": {
|
|
"file": "",
|
|
"row": 5,
|
|
"col": 2
|
|
}
|
|
},
|
|
{
|
|
"message": "var reference is unsafe",
|
|
"code": "rego_unsafe_var_error",
|
|
"location": {
|
|
"file": "",
|
|
"row": 5,
|
|
"col": 2
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorStructuredLoaderErrList(t *testing.T) {
|
|
files := map[string]string{
|
|
// bundle a
|
|
"a/data.json": "{{{",
|
|
"b/data.json": "...",
|
|
}
|
|
|
|
var err error
|
|
var tmpPath string
|
|
test.WithTempFS(files, func(path string) {
|
|
tmpPath = path
|
|
_, err = loader.NewFileLoader().All([]string{path})
|
|
})
|
|
|
|
expected := fmt.Sprintf(`{
|
|
"errors": [
|
|
{
|
|
"message": "%s/a/data.json: invalid character '{' looking for beginning of object key string"
|
|
},
|
|
{
|
|
"message": "%s/b/data.json: invalid character '.' looking for beginning of value"
|
|
}
|
|
]
|
|
}
|
|
`, tmpPath, tmpPath)
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestOutputJSONErrorStructuredRegoErrList(t *testing.T) {
|
|
mod := `
|
|
package test
|
|
|
|
p {
|
|
bad_func1()
|
|
}
|
|
|
|
q {
|
|
bad_func2()
|
|
}
|
|
`
|
|
_, err := rego.New(
|
|
rego.Module("error.rego", mod),
|
|
rego.Query("data"),
|
|
).PrepareForEval(context.Background())
|
|
|
|
expected := `{
|
|
"errors": [
|
|
{
|
|
"message": "undefined function bad_func1",
|
|
"code": "rego_type_error",
|
|
"location": {
|
|
"file": "error.rego",
|
|
"row": 5,
|
|
"col": 2
|
|
}
|
|
},
|
|
{
|
|
"message": "undefined function bad_func2",
|
|
"code": "rego_type_error",
|
|
"location": {
|
|
"file": "error.rego",
|
|
"row": 9,
|
|
"col": 2
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
validateJSONOutput(t, err, expected)
|
|
}
|
|
|
|
func TestSource(t *testing.T) {
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
Source(buf, Output{
|
|
Partial: ®o.PartialQueries{
|
|
Queries: []ast.Body{
|
|
ast.MustParseBody("a = 1; b = 2"),
|
|
},
|
|
Support: []*ast.Module{
|
|
ast.MustParseModule(`
|
|
package test
|
|
p = 1
|
|
`),
|
|
},
|
|
},
|
|
})
|
|
|
|
exp := `# Query 1
|
|
a = 1
|
|
b = 2
|
|
|
|
# Module 1
|
|
package test
|
|
|
|
p = 1
|
|
`
|
|
|
|
if buf.String() != exp {
|
|
t.Fatalf("Expected:\n\n%v\n\nGot:\n\n%v", exp, buf.String())
|
|
}
|
|
|
|
}
|