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 }