From b40a39c4cd14e784efbceeec2cf533eb15a72138 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 12 Jul 2023 12:19:24 +0100 Subject: [PATCH] VAULT-17080: audit formatter node (JSON) (#21769) * Export AuditFormatter, improve tests * Correct issues in 'Date' for tests --- audit/format.go | 18 +- audit/format_json_test.go | 21 +- audit/format_test.go | 231 +++++++++++++---- .../event/event_type_audit_test.go | 4 +- .../event/node_formatter_audit_json.go | 103 ++++++++ .../event/node_formatter_audit_json_test.go | 242 ++++++++++++++++++ internal/observability/event/options_test.go | 16 +- 7 files changed, 548 insertions(+), 87 deletions(-) create mode 100644 internal/observability/event/node_formatter_audit_json.go create mode 100644 internal/observability/event/node_formatter_audit_json_test.go diff --git a/audit/format.go b/audit/format.go index 44dc01ece2..5e461ef61a 100644 --- a/audit/format.go +++ b/audit/format.go @@ -44,13 +44,13 @@ type Writer interface { } var ( - _ Formatter = (*auditFormatter)(nil) + _ Formatter = (*AuditFormatter)(nil) _ Formatter = (*AuditFormatterWriter)(nil) _ Writer = (*AuditFormatterWriter)(nil) ) -// auditFormatter should be used to format audit requests and responses. -type auditFormatter struct { +// AuditFormatter should be used to format audit requests and responses. +type AuditFormatter struct { salter Salter } @@ -98,13 +98,13 @@ func (s *nonPersistentSalt) Salt(_ context.Context) (*salt.Salt, error) { return salt.NewNonpersistentSalt(), nil } -// NewAuditFormatter should be used to create an auditFormatter. -func NewAuditFormatter(salter Salter) (*auditFormatter, error) { +// NewAuditFormatter should be used to create an AuditFormatter. +func NewAuditFormatter(salter Salter) (*AuditFormatter, error) { if salter == nil { return nil, errors.New("cannot create a new audit formatter with nil salter") } - return &auditFormatter{salter: salter}, nil + return &AuditFormatter{salter: salter}, nil } // NewAuditFormatterWriter should be used to create a new AuditFormatterWriter. @@ -125,10 +125,10 @@ func NewAuditFormatterWriter(formatter Formatter, writer Writer) (*AuditFormatte } // FormatRequest attempts to format the specified logical.LogInput into an AuditRequestEntry. -func (f *auditFormatter) FormatRequest(ctx context.Context, config FormatterConfig, in *logical.LogInput) (*AuditRequestEntry, error) { +func (f *AuditFormatter) FormatRequest(ctx context.Context, config FormatterConfig, in *logical.LogInput) (*AuditRequestEntry, error) { switch { case in == nil || in.Request == nil: - return nil, errors.New("request to response-audit a nil request") + return nil, errors.New("request to request-audit a nil request") case f.salter == nil: return nil, errors.New("salt func not configured") } @@ -255,7 +255,7 @@ func (f *auditFormatter) FormatRequest(ctx context.Context, config FormatterConf } // FormatResponse attempts to format the specified logical.LogInput into an AuditResponseEntry. -func (f *auditFormatter) FormatResponse(ctx context.Context, config FormatterConfig, in *logical.LogInput) (*AuditResponseEntry, error) { +func (f *AuditFormatter) FormatResponse(ctx context.Context, config FormatterConfig, in *logical.LogInput) (*AuditResponseEntry, error) { switch { case in == nil || in.Request == nil: return nil, errors.New("request to response-audit a nil request") diff --git a/audit/format_json_test.go b/audit/format_json_test.go index b90c466d05..4ef0a64af0 100644 --- a/audit/format_json_test.go +++ b/audit/format_json_test.go @@ -5,7 +5,6 @@ package audit import ( "bytes" - "context" "encoding/json" "errors" "fmt" @@ -17,27 +16,13 @@ import ( "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/sdk/helper/jsonutil" - "github.com/hashicorp/vault/sdk/helper/salt" "github.com/hashicorp/vault/sdk/logical" ) -// staticSalt is a struct which can be used to obtain a static salt. -// a salt must be assigned when the struct is initialized. -type staticSalt struct { - salt *salt.Salt -} - -// Salt returns the static salt and no error. -func (s *staticSalt) Salt(ctx context.Context) (*salt.Salt, error) { - return s.salt, nil -} - func TestFormatJSON_formatRequest(t *testing.T) { - s, err := salt.NewSalt(context.Background(), nil, nil) - require.NoError(t, err) - tempStaticSalt := &staticSalt{salt: s} + ss := newStaticSalt(t) - expectedResultStr := fmt.Sprintf(testFormatJSONReqBasicStrFmt, s.GetIdentifiedHMAC("foo")) + expectedResultStr := fmt.Sprintf(testFormatJSONReqBasicStrFmt, ss.salt.GetIdentifiedHMAC("foo")) issueTime, _ := time.Parse(time.RFC3339, "2020-05-28T13:40:18-05:00") cases := map[string]struct { @@ -113,7 +98,7 @@ func TestFormatJSON_formatRequest(t *testing.T) { for name, tc := range cases { var buf bytes.Buffer - f, err := NewAuditFormatter(tempStaticSalt) + f, err := NewAuditFormatter(ss) require.NoError(t, err) formatter := AuditFormatterWriter{ Formatter: f, diff --git a/audit/format_test.go b/audit/format_test.go index 3c8499ee17..7dbbf98a04 100644 --- a/audit/format_test.go +++ b/audit/format_test.go @@ -16,6 +16,25 @@ import ( "github.com/stretchr/testify/require" ) +// newStaticSalt returns a new staticSalt for use in testing. +func newStaticSalt(t *testing.T) *staticSalt { + s, err := salt.NewSalt(context.Background(), nil, nil) + require.NoError(t, err) + + return &staticSalt{salt: s} +} + +// staticSalt is a struct which can be used to obtain a static salt. +// a salt must be assigned when the struct is initialized. +type staticSalt struct { + salt *salt.Salt +} + +// Salt returns the static salt and no error. +func (s *staticSalt) Salt(_ context.Context) (*salt.Salt, error) { + return s.salt, nil +} + type testingFormatWriter struct { salt *salt.Salt lastRequest *AuditRequestEntry @@ -67,66 +86,178 @@ func (fw *testingFormatWriter) hashExpectedValueForComparison(input map[string]i return copiedAsMap } -func TestAuditFormat_FormatRequest_Errors(t *testing.T) { - config := FormatterConfig{} - formatter := auditFormatter{} - - entry, err := formatter.FormatRequest(context.Background(), config, &logical.LogInput{}) - require.Error(t, err) - require.Nil(t, entry) -} - -func TestAuditFormatWriter_FormatRequest_Errors(t *testing.T) { - config := FormatterConfig{} - formatter := AuditFormatterWriter{ - Formatter: &auditFormatter{}, - Writer: &testingFormatWriter{}, +// TestNewAuditFormatter tests that creating a new AuditFormatter can be done safely. +func TestNewAuditFormatter(t *testing.T) { + tests := map[string]struct { + Salter Salter + UseStaticSalter bool + IsErrorExpected bool + ExpectedErrorMessag string + }{ + "nil": { + Salter: nil, + IsErrorExpected: true, + ExpectedErrorMessag: "cannot create a new audit formatter with nil salter", + }, + "static": { + UseStaticSalter: true, + IsErrorExpected: false, + ExpectedErrorMessag: "", + }, } - entry, err := formatter.FormatRequest(context.Background(), config, &logical.LogInput{}) - require.Error(t, err) - require.Nil(t, entry) + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + var s Salter + switch { + case tc.UseStaticSalter: + s = newStaticSalt(t) + default: + s = tc.Salter + } - in := &logical.LogInput{ - Request: &logical.Request{}, + f, err := NewAuditFormatter(s) + switch { + case tc.IsErrorExpected: + require.Error(t, err) + require.Nil(t, f) + default: + require.NoError(t, err) + require.NotNil(t, f) + } + }) } - - entry, err = formatter.FormatRequest(context.Background(), config, in) - require.Error(t, err) - require.Nil(t, entry) } -func TestAuditFormat_FormatResponse_Errors(t *testing.T) { - config := FormatterConfig{} - formatter := auditFormatter{} - - entry, err := formatter.FormatResponse(context.Background(), config, &logical.LogInput{}) - require.Error(t, err) - require.Nil(t, entry) - - in := &logical.LogInput{Request: &logical.Request{}} - - entry, err = formatter.FormatResponse(context.Background(), config, in) - require.Error(t, err) - require.Nil(t, entry) -} - -func TestAuditFormatWriter_FormatResponse_Errors(t *testing.T) { - config := FormatterConfig{} - formatter := AuditFormatterWriter{ - Formatter: &auditFormatter{}, - Writer: &testingFormatWriter{}, +// TestAuditFormatter_FormatRequest exercises AuditFormatter.FormatRequest with +// varying inputs. +func TestAuditFormatter_FormatRequest(t *testing.T) { + tests := map[string]struct { + Input *logical.LogInput + IsErrorExpected bool + ExpectedErrorMessage string + RootNamespace bool + }{ + "nil": { + Input: nil, + IsErrorExpected: true, + ExpectedErrorMessage: "request to request-audit a nil request", + }, + "basic-input": { + Input: &logical.LogInput{}, + IsErrorExpected: true, + ExpectedErrorMessage: "request to request-audit a nil request", + }, + "input-and-request-no-ns": { + Input: &logical.LogInput{Request: &logical.Request{ID: "123"}}, + IsErrorExpected: true, + ExpectedErrorMessage: "no namespace", + RootNamespace: false, + }, + "input-and-request-with-ns": { + Input: &logical.LogInput{Request: &logical.Request{ID: "123"}}, + IsErrorExpected: false, + RootNamespace: true, + }, } - entry, err := formatter.FormatResponse(context.Background(), config, &logical.LogInput{}) - require.Error(t, err) - require.Nil(t, entry) + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + config := FormatterConfig{} + f, err := NewAuditFormatter(newStaticSalt(t)) + require.NoError(t, err) - in := &logical.LogInput{Request: &logical.Request{}} + var ctx context.Context + switch { + case tc.RootNamespace: + ctx = namespace.RootContext(context.Background()) + default: + ctx = context.Background() + } - entry, err = formatter.FormatResponse(context.Background(), config, in) - require.Error(t, err) - require.Nil(t, entry) + entry, err := f.FormatRequest(ctx, config, tc.Input) + + switch { + case tc.IsErrorExpected: + require.Error(t, err) + require.EqualError(t, err, tc.ExpectedErrorMessage) + require.Nil(t, entry) + default: + require.NoError(t, err) + require.NotNil(t, entry) + } + }) + } +} + +// TestAuditFormatter_FormatResponse exercises AuditFormatter.FormatResponse with +// varying inputs. +func TestAuditFormatter_FormatResponse(t *testing.T) { + tests := map[string]struct { + Input *logical.LogInput + IsErrorExpected bool + ExpectedErrorMessage string + RootNamespace bool + }{ + "nil": { + Input: nil, + IsErrorExpected: true, + ExpectedErrorMessage: "request to response-audit a nil request", + }, + "basic-input": { + Input: &logical.LogInput{}, + IsErrorExpected: true, + ExpectedErrorMessage: "request to response-audit a nil request", + }, + "input-and-request-no-ns": { + Input: &logical.LogInput{Request: &logical.Request{ID: "123"}}, + IsErrorExpected: true, + ExpectedErrorMessage: "no namespace", + RootNamespace: false, + }, + "input-and-request-with-ns": { + Input: &logical.LogInput{Request: &logical.Request{ID: "123"}}, + IsErrorExpected: false, + RootNamespace: true, + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + config := FormatterConfig{} + f, err := NewAuditFormatter(newStaticSalt(t)) + require.NoError(t, err) + + var ctx context.Context + switch { + case tc.RootNamespace: + ctx = namespace.RootContext(context.Background()) + default: + ctx = context.Background() + } + + entry, err := f.FormatResponse(ctx, config, tc.Input) + + switch { + case tc.IsErrorExpected: + require.Error(t, err) + require.EqualError(t, err, tc.ExpectedErrorMessage) + require.Nil(t, entry) + default: + require.NoError(t, err) + require.NotNil(t, entry) + } + }) + } } func TestElideListResponses(t *testing.T) { diff --git a/internal/observability/event/event_type_audit_test.go b/internal/observability/event/event_type_audit_test.go index ba4ca0ea41..87a2d00e9c 100644 --- a/internal/observability/event/event_type_audit_test.go +++ b/internal/observability/event/event_type_audit_test.go @@ -42,11 +42,11 @@ func TestAuditEvent_New(t *testing.T) { WithID("audit_123"), WithFormat(string(AuditFormatJSON)), WithSubtype(string(AuditResponse)), - WithNow(time.Date(2023, time.July, 4, 12, 0o3, 0o0, 0o0, &time.Location{})), + WithNow(time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local)), }, IsErrorExpected: false, ExpectedID: "audit_123", - ExpectedTimestamp: time.Date(2023, time.July, 4, 12, 0o3, 0o0, 0o0, &time.Location{}), + ExpectedTimestamp: time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local), ExpectedSubtype: AuditResponse, ExpectedFormat: AuditFormatJSON, }, diff --git a/internal/observability/event/node_formatter_audit_json.go b/internal/observability/event/node_formatter_audit_json.go new file mode 100644 index 0000000000..25f4e9ee94 --- /dev/null +++ b/internal/observability/event/node_formatter_audit_json.go @@ -0,0 +1,103 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package event + +import ( + "context" + "fmt" + + vaultaudit "github.com/hashicorp/vault/audit" + "github.com/hashicorp/vault/sdk/helper/jsonutil" + + "github.com/hashicorp/eventlogger" +) + +var _ eventlogger.Node = (*AuditFormatterJSON)(nil) + +// AuditFormatterJSON represents the formatter node which is used to handle +// formatting audit events as JSON. +type AuditFormatterJSON struct { + config vaultaudit.FormatterConfig + format auditFormat + formatter vaultaudit.Formatter +} + +// NewAuditFormatterJSON should be used to create an AuditFormatterJSON. +func NewAuditFormatterJSON(config vaultaudit.FormatterConfig, salter vaultaudit.Salter) (*AuditFormatterJSON, error) { + const op = "event.NewAuditFormatterJSON" + + f, err := vaultaudit.NewAuditFormatter(salter) + if err != nil { + return nil, fmt.Errorf("%s: unable to create new JSON audit formatter: %w", op, err) + } + + jsonFormatter := &AuditFormatterJSON{ + format: AuditFormatJSON, + config: config, + formatter: f, + } + + return jsonFormatter, nil +} + +// Reopen is a no-op for a formatter node. +func (_ *AuditFormatterJSON) Reopen() error { + return nil +} + +// Type describes the type of this node (formatter). +func (_ *AuditFormatterJSON) Type() eventlogger.NodeType { + return eventlogger.NodeTypeFormatter +} + +// Process will attempt to parse the incoming event data into a corresponding +// audit request/response entry which is serialized to JSON and stored within the event. +func (f *AuditFormatterJSON) Process(ctx context.Context, e *eventlogger.Event) (*eventlogger.Event, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + const op = "event.(AuditFormatterJSON).Process" + if e == nil { + return nil, fmt.Errorf("%s: event is nil: %w", op, ErrInvalidParameter) + } + + a, ok := e.Payload.(*audit) + if !ok { + return nil, fmt.Errorf("%s: cannot parse event payload: %w", op, ErrInvalidParameter) + } + + var formatted []byte + + switch a.Subtype { + case AuditRequest: + entry, err := f.formatter.FormatRequest(ctx, f.config, a.Data) + if err != nil { + return nil, fmt.Errorf("%s: unable to parse request from audit event: %w", op, err) + } + + formatted, err = jsonutil.EncodeJSON(entry) + if err != nil { + return nil, fmt.Errorf("%s: unable to format request: %w", op, err) + } + case AuditResponse: + entry, err := f.formatter.FormatResponse(ctx, f.config, a.Data) + if err != nil { + return nil, fmt.Errorf("%s: unable to parse response from audit event: %w", op, err) + } + + formatted, err = jsonutil.EncodeJSON(entry) + if err != nil { + return nil, fmt.Errorf("%s: unable to format response: %w", op, err) + } + default: + return nil, fmt.Errorf("%s: unknown audit event subtype: %q", op, a.Subtype) + } + + e.FormattedAs(f.format.String(), formatted) + + return e, nil +} diff --git a/internal/observability/event/node_formatter_audit_json_test.go b/internal/observability/event/node_formatter_audit_json_test.go new file mode 100644 index 0000000000..85674edeec --- /dev/null +++ b/internal/observability/event/node_formatter_audit_json_test.go @@ -0,0 +1,242 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package event + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/vault/helper/namespace" + + "github.com/hashicorp/vault/sdk/logical" + + vaultaudit "github.com/hashicorp/vault/audit" + + "github.com/hashicorp/vault/sdk/helper/salt" + + "github.com/hashicorp/eventlogger" + "github.com/stretchr/testify/require" +) + +// fakeJSONAuditEvent will return a new fake event containing audit data based +// on the specified auditSubtype and logical.LogInput. +func fakeJSONAuditEvent(t *testing.T, subtype auditSubtype, input *logical.LogInput) *eventlogger.Event { + t.Helper() + + date := time.Date(2023, time.July, 11, 15, 49, 10, 0o0, time.Local) + + auditEvent, err := newAudit( + WithID("123"), + WithSubtype(string(subtype)), + WithFormat(string(AuditFormatJSON)), + WithNow(date), + ) + require.NoError(t, err) + require.NotNil(t, auditEvent) + require.Equal(t, "123", auditEvent.ID) + require.Equal(t, "v0.1", auditEvent.Version) + require.Equal(t, AuditFormatJSON, auditEvent.RequiredFormat) + require.Equal(t, subtype, auditEvent.Subtype) + require.Equal(t, date, auditEvent.Timestamp) + + auditEvent.Data = input + + e := &eventlogger.Event{ + Type: eventlogger.EventType(AuditType), + CreatedAt: auditEvent.Timestamp, + Formatted: make(map[string][]byte), + Payload: auditEvent, + } + + return e +} + +// newStaticSalt returns a new staticSalt for use in testing. +func newStaticSalt(t *testing.T) *staticSalt { + s, err := salt.NewSalt(context.Background(), nil, nil) + require.NoError(t, err) + + return &staticSalt{salt: s} +} + +// staticSalt is a struct which can be used to obtain a static salt. +// a salt must be assigned when the struct is initialized. +type staticSalt struct { + salt *salt.Salt +} + +// Salt returns the static salt and no error. +func (s *staticSalt) Salt(_ context.Context) (*salt.Salt, error) { + return s.salt, nil +} + +// TestNewAuditFormatterJSON ensures we can create new AuditFormatterJSONX structs. +func TestNewAuditFormatterJSON(t *testing.T) { + tests := map[string]struct { + UseStaticSalt bool + IsErrorExpected bool + ExpectedErrorMessage string + }{ + "nil-salter": { + UseStaticSalt: false, + IsErrorExpected: true, + ExpectedErrorMessage: "event.NewAuditFormatterJSON: unable to create new JSON audit formatter: cannot create a new audit formatter with nil salter", + }, + "static-salter": { + UseStaticSalt: true, + IsErrorExpected: false, + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + cfg := vaultaudit.FormatterConfig{} + var ss vaultaudit.Salter + if tc.UseStaticSalt { + ss = newStaticSalt(t) + } + + f, err := NewAuditFormatterJSON(cfg, ss) + + switch { + case tc.IsErrorExpected: + require.Error(t, err) + require.EqualError(t, err, tc.ExpectedErrorMessage) + require.Nil(t, f) + default: + require.NoError(t, err) + require.NotNil(t, f) + } + }) + } +} + +// TestAuditFormatterJSONX_Reopen ensures that we do no get an error when calling Reopen. +func TestAuditFormatterJSON_Reopen(t *testing.T) { + ss := newStaticSalt(t) + cfg := vaultaudit.FormatterConfig{} + + f, err := NewAuditFormatterJSON(cfg, ss) + require.NoError(t, err) + require.NotNil(t, f) + require.NoError(t, f.Reopen()) +} + +// TestAuditFormatterJSONX_Type ensures that the node is a 'formatter' type. +func TestAuditFormatterJSON_Type(t *testing.T) { + ss := newStaticSalt(t) + cfg := vaultaudit.FormatterConfig{} + + f, err := NewAuditFormatterJSON(cfg, ss) + require.NoError(t, err) + require.NotNil(t, f) + require.Equal(t, eventlogger.NodeTypeFormatter, f.Type()) +} + +// TestAuditFormatterJSON_Process attempts to run the Process method to convert +// the logical.LogInput within an audit event to JSON (AuditRequestEntry or AuditResponseEntry). +func TestAuditFormatterJSON_Process(t *testing.T) { + tests := map[string]struct { + IsErrorExpected bool + ExpectedErrorMessage string + Subtype auditSubtype + Data *logical.LogInput + RootNamespace bool + }{ + "request-no-data": { + IsErrorExpected: true, + ExpectedErrorMessage: "event.(AuditFormatterJSON).Process: unable to parse request from audit event: request to request-audit a nil request", + Subtype: AuditRequest, + Data: nil, + }, + "response-no-data": { + IsErrorExpected: true, + ExpectedErrorMessage: "event.(AuditFormatterJSON).Process: unable to parse response from audit event: request to response-audit a nil request", + Subtype: AuditResponse, + Data: nil, + }, + "request-basic-input": { + IsErrorExpected: true, + ExpectedErrorMessage: "event.(AuditFormatterJSON).Process: unable to parse request from audit event: request to request-audit a nil request", + Subtype: AuditRequest, + Data: &logical.LogInput{Type: "magic"}, + }, + "response-basic-input": { + IsErrorExpected: true, + ExpectedErrorMessage: "event.(AuditFormatterJSON).Process: unable to parse response from audit event: request to response-audit a nil request", + Subtype: AuditResponse, + Data: &logical.LogInput{Type: "magic"}, + }, + "request-basic-input-and-request-no-ns": { + IsErrorExpected: true, + ExpectedErrorMessage: "event.(AuditFormatterJSON).Process: unable to parse request from audit event: no namespace", + Subtype: AuditRequest, + Data: &logical.LogInput{Request: &logical.Request{ID: "123"}}, + }, + "response-basic-input-and-request-no-ns": { + IsErrorExpected: true, + ExpectedErrorMessage: "event.(AuditFormatterJSON).Process: unable to parse response from audit event: no namespace", + Subtype: AuditResponse, + Data: &logical.LogInput{Request: &logical.Request{ID: "123"}}, + }, + "request-basic-input-and-request-with-ns": { + IsErrorExpected: false, + Subtype: AuditRequest, + Data: &logical.LogInput{Request: &logical.Request{ID: "123"}}, + RootNamespace: true, + }, + "response-basic-input-and-request-with-ns": { + IsErrorExpected: false, + Subtype: AuditResponse, + Data: &logical.LogInput{Request: &logical.Request{ID: "123"}}, + RootNamespace: true, + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + e := fakeJSONAuditEvent(t, tc.Subtype, tc.Data) + require.NotNil(t, e) + + ss := newStaticSalt(t) + cfg := vaultaudit.FormatterConfig{} + + f, err := NewAuditFormatterJSON(cfg, ss) + require.NoError(t, err) + require.NotNil(t, f) + + var ctx context.Context + switch { + case tc.RootNamespace: + ctx = namespace.RootContext(context.Background()) + default: + ctx = context.Background() + } + + processed, err := f.Process(ctx, e) + b, found := e.Format(string(AuditFormatJSON)) + + switch { + case tc.IsErrorExpected: + require.Error(t, err) + require.EqualError(t, err, tc.ExpectedErrorMessage) + require.Nil(t, processed) + require.False(t, found) + require.Nil(t, b) + default: + require.NoError(t, err) + require.NotNil(t, processed) + require.True(t, found) + require.NotNil(t, b) + } + }) + } +} diff --git a/internal/observability/event/options_test.go b/internal/observability/event/options_test.go index 8a971e4d4b..e236872f93 100644 --- a/internal/observability/event/options_test.go +++ b/internal/observability/event/options_test.go @@ -114,9 +114,9 @@ func TestOptions_WithNow(t *testing.T) { ExpectedErrorMessage: "cannot specify 'now' to be the zero time instant", }, "valid-time": { - Value: time.Date(2023, time.July, 4, 12, 0o3, 0o0, 0o0, &time.Location{}), + Value: time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local), IsErrorExpected: false, - ExpectedValue: time.Date(2023, time.July, 4, 12, 0o3, 0o0, 0o0, &time.Location{}), + ExpectedValue: time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local), }, } @@ -245,16 +245,16 @@ func TestOptions_Opts(t *testing.T) { }, "with-multiple-valid-now": { opts: []Option{ - WithNow(time.Date(2023, time.July, 4, 12, 0o3, 0o0, 0o0, &time.Location{})), - WithNow(time.Date(2023, time.July, 4, 13, 0o3, 0o0, 0o0, &time.Location{})), + WithNow(time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local)), + WithNow(time.Date(2023, time.July, 4, 13, 3, 0, 0, time.Local)), }, IsErrorExpected: false, - ExpectedNow: time.Date(2023, time.July, 4, 13, 0o3, 0o0, 0o0, &time.Location{}), + ExpectedNow: time.Date(2023, time.July, 4, 13, 3, 0, 0, time.Local), IsNowExpected: false, }, "with-multiple-valid-then-invalid-now": { opts: []Option{ - WithNow(time.Date(2023, time.July, 4, 12, 0o3, 0o0, 0o0, &time.Location{})), + WithNow(time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local)), WithNow(time.Time{}), }, IsErrorExpected: true, @@ -265,13 +265,13 @@ func TestOptions_Opts(t *testing.T) { WithID("qwerty"), WithSubtype("typey2"), WithFormat("json"), - WithNow(time.Date(2023, time.July, 4, 12, 0o3, 0o0, 0o0, &time.Location{})), + WithNow(time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local)), }, IsErrorExpected: false, ExpectedID: "qwerty", ExpectedSubtype: "typey2", ExpectedFormat: "json", - ExpectedNow: time.Date(2023, time.July, 4, 12, 0o3, 0o0, 0o0, &time.Location{}), + ExpectedNow: time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local), }, }