github.com/rajeev159/opa@v0.45.0/topdown/print_test.go (about)

     1  package topdown
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/open-policy-agent/opa/ast"
    11  	"github.com/open-policy-agent/opa/topdown/print"
    12  )
    13  
    14  func TestTopDownPrint(t *testing.T) {
    15  
    16  	cases := []struct {
    17  		note   string
    18  		module string
    19  		exp    string
    20  	}{
    21  		{
    22  			note: "empty",
    23  			module: `
    24  				package test
    25  
    26  				p { print() }
    27  			`,
    28  			exp: "\n",
    29  		},
    30  		{
    31  			note: "strings",
    32  			module: `
    33  				package test
    34  
    35  				p {
    36  					x := "world"
    37  					print("hello", x)
    38  				}
    39  			`,
    40  			exp: "hello world\n",
    41  		},
    42  		{
    43  			note: "collections",
    44  			module: `
    45  				package test
    46  
    47  				xs := [1,2]
    48  
    49  				p {
    50  					print("the value of xs is:", xs)
    51  				}
    52  			`,
    53  			exp: "the value of xs is: [1, 2]\n",
    54  		},
    55  		{
    56  			note: "undefined - does not affect rule evaluation and output contains marker",
    57  			module: `
    58  				package test
    59  
    60  				p {
    61  					print("the value of foo is:", input.foo)
    62  				}
    63  			`,
    64  			exp: "the value of foo is: <undefined>\n",
    65  		},
    66  		{
    67  			note: "undefined nested term does not affect rule evaluation and output contains marker",
    68  			module: `
    69  				package test
    70  
    71  				p {
    72  					print("the value of foo is:", [input.foo])
    73  				}
    74  			`,
    75  			exp: "the value of foo is: <undefined>\n",
    76  		},
    77  		{
    78  			note: "built-in error as undefined",
    79  			module: `
    80  				package test
    81  
    82  				p {
    83  					print("div by zero:", 1/0) # divide by zero will be undefined unless strict-builtin-errors are enabled
    84  				}
    85  			`,
    86  			exp: "div by zero: <undefined>\n",
    87  		},
    88  		{
    89  			note: "cross-product",
    90  			module: `
    91  				package test
    92  
    93  				xs := {1}
    94  				ys := {"a"}
    95  
    96  				p {
    97  					print(walk(xs), walk(ys))
    98  				}
    99  			`,
   100  			exp: `[[], {1}] [[], {"a"}]
   101  [[], {1}] [["a"], "a"]
   102  [[1], 1] [[], {"a"}]
   103  [[1], 1] [["a"], "a"]
   104  `,
   105  		},
   106  	}
   107  
   108  	for _, tc := range cases {
   109  		t.Run(tc.note, func(t *testing.T) {
   110  
   111  			c := ast.MustCompileModulesWithOpts(map[string]string{"test.rego": tc.module}, ast.CompileOpts{EnablePrintStatements: true})
   112  			buf := bytes.NewBuffer(nil)
   113  			q := NewQuery(ast.MustParseBody("data.test.p = x")).
   114  				WithPrintHook(NewPrintHook(buf)).
   115  				WithCompiler(c)
   116  
   117  			qrs, err := q.Run(context.Background())
   118  			if err != nil {
   119  				t.Fatal(err)
   120  			}
   121  
   122  			if buf.String() != tc.exp {
   123  				t.Fatalf("expected: %q but got: %q", tc.exp, buf.String())
   124  			}
   125  
   126  			exp := ast.MustParseTerm(`{{x: true}}`)
   127  
   128  			if !queryResultSetToTerm(qrs).Equal(exp) {
   129  				t.Fatal("expected:", exp, "got:", qrs)
   130  			}
   131  		})
   132  	}
   133  }
   134  
   135  func TestTopDownPrintInternalError(t *testing.T) {
   136  
   137  	buf := bytes.NewBuffer(nil)
   138  
   139  	q := NewQuery(ast.MustParseBody("internal.print([1])")).WithPrintHook(NewPrintHook(buf))
   140  
   141  	_, err := q.Run(context.Background())
   142  	if err == nil {
   143  		t.Fatal("expected error")
   144  	}
   145  
   146  	asTopDownErr, ok := err.(*Error)
   147  	if !ok {
   148  		t.Fatal("expected topdown error but got:", err)
   149  	} else if asTopDownErr.Code != InternalErr || asTopDownErr.Message != "illegal argument type: number" {
   150  		t.Fatal("unexpected code or reason:", err)
   151  	}
   152  }
   153  
   154  func TestTopDownPrintHookNotSupplied(t *testing.T) {
   155  
   156  	// NOTE(tsandall): The built-in function implementation expects all inputs
   157  	// to be _sets_, even scalar values are wrapped. This expectation comes from
   158  	// the fact that the compiler rewrites all of the operands by wrapping them
   159  	// in set comprehensions to avoid short-circuiting on undefined.
   160  	q := NewQuery(ast.MustParseBody(`x = 1; internal.print({1})`))
   161  
   162  	qrs, err := q.Run(context.Background())
   163  	if err != nil {
   164  		t.Fatal(err)
   165  	}
   166  
   167  	result := queryResultSetToTerm(qrs)
   168  	exp := ast.MustParseTerm(`{{x: 1}}`)
   169  
   170  	if result.Value.Compare(exp.Value) != 0 {
   171  		t.Fatal("expected:", exp, "but got:", result)
   172  	}
   173  }
   174  
   175  func TestTopDownPrintWithStrictBuiltinErrors(t *testing.T) {
   176  
   177  	buf := bytes.NewBuffer(nil)
   178  
   179  	// NOTE(tsandall): See comment above about wrapping operands in sets.
   180  	q := NewQuery(ast.MustParseBody(`x = {1 | div(1, 0, y)}; internal.print([{"the value of 1/0 is:"}, x])`)).
   181  		WithPrintHook(NewPrintHook(buf)).
   182  		WithStrictBuiltinErrors(true).
   183  		WithCompiler(ast.NewCompiler())
   184  
   185  	_, err := q.Run(context.Background())
   186  	if err == nil {
   187  		t.Fatal("expected error")
   188  	}
   189  
   190  	asTopDownErr, ok := err.(*Error)
   191  	if !ok {
   192  		t.Fatal("expected topdown error but got:", err)
   193  	} else if asTopDownErr.Code != BuiltinErr || asTopDownErr.Message != "div: divide by zero" {
   194  		t.Fatal("unexpected code or reason:", err)
   195  	}
   196  
   197  	exp := "the value of 1/0 is: <undefined>\n"
   198  
   199  	if buf.String() != exp {
   200  		t.Fatalf("expected: %q but got: %q", exp, buf.String())
   201  	}
   202  
   203  }
   204  
   205  type erroringPrintHook struct{}
   206  
   207  func (erroringPrintHook) Print(print.Context, string) error {
   208  	return errors.New("print hook error")
   209  }
   210  
   211  func TestTopDownPrintHookErrorPropagation(t *testing.T) {
   212  
   213  	// NOTE(tsandall): See comment above about wrapping operands in sets.
   214  	q := NewQuery(ast.MustParseBody(`internal.print([{"some message"}])`)).
   215  		WithPrintHook(erroringPrintHook{}).
   216  		WithStrictBuiltinErrors(true).
   217  		WithCompiler(ast.NewCompiler())
   218  
   219  	_, err := q.Run(context.Background())
   220  	if err == nil {
   221  		t.Fatal("expected error")
   222  	} else if !strings.Contains(err.Error(), "print hook error") {
   223  		t.Fatal("expected print hook error but got:", err)
   224  	}
   225  
   226  }