Files
threshold/internal/observability/event/cloudevents_formatter_node_test.go
T
Jeff MitchellandJim 7c139d28b2 Have hclog payloads use json tags (#2171)
* Have hclog payloads use `json` tags

Previously a global setting for the `structs` lib was causing the `json`
tag to be used, but this could have side effects from other packages, or
could be affected by other packages' behavior. This behavior was removed
earlier but it means this format call no longer uses `json` tags; this
behavior reverts it.

It also normalizes using mapstructure in a few other places.

* Update some tests

* Fix incorrect op const

* Fix more tests

* refactor (events): tell filter to use json tags (fix related tests)

Explicitly tell the filter to use the "json" tags so we don't have to
re-tag everything with a mapstructure tag via bexpr.WithTagName("json")

Update docs

Co-authored-by: Jim <jlambert@hashicorp.com>
2022-06-13 19:02:11 -04:00

391 lines
10 KiB
Go

package event
import (
"context"
"encoding/json"
"fmt"
"net/url"
"testing"
"time"
"github.com/hashicorp/eventlogger"
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_newCloudEventsFormatterFilter(t *testing.T) {
t.Parallel()
testSource, err := url.Parse("https://localhost:9200")
require.NoError(t, err)
tests := []struct {
name string
source *url.URL
format cloudevents.Format
opt []Option
wantErr bool
wantIsError error
wantErrContains string
wantAllow []string
wantDeny []string
}{
{
name: "no-opts",
source: testSource,
format: cloudevents.FormatJSON,
},
{
name: "bad-allow-filter",
source: testSource,
format: cloudevents.FormatJSON,
opt: []Option{
WithAllow("foo=;22", "foo==bar"),
},
wantErr: true,
wantErrContains: "invalid allow filter 'foo=;22'",
},
{
name: "bad-deny-filter",
source: testSource,
format: cloudevents.FormatJSON,
opt: []Option{
WithDeny("foo=;22", "foo==bar"),
},
wantErr: true,
wantErrContains: "invalid deny filter 'foo=;22'",
},
{
name: "empty-allow-filter",
source: testSource,
format: cloudevents.FormatJSON,
opt: []Option{
WithAllow(""),
},
wantErr: true,
wantErrContains: "missing filter",
},
{
name: "empty-deny-filter",
source: testSource,
format: cloudevents.FormatJSON,
opt: []Option{
WithDeny(""),
},
wantErr: true,
wantErrContains: "missing filter",
},
{
name: "empty-source",
format: cloudevents.FormatJSON,
opt: []Option{
WithAllow("alice==friend", "bob==friend"),
WithDeny("eve==acquaintance", "fido!=dog"),
},
wantErr: true,
wantErrContains: "missing source",
},
{
name: "bad-format",
source: testSource,
format: "invalid-format",
opt: []Option{
WithAllow("alice==friend", "bob==friend"),
WithDeny("eve==acquaintance", "fido!=dog"),
},
wantErr: true,
wantErrContains: "invalid format",
},
{
name: "valid-filters",
source: testSource,
format: cloudevents.FormatJSON,
opt: []Option{
WithAllow("alice==friend", "bob==friend"),
WithDeny("eve==acquaintance", "fido!=dog"),
},
wantAllow: []string{"alice==friend", "bob==friend"},
wantDeny: []string{"eve==acquaintance", "fido!=dog"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := newCloudEventsFormatterFilter(tt.source, tt.format, tt.opt...)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
if tt.wantIsError != nil {
assert.ErrorIs(err, tt.wantIsError)
}
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
assert.NotNil(got)
assert.Len(got.allow, len(tt.wantAllow))
for _, f := range got.allow {
assert.Contains(tt.wantAllow, f.raw)
}
assert.Len(got.deny, len(tt.wantDeny)+1) // +1 since there's always a default deny
defs, err := defaultCloudEventsDenyFilters()
require.NoError(err)
for _, f := range defs {
tt.wantDeny = append(tt.wantDeny, f.raw)
}
for _, f := range got.deny {
assert.Contains(tt.wantDeny, f.raw)
}
assert.Equal([]string{string(AuditType)}, got.SignEventTypes)
})
}
}
func TestNode_Process(t *testing.T) {
t.Parallel()
ctx := context.Background()
testUrl, err := url.Parse("https://localhost")
require.NoError(t, err)
now := time.Now()
testNode, err := newCloudEventsFormatterFilter(testUrl, cloudevents.FormatJSON, WithSchema(testUrl))
require.NoError(t, err)
f, err := newFilter(`data == "match-filter"`)
require.NoError(t, err)
tests := []struct {
name string
n *cloudEventsFormatterFilter
e *eventlogger.Event
format cloudevents.Format
predicate func(ctx context.Context, ce interface{}) (bool, error)
wantCloudEvent *cloudevents.Event
wantText string
wantIsError error
wantErrContains string
}{
{
name: "simple-JSON",
n: testNode,
e: &eventlogger.Event{
Type: "test",
CreatedAt: now,
Payload: "test-string",
},
format: cloudevents.FormatJSON,
wantCloudEvent: &cloudevents.Event{
Source: testUrl.String(),
DataSchema: testUrl.String(),
SpecVersion: cloudevents.SpecVersion,
Type: "test",
Data: "test-string",
DataContentType: "application/cloudevents",
Time: now,
},
},
{
name: "deny-filter-match",
n: testNode,
e: &eventlogger.Event{
Type: "test",
CreatedAt: now,
Payload: "match-filter",
},
format: cloudevents.FormatJSON,
predicate: newPredicate(nil, []*filter{f}),
},
{
name: "deny-filter-not-matching",
n: testNode,
e: &eventlogger.Event{
Type: "test",
CreatedAt: now,
Payload: "not-matching-filter",
},
format: cloudevents.FormatJSON,
predicate: newPredicate(nil, []*filter{f}),
wantCloudEvent: &cloudevents.Event{
Source: testUrl.String(),
DataSchema: testUrl.String(),
SpecVersion: cloudevents.SpecVersion,
Type: "test",
Data: "not-matching-filter",
DataContentType: "application/cloudevents",
Time: now,
},
},
{
name: "allow-filter-match",
n: testNode,
e: &eventlogger.Event{
Type: "test",
CreatedAt: now,
Payload: "match-filter",
},
format: cloudevents.FormatJSON,
predicate: newPredicate([]*filter{f}, nil),
wantCloudEvent: &cloudevents.Event{
Source: testUrl.String(),
DataSchema: testUrl.String(),
SpecVersion: cloudevents.SpecVersion,
Type: "test",
Data: "match-filter",
DataContentType: "application/cloudevents",
Time: now,
},
},
{
name: "allow-filter-not-matching",
n: testNode,
e: &eventlogger.Event{
Type: "test",
CreatedAt: now,
Payload: "not-matching-filter",
},
format: cloudevents.FormatJSON,
predicate: newPredicate([]*filter{f}, nil),
},
{
name: "no-filters",
n: testNode,
e: &eventlogger.Event{
Type: "test",
CreatedAt: now,
Payload: "test-data",
},
format: cloudevents.FormatJSON,
predicate: newPredicate(nil, nil),
wantCloudEvent: &cloudevents.Event{
Source: testUrl.String(),
DataSchema: testUrl.String(),
SpecVersion: cloudevents.SpecVersion,
Type: "test",
Data: "test-data",
DataContentType: "application/cloudevents",
Time: now,
},
},
{
name: "simple-Text",
n: testNode,
e: &eventlogger.Event{
Type: "test",
CreatedAt: now,
Payload: "test-string",
},
format: cloudevents.FormatText,
wantCloudEvent: &cloudevents.Event{
Source: testUrl.String(),
DataSchema: testUrl.String(),
SpecVersion: cloudevents.SpecVersion,
Type: "test",
Data: "test-string",
DataContentType: "text/plain",
Time: now,
},
wantText: `{
"id": "%s",
"source": "https://localhost",
"specversion": "1.0",
"type": "test",
"data": "test-string",
"datacontentype": "text/plain",
"dataschema": "https://localhost",
"time": %s
}
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// these subtests cannot be run in parallel since they all depend on
// a shared testNode which may be modified during the test.
assert, require := assert.New(t), require.New(t)
tt.n.Format = tt.format
tt.n.Predicate = tt.predicate
gotEvent, err := tt.n.Process(ctx, tt.e)
if tt.wantIsError != nil {
require.Error(err)
assert.Nil(gotEvent)
assert.ErrorIs(err, tt.wantIsError)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
if tt.wantCloudEvent == nil {
assert.Nil(gotEvent)
return
}
gotFormatted, ok := gotEvent.Format(string(tt.format))
require.True(ok)
var gotCloudEvent cloudevents.Event
require.NoError(json.Unmarshal(gotFormatted, &gotCloudEvent))
if tt.wantCloudEvent.ID == "" {
tt.wantCloudEvent.ID = gotCloudEvent.ID
}
var wantJSON []byte
switch tt.format {
case cloudevents.FormatJSON:
wantJSON, err = json.Marshal(tt.wantCloudEvent)
case cloudevents.FormatText:
// test the raw JSON
jsonTime, err := gotCloudEvent.Time.MarshalJSON()
require.NoError(err)
wantRawText := []byte(fmt.Sprintf(tt.wantText, gotCloudEvent.ID, jsonTime))
assert.Equal(string(wantRawText), string(gotFormatted))
// test the marshaled JSON
wantJSON, err = json.MarshalIndent(tt.wantCloudEvent, cloudevents.TextIndent, cloudevents.TextIndent)
require.NoError(err)
}
require.NoError(err)
assert.JSONEq(string(wantJSON), string(gotFormatted))
t.Log(string(gotFormatted))
})
}
}
func Test_cloudEventsFormatter_Rotate(t *testing.T) {
tests := []struct {
name string
f *cloudEventsFormatterFilter
w wrapping.Wrapper
wantIsError error
wantErrContains string
}{
{
name: "missing-wrapper",
f: &cloudEventsFormatterFilter{FormatterFilter: &cloudevents.FormatterFilter{}},
wantIsError: ErrInvalidParameter,
wantErrContains: "missing wrapper",
},
{
name: "valid",
f: &cloudEventsFormatterFilter{FormatterFilter: &cloudevents.FormatterFilter{}},
w: testWrapper(t),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := tt.f.Rotate(tt.w)
if tt.wantIsError != nil {
require.Error(err)
assert.ErrorIs(err, tt.wantIsError)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
assert.NotNil(tt.f.Signer)
})
}
}