Files
threshold/internal/observability/event/eventer_test.go
T
Jeff Mitchell 1782f6a4e4 Enhance worker connection security (#1641)
This stores worker connection nonces within the database to ensure
uniqueness of connections. Prior the connection required any attacker to
be able to decrypt the incoming encrypted authentication message in
order to replay (and if the attacker has access to the KMS you have
bigger problems), but with this change even if an attacker does have
access to the KMS key, they cannot reuse the same message. (If they're
able to encrypt against the KMS key, they can still act as a worker with
a new message, so this only really helps if they only have decryption
capabilities.)
2021-10-27 20:32:07 -04:00

1187 lines
31 KiB
Go

package event
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"sync"
"testing"
"github.com/hashicorp/eventlogger"
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_InitSysEventer(t *testing.T) {
// this test and its subtests cannot be run in parallel because of it's
// dependency on the sysEventer
testConfig := TestEventerConfig(t, "InitSysEventer")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
})
testEventer, err := NewEventer(testLogger, testLock, "Test_InitSysEventer", testConfig.EventerConfig)
require.NoError(t, err)
tests := []struct {
name string
log hclog.Logger
lock *sync.Mutex
serverName string
opt []Option
want *Eventer
wantErrIs error
}{
{
name: "missing-both-eventer-and-config",
serverName: "missing-both-eventer-and-config",
wantErrIs: ErrInvalidParameter,
},
{
name: "missing-hclog",
opt: []Option{WithEventerConfig(&testConfig.EventerConfig)},
lock: testLock,
serverName: "missing-hclog",
wantErrIs: ErrInvalidParameter,
},
{
name: "missing-lock",
opt: []Option{WithEventerConfig(&testConfig.EventerConfig)},
log: testLogger,
serverName: "missing-lock",
wantErrIs: ErrInvalidParameter,
},
{
name: "missing-serverName",
opt: []Option{WithEventerConfig(&testConfig.EventerConfig)},
log: testLogger,
lock: testLock,
wantErrIs: ErrInvalidParameter,
},
{
name: "missing-eventer-and-config",
log: testLogger,
lock: testLock,
serverName: "success-with-config",
wantErrIs: ErrInvalidParameter,
},
{
name: "both-eventer-and-config",
opt: []Option{WithEventerConfig(&testConfig.EventerConfig), WithEventer(testEventer)},
log: testLogger,
lock: testLock,
serverName: "success-with-config",
wantErrIs: ErrInvalidParameter,
},
{
name: "bad-config",
opt: []Option{WithEventerConfig(&EventerConfig{Sinks: []*SinkConfig{{Format: "bad-format"}}})},
log: testLogger,
lock: testLock,
serverName: "success-with-config",
wantErrIs: ErrInvalidParameter,
},
{
name: "success-with-config",
opt: []Option{WithEventerConfig(&testConfig.EventerConfig)},
log: testLogger,
lock: testLock,
serverName: "success-with-config",
want: &Eventer{
logger: testLogger,
conf: testConfig.EventerConfig,
},
},
{
name: "success-with-default-config",
opt: []Option{WithEventerConfig(&EventerConfig{})},
log: testLogger,
lock: testLock,
serverName: "success-with-default-config",
want: &Eventer{
logger: testLogger,
conf: EventerConfig{
Sinks: []*SinkConfig{
{
Name: "default",
EventTypes: []Type{EveryType},
Format: JSONSinkFormat,
Type: StderrSink,
AuditConfig: DefaultAuditConfig(),
},
},
},
},
},
{
name: "success-with-eventer",
opt: []Option{WithEventer(testEventer)},
log: testLogger,
lock: testLock,
serverName: "success-with-eventer",
want: testEventer,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer TestResetSystEventer(t)
assert, require := assert.New(t), require.New(t)
err := InitSysEventer(tt.log, tt.lock, tt.serverName, tt.opt...)
got := SysEventer()
if tt.wantErrIs != nil {
require.Nil(got)
require.Error(err)
assert.ErrorIs(err, tt.wantErrIs)
return
}
require.NoError(err)
require.NotNil(got)
tt.want.broker = got.broker
tt.want.flushableNodes = got.flushableNodes
tt.want.auditPipelines = got.auditPipelines
tt.want.errPipelines = got.errPipelines
tt.want.observationPipelines = got.observationPipelines
assert.Equal(tt.want, got)
})
}
}
func TestEventer_writeObservation(t *testing.T) {
t.Parallel()
ctx := context.Background()
testSetup := TestEventerConfig(t, "TestEventer_writeObservation")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
})
eventer, err := NewEventer(testLogger, testLock, "TestEventer_writeObservation", testSetup.EventerConfig)
require.NoError(t, err)
testHeader := map[string]interface{}{"name": "header"}
testDetail := map[string]interface{}{"name": "details"}
testObservation, err := newObservation("Test_NewEventer", WithHeader(testHeader), WithDetails(testDetail))
require.NoError(t, err)
tests := []struct {
name string
broker broker
observation *observation
wantErrIs error
}{
{
name: "missing-observation",
broker: &testMockBroker{},
wantErrIs: ErrInvalidParameter,
},
{
name: "send-fails",
broker: &testMockBroker{errorOnSend: fmt.Errorf("%s: no msg: %w", "test", ErrIo)},
observation: testObservation,
wantErrIs: ErrMaxRetries,
},
{
name: "success",
broker: &testMockBroker{},
observation: testObservation,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eventer.broker = tt.broker
err = eventer.writeObservation(ctx, tt.observation)
if tt.wantErrIs != nil {
require.Error(err)
assert.ErrorIs(err, tt.wantErrIs)
return
}
require.NoError(err)
})
}
t.Run("e2e", func(t *testing.T) {
require := require.New(t)
c := EventerConfig{
ObservationsEnabled: true,
}
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
// with no defined config, it will default to a stderr sink
e, err := NewEventer(testLogger, testLock, "e2e-test", c)
require.NoError(err)
m := map[string]interface{}{
"name": "bar",
"list": []string{"1", "2"},
}
observationEvent, err := newObservation("Test_NewEventer", WithHeader(m))
require.NoError(err)
require.NoError(e.writeObservation(context.Background(), observationEvent))
})
}
func TestEventer_writeAudit(t *testing.T) {
t.Parallel()
ctx := context.Background()
testSetup := TestEventerConfig(t, "Test_NewEventer")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
eventer, err := NewEventer(testLogger, testLock, "TestEventer_writeAudit", testSetup.EventerConfig)
require.NoError(t, err)
testAudit, err := newAudit(
"TestEventer_writeAudit",
WithRequestInfo(TestRequestInfo(t)),
WithAuth(testAuth(t)),
WithRequest(testRequest(t)),
WithResponse(testResponse(t)))
require.NoError(t, err)
tests := []struct {
name string
broker broker
audit *audit
wantErrIs error
}{
{
name: "missing-audit",
broker: &testMockBroker{},
wantErrIs: ErrInvalidParameter,
},
{
name: "send-fails",
broker: &testMockBroker{errorOnSend: fmt.Errorf("%s: no msg: %w", "test", ErrIo)},
audit: testAudit,
wantErrIs: ErrIo,
},
{
name: "success",
broker: &testMockBroker{},
audit: testAudit,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eventer.broker = tt.broker
err = eventer.writeAudit(ctx, tt.audit)
if tt.wantErrIs != nil {
require.Error(err)
assert.ErrorIs(err, tt.wantErrIs)
return
}
require.NoError(err)
})
}
}
func TestEventer_writeError(t *testing.T) {
t.Parallel()
ctx := context.Background()
testSetup := TestEventerConfig(t, "Test_NewEventer")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
eventer, er := NewEventer(testLogger, testLock, "TestEventer_writeError", testSetup.EventerConfig)
require.NoError(t, er)
testError, er := newError("TestEventer_writeError", fmt.Errorf("%s: no msg: test", ErrIo))
require.NoError(t, er)
tests := []struct {
name string
broker broker
err *err
wantErrIs error
}{
{
name: "missing-error",
broker: &testMockBroker{},
wantErrIs: ErrInvalidParameter,
},
{
name: "send-fails",
broker: &testMockBroker{errorOnSend: fmt.Errorf("%s: no msg: test", ErrIo)},
err: testError,
wantErrIs: ErrMaxRetries,
},
{
name: "success",
broker: &testMockBroker{},
err: testError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eventer.broker = tt.broker
err := eventer.writeError(ctx, tt.err)
if tt.wantErrIs != nil {
require.Error(err)
assert.ErrorIs(err, tt.wantErrIs)
return
}
require.NoError(err)
})
}
}
func Test_NewEventer(t *testing.T) {
t.Parallel()
testSetup := TestEventerConfig(t, "Test_NewEventer")
testSetupWithOpts := TestEventerConfig(t, "Test_NewEventer", TestWithAuditSink(t), TestWithObservationSink(t), TestWithSysSink(t))
testHclogSetup := TestEventerConfig(t, "Test_NewEventer", testWithSinkFormat(t, TextHclogSinkFormat))
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
twrapper := testWrapper(t)
tests := []struct {
name string
config EventerConfig
opts []Option
logger hclog.Logger
lock *sync.Mutex
serverName string
want *Eventer
wantRegistered []string
wantPipelines []string
wantThresholds map[eventlogger.EventType]int
wantErrIs error
wantErrContains string
}{
{
name: "missing-require-wrapper",
config: func() EventerConfig {
cfg := EventerConfig{
AuditEnabled: true,
Sinks: []*SinkConfig{
{
Name: "test",
EventTypes: []Type{AuditType},
Type: StderrSink,
Format: JSONHclogSinkFormat,
AuditConfig: &AuditConfig{
FilterOverrides: AuditFilterOperations{
SensitiveClassification: EncryptOperation,
},
},
},
},
}
return cfg
}(),
lock: testLock,
logger: testLogger,
serverName: "missing-required-wrapper",
wantErrIs: ErrInvalidParameter,
wantErrContains: "missing wrapper and encrypt filter operation requires a wrapper",
},
{
name: "valid-audit-config-with-wrapper",
config: func() EventerConfig {
cfg := EventerConfig{
AuditEnabled: true,
Sinks: []*SinkConfig{
{
Name: "test",
EventTypes: []Type{AuditType},
Type: StderrSink,
Format: JSONSinkFormat,
AuditConfig: &AuditConfig{
FilterOverrides: AuditFilterOperations{
SensitiveClassification: EncryptOperation,
},
},
},
},
}
return cfg
}(),
opts: []Option{WithAuditWrapper(twrapper)},
lock: testLock,
logger: testLogger,
serverName: "valid-audit-config-with-wrapper",
want: &Eventer{
logger: testLogger,
conf: EventerConfig{
AuditEnabled: true,
Sinks: []*SinkConfig{
{
Name: "test",
EventTypes: []Type{AuditType},
Format: JSONSinkFormat,
Type: StderrSink,
AuditConfig: &AuditConfig{
wrapper: twrapper,
FilterOverrides: AuditFilterOperations{
SensitiveClassification: EncryptOperation,
},
},
},
},
},
},
wantRegistered: []string{
"cloudevents", // fmt for everything
"stderr", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
},
wantPipelines: []string{
"audit", // stderr
},
wantThresholds: map[eventlogger.EventType]int{
"error": 0,
"system": 0,
"observation": 0,
"audit": 1,
},
},
{
name: "missing-logger",
config: testSetup.EventerConfig,
lock: testLock,
serverName: "missing-logger",
wantErrIs: ErrInvalidParameter,
},
{
name: "missing-lock",
config: testSetup.EventerConfig,
logger: testLogger,
serverName: "missing-lock",
wantErrIs: ErrInvalidParameter,
},
{
name: "missing-server-name",
config: testSetup.EventerConfig,
logger: testLogger,
lock: testLock,
wantErrIs: ErrInvalidParameter,
},
{
name: "dup-sink-filename",
config: func() EventerConfig {
dupFileConfig := TestEventerConfig(t, "dup-sink-filename")
dupFileConfig.EventerConfig.Sinks = append(dupFileConfig.EventerConfig.Sinks,
&SinkConfig{
Name: "err-file-sink",
Type: FileSink,
EventTypes: []Type{ErrorType},
Format: JSONSinkFormat,
FileConfig: &FileSinkTypeConfig{
Path: "./",
FileName: dupFileConfig.ErrorEvents.Name(),
},
},
)
return dupFileConfig.EventerConfig
}(),
logger: testLogger,
lock: testLock,
serverName: "dup-sink-filename",
wantErrIs: ErrInvalidParameter,
},
{
name: "success-with-default-config",
config: EventerConfig{},
logger: testLogger,
lock: testLock,
serverName: "success-with-default-config",
want: &Eventer{
logger: testLogger,
conf: EventerConfig{
Sinks: []*SinkConfig{
{
Name: "default",
EventTypes: []Type{EveryType},
Format: JSONSinkFormat,
Type: StderrSink,
AuditConfig: DefaultAuditConfig(),
},
},
},
},
wantRegistered: []string{
"cloudevents", // fmt for everything
"stderr", // stderr
"gated-observation", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
},
wantPipelines: []string{
"audit", // stderr
"observation", // stderr
"error", // stderr
"system", // stderr
},
wantThresholds: map[eventlogger.EventType]int{
"error": 1,
"system": 1,
"observation": 1,
"audit": 1,
},
},
{
name: "testSetup",
config: testSetup.EventerConfig,
logger: testLogger,
lock: testLock,
serverName: "testSetup",
want: &Eventer{
logger: testLogger,
conf: testSetup.EventerConfig,
},
wantRegistered: []string{
"cloudevents", // stderr
"stderr", // stderr
"gated-observation", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
"cloudevents", // every-type-file-sync
"tmp-all-events", // every-type-file-sync
"gated-observation", // every-type-file-sync
"gated-audit", // every-type-file-sync
"encrypt-audit", // every-type-file-sync
"cloudevents", // error-file-sink
"tmp-errors", // error-file-sink
},
wantPipelines: []string{
"audit", // every-type-file-sync
"audit", // stderr
"observation", // every-type-file-sync
"observation", // stderr
"error", // every-type-file-sync
"error", // stderr
"error", // error-file-sink
"system", // stderr
"system", // stderr
},
wantThresholds: map[eventlogger.EventType]int{
"error": 3,
"system": 2,
"observation": 2,
"audit": 2,
},
},
{
name: "testSetup-with-all-opts",
config: testSetupWithOpts.EventerConfig,
logger: testLogger,
lock: testLock,
serverName: "testSetup-with-all-opts",
want: &Eventer{
logger: testLogger,
conf: testSetupWithOpts.EventerConfig,
},
wantRegistered: []string{
"cloudevents", // stderr
"stderr", // stderr
"gated-observation", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
"cloudevents", // every-type-file-sync
"tmp-all-events", // every-type-file-sync
"gated-observation", // every-type-file-sync
"gated-audit", // every-type-file-sync
"encrypt-audit", // every-type-file-sync
"cloudevents", // error-file-sink
"tmp-errors", // error-file-sink
"cloudevents", // observation-file-sink
"gated-observation", // observation-file-sink
"tmp-observation", // observations-file-sink
"cloudevents", // audit-file-sink
"gated-audit", // audit-file-sink
"encrypt-audit", // audit-file-sink
"tmp-audit", // audit-file-sink
"cloudevents", // sys-file-sink
"tmp-sysevents", // sys-file-sink
},
wantPipelines: []string{
"audit", // every-type-file-sync
"audit", // stderr
"observation", // every-type-file-sync
"observation", // stderr
"error", // every-type-file-sync
"error", // stderr
"error", // error-file-sink
"audit", // audit-file-sink
"observation", // observation-file-sink
"system", // stderr
"system", // every-type-file-sync
"system", // sys-file-sink
},
wantThresholds: map[eventlogger.EventType]int{
"error": 3,
"system": 3,
"observation": 3,
"audit": 3,
},
},
{
name: "testSetup-with-hclog",
config: testHclogSetup.EventerConfig,
logger: testLogger,
lock: testLock,
serverName: "testSetup",
want: &Eventer{
logger: testLogger,
conf: testHclogSetup.EventerConfig,
},
wantRegistered: []string{
"hclog-text", // stderr
"stderr", // stderr
"gated-observation", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
"hclog-text", // every-type-file-sync
"tmp-all-events", // every-type-file-sync
"gated-observation", // every-type-file-sync
"gated-audit", // every-type-file-sync
"encrypt-audit", // every-type-file-sync
"hclog-text", // error-file-sink
"tmp-errors", // error-file-sink
},
wantPipelines: []string{
"audit", // every-type-file-sync
"audit", // stderr
"observation", // every-type-file-sync
"observation", // stderr
"error", // every-type-file-sync
"error", // stderr
"error", // error-file-sink
"system", // stderr
"system", // stderr
},
wantThresholds: map[eventlogger.EventType]int{
"error": 3,
"system": 2,
"observation": 2,
"audit": 2,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
testBroker := &testMockBroker{}
opts := []Option{TestWithBroker(t, testBroker)}
if tt.opts != nil {
opts = append(opts, tt.opts...)
}
got, err := NewEventer(tt.logger, tt.lock, tt.serverName, tt.config, opts...)
if tt.wantErrIs != nil {
require.Error(err)
require.Nil(got)
assert.ErrorIs(err, tt.wantErrIs)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
require.NotNil(got)
tt.want.broker = got.broker
tt.want.flushableNodes = got.flushableNodes
tt.want.auditPipelines = got.auditPipelines
tt.want.errPipelines = got.errPipelines
tt.want.observationPipelines = got.observationPipelines
assert.Equal(tt.want, got)
assert.Lenf(testBroker.registeredNodeIds, len(tt.wantRegistered), "got nodes: %q", testBroker.registeredNodeIds)
registeredNodeIds := map[string]bool{}
for _, id := range testBroker.registeredNodeIds {
registeredNodeIds[string(id)] = true
}
for _, want := range tt.wantRegistered {
found := false
for got := range registeredNodeIds {
if strings.Contains(got, want) {
found = true
delete(registeredNodeIds, got)
break
}
}
assert.Truef(found, "did not find %s in the registered nodes: %s", want, testBroker.registeredNodeIds)
}
assert.Lenf(testBroker.pipelines, len(tt.wantPipelines), "got pipelines: %q", testBroker.pipelines)
registeredPipelines := map[string]eventlogger.Pipeline{}
gotAuditCnt := 0
gotErrCnt := 0
gotObservationCnt := 0
for _, got := range testBroker.pipelines {
registeredPipelines[string(got.PipelineID)] = got
switch got.EventType {
case eventlogger.EventType(AuditType):
gotAuditCnt += 1
case eventlogger.EventType(ErrorType):
gotErrCnt += 1
case eventlogger.EventType(ObservationType):
gotObservationCnt += 1
}
}
wantAuditCnt := 0
wantErrCnt := 0
wantObservationCnt := 0
for _, want := range tt.wantPipelines {
switch want {
case string(AuditType):
wantAuditCnt += 1
case string(ErrorType):
wantErrCnt += 1
case string(ObservationType):
wantObservationCnt += 1
}
found := false
for id, got := range registeredPipelines {
if strings.Contains(string(got.EventType), want) {
found = true
delete(registeredPipelines, id)
break
}
}
assert.Truef(found, "did not find %s in the registered pipelines: %s", want, testBroker.pipelines)
}
assert.Equal(wantAuditCnt, gotAuditCnt)
assert.Equal(wantErrCnt, gotErrCnt)
assert.Equal(wantObservationCnt, gotObservationCnt)
assert.Equal(tt.wantThresholds, testBroker.successThresholds)
})
}
}
func TestEventer_Reopen(t *testing.T) {
t.Parallel()
t.Run("simple", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
e, err := NewEventer(testLogger, testLock, "TestEventer_Reopen", EventerConfig{})
require.NoError(err)
e.broker = nil
require.NoError(e.Reopen())
e.broker = &testMockBroker{}
require.NoError(e.Reopen())
assert.True(e.broker.(*testMockBroker).reopened)
})
}
func TestEventer_FlushNodes(t *testing.T) {
t.Parallel()
t.Run("simple", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
e, err := NewEventer(testLogger, testLock, "TestEventer_FlushNodes", EventerConfig{})
require.NoError(err)
node := &testFlushNode{}
e.flushableNodes = append(e.flushableNodes, node)
require.NoError(e.FlushNodes(context.Background()))
node.raiseError = true
require.Error(e.FlushNodes(context.Background()))
assert.True(node.flushed)
})
}
type testFlushNode struct {
flushed bool
raiseError bool
}
func (t *testFlushNode) FlushAll(_ context.Context) error {
t.flushed = true
if t.raiseError {
return fmt.Errorf("%s: test error: flush-all", ErrInvalidParameter)
}
return nil
}
func Test_StandardLogger(t *testing.T) {
// this test and its subtests cannot be run in parallel because of it's
// dependency on the sysEventer
testCtx := context.Background()
c := TestEventerConfig(t, "Test_StandardLogger")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, InitSysEventer(testLogger, testLock, "Test_StandardLogger", WithEventerConfig(&c.EventerConfig)))
tests := []struct {
name string
eventer *Eventer
ctx context.Context
eventType Type
wantErr bool
wantErrIs error
wantErrContains string
wantLogger *log.Logger
}{
{
name: "missing-eventer",
ctx: testCtx,
eventType: ErrorType,
wantErr: true,
wantErrIs: ErrInvalidParameter,
wantErrContains: "nil eventer",
},
{
name: "missing-ctx",
eventer: SysEventer(),
eventType: ErrorType,
wantErr: true,
wantErrIs: ErrInvalidParameter,
wantErrContains: "missing context",
},
{
name: "missing-type",
ctx: testCtx,
eventer: SysEventer(),
wantErr: true,
wantErrIs: ErrInvalidParameter,
wantErrContains: "missing type",
},
{
name: "invalid-type",
ctx: testCtx,
eventer: SysEventer(),
eventType: "invalid",
wantErr: true,
wantErrIs: ErrInvalidParameter,
wantErrContains: "'invalid' is not a valid event type",
},
{
name: "okay",
ctx: testCtx,
eventer: SysEventer(),
eventType: ErrorType,
wantLogger: func() *log.Logger {
w, err := SysEventer().StandardWriter(testCtx, ErrorType)
require.NoError(t, err)
return log.New(w, "okay", 0)
}(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
e := SysEventer()
require.NotNil(e)
l, err := tt.eventer.StandardLogger(tt.ctx, tt.name, tt.eventType)
if tt.wantErr {
require.Error(err)
assert.Nil(l)
if tt.wantErrIs != nil {
assert.ErrorIs(err, tt.wantErrIs)
}
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
assert.NotNil(l)
assert.Equal(tt.wantLogger, l)
})
}
}
func Test_StandardWriter(t *testing.T) {
// this test and its subtests cannot be run in parallel because of it's
// dependency on the sysEventer
c := TestEventerConfig(t, "Test_StandardLogger")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, InitSysEventer(testLogger, testLock, "Test_StandardLogger", WithEventerConfig(&c.EventerConfig)))
testCtx, err := NewEventerContext(context.Background(), SysEventer())
require.NoError(t, err)
tests := []struct {
name string
eventer *Eventer
ctx context.Context
eventType Type
wantErr bool
wantErrIs error
wantErrContains string
wantWriter io.Writer
}{
{
name: "missing-eventer",
ctx: testCtx,
eventType: ErrorType,
wantErr: true,
wantErrIs: ErrInvalidParameter,
wantErrContains: "nil eventer",
},
{
name: "missing-ctx",
eventer: SysEventer(),
eventType: ErrorType,
wantErr: true,
wantErrIs: ErrInvalidParameter,
wantErrContains: "missing context",
},
{
name: "missing-type",
ctx: testCtx,
eventer: SysEventer(),
wantErr: true,
wantErrIs: ErrInvalidParameter,
wantErrContains: "missing type",
},
{
name: "invalid-type",
ctx: testCtx,
eventer: SysEventer(),
eventType: "invalid",
wantErr: true,
wantErrIs: ErrInvalidParameter,
wantErrContains: "'invalid' is not a valid event type",
},
{
name: "okay",
ctx: context.Background(),
eventer: SysEventer(),
eventType: ErrorType,
wantWriter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
emitEventType: ErrorType,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
e := SysEventer()
require.NotNil(e)
l, err := tt.eventer.StandardWriter(tt.ctx, tt.eventType)
if tt.wantErr {
require.Error(err)
assert.Nil(l)
if tt.wantErrIs != nil {
assert.ErrorIs(err, tt.wantErrIs)
}
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
assert.NotNil(l)
assert.Equal(tt.wantWriter, l)
})
}
}
func Test_logAdapter_Write(t *testing.T) {
// this test and its subtests cannot be run in parallel because of it's
// dependency on the sysEventer
c := TestEventerConfig(t, "Test_StandardLogger")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, InitSysEventer(testLogger, testLock, "Test_StandardLogger", WithEventerConfig(&c.EventerConfig)))
testCtx, err := NewEventerContext(context.Background(), SysEventer())
require.NoError(t, err)
tests := []struct {
name string
adapter *logAdapter
data []byte
wantErr bool
wantIsError error
wantErrContains string
wantErrorEvent string
wantSystemEvent string
}{
{
name: "nil-adapter",
data: []byte("nil-adapter"),
wantErr: true,
wantIsError: ErrInvalidParameter,
wantErrContains: "nil log adapter",
},
{
name: "emit-sys-event",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
emitEventType: SystemType,
},
data: []byte("emit-sys-event"),
wantSystemEvent: "emit-sys-event",
},
{
name: "pick-type-DEBUG",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
},
data: []byte("[DEBUG] pick-type-DEBUG"),
wantSystemEvent: "pick-type-DEBUG",
},
{
name: "pick-type-TRACE",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
},
data: []byte("[TRACE] pick-type-TRACE"),
wantSystemEvent: "pick-type-TRACE",
},
{
name: "pick-type-INFO",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
},
data: []byte("[INFO] pick-type-INFO"),
wantSystemEvent: "pick-type-INFO",
},
{
name: "pick-type-WARN",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
},
data: []byte("[WARN] pick-type-WARN"),
wantSystemEvent: "pick-type-WARN",
},
{
name: "emit-error-event",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
emitEventType: ErrorType,
},
data: []byte("emit-error-event"),
wantErrorEvent: "emit-error-event",
},
{
name: "pick-type-ERR",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
},
data: []byte("[ERR] pick-type-ERR"),
wantErrorEvent: "pick-type-ERR",
},
{
name: "pick-type-ERROR",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
},
data: []byte("[ERROR] pick-type-ERROR"),
wantErrorEvent: "pick-type-ERROR",
},
{
name: "emit-every-type-event",
adapter: &logAdapter{
ctxWithEventer: testCtx,
e: SysEventer(),
emitEventType: EveryType,
},
data: []byte("emit-every-type-event"),
wantSystemEvent: "emit-every-type-event",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
i, err := tt.adapter.Write(tt.data)
if tt.wantErr {
require.Error(err)
assert.Zero(i)
if tt.wantIsError != nil {
assert.ErrorIs(err, tt.wantIsError)
}
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(len(tt.data), i)
sinkFileName := c.AllEvents.Name()
defer func() { _ = os.WriteFile(sinkFileName, nil, 0o666) }()
b, err := ioutil.ReadFile(sinkFileName)
require.NoError(err)
gotEvent := &cloudevents.Event{}
err = json.Unmarshal(b, gotEvent)
require.NoErrorf(err, "json: %s", string(b))
if tt.wantErrorEvent != "" {
gotData := gotEvent.Data.(map[string]interface{})
t.Log(tt.name, gotData)
assert.Equal(tt.wantErrorEvent, gotData["error"])
}
if tt.wantSystemEvent != "" {
gotData := gotEvent.Data.(map[string]interface{})["data"].(map[string]interface{})
t.Log(tt.name, gotData)
assert.Equal(tt.wantSystemEvent, gotData["msg"])
}
})
}
}