Files
releases/plugins/logs/plugin_test.go
T
Patrick East 346aa964e8 Add support for multiple bundles
This change brings in support for multiple bundles to be downloaded
and activated OPA.

This is enabled by using the new config option `bundles` to define
the bundles, and deprecates the older `bundle` option.

The new `bundles` keyword and structure is propagated through to the
decision logs, status API, provenance, stored manifests, etc. Check
out the doc changes for all the updated structures.

That being said any existing configuration using `bundle` will *not*
see the new structure, everything is intended to be backwards
compatible (almost to a fault).

Fixes: #721

Signed-off-by: Patrick East <east.patrick@gmail.com>
2019-07-31 03:43:38 -04:00

1024 lines
23 KiB
Go

// Copyright 2018 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 logs
import (
"compress/gzip"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"reflect"
"testing"
"time"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/metrics"
"github.com/open-policy-agent/opa/plugins"
"github.com/open-policy-agent/opa/server"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
"github.com/open-policy-agent/opa/util"
"github.com/open-policy-agent/opa/version"
)
func TestMain(m *testing.M) {
// call flag.Parse() here if TestMain uses flags
setVersion("XY.Z")
os.Exit(m.Run())
}
type testPlugin struct {
events []EventV1
}
func (p *testPlugin) Start(context.Context) error {
return nil
}
func (p *testPlugin) Stop(context.Context) {
}
func (p *testPlugin) Reconfigure(context.Context, interface{}) {
}
func (p *testPlugin) Log(_ context.Context, event EventV1) error {
p.events = append(p.events, event)
return nil
}
func TestPluginCustomBackend(t *testing.T) {
ctx := context.Background()
manager, _ := plugins.New(nil, "test-instance-id", inmem.New())
backend := &testPlugin{}
manager.Register("test_plugin", backend)
config, err := ParseConfig([]byte(`{"plugin": "test_plugin"}`), nil, []string{"test_plugin"})
if err != nil {
t.Fatal(err)
}
plugin := New(config, manager)
plugin.Log(ctx, &server.Info{Revision: "A"})
plugin.Log(ctx, &server.Info{Revision: "B"})
if len(backend.events) != 2 || backend.events[0].Revision != "A" || backend.events[1].Revision != "B" {
t.Fatal("Unexpected events:", backend.events)
}
// Server events with only `Revision` should not include bundles in the EventV1 struct
for _, e := range backend.events {
if len(e.Bundles) > 0 {
t.Errorf("Unexpected `bundles` in event")
}
}
}
func TestPluginSingleBundle(t *testing.T) {
ctx := context.Background()
manager, _ := plugins.New(nil, "test-instance-id", inmem.New())
backend := &testPlugin{}
manager.Register("test_plugin", backend)
config, err := ParseConfig([]byte(`{"plugin": "test_plugin"}`), nil, []string{"test_plugin"})
if err != nil {
t.Fatal(err)
}
plugin := New(config, manager)
plugin.Log(ctx, &server.Info{Bundles: map[string]server.BundleInfo{"b1": {Revision: "A"}}})
// Server events with `Bundles` should *not* have `Revision` set
if len(backend.events) != 1 {
t.Fatalf("Unexpected number of events: %v", backend.events)
}
if backend.events[0].Revision != "" || backend.events[0].Bundles["b1"].Revision != "A" {
t.Fatal("Unexpected events: ", backend.events)
}
}
func TestPluginMultiBundle(t *testing.T) {
}
func TestPluginErrorNoResult(t *testing.T) {
ctx := context.Background()
manager, _ := plugins.New(nil, "test-instance-id", inmem.New())
backend := &testPlugin{}
manager.Register("test_plugin", backend)
config, err := ParseConfig([]byte(`{"plugin": "test_plugin"}`), nil, []string{"test_plugin"})
if err != nil {
t.Fatal(err)
}
plugin := New(config, manager)
plugin.Log(ctx, &server.Info{Error: fmt.Errorf("some error")})
plugin.Log(ctx, &server.Info{Error: ast.Errors{&ast.Error{
Code: "some_error",
}}})
if len(backend.events) != 2 || backend.events[0].Error == nil || backend.events[1].Error == nil {
t.Fatal("Unexpected events:", backend.events)
}
}
func TestPluginQueriesAndPaths(t *testing.T) {
ctx := context.Background()
manager, _ := plugins.New(nil, "test-instance-id", inmem.New())
backend := &testPlugin{}
manager.Register("test_plugin", backend)
config, err := ParseConfig([]byte(`{"plugin": "test_plugin"}`), nil, []string{"test_plugin"})
if err != nil {
t.Fatal(err)
}
plugin := New(config, manager)
plugin.Log(ctx, &server.Info{Path: "data.foo"})
plugin.Log(ctx, &server.Info{Path: "data.foo.bar"})
plugin.Log(ctx, &server.Info{Query: "a = data.foo"})
exp := []struct {
query string
path string
}{
// TODO(tsandall): we need to fix how POST /v1/data (and
// friends) are represented here. Currently we can't tell the
// difference between /v1/data and /v1/data/data. The decision
// log event paths should be slash prefixed to avoid ambiguity.
// {path: "data"},
{path: "foo"},
{path: "foo/bar"},
{query: "a = data.foo"},
}
if len(exp) != len(backend.events) {
t.Fatalf("Expected %d events but got %v", len(exp), len(backend.events))
}
for i, e := range exp {
if e.query != backend.events[i].Query || e.path != backend.events[i].Path {
t.Fatalf("Unexpected event %d, want %v but got %v", i, e, backend.events[i])
}
}
}
func TestPluginStartSameInput(t *testing.T) {
ctx := context.Background()
fixture := newTestFixture(t)
defer fixture.server.stop()
fixture.server.ch = make(chan []EventV1, 4)
var result interface{} = false
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
if err != nil {
panic(err)
}
testMetrics := getWellKnownMetrics()
var input interface{} = map[string]interface{}{"method": "GET"}
for i := 0; i < 400; i++ {
fixture.plugin.Log(ctx, &server.Info{
Revision: fmt.Sprint(i),
DecisionID: fmt.Sprint(i),
Path: "data.tda.bar",
Input: &input,
Results: &result,
RemoteAddr: "test",
Timestamp: ts,
Metrics: testMetrics,
})
}
_, err = fixture.plugin.oneShot(ctx)
if err != nil {
t.Fatal(err)
}
chunk1 := <-fixture.server.ch
chunk2 := <-fixture.server.ch
chunk3 := <-fixture.server.ch
chunk4 := <-fixture.server.ch
expLen1 := 122
expLen2 := 121
expLen3 := 121
expLen4 := 36
if len(chunk1) != expLen1 || len(chunk2) != expLen2 || len(chunk3) != expLen3 || len(chunk4) != expLen4 {
t.Fatalf("Expected chunk lens %v, %v, %v and %v but got: %v, %v, %v and %v", expLen1, expLen2, expLen3, expLen4, len(chunk1), len(chunk2), len(chunk3), len(chunk4))
}
var expInput interface{} = map[string]interface{}{"method": "GET"}
msAsFloat64 := map[string]interface{}{}
for k, v := range testMetrics.All() {
msAsFloat64[k] = float64(v.(uint64))
}
exp := EventV1{
Labels: map[string]string{
"id": "test-instance-id",
"app": "example-app",
"version": getVersion(),
},
Revision: "399",
DecisionID: "399",
Path: "tda/bar",
Input: &expInput,
Result: &result,
RequestedBy: "test",
Timestamp: ts,
Metrics: msAsFloat64,
}
if !reflect.DeepEqual(chunk4[expLen4-1], exp) {
t.Fatalf("Expected %+v but got %+v", exp, chunk4[expLen4-1])
}
}
func TestPluginStartChangingInputValues(t *testing.T) {
ctx := context.Background()
fixture := newTestFixture(t)
defer fixture.server.stop()
fixture.server.ch = make(chan []EventV1, 4)
var result interface{} = false
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
if err != nil {
panic(err)
}
var input interface{}
for i := 0; i < 400; i++ {
input = map[string]interface{}{"method": getValueForMethod(i), "path": getValueForPath(i), "user": getValueForUser(i)}
fixture.plugin.Log(ctx, &server.Info{
Revision: fmt.Sprint(i),
DecisionID: fmt.Sprint(i),
Path: "data.foo.bar",
Input: &input,
Results: &result,
RemoteAddr: "test",
Timestamp: ts,
})
}
_, err = fixture.plugin.oneShot(ctx)
if err != nil {
t.Fatal(err)
}
chunk1 := <-fixture.server.ch
chunk2 := <-fixture.server.ch
chunk3 := <-fixture.server.ch
chunk4 := <-fixture.server.ch
expLen1 := 124
expLen2 := 123
expLen3 := 123
expLen4 := 30
if len(chunk1) != expLen1 || len(chunk2) != expLen2 || len((chunk3)) != expLen3 || len(chunk4) != expLen4 {
t.Fatalf("Expected chunk lens %v, %v, %v and %v but got: %v, %v, %v and %v", expLen1, expLen2, expLen3, expLen4, len(chunk1), len(chunk2), len(chunk3), len(chunk4))
}
var expInput interface{} = input
exp := EventV1{
Labels: map[string]string{
"id": "test-instance-id",
"app": "example-app",
"version": getVersion(),
},
Revision: "399",
DecisionID: "399",
Path: "foo/bar",
Input: &expInput,
Result: &result,
RequestedBy: "test",
Timestamp: ts,
}
if !reflect.DeepEqual(chunk4[expLen4-1], exp) {
t.Fatalf("Expected %+v but got %+v", exp, chunk4[expLen4-1])
}
}
func TestPluginStartChangingInputKeysAndValues(t *testing.T) {
ctx := context.Background()
fixture := newTestFixture(t)
defer fixture.server.stop()
fixture.server.ch = make(chan []EventV1, 5)
var result interface{} = false
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
if err != nil {
panic(err)
}
var input interface{}
for i := 0; i < 250; i++ {
input = generateInputMap(i)
fixture.plugin.Log(ctx, &server.Info{
Revision: fmt.Sprint(i),
DecisionID: fmt.Sprint(i),
Path: "data.foo.bar",
Input: &input,
Results: &result,
RemoteAddr: "test",
Timestamp: ts,
})
}
_, err = fixture.plugin.oneShot(ctx)
if err != nil {
t.Fatal(err)
}
<-fixture.server.ch
chunk2 := <-fixture.server.ch
var expInput interface{} = input
exp := EventV1{
Labels: map[string]string{
"id": "test-instance-id",
"app": "example-app",
"version": getVersion(),
},
Revision: "249",
DecisionID: "249",
Path: "foo/bar",
Input: &expInput,
Result: &result,
RequestedBy: "test",
Timestamp: ts,
}
if !reflect.DeepEqual(chunk2[len(chunk2)-1], exp) {
t.Fatalf("Expected %+v but got %+v", exp, chunk2[len(chunk2)-1])
}
}
func TestPluginRequeue(t *testing.T) {
ctx := context.Background()
fixture := newTestFixture(t)
defer fixture.server.stop()
fixture.server.ch = make(chan []EventV1, 1)
var input interface{} = map[string]interface{}{"method": "GET"}
var result1 interface{} = false
fixture.plugin.Log(ctx, &server.Info{
DecisionID: "abc",
Path: "data.foo.bar",
Input: &input,
Results: &result1,
RemoteAddr: "test",
Timestamp: time.Now().UTC(),
})
fixture.server.expCode = 500
_, err := fixture.plugin.oneShot(ctx)
if err == nil {
t.Fatal("Expected error")
}
events1 := <-fixture.server.ch
fixture.server.expCode = 200
_, err = fixture.plugin.oneShot(ctx)
if err != nil {
t.Fatal(err)
}
events2 := <-fixture.server.ch
if !reflect.DeepEqual(events1, events2) {
t.Fatalf("Expected %v but got: %v", events1, events2)
}
uploaded, err := fixture.plugin.oneShot(ctx)
if uploaded || err != nil {
t.Fatalf("Unexpected error or upload, err: %v", err)
}
}
func TestPluginReconfigure(t *testing.T) {
ctx := context.Background()
fixture := newTestFixture(t)
defer fixture.server.stop()
if err := fixture.plugin.Start(ctx); err != nil {
t.Fatal(err)
}
minDelay := 2
maxDelay := 3
pluginConfig := []byte(fmt.Sprintf(`{
"service": "example",
"reporting": {
"min_delay_seconds": %v,
"max_delay_seconds": %v
}
}`, minDelay, maxDelay))
config, _ := ParseConfig(pluginConfig, fixture.manager.Services(), nil)
fixture.plugin.Reconfigure(ctx, config)
fixture.plugin.Stop(ctx)
actualMin := time.Duration(*fixture.plugin.config.Reporting.MinDelaySeconds) / time.Nanosecond
expectedMin := time.Duration(minDelay) * time.Second
if actualMin != expectedMin {
t.Fatalf("Expected minimum polling interval: %v but got %v", expectedMin, actualMin)
}
actualMax := time.Duration(*fixture.plugin.config.Reporting.MaxDelaySeconds) / time.Nanosecond
expectedMax := time.Duration(maxDelay) * time.Second
if actualMax != expectedMax {
t.Fatalf("Expected maximum polling interval: %v but got %v", expectedMax, actualMax)
}
}
func TestPluginMasking(t *testing.T) {
// Setup masking fixture. Populate store with simple masking policy.
ctx := context.Background()
store := inmem.New()
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
if err := store.UpsertPolicy(ctx, txn, "test.rego", []byte(`
package system.log
mask["/input/password"] {
input.input.is_sensitive
}
`)); err != nil {
return err
}
return nil
})
if err != nil {
t.Fatal(err)
}
// Create and start manager. Start is required so that stored policies
// get compiled and made available to the plugin.
manager, err := plugins.New(nil, "test", store)
if err != nil {
t.Fatal(err)
} else if err := manager.Start(ctx); err != nil {
t.Fatal(err)
}
// Instantiate the plugin.
cfg := &Config{Service: "svc"}
cfg.validateAndInjectDefaults([]string{"svc"}, nil)
plugin := New(cfg, manager)
if err := plugin.Start(ctx); err != nil {
t.Fatal(err)
}
// Test input that requires masking.
var input interface{} = map[string]interface{}{
"is_sensitive": true,
"password": "secret",
}
event := &EventV1{
Input: &input,
}
if err := plugin.maskEvent(ctx, nil, event); err != nil {
t.Fatal(err)
}
var exp interface{} = map[string]interface{}{
"is_sensitive": true,
}
if !reflect.DeepEqual(exp, *event.Input) {
t.Fatalf("Expected %v but got %v:", exp, *event.Input)
}
expErased := []string{"/input/password"}
if !reflect.DeepEqual(expErased, event.Erased) {
t.Fatalf("Expected %v but got %v:", expErased, event.Erased)
}
// Test input that DOES NOT require masking.
input = map[string]interface{}{
"password": "secret", // is_sensitive not set.
}
event = &EventV1{
Input: &input,
}
if err := plugin.maskEvent(ctx, nil, event); err != nil {
t.Fatal(err)
}
exp = map[string]interface{}{
"password": "secret",
}
if !reflect.DeepEqual(exp, *event.Input) {
t.Fatalf("Expected %v but got %v:", exp, *event.Input)
}
if len(event.Erased) != 0 {
t.Fatalf("Expected empty set but got %v", event.Erased)
}
// Update policy to mask all of input and exercise.
err = storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
if err := store.UpsertPolicy(ctx, txn, "test.rego", []byte(`
package system.log
mask["/input"]
`)); err != nil {
return err
}
return nil
})
if err != nil {
t.Fatal(err)
}
event = &EventV1{
Input: &input,
}
if err := plugin.maskEvent(ctx, nil, event); err != nil {
t.Fatal(err)
}
if event.Input != nil {
t.Fatalf("Expected input to be nil but got: %v", *event.Input)
}
// Reconfigure and ensure that mask is invalidated.
maskDecision := "dead/beef"
newConfig := &Config{Service: "svc", MaskDecision: &maskDecision}
if err := newConfig.validateAndInjectDefaults([]string{"svc"}, nil); err != nil {
t.Fatal(err)
}
plugin.Reconfigure(ctx, newConfig)
input = map[string]interface{}{
"password": "secret",
"is_sensitive": true,
}
event = &EventV1{
Input: &input,
}
if err := plugin.maskEvent(ctx, nil, event); err != nil {
t.Fatal(err)
}
exp = map[string]interface{}{
"password": "secret",
"is_sensitive": true,
}
if !reflect.DeepEqual(exp, input) {
t.Fatalf("Expected %v but got modified input %v", exp, input)
}
}
const largeEvent = `{
"_id": "15596749567705615560",
"decision_id": "0e67fda0-170b-454d-9f5e-29691073f97e",
"input": {
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"request": {
"kind": {
"group": "",
"kind": "Pod",
"version": "v1"
},
"namespace": "demo",
"object": {
"metadata": {
"creationTimestamp": "2019-06-04T19:02:35Z",
"labels": {
"run": "nginx"
},
"name": "nginx",
"namespace": "demo",
"uid": "507e4c3c-86fb-11e9-b289-42010a8000b2"
},
"spec": {
"containers": [
{
"image": "nginx",
"imagePullPolicy": "Always",
"name": "nginx",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
"name": "default-token-5vjbc",
"readOnly": true
}
]
}
],
"dnsPolicy": "ClusterFirst",
"priority": 0,
"restartPolicy": "Never",
"schedulerName": "default-scheduler",
"securityContext": {},
"serviceAccount": "default",
"serviceAccountName": "default",
"terminationGracePeriodSeconds": 30,
"tolerations": [
{
"effect": "NoExecute",
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"tolerationSeconds": 300
},
{
"effect": "NoExecute",
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"tolerationSeconds": 300
}
],
"volumes": [
{
"name": "default-token-5vjbc",
"secret": {
"secretName": "default-token-5vjbc"
}
}
]
},
"status": {
"phase": "Pending",
"qosClass": "BestEffort"
}
},
"oldObject": null,
"operation": "CREATE",
"resource": {
"group": "",
"resource": "pods",
"version": "v1"
},
"userInfo": {
"groups": [
"system:serviceaccounts",
"system:serviceaccounts:opa-system",
"system:authenticated"
],
"username": "system:serviceaccount:opa-system:default"
}
}
},
"labels": {
"id": "462a43bd-6a5f-4530-9386-30b0f4e0c8af",
"policy-type": "kubernetes/admission_control",
"system-type": "kubernetes",
"version": "0.10.5"
},
"metrics": {
"timer_rego_module_compile_ns": 222,
"timer_rego_module_parse_ns": 313,
"timer_rego_query_compile_ns": 121360,
"timer_rego_query_eval_ns": 923279,
"timer_rego_query_parse_ns": 287152,
"timer_server_handler_ns": 2563846
},
"path": "admission_control/main",
"requested_by": "10.52.0.1:53848",
"result": {
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"response": {
"allowed": false,
"status": {
"reason": "Resource Pod/demo/nginx includes container image 'nginx' from prohibited registry"
}
}
},
"revision": "jafsdkjfhaslkdfjlaksdjflaksjdflkajsdlkfjasldkfjlaksdjflkasdjflkasjdflkajsdflkjasdklfjalsdjf",
"timestamp": "2019-06-04T19:02:35.692Z"
}`
func BenchmarkMaskingNop(b *testing.B) {
ctx := context.Background()
store := inmem.New()
manager, err := plugins.New(nil, "test", store)
if err != nil {
b.Fatal(err)
} else if err := manager.Start(ctx); err != nil {
b.Fatal(err)
}
cfg := &Config{Service: "svc"}
cfg.validateAndInjectDefaults([]string{"svc"}, nil)
plugin := New(cfg, manager)
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
var event EventV1
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
b.Fatal(err)
}
b.StartTimer()
if err := plugin.maskEvent(ctx, nil, &event); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkMaskingErase(b *testing.B) {
ctx := context.Background()
store := inmem.New()
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
if err := store.UpsertPolicy(ctx, txn, "test.rego", []byte(`
package system.log
mask["/input"] {
input.input.request.kind.kind == "Pod"
}
`)); err != nil {
return err
}
return nil
})
if err != nil {
b.Fatal(err)
}
manager, err := plugins.New(nil, "test", store)
if err != nil {
b.Fatal(err)
} else if err := manager.Start(ctx); err != nil {
b.Fatal(err)
}
cfg := &Config{Service: "svc"}
cfg.validateAndInjectDefaults([]string{"svc"}, nil)
plugin := New(cfg, manager)
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
var event EventV1
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
b.Fatal(err)
}
b.StartTimer()
if err := plugin.maskEvent(ctx, nil, &event); err != nil {
b.Fatal(err)
}
if event.Input != nil {
b.Fatal("Expected input to be erased")
}
}
}
type testFixture struct {
manager *plugins.Manager
plugin *Plugin
server *testServer
}
func newTestFixture(t *testing.T) testFixture {
ts := testServer{
t: t,
expCode: 200,
}
ts.start()
managerConfig := []byte(fmt.Sprintf(`{
"labels": {
"app": "example-app"
},
"services": [
{
"name": "example",
"url": %q,
"credentials": {
"bearer": {
"scheme": "Bearer",
"token": "secret"
}
}
}
]}`, ts.server.URL))
manager, err := plugins.New(managerConfig, "test-instance-id", inmem.New())
if err != nil {
t.Fatal(err)
}
pluginConfig := []byte(fmt.Sprintf(`{
"service": "example",
}`))
config, _ := ParseConfig([]byte(pluginConfig), manager.Services(), nil)
p := New(config, manager)
return testFixture{
manager: manager,
plugin: p,
server: &ts,
}
}
func TestParseConfigUseDefaultServiceNoConsole(t *testing.T) {
services := []string{
"s0",
"s1",
"s3",
}
loggerConfig := []byte(fmt.Sprintf(`{
"console": false
}`))
config, err := ParseConfig([]byte(loggerConfig), services, nil)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
if config.Service != services[0] {
t.Errorf("Expected %s service in config, actual = '%s'", services[0], config.Service)
}
}
func TestParseConfigDefaultServiceWithConsole(t *testing.T) {
services := []string{
"s0",
"s1",
"s3",
}
loggerConfig := []byte(fmt.Sprintf(`{
"console": true
}`))
config, err := ParseConfig([]byte(loggerConfig), services, nil)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
if config.Service != "" {
t.Errorf("Expected no service in config, actual = '%s'", config.Service)
}
}
func TestParseConfigDefaultServiceWithNoServiceOrConsole(t *testing.T) {
loggerConfig := []byte(fmt.Sprintf(`{}`))
_, err := ParseConfig([]byte(loggerConfig), []string{}, nil)
if err == nil {
t.Errorf("Expected an error but err==nil")
}
}
type testServer struct {
t *testing.T
expCode int
server *httptest.Server
ch chan []EventV1
}
func (t *testServer) handle(w http.ResponseWriter, r *http.Request) {
gr, err := gzip.NewReader(r.Body)
if err != nil {
t.t.Fatal(err)
}
var events []EventV1
if err := json.NewDecoder(gr).Decode(&events); err != nil {
t.t.Fatal(err)
}
if err := gr.Close(); err != nil {
t.t.Fatal(err)
}
t.t.Logf("decision log test server received %d events", len(events))
t.ch <- events
w.WriteHeader(t.expCode)
}
func (t *testServer) start() {
t.server = httptest.NewServer(http.HandlerFunc(t.handle))
}
func (t *testServer) stop() {
t.server.Close()
}
func getValueForMethod(idx int) string {
methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH"}
return methods[idx%len(methods)]
}
func getValueForPath(idx int) string {
paths := []string{"/blah1", "/blah2", "/blah3", "/blah4"}
return paths[idx%len(paths)]
}
func getValueForUser(idx int) string {
users := []string{"Alice", "Bob", "Charlie", "David", "Ed"}
return users[idx%len(users)]
}
func generateInputMap(idx int) map[string]interface{} {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
result := make(map[string]interface{})
for i := 0; i < 20; i++ {
n := idx % len(letters)
key := string(letters[n])
result[key] = fmt.Sprint(idx)
}
return result
}
func setVersion(opaVersion string) {
if version.Version == "" {
version.Version = opaVersion
}
}
func getVersion() string {
return version.Version
}
func getWellKnownMetrics() metrics.Metrics {
m := metrics.New()
m.Counter("test_counter").Incr()
return m
}