func TestCompilerGetRulesExact(t *testing.T) { mods := getCompilerTestModules() // Add incrementally defined rules. mods["mod-incr"] = MustParseModule(` package a.b.c p[1] :- true p[2] :- true `) c := NewCompiler() c.Compile(mods) assertNotFailed(t, c) tests := []struct { note string ref interface{} expected []*Rule }{ {"exact", "data.a.b.c.p", []*Rule{ c.Modules["mod-incr"].Rules[0], c.Modules["mod-incr"].Rules[1], c.Modules["mod1"].Rules[0], }}, {"too short", "data.a", []*Rule{}}, {"too long/not found", "data.a.b.c.p.q", []*Rule{}}, {"outside data", "req.a.b.c.p", []*Rule{}}, } for _, tc := range tests { test.Subtest(t, tc.note, func(t *testing.T) { var ref Ref switch r := tc.ref.(type) { case string: ref = MustParseRef(r) case Ref: ref = r } rules := c.GetRulesExact(ref) if len(rules) != len(tc.expected) { t.Fatalf("Expected exactly %v rules but got: %v", len(tc.expected), rules) } for i := range rules { found := false for j := range tc.expected { if rules[i].Equal(tc.expected[j]) { found = true break } } if !found { t.Fatalf("Expected exactly %v but got: %v", tc.expected, rules) } } }) } }
func runQueryCompilerTest(t *testing.T, note, q, pkg string, imports []string, expected interface{}) { test.Subtest(t, note, func(t *testing.T) { c := NewCompiler() c.Compile(getCompilerTestModules()) assertNotFailed(t, c) qc := c.QueryCompiler() query := MustParseBody(q) var qctx *QueryContext if pkg != "" { pkg := MustParsePackage(pkg) qctx = NewQueryContext(pkg, nil) if len(imports) > 0 { qctx.Imports = MustParseImports(strings.Join(imports, "\n")) } } if qctx != nil { qc.WithContext(qctx) } switch expected := expected.(type) { case string: expectedQuery := MustParseBody(expected) result, err := qc.Compile(query) if err != nil { t.Fatalf("Unexpected error from %v: %v", query, err) } if !expectedQuery.Equal(result) { t.Fatalf("Expected:\n%v\n\nGot:\n%v", expectedQuery, result) } case error: result, err := qc.Compile(query) if err == nil { t.Fatalf("Expected error from %v but got: %v", query, result) } if err.Error() != expected.Error() { t.Fatalf("Expected error %v but got: %v", expected, err) } } }) }
func TestDataV1(t *testing.T) { testMod1 := `package testmod p[x] :- q[x], not r[x] q[x] :- data.x.y[i] = x r[x] :- data.x.z[i] = x import request.req1 import request.req2 as reqx import request.req3.attr1 g :- req1.a[0] = 1, reqx.b[i] = 1 h :- attr1[i] > 1 gt1 :- req1 > 1 arr = [1,2,3,4] undef :- false ` testMod2 := `package testmod p = [1,2,3,4] q = {"a": 1, "b": 2} ` tests := []struct { note string reqs []tr }{ {"add root", []tr{ tr{"PATCH", "/data/x", `[{"op": "add", "path": "/", "value": {"a": 1}}]`, 204, ""}, tr{"GET", "/data/x/a", "", 200, "1"}, }}, {"append array", []tr{ tr{"PATCH", "/data/x", `[{"op": "add", "path": "/", "value": []}]`, 204, ""}, tr{"PATCH", "/data/x", `[{"op": "add", "path": "-", "value": {"a": 1}}]`, 204, ""}, tr{"PATCH", "/data/x", `[{"op": "add", "path": "-", "value": {"a": 2}}]`, 204, ""}, tr{"GET", "/data/x/0/a", "", 200, "1"}, tr{"GET", "/data/x/1/a", "", 200, "2"}, }}, {"append array one-shot", []tr{ tr{"PATCH", "/data/x", `[ {"op": "add", "path": "/", "value": []}, {"op": "add", "path": "-", "value": {"a": 1}}, {"op": "add", "path": "-", "value": {"a": 2}} ]`, 204, ""}, tr{"GET", "/data/x/1/a", "", 200, "2"}, }}, {"insert array", []tr{ tr{"PATCH", "/data/x", `[{"op": "add", "path": "/", "value": { "y": [ {"z": [1,2,3]}, {"z": [4,5,6]} ] }}]`, 204, ""}, tr{"GET", "/data/x/y/1/z/2", "", 200, "6"}, tr{"PATCH", "/data/x/y/1", `[{"op": "add", "path": "/z/1", "value": 100}]`, 204, ""}, tr{"GET", "/data/x/y/1/z", "", 200, "[4, 100, 5, 6]"}, }}, {"patch root", []tr{ tr{"PATCH", "/data", `[ {"op": "add", "path": "/", "value": {"a": 1, "b": 2} } ]`, 204, ""}, tr{"GET", "/data", "", 200, `{"a": 1, "b": 2}`}, }}, {"patch invalid", []tr{ tr{"PATCH", "/data", `[ { "op": "remove", "path": "/" } ]`, 400, ""}, }}, {"put root", []tr{ tr{"PUT", "/data", `{"foo": [1,2,3]}`, 204, ""}, tr{"GET", "/data", "", 200, `{"foo": [1,2,3]}`}, }}, {"put deep makedir", []tr{ tr{"PUT", "/data/a/b/c/d", `1`, 204, ""}, tr{"GET", "/data/a/b/c", "", 200, `{"d": 1}`}, }}, {"put deep makedir partial", []tr{ tr{"PUT", "/data/a/b", `{}`, 204, ""}, tr{"PUT", "/data/a/b/c/d", `0`, 204, ""}, tr{"GET", "/data/a/b/c", "", 200, `{"d": 0}`}, }}, {"put exists overwrite", []tr{ tr{"PUT", "/data/a/b/c", `"hello"`, 204, ""}, tr{"PUT", "/data/a/b", `"goodbye"`, 204, ""}, tr{"GET", "/data/a", "", 200, `{"b": "goodbye"}`}, }}, {"put base write conflict", []tr{ tr{"PUT", "/data/a/b", `[1,2,3,4]`, 204, ""}, tr{"PUT", "/data/a/b/c/d", "0", 404, `{ "Code": 404, "Message": "write conflict: /a/b" }`}, }}, {"put virtual write conflict", []tr{ tr{"PUT", "/policies/test", testMod2, 200, ""}, tr{"PUT", "/data/testmod/q/x", "0", 404, `{ "Code": 404, "Message": "write conflict: /testmod/q" }`}, }}, {"get virtual", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"PATCH", "/data/x", `[{"op": "add", "path": "/", "value": {"y": [1,2,3,4], "z": [3,4,5,6]}}]`, 204, ""}, tr{"GET", "/data/testmod/p", "", 200, "[1,2]"}, }}, {"patch virtual error", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"PATCH", "/data/testmod/p", `[{"op": "add", "path": "-", "value": 1}]`, 404, `{ "Code": 404, "Message": "write conflict: /testmod/p" }`}, }}, {"get with request", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"GET", "/data/testmod/g?request=req1%3A%7B%22a%22%3A%5B1%5D%7D&request=req2%3A%7B%22b%22%3A%5B0%2C1%5D%7D", "", 200, "true"}, }}, {"get with request (missing request value)", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"GET", "/data/testmod/g?request=req1%3A%7B%22a%22%3A%5B1%5D%7D", "", 404, ""}, }}, {"get with request (namespaced)", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"GET", "/data/testmod/h?request=req3.attr1%3A%5B4%2C3%2C2%2C1%5D", "", 200, `true`}, }}, {"get with request (non-ground ref)", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"GET", "/data/testmod/gt1?request=req1:data.testmod.arr[i]", "", 200, `[[true, {"i": 1}], [true, {"i": 2}], [true, {"i": 3}]]`}, }}, {"get with request (root)", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"GET", `/data/testmod/gt1?request=:{"req1":2}`, "", 200, `true`}, }}, {"get with request (root-2)", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"GET", `/data/testmod/gt1?request={"req1":2}`, "", 200, `true`}, }}, {"get with request (root+non-ground)", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"GET", `/data/testmod/gt1?request={"req1":data.testmod.arr[i]}`, "", 200, `[[true, {"i": 1}], [true, {"i": 2}], [true, {"i": 3}]]`}, }}, {"get with request (bad format)", []tr{ tr{"GET", `/data/deadbeef?request="foo`, "", 400, `{ "Code": 400, "Message": "request parameter format is [[<path>]:]<value> where <path> is either var or ref" }`}, }}, {"get with request (path error)", []tr{ tr{"GET", `/data/deadbeef?request="foo:1`, "", 400, `{ "Code": 400, "Message": "request parameter format is [[<path>]:]<value> where <path> is either var or ref" }`}, }}, {"get undefined", []tr{ tr{"PUT", "/policies/test", testMod1, 200, ""}, tr{"GET", "/data/testmod/undef", "", 404, ""}, tr{"GET", "/data/does/not/exist", "", 404, ""}, }}, {"get root", []tr{ tr{"PUT", "/policies/test", testMod2, 200, ""}, tr{"PATCH", "/data/x", `[{"op": "add", "path": "/", "value": [1,2,3,4]}]`, 204, ""}, tr{"GET", "/data", "", 200, `{"testmod": {"p": [1,2,3,4], "q": {"a":1, "b": 2}}, "x": [1,2,3,4]}`}, }}, {"query wildcards omitted", []tr{ tr{"PATCH", "/data/x", `[{"op": "add", "path": "/", "value": [1,2,3,4]}]`, 204, ""}, tr{"GET", "/query?q=data.x[_]%20=%20x", "", 200, `[{"x": 1}, {"x": 2}, {"x": 3}, {"x": 4}]`}, }}, {"query compiler error", []tr{ tr{"GET", "/query?q=x", "", 400, ""}, // Subsequent query should not fail. tr{"GET", "/query?q=x=1", "", 200, `[{"x": 1}]`}, }}, } for _, tc := range tests { test.Subtest(t, tc.note, func(t *testing.T) { executeRequests(t, tc.reqs) }) } }