Files
releases/runtime/server_test.go
T
Torin Sandall 12dad370d5 Add basic REST API support to server mode
- REST APIs

    * CRUDL on policy modules
    * Ad-hoc queries
    * Query and patch base documents
    * Query virtual documents

- Add PolicyStore to manage policy definition/module CRUDL operations.

    * Supports persistence of policy definitons.
    * Serve REST API CRUDL operations.
    * Manage install/uninstall of rules into data store.
    * Manage persistence of policy definitions.

- Misc. refactoring

    * Move storage creation into runtime Init.
    * Make AST types JSON serializable. Tweaked ast.Import to use Term instead
    of Value for the path.
2016-05-13 15:34:02 -07:00

361 lines
8.0 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.
package runtime
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"reflect"
"strings"
"testing"
"github.com/open-policy-agent/opa/ast"
)
var policyDir string
// TestMain creates a temporary direcotry for the server to
// save policies to. The directory name is stored in policyDir
// and is used by the newFixture function.
func TestMain(m *testing.M) {
d, err := ioutil.TempDir("", "server_test")
if err != nil {
panic(err)
}
defer os.RemoveAll(d)
policyDir = d
rc := m.Run()
os.Exit(rc)
}
func TestDataPatchV1(t *testing.T) {
f := newFixture(t)
patch := newReqV1("PATCH", "/data/x", `[{"op": "add", "path": "/", "value": {"a": 1, "b": 2}}]`)
f.server.Router.ServeHTTP(f.recorder, patch)
if f.recorder.Code != 204 {
t.Errorf("Expected success/no-content but got %v", f.recorder)
return
}
get := newReqV1("GET", "/data/x/a", "")
f.reset()
f.server.Router.ServeHTTP(f.recorder, get)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
resp := f.loadResponse().(float64)
exp := float64(1)
if resp != exp {
t.Errorf("Expected %v but got: %v", exp, resp)
}
}
func TestIndexGet(t *testing.T) {
f := newFixture(t)
get, err := http.NewRequest("GET", `/?q=foo = 1`, strings.NewReader(""))
if err != nil {
panic(err)
}
f.server.Router.ServeHTTP(f.recorder, get)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got: %v", f.recorder)
return
}
page := f.recorder.Body.String()
if !strings.Contains(page, "Query result") {
t.Errorf("Expected page to contain 'Query result' but got: %v", page)
return
}
}
func TestPoliciesPutV1(t *testing.T) {
f := newFixture(t)
req := newReqV1("PUT", "/policies/1", testMod)
f.server.Router.ServeHTTP(f.recorder, req)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
policy := f.loadPolicy()
expected := newPolicy("1", testMod)
if !expected.Equal(policy) {
t.Errorf("Expected policies to be equal. Expected:\n\n%v\n\nGot:\n\n%v\n", expected, policy)
}
}
func TestPoliciesPutV1Empty(t *testing.T) {
f := newFixture(t)
req := newReqV1("PUT", "/policies/1", "")
f.server.Router.ServeHTTP(f.recorder, req)
if f.recorder.Code != 400 {
t.Errorf("Expected bad request but got %v", f.recorder)
return
}
}
func TestPoliciesPutV1ParseError(t *testing.T) {
f := newFixture(t)
req := newReqV1("PUT", "/policies/1", `
package a.b.c
p[x] %%^ ;-
`)
f.server.Router.ServeHTTP(f.recorder, req)
if f.recorder.Code != 400 {
t.Errorf("Expected bad request but got %v", f.recorder)
return
}
}
// TODO(tsandall): revisit once safety checks are in place
func testPoliciesPutV1CompileError(t *testing.T) {
f := newFixture(t)
req := newReqV1("PUT", "/policies/1", `
package a.b.c
p[x] :- q[x]
q[x] :- p[x]
`)
f.server.Router.ServeHTTP(f.recorder, req)
if f.recorder.Code != 400 {
t.Errorf("Expected bad request but got %v", f.recorder)
return
}
}
func TestPoliciesListV1(t *testing.T) {
f := newFixture(t)
put := newReqV1("PUT", "/policies/1", testMod)
f.server.Router.ServeHTTP(f.recorder, put)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
f.reset()
list := newReqV1("GET", "/policies", "")
f.server.Router.ServeHTTP(f.recorder, list)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
var policies []*policyV1
err := json.NewDecoder(f.recorder.Body).Decode(&policies)
if err != nil {
t.Errorf("Expected policy list but got error: %v", err)
return
}
expected := []*policyV1{
newPolicy("1", testMod),
}
if len(expected) != len(policies) {
t.Errorf("Expected %d policies but got: %v", len(expected), policies)
return
}
for i := range expected {
if !expected[i].Equal(policies[i]) {
t.Errorf("Expected policies to be equal. Expected:\n\n%v\n\nGot:\n\n%v\n", expected[i], policies[i])
}
}
}
func TestPoliciesGetV1(t *testing.T) {
f := newFixture(t)
put := newReqV1("PUT", "/policies/1", testMod)
f.server.Router.ServeHTTP(f.recorder, put)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
f.reset()
get := newReqV1("GET", "/policies/1", "")
f.server.Router.ServeHTTP(f.recorder, get)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
policy := f.loadPolicy()
expected := newPolicy("1", testMod)
if !expected.Equal(policy) {
t.Errorf("Expected policies to be equal. Expected:\n\n%v\n\nGot:\n\n%v\n", expected, policy)
}
}
func TestPoliciesGetRawV1(t *testing.T) {
f := newFixture(t)
put := newReqV1("PUT", "/policies/1", testMod)
f.server.Router.ServeHTTP(f.recorder, put)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
f.reset()
get := newReqV1("GET", "/policies/1/raw", "")
f.server.Router.ServeHTTP(f.recorder, get)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
raw := f.recorder.Body.String()
if raw != testMod {
t.Errorf("Expected raw string to equal testMod:\n\nExpected:\n\n%v\n\nGot:\n\n%v\n", testMod, raw)
}
}
func TestPoliciesDeleteV1(t *testing.T) {
f := newFixture(t)
put := newReqV1("PUT", "/policies/1", testMod)
f.server.Router.ServeHTTP(f.recorder, put)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
f.reset()
del := newReqV1("DELETE", "/policies/1", "")
f.server.Router.ServeHTTP(f.recorder, del)
if f.recorder.Code != 204 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
f.reset()
get := newReqV1("GET", "/policies/1", "")
f.server.Router.ServeHTTP(f.recorder, get)
if f.recorder.Code != 404 {
t.Errorf("Expected not found but got %v", f.recorder)
return
}
}
func TestQueryV1(t *testing.T) {
f := newFixture(t)
get := newReqV1("GET", `/query?q=a=[1,2,3],a[i]=x`, "")
f.server.Router.ServeHTTP(f.recorder, get)
if f.recorder.Code != 200 {
t.Errorf("Expected success but got %v", f.recorder)
return
}
var expected resultSetV1
err := json.Unmarshal([]byte(`[{"a":[1,2,3],"i":0,"x":1},{"a":[1,2,3],"i":1,"x":2},{"a":[1,2,3],"i":2,"x":3}]`), &expected)
if err != nil {
panic(err)
}
var result resultSetV1
err = json.Unmarshal(f.recorder.Body.Bytes(), &result)
if err != nil {
t.Errorf("Unexpected error while unmarshalling result: %v", err)
return
}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v but got: %v", expected, result)
}
}
const (
testMod = `
package a.b.c
import data.x.y as z
import data.p
q[x] :- p[x], not r[x]
r[x] :- z[x] = 4`
)
type fixture struct {
runtime *Runtime
server *Server
recorder *httptest.ResponseRecorder
t *testing.T
}
func newFixture(t *testing.T) *fixture {
runtime := &Runtime{}
runtime.Init(&Params{Server: true, PolicyDir: policyDir})
server := NewServer(runtime, ":8182", false)
recorder := httptest.NewRecorder()
return &fixture{
runtime: runtime,
server: server,
recorder: recorder,
t: t,
}
}
func (f *fixture) loadPolicy() *policyV1 {
policy := &policyV1{}
err := json.NewDecoder(f.recorder.Body).Decode(policy)
if err != nil {
panic(err)
}
return policy
}
func (f *fixture) loadResponse() interface{} {
var v interface{}
err := json.NewDecoder(f.recorder.Body).Decode(&v)
if err != nil {
panic(err)
}
return v
}
func (f *fixture) reset() {
f.recorder = httptest.NewRecorder()
}
func newPolicy(id, s string) *policyV1 {
compiler := ast.NewCompiler()
parsed := ast.MustParseModule(s)
if compiler.Compile(map[string]*ast.Module{"": parsed}); compiler.Failed() {
panic(compiler.FlattenErrors())
}
mod := compiler.Modules[""]
return &policyV1{ID: id, Module: mod}
}
func newReqV1(method string, path string, body string) *http.Request {
req, err := http.NewRequest(method, "/v1"+path, strings.NewReader(body))
if err != nil {
panic(err)
}
return req
}