Files
threshold/internal/observability/event/hclog_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

438 lines
9.8 KiB
Go

package event
import (
"context"
"encoding/json"
"testing"
"github.com/hashicorp/eventlogger"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHclogFormatter_Process(t *testing.T) {
t.Parallel()
ctx := context.Background()
f, e := newFilter(`op == "match-filter"`)
require.NoError(t, e)
testPredicate := newPredicate([]*filter{f}, nil)
tests := []struct {
name string
formatter *hclogFormatterFilter
e *eventlogger.Event
wantErrContains string
want []string
}{
{
name: "nil event",
formatter: &hclogFormatterFilter{
jsonFormat: false,
},
wantErrContains: "event is nil",
},
{
name: "invalid-event-type",
formatter: &hclogFormatterFilter{
jsonFormat: false,
},
e: &eventlogger.Event{Type: eventlogger.EventType("invalid-type")},
wantErrContains: "unknown event type invalid-type",
},
{
name: "sys-text",
formatter: &hclogFormatterFilter{
jsonFormat: false,
},
e: &eventlogger.Event{
Type: eventlogger.EventType(SystemType),
Payload: &sysEvent{
Id: "1",
Version: errorVersion,
Op: Op("text"),
Data: map[string]interface{}{
"msg": "hello",
},
},
},
want: []string{
"[INFO] system event:",
"data:msg=hello",
"version=v0.1",
"op=text",
},
},
{
name: "observation-text",
formatter: &hclogFormatterFilter{
jsonFormat: false,
},
e: &eventlogger.Event{
Type: eventlogger.EventType(ObservationType),
Payload: map[string]interface{}{
"id": "1",
"version": observationVersion,
"latency-ms": 10,
},
},
want: []string{
"[INFO] observation event:",
"latency-ms=10",
"id=1",
"version=v0.1",
},
},
{
name: "observation-json",
formatter: &hclogFormatterFilter{
jsonFormat: true,
},
e: &eventlogger.Event{
Type: eventlogger.EventType(ObservationType),
Payload: map[string]interface{}{
"id": "1",
"version": observationVersion,
"latency-ms": 10,
},
},
want: []string{
"{\"@level\":\"info\",\"@message\":\"observation event\"",
"\"latency-ms\":10",
"\"id\":\"1\"",
"\"version\":\"v0.1\"}\n",
},
},
{
name: "err-text",
formatter: &hclogFormatterFilter{
jsonFormat: false,
},
e: &eventlogger.Event{
Type: eventlogger.EventType(ErrorType),
Payload: &err{
Id: "1",
Version: errorVersion,
Error: ErrInvalidParameter.Error(),
Op: Op("text"),
},
},
want: []string{
"[ERROR] error event:",
"error=\"invalid parameter\"",
"id=1",
"version=v0.1",
"op=text",
},
},
{
name: "err-json",
formatter: &hclogFormatterFilter{
jsonFormat: true,
},
e: &eventlogger.Event{
Type: eventlogger.EventType(ErrorType),
Payload: &err{
Id: "1",
Version: errorVersion,
Error: ErrInvalidParameter.Error(),
Op: Op("text"),
},
},
want: []string{
"{\"@level\":\"error\",\"@message\":\"error event\"",
"\"error\":\"invalid parameter\"",
"\"id\":\"1\"",
"\"version\":\"v0.1\"",
"\"op\":\"text\"",
},
},
{
name: "err-text-with-optional",
formatter: &hclogFormatterFilter{
jsonFormat: false,
},
e: &eventlogger.Event{
Type: eventlogger.EventType(ErrorType),
Payload: &err{
Id: "1",
Version: errorVersion,
Error: ErrInvalidParameter.Error(),
Op: Op("text"),
Info: map[string]interface{}{"name": "alice"},
},
},
want: []string{
"[ERROR] error event:",
"error=\"invalid parameter\"",
"id=1",
"version=v0.1",
"info:name=alice",
"op=text",
},
},
{
name: "filter-match",
formatter: &hclogFormatterFilter{
jsonFormat: false,
predicate: testPredicate,
},
e: &eventlogger.Event{
Type: eventlogger.EventType(SystemType),
Payload: &sysEvent{
Id: "1",
Version: errorVersion,
Op: Op("match-filter"),
Data: map[string]interface{}{
"msg": "hello",
},
},
},
want: []string{
"[INFO] system event:",
"data:msg=hello",
"version=v0.1",
"op=match-filter",
},
},
{
name: "filter-no-match",
formatter: &hclogFormatterFilter{
jsonFormat: false,
predicate: testPredicate,
},
e: &eventlogger.Event{
Type: eventlogger.EventType(SystemType),
Payload: &sysEvent{
Id: "1",
Version: errorVersion,
Op: Op("doesn't match"),
Data: map[string]interface{}{
"msg": "hello",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
e, err := tt.formatter.Process(ctx, tt.e)
if tt.wantErrContains != "" {
require.Error(err)
assert.Contains(err.Error(), tt.wantErrContains)
return
}
require.NoError(err)
if len(tt.want) == 0 {
assert.Nil(e)
return
}
assert.NotNil(e)
var b []byte
var ok bool
switch tt.formatter.jsonFormat {
case true:
b, ok = e.Format(string(JSONHclogSinkFormat))
case false:
b, ok = e.Format(string(TextHclogSinkFormat))
}
t.Log(string(b))
require.True(ok)
for _, txt := range tt.want {
assert.Contains(string(b), txt)
}
})
}
t.Run("with-signing", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
wrapper := testWrapper(t)
f, err := newHclogFormatterFilter(true) // produce json formatted events
require.NoError(err)
require.NoError(f.Rotate(wrapper))
require.NotNil(f.signer)
e := &eventlogger.Event{
Type: eventlogger.EventType(AuditType),
Payload: &audit{
Id: "1",
Version: auditVersion,
Auth: &Auth{UserName: "alice"},
},
}
gotEvent, err := f.Process(ctx, e)
require.NoError(err)
b, ok := gotEvent.Format(string(JSONHclogSinkFormat))
require.True(ok)
var rep map[string]interface{}
require.NoError(json.Unmarshal(b, &rep))
assert.NotEmpty(rep["serialized"])
assert.NotEmpty(rep["serialized_hmac"])
})
}
func Test_hclogFormatterFilter_Name(t *testing.T) {
t.Parallel()
t.Run("simple", func(t *testing.T) {
ff := &hclogFormatterFilter{}
assert.Equal(t, hclogNodeName, ff.Name())
})
}
func Test_hclogFormatterFilter_Reopen(t *testing.T) {
t.Parallel()
t.Run("simple", func(t *testing.T) {
ff := &hclogFormatterFilter{}
assert.Equal(t, nil, ff.Reopen())
})
}
func Test_hclogFormatterFilter_Type(t *testing.T) {
t.Parallel()
t.Run("simple", func(t *testing.T) {
ff := &hclogFormatterFilter{}
assert.Equal(t, eventlogger.NodeTypeFormatterFilter, ff.Type())
})
}
func Test_newHclogFormatterFilter(t *testing.T) {
t.Parallel()
tests := []struct {
name string
jsonFormat bool
opt []Option
wantErr bool
wantIsError error
wantErrContains string
wantAllow []string
wantDeny []string
}{
{
name: "no-opts",
},
{
name: "bad-allow-filter",
jsonFormat: true,
opt: []Option{
WithAllow("foo=;22", "foo==bar"),
},
wantErr: true,
wantErrContains: "invalid allow filter 'foo=;22'",
},
{
name: "bad-deny-filter",
jsonFormat: true,
opt: []Option{
WithDeny("foo=;22", "foo==bar"),
},
wantErr: true,
wantErrContains: "invalid deny filter 'foo=;22'",
},
{
name: "empty-allow-filter",
jsonFormat: true,
opt: []Option{
WithAllow(""),
},
wantErr: true,
wantErrContains: "missing filter",
},
{
name: "empty-deny-filter",
jsonFormat: true,
opt: []Option{
WithDeny(""),
},
wantErr: true,
wantErrContains: "missing filter",
},
{
name: "valid-filters",
jsonFormat: true,
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 := newHclogFormatterFilter(tt.jsonFormat, 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.Equal(tt.jsonFormat, got.jsonFormat)
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 := defaultHclogEventsDenyFilters()
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)
}
})
}
}
func Test_hclogFormatterFilter_Rotate(t *testing.T) {
tests := []struct {
name string
f *hclogFormatterFilter
w wrapping.Wrapper
wantIsError error
wantErrContains string
}{
{
name: "missing-wrapper",
f: &hclogFormatterFilter{},
wantIsError: ErrInvalidParameter,
wantErrContains: "missing wrapper",
},
{
name: "valid",
f: &hclogFormatterFilter{},
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)
})
}
}