mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-13 03:42:35 -06:00
b36151d992
In addition to those commands already supported: * build * check * eval * fmt * test support has been added to the following commands: * `bench` * `deps` * `exec` * `inspect` * `parse` * `run` (command `server` and `REPL`) Fixes: #6520 Signed-off-by: Johan Fylling <johan.dev@fylling.se>
3075 lines
72 KiB
Go
3075 lines
72 KiB
Go
// Copyright 2016 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.
|
|
|
|
// nolint: goconst // string duplication is for test readability.
|
|
package repl
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/open-policy-agent/opa/ast"
|
|
"github.com/open-policy-agent/opa/internal/presentation"
|
|
"github.com/open-policy-agent/opa/storage"
|
|
inmem "github.com/open-policy-agent/opa/storage/inmem/test"
|
|
"github.com/open-policy-agent/opa/util"
|
|
)
|
|
|
|
func TestFunction(t *testing.T) {
|
|
store := newTestStore()
|
|
ctx := context.Background()
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
mod1 := []byte(`package a.b.c
|
|
|
|
foo(x) = y {
|
|
split(x, ".", y)
|
|
}
|
|
|
|
bar([x, y]) = z {
|
|
trim(x, y, z)
|
|
}
|
|
`)
|
|
|
|
mod2 := []byte(`package a.b.d
|
|
|
|
baz(_) = y {
|
|
data.a.b.c.foo("barfoobar.bar", x)
|
|
data.a.b.c.bar(x, y)
|
|
}`)
|
|
|
|
if err := store.UpsertPolicy(ctx, txn, "mod1", mod1); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := store.UpsertPolicy(ctx, txn, "mod2", mod2); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := store.Commit(ctx, txn); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
repl := newRepl(store, &buf)
|
|
|
|
if err := repl.OneShot(ctx, "json"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "data.a.b.d.baz(null, x)"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
exp := util.MustUnmarshalJSON([]byte(`{"result": [{"expressions": [{"text":"data.a.b.d.baz(null, x)", "value": true, "location": {"row": 1, "col": 1}}], "bindings": {"x": "foo"}}]}`))
|
|
result := util.MustUnmarshalJSON(buf.Bytes())
|
|
if !reflect.DeepEqual(exp, result) {
|
|
t.Fatalf("expected data.a.b.d.baz(x) to be %v, got %v", exp, result)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "p(x) = y { y = x+4 }"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
buf.Reset()
|
|
if err := repl.OneShot(ctx, "data.repl.p(5, y)"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
exp = util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"text": "data.repl.p(5, y)",
|
|
"value": true,
|
|
"location": {
|
|
"col": 1,
|
|
"row": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"y": 9
|
|
}
|
|
}
|
|
]
|
|
}`))
|
|
result = util.MustUnmarshalJSON(buf.Bytes())
|
|
if !reflect.DeepEqual(exp, result) {
|
|
t.Fatalf("expected datrepl.p(x) to be %v, got %v", exp, result)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "f(1, x) = y { y = x }"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "f(2, x) = y { y = x*2 }"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
buf.Reset()
|
|
if err := repl.OneShot(ctx, "data.repl.f(1, 2, y)"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
exp = util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"text": "data.repl.f(1, 2, y)",
|
|
"location": {
|
|
"col": 1,
|
|
"row": 1
|
|
},
|
|
"value": true
|
|
}
|
|
],
|
|
"bindings": {
|
|
"y": 2
|
|
}
|
|
}
|
|
]
|
|
}`))
|
|
result = util.MustUnmarshalJSON(buf.Bytes())
|
|
if !reflect.DeepEqual(exp, result) {
|
|
t.Fatalf("expected data.repl.f(1, 2, y) to be %v, got %v", exp, result)
|
|
}
|
|
buf.Reset()
|
|
if err := repl.OneShot(ctx, "data.repl.f(2, 2, y)"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
exp = util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"text": "data.repl.f(2, 2, y)",
|
|
"location": {
|
|
"col": 1,
|
|
"row": 1
|
|
},
|
|
"value": true
|
|
}
|
|
],
|
|
"bindings": {
|
|
"y": 4
|
|
}
|
|
}
|
|
]
|
|
}`))
|
|
result = util.MustUnmarshalJSON(buf.Bytes())
|
|
if !reflect.DeepEqual(exp, result) {
|
|
t.Fatalf("expected data.repl.f(2, 2, y) to be %v, got %v", exp, result)
|
|
}
|
|
}
|
|
|
|
func TestComplete(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
mod1 := []byte(`package a.b.c
|
|
|
|
p = 1 { true }
|
|
q = 2 { true }
|
|
q = 3 { false }`)
|
|
|
|
mod2 := []byte(`package a.b.d
|
|
|
|
r = 3 { true }`)
|
|
|
|
if err := store.UpsertPolicy(ctx, txn, "mod1", mod1); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := store.UpsertPolicy(ctx, txn, "mod2", mod2); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := store.Commit(ctx, txn); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
repl := newRepl(store, &buf)
|
|
if err := repl.OneShot(ctx, "s = 4"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buf.Reset()
|
|
|
|
result := repl.complete("")
|
|
expected := []string{
|
|
"data.a.b.c.p",
|
|
"data.a.b.c.q",
|
|
"data.a.b.d.r",
|
|
"data.repl.s",
|
|
}
|
|
|
|
sort.Strings(result)
|
|
sort.Strings(expected)
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Fatalf("Expected %v but got: %v", expected, result)
|
|
}
|
|
|
|
result = repl.complete("data.a.b")
|
|
expected = []string{
|
|
"data.a.b.c.p",
|
|
"data.a.b.c.q",
|
|
"data.a.b.d.r",
|
|
}
|
|
|
|
sort.Strings(result)
|
|
sort.Strings(expected)
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Fatalf("Expected %v but got: %v", expected, result)
|
|
}
|
|
|
|
result = repl.complete("data.a.b.c.p[x]")
|
|
expected = []string{}
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Fatalf("Expected %v but got: %v", expected, result)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "import data.a.b.c.p as xyz"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "import data.a.b.d"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result = repl.complete("x")
|
|
expected = []string{
|
|
"xyz",
|
|
}
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Fatalf("Expected %v but got: %v", expected, result)
|
|
}
|
|
}
|
|
|
|
func TestDump(t *testing.T) {
|
|
ctx := context.Background()
|
|
input := `{"a": [1,2,3,4]}`
|
|
var data map[string]interface{}
|
|
err := util.UnmarshalJSON([]byte(input), &data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
store := inmem.NewFromObject(data)
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "dump"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "{\"a\":[1,2,3,4]}\n")
|
|
}
|
|
|
|
func TestDumpPath(t *testing.T) {
|
|
ctx := context.Background()
|
|
input := `{"a": [1,2,3,4]}`
|
|
var data map[string]interface{}
|
|
err := util.UnmarshalJSON([]byte(input), &data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
store := inmem.NewFromObject(data)
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
dir, err := os.MkdirTemp("", "dump-path-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
err := os.RemoveAll(dir)
|
|
if err != nil {
|
|
t.Errorf("error cleaning up with RemoveAll(): %v", err)
|
|
}
|
|
})
|
|
file := filepath.Join(dir, "tmpfile")
|
|
if err := repl.OneShot(ctx, fmt.Sprintf("dump %s", file)); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if buffer.String() != "" {
|
|
t.Errorf("Expected no output but got: %v", buffer.String())
|
|
}
|
|
|
|
bs, err := os.ReadFile(file)
|
|
if err != nil {
|
|
t.Fatalf("Expected file read to succeed but got: %v", err)
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
if err := util.UnmarshalJSON(bs, &result); err != nil {
|
|
t.Fatalf("Expected json unmarshal to succeed but got: %v", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(data, result) {
|
|
t.Fatalf("Expected dumped json to equal %v but got: %v", data, result)
|
|
}
|
|
}
|
|
|
|
func TestDumpPathCaseSensitive(t *testing.T) {
|
|
ctx := context.Background()
|
|
input := `{"a": [1,2,3,4]}`
|
|
var data map[string]interface{}
|
|
err := util.UnmarshalJSON([]byte(input), &data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
store := inmem.NewFromObject(data)
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
dir, err := os.MkdirTemp("", "DumpPathCaseSensitiveTest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
err := os.RemoveAll(dir)
|
|
if err != nil {
|
|
t.Errorf("error cleaning up with RemoveAll(): %v", err)
|
|
}
|
|
})
|
|
file := filepath.Join(dir, "tmpfile")
|
|
if err := repl.OneShot(ctx, fmt.Sprintf("dump %s", file)); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if buffer.String() != "" {
|
|
t.Errorf("Expected no output but got: %v", buffer.String())
|
|
}
|
|
|
|
bs, err := os.ReadFile(file)
|
|
if err != nil {
|
|
t.Fatalf("Expected file read to succeed but got: %v", err)
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
if err := util.UnmarshalJSON(bs, &result); err != nil {
|
|
t.Fatalf("Expected json unmarshal to succeed but got: %v", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(data, result) {
|
|
t.Fatalf("Expected dumped json to equal %v but got: %v", data, result)
|
|
}
|
|
}
|
|
|
|
func TestHelp(t *testing.T) {
|
|
topics["deadbeef"] = topicDesc{
|
|
fn: func(w io.Writer) error {
|
|
fmt.Fprintln(w, "blah blah blah")
|
|
return nil
|
|
},
|
|
}
|
|
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "help deadbeef"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expected := "blah blah blah\n"
|
|
|
|
if buffer.String() != expected {
|
|
t.Fatalf("Unexpected output from help topic: %v", buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestHelpWithOPAVersionReport(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
// empty report
|
|
repl.SetOPAVersionReport(nil)
|
|
if err := repl.OneShot(ctx, "help"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if strings.Contains(buffer.String(), "Version Info") {
|
|
t.Fatalf("Unexpected output from help: \"%v\"", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
repl.SetOPAVersionReport([][2]string{
|
|
{"Latest Upstream Version", "0.19.2"},
|
|
{"Download", "https://openpolicyagent.org/downloads/v0.19.2/opa_darwin_amd64"},
|
|
{"Release Notes", "https://github.com/open-policy-agent/opa/releases/tag/v0.19.2"},
|
|
})
|
|
if err := repl.OneShot(ctx, "help"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
exp := `Latest Upstream Version : 0.19.2
|
|
Download : https://openpolicyagent.org/downloads/v0.19.2/opa_darwin_amd64
|
|
Release Notes : https://github.com/open-policy-agent/opa/releases/tag/v0.19.2`
|
|
|
|
if !strings.Contains(buffer.String(), exp) {
|
|
t.Fatalf("Expected output from help to contain: \"%v\" but got \"%v\"", exp, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestShowDebug(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "show debug"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var result replDebugState
|
|
|
|
if err := util.Unmarshal(buffer.Bytes(), &result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var exp replDebugState
|
|
exp.Explain = explainOff
|
|
|
|
if !reflect.DeepEqual(result, exp) {
|
|
t.Fatalf("Expected %+v but got %+v", exp, result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "trace"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "metrics"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "instrument"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "profile"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "show debug"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
exp.Explain = explainFull
|
|
exp.Metrics = true
|
|
exp.Instrument = true
|
|
exp.Profile = true
|
|
|
|
if err := util.Unmarshal(buffer.Bytes(), &result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(result, exp) {
|
|
t.Fatalf("Expected %+v but got %+v", exp, result)
|
|
}
|
|
}
|
|
|
|
func TestShow(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, `package repl_test`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, "package repl_test\n")
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "import input.xyz"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expected := `package repl_test
|
|
|
|
import input.xyz` + "\n"
|
|
assertREPLText(t, buffer, expected)
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "import data.foo as bar"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expected = `package repl_test
|
|
|
|
import data.foo as bar
|
|
import input.xyz` + "\n"
|
|
assertREPLText(t, buffer, expected)
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, `p[1] { true }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `p[2] { true }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expected = `package repl_test
|
|
|
|
import data.foo as bar
|
|
import input.xyz
|
|
|
|
p[1]
|
|
|
|
p[2]` + "\n"
|
|
assertREPLText(t, buffer, expected)
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "package abc"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
assertREPLText(t, buffer, "package abc\n")
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "package repl_test"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
assertREPLText(t, buffer, expected)
|
|
buffer.Reset()
|
|
}
|
|
|
|
func TestTypes(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, "types"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `p[x] = y { x := "a"; y := 1 }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `p[x]`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
output := strings.TrimSpace(buffer.String())
|
|
|
|
exp := []string{
|
|
"# data.repl.p[x]: number",
|
|
"# x: string",
|
|
}
|
|
|
|
for i := range exp {
|
|
if !strings.Contains(output, exp[i]) {
|
|
t.Fatalf("Expected output to contain %q but got: %v", exp[i], output)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestUnknown(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, "xs = [1,2,3]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
|
|
err := repl.OneShot(ctx, "unknown input")
|
|
if err != nil {
|
|
t.Fatal("Unexpected command error:", err)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "data.repl.xs[i] = x; input.x = x"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
output := strings.TrimSpace(buffer.String())
|
|
expected := strings.TrimSpace(`
|
|
+---------+-------------+
|
|
| Query 1 | input.x = 1 |
|
|
| | i = 0 |
|
|
| | x = 1 |
|
|
+---------+-------------+
|
|
| Query 2 | input.x = 2 |
|
|
| | i = 1 |
|
|
| | x = 2 |
|
|
+---------+-------------+
|
|
| Query 3 | input.x = 3 |
|
|
| | i = 2 |
|
|
| | x = 3 |
|
|
+---------+-------------+
|
|
`)
|
|
|
|
if output != expected {
|
|
t.Fatalf("Unexpected output. Expected:\n\n%v\n\nGot:\n\n%v", expected, output)
|
|
}
|
|
}
|
|
func TestUnknownMetrics(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, "xs = [1,2,3]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
|
|
err := repl.OneShot(ctx, "unknown input")
|
|
if err != nil {
|
|
t.Fatal("Unexpected command error:", err)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "metrics"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "data.repl.xs[i] = x; input.x = x"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
output := strings.TrimSpace(buffer.String())
|
|
expected := strings.TrimSpace(`
|
|
+---------+-------------+
|
|
| Query 1 | input.x = 1 |
|
|
| | i = 0 |
|
|
| | x = 1 |
|
|
+---------+-------------+
|
|
| Query 2 | input.x = 2 |
|
|
| | i = 1 |
|
|
| | x = 2 |
|
|
+---------+-------------+
|
|
| Query 3 | input.x = 3 |
|
|
| | i = 2 |
|
|
| | x = 3 |
|
|
+---------+-------------+
|
|
`)
|
|
|
|
if !strings.HasPrefix(output, expected) {
|
|
t.Fatalf("Unexpected partial eval results. Expected:\n\n%v\n\nGot:\n\n%v", expected, output)
|
|
}
|
|
|
|
if !strings.Contains(output, "timer_rego_partial_eval_ns") {
|
|
t.Fatal("Expected timer_rego_partial_eval_ns but got:\n\n", output)
|
|
}
|
|
}
|
|
|
|
func TestUnknownJSON(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, "xs = [1,2,3]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
|
|
err := repl.OneShot(ctx, "unknown input")
|
|
if err != nil {
|
|
t.Fatal("Unexpected command error:", err)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "json"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "data.repl.xs[i] = x; input.x = x"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var result presentation.Output
|
|
|
|
if err := json.NewDecoder(&buffer).Decode(&result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(result.Partial.Queries) != 3 {
|
|
t.Fatalf("Expected exactly 3 queries in partial evaluation output but got: %v", result)
|
|
}
|
|
}
|
|
|
|
func TestUnknownInvalid(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
err := repl.OneShot(ctx, "unknown x-1")
|
|
if err == nil || !strings.Contains(err.Error(), "usage: unknown <input/data reference>") {
|
|
t.Fatal("expected error from setting bad unknown but got:", err)
|
|
}
|
|
|
|
// Ensure that partial evaluation has not been enabled.
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "1+2"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := strings.TrimSpace(buffer.String())
|
|
if result != "3" {
|
|
t.Fatal("want true but got:", result)
|
|
}
|
|
}
|
|
|
|
func TestUnset(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
var err error
|
|
|
|
if err := repl.OneShot(ctx, "magic = 23"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "p = 3.14"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "unset p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
err = repl.OneShot(ctx, "p")
|
|
|
|
if _, ok := err.(ast.Errors); !ok {
|
|
t.Fatalf("Expected AST error but got: %v", err)
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p = 3.14"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `p = 3 { false }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "unset p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
err = repl.OneShot(ctx, "p")
|
|
if _, ok := err.(ast.Errors); !ok {
|
|
t.Fatalf("Expected AST error but got err: %v, output: %v", err, buffer.String())
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "unset "); err == nil {
|
|
t.Fatalf("Expected unset error for bad syntax but got: %v", buffer.String())
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "unset 1=1"); err == nil {
|
|
t.Fatalf("Expected unset error for bad syntax but got: %v", buffer.String())
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, `unset "p"`); err == nil {
|
|
t.Fatalf("Expected unset error for bad syntax but got: %v", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p(x) = y { y = x }"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "unset p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
err = repl.OneShot(ctx, "data.repl.p(1, 2)")
|
|
if err == nil || err.Error() != `1 error occurred: 1:1: rego_type_error: undefined function data.repl.p` {
|
|
t.Fatalf("Expected eval error (undefined built-in) but got err: '%v'", err)
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p(1, x) = y { y = x }"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "p(2, x) = y { y = x+1 }"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "unset p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
err = repl.OneShot(ctx, "data.repl.p(1, 2, 3)")
|
|
if err == nil || err.Error() != `1 error occurred: 1:1: rego_type_error: undefined function data.repl.p` {
|
|
t.Fatalf("Expected eval error (undefined built-in) but got err: '%v'", err)
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `unset q`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if buffer.String() != "warning: no matching rules in current module\n" {
|
|
t.Fatalf("Expected unset error for missing rule but got: %v", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `unset q`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if buffer.String() != "warning: no matching rules in current module\n" {
|
|
t.Fatalf("Expected unset error for missing function but got: %v", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `magic`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if buffer.String() != "23\n" {
|
|
t.Fatalf("Expected magic to be defined but got: %v", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `package data.other`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `unset magic`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if buffer.String() != "warning: no matching rules in current module\n" {
|
|
t.Fatalf("Expected unset error for bad syntax but got: %v", buffer.String())
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, `input = {}`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, `unset input`); err != nil {
|
|
t.Fatalf("Expected unset to succeed for input: %v", err)
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `not input`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if buffer.String() != "true\n" {
|
|
t.Fatalf("Expected unset input to remove input document: %v", buffer.String())
|
|
}
|
|
|
|
}
|
|
|
|
func TestOneShotEmptyBufferOneExpr(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "data.a[i].b.c[j] = 2"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "+---+---+\n| i | j |\n+---+---+\n| 0 | 1 |\n+---+---+\n")
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "data.a[i].b.c[j] = \"deadbeef\""); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "undefined\n")
|
|
}
|
|
|
|
func TestOneShotEmptyBufferOneRule(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, `p[x] { data.a[i] = x }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "Rule 'p' defined in package repl. Type 'show' to see rules.\n")
|
|
}
|
|
|
|
func TestOneShotBufferedExpr(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "data.a[i].b.c[j] = "); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "")
|
|
if err := repl.OneShot(ctx, "2"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "")
|
|
if err := repl.OneShot(ctx, ""); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "+---+---+\n| i | j |\n+---+---+\n| 0 | 1 |\n+---+---+\n")
|
|
}
|
|
|
|
func TestOneShotBufferedRule(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "p[x] { "); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "")
|
|
if err := repl.OneShot(ctx, "data.a[i].b.c[1]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "")
|
|
if err := repl.OneShot(ctx, " = "); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "")
|
|
if err := repl.OneShot(ctx, "x"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "")
|
|
if err := repl.OneShot(ctx, "}"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "")
|
|
if err := repl.OneShot(ctx, ""); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "Rule 'p' defined in package repl. Type 'show' to see rules.\n")
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p[2]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), "2\n")
|
|
}
|
|
|
|
func TestOneShotJSON(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
repl.outputFormat = "json"
|
|
if err := repl.OneShot(ctx, "data.a[i] = x"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
var expected interface{}
|
|
if err := util.UnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": true,
|
|
"text": "data.a[i] = x",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"i": 0,
|
|
"x": {
|
|
"b": {
|
|
"c": [
|
|
true,
|
|
2,
|
|
false
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": true,
|
|
"text": "data.a[i] = x",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"i": 1,
|
|
"x": {
|
|
"b": {
|
|
"c": [
|
|
false,
|
|
true,
|
|
1
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}`), &expected); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var result interface{}
|
|
|
|
if err := util.UnmarshalJSON(buffer.Bytes(), &result); err != nil {
|
|
t.Errorf("Unexpected output format: %v", err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Errorf("Expected %v but got: %v", expected, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestOneShotV1Compatible(t *testing.T) {
|
|
type action struct {
|
|
line string
|
|
expOutput string
|
|
expErrs []string
|
|
}
|
|
tests := []struct {
|
|
note string
|
|
actions []action
|
|
v1Compatible bool
|
|
}{
|
|
{
|
|
note: "v0.x, keywords used",
|
|
actions: []action{
|
|
{
|
|
line: "a contains 2 if { true }",
|
|
expErrs: []string{"rego_unsafe_var_error: var a is unsafe"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
note: "v0.x, keywords not used",
|
|
actions: []action{
|
|
{
|
|
line: "a[2] { true }",
|
|
expOutput: "Rule 'a' defined in package repl. Type 'show' to see rules.\n",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
note: "v0.x, keywords imported",
|
|
actions: []action{
|
|
{
|
|
line: "import future.keywords",
|
|
},
|
|
{
|
|
line: "a contains 2 if { true }",
|
|
expOutput: "Rule 'a' defined in package repl. Type 'show' to see rules.\n",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
note: "v0.x, rego.v1 imported",
|
|
actions: []action{
|
|
{
|
|
line: "import rego.v1",
|
|
},
|
|
{
|
|
line: "a contains 2 if { true }",
|
|
expOutput: "Rule 'a' defined in package repl. Type 'show' to see rules.\n",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
note: "v1.0, keywords not used",
|
|
v1Compatible: true,
|
|
actions: []action{
|
|
{
|
|
line: "a[2] { true }",
|
|
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, keywords used, not imported",
|
|
v1Compatible: true,
|
|
actions: []action{
|
|
{
|
|
line: "a contains 2 if { true }",
|
|
expOutput: "Rule 'a' defined in package repl. Type 'show' to see rules.\n",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
note: "v1.0, keywords used, keywords imported",
|
|
v1Compatible: true,
|
|
actions: []action{
|
|
{
|
|
line: "import future.keywords",
|
|
},
|
|
{
|
|
line: "a contains 2 if { true }",
|
|
expOutput: "Rule 'a' defined in package repl. Type 'show' to see rules.\n",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
note: "v1.0, keywords used, rego.v1 imported",
|
|
v1Compatible: true,
|
|
actions: []action{
|
|
{
|
|
line: "import rego.v1",
|
|
},
|
|
{
|
|
line: "a contains 2 if { true }",
|
|
expOutput: "Rule 'a' defined in package repl. Type 'show' to see rules.\n",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer).
|
|
WithV1Compatible(tc.v1Compatible)
|
|
|
|
for _, action := range tc.actions {
|
|
err := repl.OneShot(ctx, action.line)
|
|
|
|
if len(action.expErrs) != 0 {
|
|
if err == nil {
|
|
t.Fatalf("Expected error but got: %s", buffer.String())
|
|
}
|
|
|
|
for _, e := range action.expErrs {
|
|
if !strings.Contains(err.Error(), e) {
|
|
t.Fatalf("Expected error to contain:\n\n%q\n\nbut got:\n\n%v", e, err)
|
|
}
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectOutput(t, buffer.String(), action.expOutput)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStoredModuleV1Compatible(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
v1Compatible bool
|
|
module string
|
|
line string
|
|
expOutput string
|
|
expErrs []string
|
|
}{
|
|
{
|
|
note: "v0.x keywords not used",
|
|
module: `package example
|
|
p[2] { 1 == 1 }`,
|
|
line: "data.example.p",
|
|
expOutput: "[\n 2\n]\n",
|
|
},
|
|
{
|
|
note: "v0.x, keywords not imported but used",
|
|
module: `package example
|
|
p contains 2 if { 1 == 1 }`,
|
|
line: "data.example.p",
|
|
expErrs: []string{
|
|
"rego_parse_error: var cannot be used for rule name",
|
|
"rego_parse_error: number cannot be used for rule name",
|
|
},
|
|
},
|
|
{
|
|
note: "v0.x, keywords imported",
|
|
module: `package example
|
|
import future.keywords
|
|
p contains 2 if { 1 == 1 }`,
|
|
line: "data.example.p",
|
|
expOutput: "[\n 2\n]\n",
|
|
},
|
|
{
|
|
note: "v0.x, rego.v1 imported",
|
|
module: `package example
|
|
import rego.v1
|
|
p contains 2 if { 1 == 1 }`,
|
|
line: "data.example.p",
|
|
expOutput: "[\n 2\n]\n",
|
|
},
|
|
{
|
|
note: "v1.0, keywords not used",
|
|
v1Compatible: true,
|
|
module: `package example
|
|
p[2] { 1 == 1 }`,
|
|
line: "data.example.p",
|
|
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, keywords not imported",
|
|
v1Compatible: true,
|
|
module: `package example
|
|
p contains 2 if { 1 == 1 }`,
|
|
line: "data.example.p",
|
|
expOutput: "[\n 2\n]\n",
|
|
},
|
|
{
|
|
note: "v1.0, keywords imported",
|
|
v1Compatible: true,
|
|
module: `package example
|
|
import future.keywords
|
|
p contains 2 if { 1 == 1 }`,
|
|
line: "data.example.p",
|
|
expOutput: "[\n 2\n]\n",
|
|
},
|
|
{
|
|
note: "v1.0, rego.v1 imported",
|
|
v1Compatible: true,
|
|
module: `package example
|
|
import rego.v1
|
|
p contains 2 if { 1 == 1 }`,
|
|
line: "data.example.p",
|
|
expOutput: "[\n 2\n]\n",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
if err := store.UpsertPolicy(ctx, txn, "policy", []byte(tc.module)); err != nil {
|
|
t.Fatalf("Unexpected error upserting policy: %v", err)
|
|
}
|
|
|
|
if err := store.Commit(ctx, txn); err != nil {
|
|
t.Fatalf("Unexpected error committing store transaction: %v", err)
|
|
}
|
|
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer).
|
|
WithV1Compatible(tc.v1Compatible)
|
|
|
|
err := repl.OneShot(ctx, tc.line)
|
|
|
|
if len(tc.expErrs) != 0 {
|
|
if err == nil {
|
|
t.Fatalf("Expected error but got: %s", buffer.String())
|
|
}
|
|
|
|
for _, e := range tc.expErrs {
|
|
if !strings.Contains(err.Error(), e) {
|
|
t.Fatalf("Expected error to contain:\n\n%q\n\nbut got:\n\n%v", e, err)
|
|
}
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expectOutput(t, buffer.String(), tc.expOutput)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEvalData(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
testMod := []byte(`package ex
|
|
|
|
p = [1, 2, 3] { true }`)
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
if err := store.UpsertPolicy(ctx, txn, "test", testMod); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := store.Commit(ctx, txn); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "data"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expected := parseJSON(`
|
|
{
|
|
"a": [
|
|
{
|
|
"b": {
|
|
"c": [
|
|
true,
|
|
2,
|
|
false
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"b": {
|
|
"c": [
|
|
false,
|
|
true,
|
|
1
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"ex": {
|
|
"p": [
|
|
1,
|
|
2,
|
|
3
|
|
]
|
|
}
|
|
}`)
|
|
result := parseJSON(buffer.String())
|
|
|
|
// Strip REPL documents out as these change depending on build settings.
|
|
data := result.(map[string]interface{})
|
|
delete(data, "repl")
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Fatalf("Expected:\n%v\n\nGot:\n%v", expected, result)
|
|
}
|
|
}
|
|
|
|
func TestEvalFalse(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "false"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := buffer.String()
|
|
if result != "false\n" {
|
|
t.Errorf("Expected result to be false but got: %v", result)
|
|
}
|
|
}
|
|
|
|
func TestEvalConstantRule(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "pi = 3.14"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := buffer.String()
|
|
if result != "Rule 'pi' defined in package repl. Type 'show' to see rules.\n" {
|
|
t.Errorf("Expected rule to be defined but got: %v", result)
|
|
return
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "pi"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = buffer.String()
|
|
expected := "3.14\n"
|
|
if result != expected {
|
|
t.Errorf("Expected pi to evaluate to 3.14 but got: %v", result)
|
|
return
|
|
}
|
|
buffer.Reset()
|
|
err := repl.OneShot(ctx, "pi.deadbeef")
|
|
result = buffer.String()
|
|
expected = "undefined ref: data.repl.pi.deadbeef"
|
|
if err == nil {
|
|
t.Fatalf("Expected OneShot to return error %v but got: %v", expected, err)
|
|
}
|
|
if result != "" || !strings.Contains(err.Error(), expected) {
|
|
t.Fatalf("Expected pi.deadbeef to fail/error but got:\nresult: %q\nerr: %v", result, err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "pi > 3"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = buffer.String()
|
|
if result != "true\n" {
|
|
t.Errorf("Expected pi > 3 to be true but got: %v", result)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestEvalBooleanFlags(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "flags = [true, true]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "flags[_]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expected := strings.TrimSpace(`
|
|
Rule 'flags' defined in package repl. Type 'show' to see rules.
|
|
+----------+
|
|
| flags[_] |
|
|
+----------+
|
|
| true |
|
|
| true |
|
|
+----------+`)
|
|
result := strings.TrimSpace(buffer.String())
|
|
if result != expected {
|
|
t.Errorf("Expected a single column with boolean output but got:\n%v", result)
|
|
}
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, `flags2 = [true, "x", 1]`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "flags2[_]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expected = strings.TrimSpace(`
|
|
Rule 'flags2' defined in package repl. Type 'show' to see rules.
|
|
+-----------+
|
|
| flags2[_] |
|
|
+-----------+
|
|
| true |
|
|
| "x" |
|
|
| 1 |
|
|
+-----------+`)
|
|
result = strings.TrimSpace(buffer.String())
|
|
if result != expected {
|
|
t.Errorf("Expected a single column with boolean output but got:\n%v", result)
|
|
}
|
|
}
|
|
|
|
func TestEvalConstantRuleDefaultRootDoc(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "input = 1"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "input = 2"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, "undefined\n")
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "input = 1"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, "true\n")
|
|
}
|
|
|
|
func TestEvalConstantRuleAssignment(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
|
|
defined := "Rule 'x' defined in package repl. Type 'show' to see rules.\n"
|
|
redefined := "Rule 'x' re-defined in package repl. Type 'show' to see rules.\n"
|
|
definedInput := "Rule 'input' defined in package repl. Type 'show' to see rules.\n"
|
|
redefinedInput := "Rule 'input' re-defined in package repl. Type 'show' to see rules.\n"
|
|
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "x = 1"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, defined)
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "x := 2"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, redefined)
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, `package repl
|
|
|
|
x := 2
|
|
`)
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "x := 3"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, redefined)
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "x"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := buffer.String()
|
|
if result != "3\n" {
|
|
t.Fatalf("Expected 3 but got: %v", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "x = 3"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = buffer.String()
|
|
if result != "true\n" {
|
|
t.Fatalf("Expected true but got: %v", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "input = 0"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, definedInput)
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "input := 1"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
assertREPLText(t, buffer, redefinedInput)
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "input"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = buffer.String()
|
|
if result != "1\n" {
|
|
t.Fatalf("Expected 1 but got: %v", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
err := repl.OneShot(ctx, "assign()")
|
|
if err == nil || !strings.Contains(err.Error(), "rego_type_error: assign: arity mismatch\n\thave: ()\n\twant: (any, any)") {
|
|
t.Fatal("Expected type check error but got:", err)
|
|
}
|
|
}
|
|
|
|
func TestEvalSingleTermMultiValue(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
repl.outputFormat = "json"
|
|
|
|
input := `{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": true,
|
|
"text": "data.a[i].b.c[_]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"i": 0
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 2,
|
|
"text": "data.a[i].b.c[_]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"i": 0
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": true,
|
|
"text": "data.a[i].b.c[_]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"i": 1
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 1,
|
|
"text": "data.a[i].b.c[_]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"i": 1
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
|
|
var expected interface{}
|
|
if err := util.UnmarshalJSON([]byte(input), &expected); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "data.a[i].b.c[_]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
var result interface{}
|
|
if err := util.UnmarshalJSON(buffer.Bytes(), &result); err != nil {
|
|
t.Errorf("Expected valid JSON document: %v: %v", err, buffer.String())
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Errorf("Expected %v but got: %v", expected, buffer.String())
|
|
return
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "data.deadbeef[x]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
s := buffer.String()
|
|
if s != "{}\n" {
|
|
t.Errorf("Expected undefined from reference but got: %v", s)
|
|
return
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, `p[x] { a = [1, 2, 3, 4]; a[_] = x }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p[x]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
input = `
|
|
{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 1,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 1
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 2,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 2
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 3,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 3
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 4,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 4
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
if err := util.UnmarshalJSON([]byte(input), &expected); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := util.UnmarshalJSON(buffer.Bytes(), &result); err != nil {
|
|
t.Errorf("Expected valid JSON document: %v: %v", err, buffer.String())
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Errorf("Exepcted %v but got: %v", expected, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestEvalSingleTermMultiValueSetRef(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
repl.outputFormat = "json"
|
|
if err := repl.OneShot(ctx, `p[1] { true }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `p[2] { true }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `q = {3, 4} { true }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `r = [x, y] { x = {5, 6}; y = [7, 8] }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if err := repl.OneShot(ctx, "p[x]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expected := parseJSON(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 1,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 1
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 2,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 2
|
|
}
|
|
}
|
|
]
|
|
}`)
|
|
result := parseJSON(buffer.String())
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "q[x]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expected = parseJSON(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 3,
|
|
"text": "q[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 3
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 4,
|
|
"text": "q[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 4
|
|
}
|
|
}
|
|
]
|
|
}`)
|
|
result = parseJSON(buffer.String())
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
|
|
}
|
|
|
|
// Example below shows behavior for ref that iterates an embedded set. The
|
|
// tricky part here is that r[_] may refer to multiple collection types. If
|
|
// we eventually have a way of distinguishing between the bindings added for
|
|
// refs to sets, then those bindings could be filtered out. For now this is
|
|
// acceptable, as it should be an edge case.
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "r[_][x]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expected = parseJSON(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 5,
|
|
"text": "r[_][x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 5
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 6,
|
|
"text": "r[_][x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 6
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 7,
|
|
"text": "r[_][x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 0
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 8,
|
|
"text": "r[_][x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 1
|
|
}
|
|
}
|
|
]
|
|
}`)
|
|
result = parseJSON(buffer.String())
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestEvalRuleCompileError(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
err := repl.OneShot(ctx, `p[x] { true }`)
|
|
expected := "x is unsafe"
|
|
if err == nil {
|
|
t.Fatalf("Expected OneShot to return error %v but got: %v", expected, err)
|
|
}
|
|
if !strings.Contains(err.Error(), expected) {
|
|
t.Errorf("Expected error to contain %v but got: %v (err: %v)", expected, buffer.String(), err)
|
|
return
|
|
}
|
|
buffer.Reset()
|
|
err = repl.OneShot(ctx, `p = true { true }`)
|
|
result := buffer.String()
|
|
if err != nil || result != "Rule 'p' defined in package repl. Type 'show' to see rules.\n" {
|
|
t.Errorf("Expected valid rule to compile (because state should be unaffected) but got: %v (err: %v)", result, err)
|
|
}
|
|
}
|
|
|
|
func TestEvalBodyCompileError(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
repl.outputFormat = "json"
|
|
err := repl.OneShot(ctx, `x = 1; y > x`)
|
|
if _, ok := err.(ast.Errors); !ok {
|
|
t.Fatalf("Expected error message in output but got`: %v", buffer.String())
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `x = 1; y = 2; y > x`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := util.MustUnmarshalJSON(buffer.Bytes())
|
|
exp := util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": true,
|
|
"text": "x = 1",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
},
|
|
{
|
|
"value": true,
|
|
"text": "y = 2",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 8
|
|
}
|
|
},
|
|
{
|
|
"value": true,
|
|
"text": "y \u003e x",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 15
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 1,
|
|
"y": 2
|
|
}
|
|
}
|
|
]
|
|
}`))
|
|
if !reflect.DeepEqual(exp, result) {
|
|
t.Errorf(`Expected %v but got: %v"`, exp, buffer.String())
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestEvalBodyContainingWildCards(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "data.a[_].b.c[_] = x"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expected := strings.TrimSpace(`
|
|
+-------+
|
|
| x |
|
|
+-------+
|
|
| true |
|
|
| 2 |
|
|
| false |
|
|
| false |
|
|
| true |
|
|
| 1 |
|
|
+-------+`)
|
|
result := strings.TrimSpace(buffer.String())
|
|
if result != expected {
|
|
t.Errorf("Expected only a single column of output but got:\n%v", result)
|
|
}
|
|
|
|
}
|
|
|
|
func TestEvalBodyInput(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, `package repl`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `input["foo.bar"] = "hello" { true }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `input["baz"] = data.a[0].b.c[2] { true }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `package test`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "import input.baz"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `p = true { input["foo.bar"] = "hello"; baz = false }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result := buffer.String()
|
|
if result != "true\n" {
|
|
t.Fatalf("expected true but got: %v", result)
|
|
}
|
|
}
|
|
|
|
func TestEvalBodyInputComplete(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
// Test that input can be defined completely:
|
|
// https://github.com/open-policy-agent/opa/issues/231
|
|
if err := repl.OneShot(ctx, `package repl`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `input = 1`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `input`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result := buffer.String()
|
|
if result != "1\n" {
|
|
t.Fatalf("Expected 1 but got: %v", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
// Test that input is as expected
|
|
if err := repl.OneShot(ctx, `package ex1`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `x = input`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `x`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result = buffer.String()
|
|
if result != "1\n" {
|
|
t.Fatalf("Expected 1 but got: %v", result)
|
|
}
|
|
|
|
// Test that local input replaces other inputs
|
|
if err := repl.OneShot(ctx, `package ex2`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `input = 2`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `input`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result = buffer.String()
|
|
|
|
if result != "2\n" {
|
|
t.Fatalf("Expected 2 but got: %v", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
// Test that original input is intact
|
|
if err := repl.OneShot(ctx, `package ex3`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `input`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result = buffer.String()
|
|
|
|
if result != "1\n" {
|
|
t.Fatalf("Expected 1 but got: %v", result)
|
|
}
|
|
|
|
// Test that deferencing undefined input results in undefined
|
|
buffer.Reset()
|
|
|
|
repl = newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, `input.p`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = buffer.String()
|
|
if result != "undefined\n" {
|
|
t.Fatalf("Expected undefined but got: %v", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `input.p = false`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = buffer.String()
|
|
if result != "undefined\n" {
|
|
t.Fatalf("Expected undefined but got: %v", result)
|
|
}
|
|
|
|
}
|
|
|
|
func TestEvalBodyWith(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, `p = true { input.foo = "bar" }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if buffer.String() != "undefined\n" {
|
|
t.Fatalf("Expected undefined but got: %v", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, `p with input.foo as "bar"`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result := buffer.String()
|
|
expected := "true\n"
|
|
|
|
if result != expected {
|
|
t.Fatalf("Expected true but got: %v", result)
|
|
}
|
|
}
|
|
|
|
func TestEvalBodyRewrittenBuiltin(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "json"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `p[x] { a[x]; a = [1,2,3,4] }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "p[x] > 1"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := util.MustUnmarshalJSON(buffer.Bytes())
|
|
expected := util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": true,
|
|
"text": "p[x] \u003e 1",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 2
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": true,
|
|
"text": "p[x] \u003e 1",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 3
|
|
}
|
|
}
|
|
]
|
|
}`))
|
|
if util.Compare(result, expected) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestEvalBodyRewrittenRef(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "json"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `i = 1`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `data.a[0].b.c[i]`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := util.MustUnmarshalJSON(buffer.Bytes())
|
|
expected := util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 2,
|
|
"text": "data.a[0].b.c[i]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`))
|
|
if util.Compare(result, expected) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p = {1,2,3}"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = util.MustUnmarshalJSON(buffer.Bytes())
|
|
expected = util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": [
|
|
1,
|
|
2,
|
|
3
|
|
],
|
|
"text": "p",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`))
|
|
if util.Compare(result, expected) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p[x]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = util.MustUnmarshalJSON(buffer.Bytes())
|
|
expected = util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 1,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 1
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 2,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 2
|
|
}
|
|
},
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": 3,
|
|
"text": "p[x]",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 1
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 3
|
|
}
|
|
}
|
|
]
|
|
}`))
|
|
if util.Compare(result, expected) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestEvalBodySomeDecl(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "json"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "some x; x = 1"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
exp := util.MustUnmarshalJSON([]byte(`{
|
|
"result": [
|
|
{
|
|
"expressions": [
|
|
{
|
|
"value": true,
|
|
"text": "x = 1",
|
|
"location": {
|
|
"row": 1,
|
|
"col": 9
|
|
}
|
|
}
|
|
],
|
|
"bindings": {
|
|
"x": 1
|
|
}
|
|
}
|
|
]
|
|
}`))
|
|
result := util.MustUnmarshalJSON(buffer.Bytes())
|
|
if util.Compare(result, exp) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", exp, result)
|
|
}
|
|
}
|
|
|
|
func TestEvalImport(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "import data.a"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if len(buffer.Bytes()) != 0 {
|
|
t.Errorf("Expected no output but got: %v", buffer.String())
|
|
return
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "a[0].b.c[0] = true"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := buffer.String()
|
|
expected := "true\n"
|
|
if result != expected {
|
|
t.Errorf("Expected expression to evaluate successfully but got: %v", result)
|
|
return
|
|
}
|
|
|
|
// https://github.com/open-policy-agent/opa/issues/158 - re-run query to
|
|
// make sure import is not lost
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "a[0].b.c[0] = true"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = buffer.String()
|
|
expected = "true\n"
|
|
if result != expected {
|
|
t.Fatalf("Expected expression to evaluate successfully but got: %v", result)
|
|
}
|
|
}
|
|
|
|
func TestEvalImportFutureKeywords(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
err := repl.OneShot(ctx, "1 in [1]")
|
|
if err == nil {
|
|
t.Fatal("Expected error got nil")
|
|
}
|
|
expected := "rego_unsafe_var_error: var in is unsafe (hint: `import future.keywords.in` to import a future keyword)"
|
|
if !strings.Contains(err.Error(), expected) {
|
|
t.Fatalf("Expected error to contain %q but got: %v", expected, err)
|
|
}
|
|
buffer.Reset()
|
|
|
|
// future keywords import
|
|
if err := repl.OneShot(ctx, "import future.keywords"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if len(buffer.Bytes()) != 0 {
|
|
t.Errorf("Expected no output but got: %v", buffer.String())
|
|
return
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "1 in [1,2,3]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := buffer.String()
|
|
expected = "true\n"
|
|
if result != expected {
|
|
t.Errorf("Expected expression to evaluate successfully but got: %v", result)
|
|
return
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
act := buffer.String()
|
|
exp := `package repl
|
|
|
|
import future.keywords
|
|
`
|
|
if act != exp {
|
|
t.Errorf("expected %q, got: %q", exp, act)
|
|
return
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `package foo.bar`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "import future.keywords.in"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if len(buffer.Bytes()) != 0 {
|
|
t.Errorf("Expected no output but got: %v", buffer.String())
|
|
return
|
|
}
|
|
if err := repl.OneShot(ctx, `p = true { 1 in [1,2,3] }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
// ignore "rule p defined" message
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
act = buffer.String()
|
|
exp = `package foo.bar
|
|
|
|
import future.keywords.in
|
|
|
|
p {
|
|
1 in [1, 2, 3]
|
|
}
|
|
`
|
|
if act != exp {
|
|
t.Errorf("expected %q, got: %q", exp, act)
|
|
return
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result = buffer.String()
|
|
expected = "true\n"
|
|
if result != expected {
|
|
t.Errorf("Expected expression to evaluate successfully but got: %v", result)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestEvalPackage(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, `package foo.bar`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `p = true { true }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `package baz.qux`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
err := repl.OneShot(ctx, "p")
|
|
expected := "p is unsafe"
|
|
if err == nil {
|
|
t.Fatalf("Expected OneShot to return error %v but got: %v", expected, err)
|
|
}
|
|
if !strings.Contains(err.Error(), expected) {
|
|
t.Fatalf("Expected unsafe variable error but got: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "import data.foo.bar.p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if buffer.String() != "true\n" {
|
|
t.Errorf("Expected expression to eval successfully but got: %v", buffer.String())
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestMetrics(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "a = {[1,2], [3,4]}"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "metrics"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `[x | a[x]]`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buffer.String(), "timer_rego_query_compile_ns") {
|
|
t.Fatal("Expected output to contain well known metric key but got:", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, `[x | a[x]]`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buffer.String(), "timer_rego_query_compile_ns") {
|
|
t.Fatal("Expected output to contain well known metric key but got:", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "metrics"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `[x | a[x]]`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expected := `[
|
|
[
|
|
1,
|
|
2
|
|
],
|
|
[
|
|
3,
|
|
4
|
|
]
|
|
]
|
|
`
|
|
|
|
if expected != buffer.String() {
|
|
t.Fatalf("Expected output to be exactly:\n%v\n\nGot:\n\n%v\n", expected, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestProfile(t *testing.T) {
|
|
store := newTestStore()
|
|
ctx := context.Background()
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
const numLines = 21
|
|
|
|
mod2 := []byte(`package rbac
|
|
|
|
input = {
|
|
"subject": "bob",
|
|
"resource": "foo123",
|
|
"action": "write",
|
|
}
|
|
bindings = [
|
|
{
|
|
"user": "alice",
|
|
"roles": ["dev", "test"],
|
|
},
|
|
{
|
|
"user": "bob",
|
|
"roles": ["test"],
|
|
},
|
|
]
|
|
|
|
roles = [
|
|
{
|
|
"name": "dev",
|
|
"permissions": [
|
|
{"resource": "foo123", "action": "write"},
|
|
{"resource": "foo123", "action": "read"},
|
|
],
|
|
},
|
|
{
|
|
"name": "test",
|
|
"permissions": [{"resource": "foo123", "action": "read"}],
|
|
},
|
|
]
|
|
|
|
default allow = false
|
|
|
|
allow {
|
|
user_has_role[role_name]
|
|
role_has_permission[role_name]
|
|
}
|
|
|
|
user_has_role[role_name] {
|
|
binding := bindings[_]
|
|
binding.user = input.subject
|
|
role_name := binding.roles[_]
|
|
}
|
|
|
|
role_has_permission[role_name] {
|
|
role := roles[_]
|
|
role_name := role.name
|
|
perm := role.permissions[_]
|
|
perm.resource = input.resource
|
|
perm.action = input.action
|
|
}`)
|
|
|
|
if err := store.UpsertPolicy(ctx, txn, "mod2", mod2); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := store.Commit(ctx, txn); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "profile"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "data.rbac.allow"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
result := buffer.String()
|
|
lines := strings.Split(result, "\n")
|
|
if len(lines) != numLines {
|
|
t.Fatal("Expected 21 lines, got :", len(lines))
|
|
}
|
|
buffer.Reset()
|
|
}
|
|
|
|
func TestStrictBuiltinErrors(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, "1/0"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result := buffer.String()
|
|
|
|
if !strings.Contains(result, "undefined") {
|
|
t.Fatal("expected undefined")
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "strict-builtin-errors"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "1/0"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result = buffer.String()
|
|
|
|
if !strings.Contains(result, "divide by zero") {
|
|
t.Fatal("expected divide by zero error")
|
|
}
|
|
}
|
|
|
|
func TestInstrument(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
|
|
repl := newRepl(store, &buffer)
|
|
|
|
// Turn on instrumentation w/o turning on metrics.
|
|
if err := repl.OneShot(ctx, "instrument"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "true"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result := buffer.String()
|
|
|
|
if !strings.Contains(result, "histogram_eval_op_plug") {
|
|
t.Fatal("Expected plug histogram in output but got:", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
// Turn off instrumentation.
|
|
if err := repl.OneShot(ctx, "instrument"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "true"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result = buffer.String()
|
|
|
|
if strings.Contains(result, "histogram_eval_op_plug") {
|
|
t.Fatal("Expected instrumentation to be turned off but got:", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
// Turn on metrics and then turn on instrumentation.
|
|
if err := repl.OneShot(ctx, "metrics"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "true"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result = buffer.String()
|
|
|
|
if strings.Contains(result, "histogram_eval_op_plug") {
|
|
t.Fatal("Expected instrumentation to be turned off but got:", result)
|
|
}
|
|
|
|
if !strings.Contains(result, "timer_rego_query_eval_ns") {
|
|
t.Fatal("Expected metrics to be turned on but got:", result)
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "instrument"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "true"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
result = buffer.String()
|
|
|
|
if !strings.Contains(result, "histogram_eval_op_plug") {
|
|
t.Fatal("Expected instrumentation to be turned on but got:", result)
|
|
}
|
|
|
|
if !strings.Contains(result, "timer_rego_query_eval_ns") {
|
|
t.Fatal("Expected metrics to be turned on but got:", result)
|
|
}
|
|
|
|
}
|
|
|
|
func TestEvalTrace(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "trace"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `data.a[i].b.c[j] = x; data.a[k].b.c[x] = 1`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expected := strings.TrimSpace(`
|
|
query:1 Enter data.a[i].b.c[j] = x; data.a[k].b.c[x] = 1
|
|
query:1 | Eval data.a[i].b.c[j] = x
|
|
query:1 | Eval data.a[k].b.c[x] = 1
|
|
query:1 | Fail data.a[k].b.c[x] = 1
|
|
query:1 | Redo data.a[i].b.c[j] = x
|
|
query:1 | Eval data.a[k].b.c[x] = 1
|
|
query:1 | Exit data.a[i].b.c[j] = x; data.a[k].b.c[x] = 1
|
|
query:1 Redo data.a[i].b.c[j] = x; data.a[k].b.c[x] = 1
|
|
query:1 | Redo data.a[k].b.c[x] = 1
|
|
query:1 | Redo data.a[i].b.c[j] = x
|
|
query:1 | Eval data.a[k].b.c[x] = 1
|
|
query:1 | Fail data.a[k].b.c[x] = 1
|
|
query:1 | Redo data.a[i].b.c[j] = x
|
|
query:1 | Eval data.a[k].b.c[x] = 1
|
|
query:1 | Fail data.a[k].b.c[x] = 1
|
|
query:1 | Redo data.a[i].b.c[j] = x
|
|
query:1 | Eval data.a[k].b.c[x] = 1
|
|
query:1 | Fail data.a[k].b.c[x] = 1
|
|
query:1 | Redo data.a[i].b.c[j] = x
|
|
query:1 | Eval data.a[k].b.c[x] = 1
|
|
query:1 | Fail data.a[k].b.c[x] = 1
|
|
query:1 | Redo data.a[i].b.c[j] = x
|
|
+---+---+---+---+
|
|
| i | j | k | x |
|
|
+---+---+---+---+
|
|
| 0 | 1 | 1 | 2 |
|
|
+---+---+---+---+`)
|
|
expected += "\n"
|
|
|
|
if expected != buffer.String() {
|
|
t.Fatalf("Expected output to be exactly:\n%v\n\nGot:\n\n%v\n", expected, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestEvalNotes(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newTestStore()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, `p { a = [1,2,3]; a[i] = x; x > 1; trace(sprintf("x = %d", [x])) }`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "notes"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "p"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expected := strings.TrimSpace(`query:1 Enter data.repl.p = _
|
|
query:1 | Enter data.repl.p
|
|
query:1 | | Note "x = 2"
|
|
true`)
|
|
expected += "\n"
|
|
if expected != buffer.String() {
|
|
t.Fatalf("Expected output to be exactly:\n%v\n\nGot:\n\n%v\n", expected, buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestTruncatePrettyOutput(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
repl.prettyLimit = 1000 // crank up limit to test repl command
|
|
if err := repl.OneShot(ctx, "pretty-limit 80"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "data[x]"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
for _, line := range strings.Split(buffer.String(), "\n") {
|
|
// | "repl" | {"version": <elided>... |
|
|
if len(line) > 96 {
|
|
t.Fatalf("Expected len(line) to be < 96 but got:\n\n%v", buffer)
|
|
}
|
|
}
|
|
buffer.Reset()
|
|
if err := repl.OneShot(ctx, "pretty-limit"); err == nil || !strings.Contains(err.Error(), "usage: pretty-limit <n>") {
|
|
t.Fatalf("Expected usage error but got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUnsetPackage(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
|
|
if err := repl.OneShot(ctx, "package a"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `unset-package 5`); err == nil {
|
|
t.Fatalf("Expected package-unset error for bad package but got: %v", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "package a"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "unset-package b"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if buffer.String() != "warning: no matching package\n" {
|
|
t.Fatalf("Expected unset-package warning no matching package but got: %v", buffer.String())
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, `package a`); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, `unset-package b`); err != nil {
|
|
t.Fatalf("Expected unset-package to succeed for input: %v", err)
|
|
}
|
|
|
|
buffer.Reset()
|
|
|
|
if err := repl.OneShot(ctx, "package a"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "unset-package a"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := repl.OneShot(ctx, "show"); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if buffer.String() != "no rules defined\n" {
|
|
t.Fatalf("Expected unset-package to return to default but got: %v", buffer.String())
|
|
}
|
|
}
|
|
|
|
func TestCapabilities(t *testing.T) {
|
|
capabilities := ast.CapabilitiesForThisVersion()
|
|
allowedBuiltins := []*ast.Builtin{}
|
|
for _, builtin := range capabilities.Builtins {
|
|
if builtin.Name != "http.send" {
|
|
allowedBuiltins = append(allowedBuiltins, builtin)
|
|
}
|
|
}
|
|
capabilities.Builtins = allowedBuiltins
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer).WithCapabilities(capabilities)
|
|
if err := repl.OneShot(ctx, `http.send({"url": "http://example.com", "method": "GET"})`); err != nil {
|
|
if !strings.Contains(fmt.Sprintf("%v", err), "undefined function http.send") {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
} else {
|
|
t.Fatalf("Expected error on http.send")
|
|
}
|
|
}
|
|
|
|
func TestTraceArgument(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
var buffer bytes.Buffer
|
|
repl := newRepl(store, &buffer)
|
|
if err := repl.OneShot(ctx, "trace debug"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repl.OneShot(ctx, "show debug"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
output := buffer.String()
|
|
expected := `"explain": "debug"`
|
|
if !strings.Contains(output, expected) {
|
|
t.Fatalf("Expected output to contain %s but got %s", expected, output)
|
|
}
|
|
}
|
|
|
|
func assertREPLText(t *testing.T, buf bytes.Buffer, expected string) {
|
|
t.Helper()
|
|
result := buf.String()
|
|
if result != expected {
|
|
t.Fatalf("Expected:\n%v\n\nString:\n\n%v\nGot:\n%v\n\nString:\n\n%v", []byte(expected), expected, []byte(result), result)
|
|
}
|
|
}
|
|
|
|
func expectOutput(t *testing.T, output string, expected string) {
|
|
t.Helper()
|
|
if output != expected {
|
|
t.Errorf("Repl output: expected %#v but got %#v", expected, output)
|
|
}
|
|
}
|
|
|
|
func newRepl(store storage.Store, buffer *bytes.Buffer) *REPL {
|
|
repl := New(store, "", buffer, "", 0, "")
|
|
return repl
|
|
}
|
|
|
|
func newTestStore() storage.Store {
|
|
input := `
|
|
{
|
|
"a": [
|
|
{
|
|
"b": {
|
|
"c": [true,2,false]
|
|
}
|
|
},
|
|
{
|
|
"b": {
|
|
"c": [false,true,1]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
var data map[string]interface{}
|
|
err := util.UnmarshalJSON([]byte(input), &data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return inmem.NewFromObject(data)
|
|
}
|
|
|
|
func parseJSON(s string) interface{} {
|
|
var v interface{}
|
|
if err := util.UnmarshalJSON([]byte(s), &v); err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
}
|