audittest

package
v0.1.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package audittest provides test helpers for consumers of the audit library. It provides an in-memory Recorder that captures audit events for assertion, a MetricsRecorder that captures all metrics calls, and convenience constructors that eliminate test boilerplate.

The test auditor is a fully functional audit.Auditor — same validation, same taxonomy enforcement — but events land in memory instead of being written to a file, syslog, or webhook.

Both New and NewQuick default to synchronous delivery: events are available in the Recorder immediately after audit.Auditor.AuditEvent returns. No Close-before-assert ceremony is needed. Use WithAsync to opt into asynchronous delivery for tests that exercise drain timeout or buffer backpressure.

Quick Start

func TestMyHandler(t *testing.T) {
    auditor, events, metrics := audittest.New(t, taxonomyYAML)
    myHandler(auditor) // code under test

    // Assert immediately — synchronous delivery means events are already available.
    require.Equal(t, 1, events.Count())
    assert.Equal(t, "user_create", events.Events()[0].EventType)
    assert.Equal(t, 1, metrics.EventDeliveries("recorder", "success"))
}

Table-Driven Tests

Use Recorder.Reset to clear captured events between sub-tests without creating a new auditor:

for _, tc := range tests {
    t.Run(tc.name, func(t *testing.T) {
        events.Reset()
        svc.Do(tc.action)
        // assert on events for this sub-test only
    })
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CNMismatchCert added in v0.1.12

func CNMismatchCert(tb testing.TB) (certPath, keyPath, caPath string)

CNMismatchCert is the matching convenience wrapper for the CN/SAN-mismatch cert pair. Returns (BadCerts.CNMismatchCertPath, .CNMismatchKeyPath, .CAPath).

func ExpiredCert added in v0.1.12

func ExpiredCert(tb testing.TB) (certPath, keyPath, caPath string)

ExpiredCert is a convenience wrapper that returns just the expired-NotAfter cert pair (plus its CA path). Equivalent to (BadCerts.ExpiredCertPath, .ExpiredKeyPath, .CAPath) from GenerateBadCerts.

func New added in v0.1.10

func New(tb testing.TB, taxonomyYAML []byte, opts ...Option) (*audit.Auditor, *Recorder, *MetricsRecorder)

New creates a test auditor with an in-memory Recorder and MetricsRecorder. The taxonomy is parsed from YAML bytes.

The auditor defaults to synchronous delivery — events are available in the Recorder immediately after audit.Auditor.AuditEvent returns, with no Close-before-assert ceremony needed. Use WithAsync to opt into asynchronous delivery for tests that exercise drain timeout or buffer backpressure.

QueueSize defaults to 100 (intentionally small — the in-memory recorder has no I/O cost). tb.Cleanup is registered to call auditor.Close() as a safety net against goroutine leaks.

Example
package main

import (
	"fmt"
	"testing"

	"github.com/axonops/audit"
	"github.com/axonops/audit/audittest"
)

var exampleTaxonomyYAML = []byte(`
version: 1
categories:
  write:
    - user_create
events:
  user_create:
    fields:
      outcome: {required: true}
      actor_id: {required: true}
`)

func main() {
	// Use a real *testing.T in actual tests — this is for runnable doc only.
	t := &testing.T{}

	auditor, events, metrics := audittest.New(t, exampleTaxonomyYAML)

	_ = auditor.AuditEvent(audit.NewEvent("user_create", audit.Fields{
		"outcome":  "success",
		"actor_id": "alice",
	}))

	fmt.Println("count:", events.Count())
	fmt.Println("type:", events.Events()[0].EventType)
	fmt.Println("deliveries:", metrics.EventDeliveries("recorder", "success"))
}
Output:
count: 1
type: user_create
deliveries: 1

func NewQuick added in v0.1.10

func NewQuick(tb testing.TB, eventTypes ...string) (*audit.Auditor, *Recorder, *MetricsRecorder)

NewQuick creates a test auditor with a permissive taxonomy containing the named event types. No required fields, no unknown field validation. Defaults to synchronous delivery — events are available in the Recorder immediately without calling Close.

Example
package main

import (
	"fmt"
	"testing"

	"github.com/axonops/audit"
	"github.com/axonops/audit/audittest"
)

func main() {
	t := &testing.T{}

	auditor, events, _ := audittest.NewQuick(t, "user_create", "user_delete")

	_ = auditor.AuditEvent(audit.NewEvent("user_create", audit.Fields{
		"any_field": "any_value",
	}))

	fmt.Println("count:", events.Count())
	fmt.Println("type:", events.Events()[0].EventType)
}
Output:
count: 1
type: user_create

func QuickTaxonomy

func QuickTaxonomy(eventTypes ...string) *audit.Taxonomy

QuickTaxonomy builds a minimal *audit.Taxonomy where every listed event type accepts any fields. All events are in a single enabled category ("test"). The returned taxonomy does not enforce required fields; pair it with audit.ValidationPermissive (as NewQuick does) for fully unconstrained testing.

func WriteKeyPEM added in v0.1.12

func WriteKeyPEM(tb testing.TB, path string, key *ecdsa.PrivateKey)

WriteKeyPEM writes an ECDSA private key as PEM to the given path.

func WritePEM added in v0.1.12

func WritePEM(tb testing.TB, path, blockType string, data []byte)

WritePEM writes a PEM-encoded block to the given path. Exposed so callers that need to assemble bespoke cert bundles can reuse the PEM-marshalling boilerplate.

Types

type BadCerts added in v0.1.12

type BadCerts struct {
	// CAPath is the self-signed CA that signed both bad-cert pairs.
	CAPath string

	// ExpiredCertPath / ExpiredKeyPath: NotAfter set in the past,
	// SANs include localhost + 127.0.0.1.
	ExpiredCertPath string
	ExpiredKeyPath  string

	// CNMismatchCertPath / CNMismatchKeyPath: NotAfter valid but
	// SANs only include "elsewhere.example.com" (CN/SAN does not
	// match a client connecting to 127.0.0.1 or localhost).
	CNMismatchCertPath string
	CNMismatchKeyPath  string
}

BadCerts holds a CA plus two server certificates with deliberate defects, used by negative-path TLS tests (#552). Both server certs are signed by the same runtime CA, so a client trusting CAPath fails for the installed defect — expired NotAfter or CN/SAN mismatch — rather than "unknown authority". A separate implementation in tests/bdd/steps/tls_negative_steps.go uses a different shape (in-memory tls.Config rather than file paths) and may be reconciled with this struct in a future PR.

func GenerateBadCerts added in v0.1.12

func GenerateBadCerts(tb testing.TB) *BadCerts

GenerateBadCerts produces a fresh CA and two server certificates (one expired, one with CN/SAN mismatch) under testing.TB.TempDir. ECDSA P-256. The two server cert pairs are independently usable in TLS server configs that need to be rejected by a client that trusts CAPath.

  • Expired: NotAfter set one hour in the past; DNSNames "localhost" + IPAddresses 127.0.0.1.
  • CN-mismatch: NotAfter valid; DNSNames "elsewhere.example.com".

Use ExpiredCert / CNMismatchCert for a single-cert convenience when only one defect is needed.

type MetricsRecorder

type MetricsRecorder struct {
	// contains filtered or unexported fields
}

MetricsRecorder implements audit.Metrics and captures all metric calls for assertion. It is safe for concurrent use.

MetricsRecorder also satisfies the output-specific extension interfaces ([file.RotationRecorder], [syslog.ReconnectRecorder]) via structural typing, enabling per-output rotation and reconnection recording.

func NewMetricsRecorder

func NewMetricsRecorder() *MetricsRecorder

NewMetricsRecorder creates a MetricsRecorder ready for use with audit.WithMetrics.

func (*MetricsRecorder) BufferDrops

func (m *MetricsRecorder) BufferDrops() int

BufferDrops returns the total number of buffer-full drops.

func (*MetricsRecorder) EventDeliveries

func (m *MetricsRecorder) EventDeliveries(output string, status audit.EventStatus) int

EventDeliveries returns the number of delivery attempts recorded for the given output and audit.EventStatus (audit.EventSuccess or audit.EventError).

func (*MetricsRecorder) FileRotations added in v0.1.2

func (m *MetricsRecorder) FileRotations(path string) int

FileRotations returns the count of file rotations for the given path.

func (*MetricsRecorder) FilteredCount

func (m *MetricsRecorder) FilteredCount(eventType string) int

FilteredCount returns the count of globally filtered events for the given event type.

func (*MetricsRecorder) OutputErrors

func (m *MetricsRecorder) OutputErrors(output string) int

OutputErrors returns the count of write errors for the given output.

func (*MetricsRecorder) OutputFiltered

func (m *MetricsRecorder) OutputFiltered(output string) int

OutputFiltered returns the count of per-output route-filtered events.

func (*MetricsRecorder) RecordBufferDrop

func (m *MetricsRecorder) RecordBufferDrop()

RecordBufferDrop implements audit.Metrics.

func (*MetricsRecorder) RecordDelivery added in v0.1.12

func (m *MetricsRecorder) RecordDelivery(output string, status audit.EventStatus)

RecordDelivery implements audit.Metrics.

func (*MetricsRecorder) RecordFiltered

func (m *MetricsRecorder) RecordFiltered(eventType string)

RecordFiltered implements audit.Metrics.

func (*MetricsRecorder) RecordOutputError

func (m *MetricsRecorder) RecordOutputError(output string)

RecordOutputError implements audit.Metrics.

func (*MetricsRecorder) RecordOutputFiltered

func (m *MetricsRecorder) RecordOutputFiltered(output string)

RecordOutputFiltered implements audit.Metrics.

func (*MetricsRecorder) RecordQueueDepth added in v0.1.10

func (m *MetricsRecorder) RecordQueueDepth(_, _ int)

RecordQueueDepth implements audit.Metrics.

func (*MetricsRecorder) RecordReconnect added in v0.1.12

func (m *MetricsRecorder) RecordReconnect(address string, success bool)

RecordReconnect satisfies syslog.ReconnectRecorder (#581).

func (*MetricsRecorder) RecordRotation added in v0.1.12

func (m *MetricsRecorder) RecordRotation(path string)

RecordRotation satisfies file.RotationRecorder (#581).

func (*MetricsRecorder) RecordSerializationError

func (m *MetricsRecorder) RecordSerializationError(eventType string)

RecordSerializationError implements audit.Metrics.

func (*MetricsRecorder) RecordSubmitted added in v0.1.10

func (m *MetricsRecorder) RecordSubmitted()

RecordSubmitted implements audit.Metrics.

func (*MetricsRecorder) RecordValidationError

func (m *MetricsRecorder) RecordValidationError(eventType string)

RecordValidationError implements audit.Metrics.

func (*MetricsRecorder) Reset

func (m *MetricsRecorder) Reset()

Reset clears all recorded metrics.

func (*MetricsRecorder) SerializationErrors

func (m *MetricsRecorder) SerializationErrors(eventType string) int

SerializationErrors returns the count of serialisation errors for the given event type.

func (*MetricsRecorder) SubmittedCount added in v0.1.10

func (m *MetricsRecorder) SubmittedCount() int

SubmittedCount returns the total number of events submitted via audit.Auditor.AuditEvent, before any filtering or buffering.

func (*MetricsRecorder) SyslogReconnects added in v0.1.2

func (m *MetricsRecorder) SyslogReconnects(address string, success bool) int

SyslogReconnects returns the count of syslog reconnections for the given address and outcome.

func (*MetricsRecorder) ValidationErrors

func (m *MetricsRecorder) ValidationErrors(eventType string) int

ValidationErrors returns the count of validation errors recorded for the given event type.

type Option

type Option func(*config)

Option configures the test auditor created by New.

func WithAsync added in v0.1.2

func WithAsync() Option

WithAsync creates an asynchronous test auditor. Events are delivered by a background goroutine, so callers MUST call auditor.Close() before making assertions. Use this only when testing async-specific behaviour such as drain timeout or buffer backpressure.

func WithAuditOption added in v0.1.10

func WithAuditOption(opt audit.Option) Option

WithAuditOption passes an arbitrary audit.Option through to the underlying audit.New call. Use for options not covered by the audittest.With* helpers (e.g., WithNamedOutput, WithFormatter, WithAppName, WithHost).

func WithDisabled

func WithDisabled() Option

WithDisabled creates a disabled (no-op) test auditor. Events are accepted without error but not delivered.

func WithExcludeLabels added in v0.1.12

func WithExcludeLabels(outputName string, labels ...string) Option

WithExcludeLabels applies sensitivity-label exclusion to the test Recorder, mirroring audit.WithExcludeLabels on a named output. Fields whose taxonomy labels match any of the given labels are stripped before delivery to the recorder.

Use this to unit-test compliance workflows — e.g., verify that a "pii"-labelled field does not appear in an output configured for an external analytics pipeline. The taxonomy passed to New MUST define a sensitivity section covering every label, else audit.New returns an error and the test fails at construction.

outputName MUST match the recorder's name — "recorder" by default, or whatever was passed to NewNamedRecorder. The parameter is explicit for symmetry with audit.WithNamedOutput and to leave room for future multi-output audittest expansion. A mismatch causes New / NewQuick to call tb.Fatalf.

Multiple calls accumulate: WithExcludeLabels("recorder", "pii"), WithExcludeLabels("recorder", "financial") strips both. Passing no labels (WithExcludeLabels("recorder")) is a no-op at the strip level — it still engages the named-output plumbing branch but no fields are stripped. Framework fields are never stripped, per audit.WithExcludeLabels.

Example

ExampleWithExcludeLabels shows how to assert that a compliance output does NOT receive pii-labelled fields. The recorder acts as the compliance destination; labels defined in the taxonomy drive the strip.

package main

import (
	"fmt"
	"testing"

	"github.com/axonops/audit"
	"github.com/axonops/audit/audittest"
)

// exampleSensitivityTaxonomyYAML registers a "pii" sensitivity label
// so ExampleWithExcludeLabels can exercise the strip path.
var exampleSensitivityTaxonomyYAML = []byte(`
version: 1
sensitivity:
  labels:
    pii:
      fields: [email]
categories:
  write:
    - user_create
events:
  user_create:
    fields:
      outcome: {required: true}
      actor_id: {required: true}
      email: {}
`)

func main() {
	t := &testing.T{}

	auditor, events, _ := audittest.New(t, exampleSensitivityTaxonomyYAML,
		audittest.WithExcludeLabels("recorder", "pii"),
	)

	_ = auditor.AuditEvent(audit.NewEvent("user_create", audit.Fields{
		"outcome":  "success",
		"actor_id": "alice",
		"email":    "alice@example.com",
	}))

	evt := events.Events()[0]
	fmt.Println("actor_id:", evt.StringField("actor_id"))
	fmt.Println("email present:", evt.Field("email") != nil)
}
Output:
actor_id: alice
email present: false

func WithSync

func WithSync() Option

WithSync creates a synchronous test auditor where events are available in the Recorder immediately after audit.Auditor.AuditEvent returns. No Close-before-assert ceremony is needed.

Both New and NewQuick default to synchronous delivery, so this option is only needed to re-enable sync after WithAsync.

func WithValidationMode

func WithValidationMode(mode audit.ValidationMode) Option

WithValidationMode sets the taxonomy validation mode. Default is audit.ValidationStrict.

func WithVerbose added in v0.1.10

func WithVerbose() Option

WithVerbose re-enables diagnostic log output from the auditor. By default, test auditors silence diagnostic logs (lifecycle messages, shutdown notices) to keep test output clean. Use this option when debugging auditor behaviour in tests.

type OutputMetricsRecorder added in v0.1.10

type OutputMetricsRecorder struct {
	// contains filtered or unexported fields
}

OutputMetricsRecorder implements audit.OutputMetrics and captures all per-output metric calls for assertion. Pass it to an output's `WithOutputMetrics` constructor option when testing custom outputs. It is safe for concurrent use.

func NewOutputMetricsRecorder added in v0.1.10

func NewOutputMetricsRecorder() *OutputMetricsRecorder

NewOutputMetricsRecorder creates an OutputMetricsRecorder.

func (*OutputMetricsRecorder) DropCount added in v0.1.10

func (m *OutputMetricsRecorder) DropCount() int

DropCount returns the number of recorded drops.

func (*OutputMetricsRecorder) ErrorCount added in v0.1.10

func (m *OutputMetricsRecorder) ErrorCount() int

ErrorCount returns the number of recorded errors.

func (*OutputMetricsRecorder) FlushCount added in v0.1.10

func (m *OutputMetricsRecorder) FlushCount() int

FlushCount returns the number of recorded flushes.

func (*OutputMetricsRecorder) RecordDrop added in v0.1.10

func (m *OutputMetricsRecorder) RecordDrop()

RecordDrop implements audit.OutputMetrics.

func (*OutputMetricsRecorder) RecordError added in v0.1.10

func (m *OutputMetricsRecorder) RecordError()

RecordError implements audit.OutputMetrics.

func (*OutputMetricsRecorder) RecordFlush added in v0.1.10

func (m *OutputMetricsRecorder) RecordFlush(_ int, d time.Duration)

RecordFlush implements audit.OutputMetrics.

func (*OutputMetricsRecorder) RecordQueueDepth added in v0.1.10

func (m *OutputMetricsRecorder) RecordQueueDepth(_, _ int)

RecordQueueDepth implements audit.OutputMetrics.

func (*OutputMetricsRecorder) RecordRetry added in v0.1.10

func (m *OutputMetricsRecorder) RecordRetry(_ int)

RecordRetry implements audit.OutputMetrics.

func (*OutputMetricsRecorder) Reset added in v0.1.10

func (m *OutputMetricsRecorder) Reset()

Reset clears all recorded output metrics.

func (*OutputMetricsRecorder) RetryCount added in v0.1.10

func (m *OutputMetricsRecorder) RetryCount() int

RetryCount returns the number of recorded retries.

type RecordedEvent

type RecordedEvent struct {
	// EventType is the event type name (e.g., "user_create").
	EventType string
	// Severity is the resolved severity (0-10).
	Severity int
	// Timestamp is the event timestamp set by the drain goroutine.
	Timestamp time.Time
	// Fields contains all non-framework fields. Note: numeric values
	// are stored as float64 due to JSON round-tripping. Use
	// [RecordedEvent.IntField] for int assertions.
	Fields map[string]any
	// RawJSON is the original serialised bytes for format-level assertions.
	RawJSON []byte
	// ParseErr holds any error from JSON deserialisation of the raw bytes.
	// When non-nil, EventType, Severity, Timestamp, and Fields are all
	// zero-valued. Tests SHOULD assert ParseErr == nil before inspecting
	// other fields to avoid masking serialisation bugs.
	ParseErr error
}

RecordedEvent is a single captured audit event with structured access to its fields. Events are captured after serialisation and deserialised back into structured form.

Fields contains the event's non-framework field values. Values follow encoding/json conventions: numbers are float64, nested objects are map[string]interface{}, arrays are []interface{}.

func (RecordedEvent) BoolField added in v0.1.10

func (e RecordedEvent) BoolField(key string) bool

BoolField returns the value of the named field as a bool. Returns false if the key is missing or the value is not a bool.

func (RecordedEvent) Field

func (e RecordedEvent) Field(key string) any

Field returns the value of the named field, or nil if not present.

func (RecordedEvent) FloatField

func (e RecordedEvent) FloatField(key string) float64

FloatField returns the value of the named field as a float64. Returns 0 if the key is missing or the value is not numeric.

func (RecordedEvent) GoString

func (e RecordedEvent) GoString() string

GoString returns a human-readable representation of the event, suitable for test failure messages.

func (RecordedEvent) HasField

func (e RecordedEvent) HasField(key string, want any) bool

HasField reports whether the event has a field with the given key and value. Comparison uses reflect.DeepEqual.

func (RecordedEvent) IntField

func (e RecordedEvent) IntField(key string) int

IntField returns the value of the named field as an int. JSON round-tripping stores all numbers as float64, so this method handles the float64→int coercion transparently.

func (RecordedEvent) StringField

func (e RecordedEvent) StringField(key string) string

StringField returns the value of the named field as a string. Returns the empty string if the key is missing or the value is not a string.

func (RecordedEvent) UserFields added in v0.1.2

func (e RecordedEvent) UserFields() map[string]any

UserFields returns a copy of the event's fields with framework fields removed (event_category, app_name, host, timezone, pid, duration_ms, _hmac, _hmac_version). This is useful for count assertions where framework fields would inflate the total.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder implements audit.Output and captures events in memory for assertion. It is safe for concurrent use by the drain goroutine (writing) and the test goroutine (reading).

The Recorder only parses JSON-formatted events. Events formatted with CEFFormatter will have ParseErr set and structured fields will be zero-valued.

func NewNamedRecorder added in v0.1.10

func NewNamedRecorder(name string) *Recorder

NewNamedRecorder creates a Recorder with a custom name. The name is returned by Recorder.Name and appears in metrics keys.

func NewRecorder

func NewRecorder() *Recorder

NewRecorder creates a Recorder with the default name "recorder". Use it with audit.WithOutputs or audit.WithNamedOutput when composing an auditor manually. For the common case, use New or NewQuick instead.

func (*Recorder) AssertContains added in v0.1.10

func (r *Recorder) AssertContains(tb testing.TB, eventType string, fields audit.Fields)

AssertContains asserts that at least one event of the given type was recorded with fields matching every key-value pair in fields. It calls tb.Error (not Fatal) on failure, allowing further assertions to run.

func (*Recorder) Close

func (r *Recorder) Close() error

Close implements audit.Output. It is a no-op — the Recorder does not own any resources.

func (*Recorder) Count

func (r *Recorder) Count() int

Count returns the number of recorded events.

func (*Recorder) Events

func (r *Recorder) Events() []RecordedEvent

Events returns a snapshot of all recorded events in drain order. The returned slice is a shallow copy of the event list. Later Reset calls do not affect the slice, but the Fields maps within events share references with the Recorder's internal state. Call after audit.Auditor.Close to ensure all events have been processed.

func (*Recorder) FindByField added in v0.1.10

func (r *Recorder) FindByField(key string, val any) []RecordedEvent

FindByField returns all recorded events where the given field matches val (compared with reflect.DeepEqual).

func (*Recorder) FindByType

func (r *Recorder) FindByType(eventType string) []RecordedEvent

FindByType returns all recorded events matching the given event type.

Example
package main

import (
	"fmt"
	"testing"

	"github.com/axonops/audit"
	"github.com/axonops/audit/audittest"
)

func main() {
	t := &testing.T{}

	auditor, events, _ := audittest.NewQuick(t, "user_create", "auth_failure")

	_ = auditor.AuditEvent(audit.NewEvent("user_create", audit.Fields{"actor_id": "alice"}))
	_ = auditor.AuditEvent(audit.NewEvent("auth_failure", audit.Fields{"actor_id": "bob"}))
	_ = auditor.AuditEvent(audit.NewEvent("user_create", audit.Fields{"actor_id": "charlie"}))

	creates := events.FindByType("user_create")
	fmt.Println("user_create count:", len(creates))
	fmt.Println("auth_failure count:", len(events.FindByType("auth_failure")))
}
Output:
user_create count: 2
auth_failure count: 1

func (*Recorder) First added in v0.1.10

func (r *Recorder) First() (RecordedEvent, bool)

First returns the earliest recorded event, or false if the Recorder is empty.

func (*Recorder) GoString

func (r *Recorder) GoString() string

GoString returns a human-readable summary of all recorded events, suitable for test failure messages.

func (*Recorder) Last added in v0.1.10

func (r *Recorder) Last() (RecordedEvent, bool)

Last returns the most recently recorded event, or false if the Recorder is empty.

func (*Recorder) Name

func (r *Recorder) Name() string

Name implements audit.Output.

func (*Recorder) RequireEmpty added in v0.1.10

func (r *Recorder) RequireEmpty(tb testing.TB)

RequireEmpty asserts that no events have been recorded. It calls tb.Fatal if any events are present.

func (*Recorder) RequireEvent added in v0.1.10

func (r *Recorder) RequireEvent(tb testing.TB, eventType string) RecordedEvent

RequireEvent asserts that exactly one event of the given type was recorded and returns it. It calls tb.Fatal if the count is not 1.

func (*Recorder) RequireEvents added in v0.1.10

func (r *Recorder) RequireEvents(tb testing.TB, n int) []RecordedEvent

RequireEvents asserts that exactly n events were recorded and returns them. It calls tb.Fatal if the count does not match.

func (*Recorder) Reset

func (r *Recorder) Reset()

Reset clears all recorded events. The underlying auditor remains open and functional. Use Reset between sub-tests to isolate assertions without creating a new auditor.

func (*Recorder) WaitForN added in v0.1.12

func (r *Recorder) WaitForN(tb testing.TB, n int, timeout time.Duration) bool

WaitForN blocks until at least n events have been recorded or timeout elapses. It returns true if the target was reached, false on timeout.

Use WaitForN in tests built with WithAsync: events are delivered by a background goroutine, so assertions made immediately after audit.Auditor.AuditEvent may race the drain. WaitForN provides a deterministic wait-until-ready barrier without time.Sleep.

For synchronous auditors (the default for New and NewQuick), events are available immediately after AuditEvent returns — prefer Recorder.Count / Recorder.RequireEvents there. If the target is already reached at call time, WaitForN returns true without sleeping.

The poll interval is 10 ms and is not configurable — pass a larger timeout to tolerate slower CI environments. Pass tb so that subsequent caller-side assertions on the returned bool attribute failure lines to the calling test.

Example

ExampleRecorder_WaitForN shows how to wait for asynchronously- delivered events to land in the Recorder before asserting. Use WaitForN with WithAsync or any auditor configured for async delivery — synchronous auditors do not need it.

package main

import (
	"fmt"
	"testing"
	"time"

	"github.com/axonops/audit"
	"github.com/axonops/audit/audittest"
)

var exampleTaxonomyYAML = []byte(`
version: 1
categories:
  write:
    - user_create
events:
  user_create:
    fields:
      outcome: {required: true}
      actor_id: {required: true}
`)

func main() {
	t := &testing.T{}

	auditor, events, _ := audittest.New(t, exampleTaxonomyYAML, audittest.WithAsync())
	defer func() { _ = auditor.Close() }() // stub *testing.T has no Cleanup — close explicitly

	// Emit events from a goroutine — simulates a service emitting
	// audit events on a hot path.
	go func() {
		for i := 0; i < 3; i++ {
			_ = auditor.AuditEvent(audit.NewEvent("user_create", audit.Fields{
				"outcome":  "success",
				"actor_id": "alice",
			}))
		}
	}()

	if ok := events.WaitForN(t, 3, 2*time.Second); !ok {
		fmt.Println("timeout waiting for 3 events")
		return
	}
	fmt.Println("count:", events.Count())
}
Output:
count: 3

func (*Recorder) Write

func (r *Recorder) Write(data []byte) error

Write implements audit.Output. It parses the serialised JSON event and appends it to the recorded events.

type TestCerts added in v0.1.12

type TestCerts struct {
	// TLSCfg is a server-side [tls.Config] with the server certificate
	// loaded, ClientCAs set to a pool containing the CA cert, and
	// ClientAuth set to [tls.VerifyClientCertIfGiven].
	TLSCfg *tls.Config

	// CAPath is the PEM-encoded self-signed CA certificate.
	CAPath string

	// CertPath is the PEM-encoded server certificate (CN=localhost,
	// SANs include localhost + 127.0.0.1).
	CertPath string

	// KeyPath is the PEM-encoded server private key (ECDSA P-256).
	KeyPath string

	// ClientCert is the PEM-encoded client certificate
	// (CN=test-client, ExtKeyUsageClientAuth).
	ClientCert string

	// ClientKey is the PEM-encoded client private key (ECDSA P-256).
	ClientKey string
}

TestCerts holds paths to test TLS certificates and a server TLS config produced by GenerateTestCerts. All files are written under testing.TB.TempDir so they are cleaned up automatically when the test exits.

func GenerateTestCerts added in v0.1.12

func GenerateTestCerts(tb testing.TB) *TestCerts

GenerateTestCerts creates a self-signed CA plus server and client certificates for testing TLS. All files are written to testing.TB.TempDir and removed when the test exits. ECDSA P-256 keys, one-hour expiry. The returned tls.Config is ready to use with net.Listen's tls wrapper for a server that wants to verify optional client certs against the same CA.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL