testing

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 6 Imported by: 0

README

GoFalcon Testing Framework

This package provides a fake client for testing GoFalcon applications without API credentials or network calls.

Overview

The fake client uses a mock handler pattern (inspired by Kubernetes' fake client) to intercept API operations at the transport layer and route them through configurable handlers.

Key Features

  • Mock Handler Chain: Configure responses per operation, with precedence control
  • Request Tracking: Record and verify all API calls
  • Thread-Safe: Safe for concurrent testing
  • Type-Safe: Uses the same generated OpenAPI types as the real client
  • Default Handlers: Catch-all wildcard handlers for unmatched operations
  • Zero Dependencies: No network calls or credentials needed

Quick Start

package myapp_test

import (
    "testing"
    faketest "github.com/crowdstrike/gofalcon/falcon/testing"
    "github.com/crowdstrike/gofalcon/falcon/client/hosts"
    "github.com/crowdstrike/gofalcon/falcon/models"
)

func TestMyApplication(t *testing.T) {
    fakeClient := faketest.NewFakeClient()
    deviceIDs := faketest.GenerateDeviceIDs(3)

    fakeClient.AddMockHandler("QueryDevicesByFilter", func(request faketest.Request) (bool, interface{}, error) {
        queryTime := 0.1
        traceID := "test-trace-id"
        total := int64(len(deviceIDs))
        limit := int32(len(deviceIDs))

        response := &hosts.QueryDevicesByFilterOK{
            Payload: &models.MsaQueryResponse{
                Meta: &models.MsaMetaInfo{
                    QueryTime:  &queryTime,
                    TraceID:    &traceID,
                    PoweredBy:  "test-powered-by",
                    Pagination: &models.MsaPaging{Total: &total, Limit: &limit},
                },
                Resources: deviceIDs,
            },
        }
        return true, response, nil
    })

    client := fakeClient.GetClient()
    result, err := client.Hosts.QueryDevicesByFilter(&hosts.QueryDevicesByFilterParams{})
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    // result.Payload.Resources == deviceIDs
}
Drop-in Replacement
// Instead of:
// client, err := falcon.NewClient(&falcon.ApiConfig{...})

// Use:
fakeClient := faketest.NewFakeClientFromConfig()
fakeClient.AddMockHandler("QueryDevicesByFilter", myHandler)
client := fakeClient.GetClient()

API Reference

Constructors
Function Returns Description
NewFakeClient() *FakeClient Create a new fake client
NewFakeClientFromConfig() *FakeClient Convenience constructor (same as NewFakeClient)
Handler Configuration
Method Description
AddMockHandler(operationID, handler) Append handler to chain
PrependMockHandler(operationID, handler) Prepend handler (higher priority)
AddStaticMockHandler(operationID, response) Always return the same response
AddErrorMockHandler(operationID, err) Always return an error
SetDefaultHandler(handler) Catch-all for unmatched operations (uses "*" wildcard)
Request Tracking
Method Description
Requests() []Request All recorded requests (chronological)
FilterRequests(operationID) []Request Requests matching an operation ID
CountRequests(operationID) int Count of requests matching an operation ID
ClearRequests() Clear request history
Reset() Clear both requests and handlers
Utility Functions
Function Description
GenerateDeviceIDs(count) Deterministic 32-char hex IDs with d prefix
GenerateDetectionIDs(count) Deterministic 32-char hex IDs with a prefix
GetParamsAs[T](request) Type-safe parameter extraction
MatchesOperation(request, operationID) Check if request matches operation
NewRequest(operationID, params) Create a Request for testing
ID Format

Generated IDs are 32-character lowercase hex strings:

faketest.GenerateDeviceIDs(2)    // ["d0000000000000000000000000000001", "d0000000000000000000000000000002"]
faketest.GenerateDetectionIDs(2) // ["a0000000000000000000000000000001", "a0000000000000000000000000000002"]

Mock Handler Patterns

Parameter Inspection
fakeClient.AddMockHandler("QueryDevicesByFilter", func(request faketest.Request) (bool, interface{}, error) {
    if params, ok := faketest.GetParamsAs[hosts.QueryDevicesByFilterParams](request); ok {
        if params.Filter != nil && strings.Contains(*params.Filter, "critical") {
            return true, criticalResponse, nil
        }
    }
    return true, defaultResponse, nil
})
Handler Precedence

Handlers are tried in chain order. PrependMockHandler adds to the front (higher priority). A handler returning (false, nil, nil) declines and falls through to the next handler:

// General handler returns all devices
fakeClient.AddMockHandler("QueryDevicesByFilter", func(request faketest.Request) (bool, interface{}, error) {
    return true, allDevicesResponse, nil
})

// Override: when called with a Windows filter, return filtered results
fakeClient.PrependMockHandler("QueryDevicesByFilter", func(request faketest.Request) (bool, interface{}, error) {
    params, ok := faketest.GetParamsAs[hosts.QueryDevicesByFilterParams](request)
    if ok && params.Filter != nil && strings.Contains(*params.Filter, "platform_name:'Windows'") {
        return true, windowsOnlyResponse, nil
    }
    return false, nil, nil // not handled, fall through to next handler
})
Default/Catch-All Handler
// Fail-fast: surface unexpected API calls as test failures
fakeClient.SetDefaultHandler(func(request faketest.Request) (bool, interface{}, error) {
    return true, nil, fmt.Errorf("unexpected operation: %s", request.GetOperationID())
})

// Permissive: return nil for any unhandled operation (useful during exploratory testing)
fakeClient.SetDefaultHandler(func(request faketest.Request) (bool, interface{}, error) {
    return true, nil, nil
})
Error Simulation
fakeClient.AddErrorMockHandler("QueryDevicesByFilter", errors.New("rate limit exceeded"))

Request Verification

client.Hosts.QueryDevicesByFilter(params)
client.Hosts.GetDeviceDetailsV2(params)

// Verify call count
if fakeClient.CountRequests("QueryDevicesByFilter") != 1 {
    t.Fatal("expected exactly 1 query call")
}

// Verify call order
requests := fakeClient.Requests()
if requests[0].GetOperationID() != "QueryDevicesByFilter" {
    t.Fatal("expected query first")
}

Thread Safety

The fake client is safe for concurrent use. Handlers execute outside the internal lock, so handlers may safely call Requests(), CountRequests(), or other FakeClient methods without deadlocking.

Examples

See example_test.go for working examples of all features.

Interface Mocking

In addition to the fake client (which intercepts at the transport layer), you can mock individual services at the interface level. Each generated service package exports a ClientService interface, and the top-level *client.CrowdStrikeAPISpecification struct holds services as interface-typed fields.

This lets you write focused unit tests that depend only on the methods you call, with no handler chain setup.

How It Works
  1. Define a mock struct that embeds the ClientService interface (satisfies methods you don't call)
  2. Override only the methods your code uses
  3. Pass the mock to your application code via the interface
Example: Mocking the Hosts Service
package myapp_test

import (
    "testing"

    "github.com/crowdstrike/gofalcon/falcon/client/hosts"
    "github.com/crowdstrike/gofalcon/falcon/models"
)

// mockHosts implements hosts.ClientService — only override what you need.
type mockHosts struct {
    hosts.ClientService // embedded interface covers methods you don't call
    QueryDevicesByFilterFunc func(params *hosts.QueryDevicesByFilterParams, opts ...hosts.ClientOption) (*hosts.QueryDevicesByFilterOK, error)
}

func (m *mockHosts) QueryDevicesByFilter(params *hosts.QueryDevicesByFilterParams, opts ...hosts.ClientOption) (*hosts.QueryDevicesByFilterOK, error) {
    return m.QueryDevicesByFilterFunc(params, opts...)
}

func TestListDevices(t *testing.T) {
    mock := &mockHosts{
        QueryDevicesByFilterFunc: func(_ *hosts.QueryDevicesByFilterParams, _ ...hosts.ClientOption) (*hosts.QueryDevicesByFilterOK, error) {
            return &hosts.QueryDevicesByFilterOK{
                Payload: &models.MsaQueryResponse{
                    Resources: []string{"device-1", "device-2"},
                },
            }, nil
        },
    }

    // Pass mock to your application code that accepts hosts.ClientService
    devices, err := listDevices(mock)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if len(devices) != 2 {
        t.Fatalf("expected 2 devices, got %d", len(devices))
    }
}

// Your application code accepts the interface, not the concrete client
func listDevices(client hosts.ClientService) ([]string, error) {
    resp, err := client.QueryDevicesByFilter(&hosts.QueryDevicesByFilterParams{})
    if err != nil {
        return nil, err
    }
    return resp.Payload.Resources, nil
}
Injecting Into the Full Client

Use NewMockClient() to get a fully-wired client where every service panics with a clear message if called without being replaced. Then swap in your mock for the service under test:

import (
    faketest "github.com/crowdstrike/gofalcon/falcon/testing"
)

func TestWithFullClient(t *testing.T) {
    mock := &mockHosts{
        QueryDevicesByFilterFunc: func(_ *hosts.QueryDevicesByFilterParams, _ ...hosts.ClientOption) (*hosts.QueryDevicesByFilterOK, error) {
            return &hosts.QueryDevicesByFilterOK{
                Payload: &models.MsaQueryResponse{Resources: []string{"dev-1"}},
            }, nil
        },
    }

    // All services wired — unconfigured ones panic with a helpful message
    apiClient := faketest.NewMockClient()
    apiClient.Hosts = mock

    result, err := apiClient.Hosts.QueryDevicesByFilter(&hosts.QueryDevicesByFilterParams{})
    // ...
}
When to Use Which Approach
Approach Best For
FakeClient (transport-level) Testing code that uses *client.CrowdStrikeAPISpecification with many services, full request tracking, handler chaining
Interface mock (service-level) Unit testing code that depends on a single ClientService interface, minimal setup, compile-time method signature safety
Available Interfaces

Every service package exports a ClientService interface. Common ones:

  • github.com/crowdstrike/gofalcon/falcon/client/hosts.ClientService
  • github.com/crowdstrike/gofalcon/falcon/client/detects.ClientService
  • github.com/crowdstrike/gofalcon/falcon/client/incidents.ClientService
  • github.com/crowdstrike/gofalcon/falcon/client/alerts.ClientService
  • github.com/crowdstrike/gofalcon/falcon/client/ioc.ClientService

See any service package's *_client.go file for its full interface definition.

Architecture

The fake client implements runtime.ClientTransport (the same interface the real HTTP transport uses). When any generated service method is called, it routes through FakeTransport.Submit()FakeClient.ProcessRequest() → handler chain. This makes it a true drop-in replacement with zero code changes to application logic.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GenerateDetectionIDs

func GenerateDetectionIDs(count int) []string

GenerateDetectionIDs generates a slice of deterministic detection IDs for testing. IDs are 32-character lowercase hex strings resembling real CrowdStrike detection IDs.

func GenerateDeviceIDs

func GenerateDeviceIDs(count int) []string

GenerateDeviceIDs generates a slice of deterministic device IDs for testing. IDs are 32-character lowercase hex strings resembling real CrowdStrike device IDs.

func GetParamsAs

func GetParamsAs[T any](request Request) (*T, bool)

GetParamsAs attempts to cast the request's parameters to the specified type. This is a helper for mock handlers that need to inspect parameters.

func MatchesOperation

func MatchesOperation(request Request, operationID string) bool

MatchesOperation returns true if the request matches the specified operation ID.

func NewMockClient

func NewMockClient() *client.CrowdStrikeAPISpecification

NewMockClient returns a fully-wired CrowdStrikeAPISpecification where every service panics with a clear message if called without being replaced. Swap individual service fields with your own ClientService interface mocks for the services under test.

Example:

mc := faketest.NewMockClient()
mc.Hosts = &myMockHosts{...}  // only mock what you test
result, err := mc.Hosts.QueryDevicesByFilter(params)

Types

type FakeClient

type FakeClient struct {
	sync.RWMutex

	// HandlerChain is the list of mock handlers that will be attempted for every
	// request in the order they are tried.
	HandlerChain []MockHandler
	// contains filtered or unexported fields
}

FakeClient provides a fake implementation of the CrowdStrike API client for testing. It uses a mock handler pattern similar to Kubernetes' fake client to allow configurable responses.

func NewFakeClient

func NewFakeClient() *FakeClient

NewFakeClient creates a new fake CrowdStrike API client for testing.

func NewFakeClientFromConfig

func NewFakeClientFromConfig() *FakeClient

NewFakeClientFromConfig creates a fake client that can be used as a drop-in replacement for falcon.NewClient(). Call GetClient() to obtain the underlying API client.

func (*FakeClient) AddErrorMockHandler

func (fc *FakeClient) AddErrorMockHandler(operationID string, err error)

AddErrorMockHandler adds a mock handler that always returns the specified error.

func (*FakeClient) AddMockHandler

func (fc *FakeClient) AddMockHandler(operationID string, handler MockHandlerFunc)

AddMockHandler appends a mock handler to the end of the chain.

func (*FakeClient) AddStaticMockHandler

func (fc *FakeClient) AddStaticMockHandler(operationID string, response interface{})

AddStaticMockHandler adds a mock handler that always returns the same static response.

func (*FakeClient) ClearRequests

func (fc *FakeClient) ClearRequests()

ClearRequests clears the history of requests called on the fake client.

func (*FakeClient) CountRequests

func (fc *FakeClient) CountRequests(operationID string) int

CountRequests returns the number of requests matching the specified operation ID.

func (*FakeClient) FilterRequests

func (fc *FakeClient) FilterRequests(operationID string) []Request

FilterRequests returns requests that match the specified operation ID.

func (*FakeClient) GetClient

GetClient returns the underlying CrowdStrike API client.

func (*FakeClient) PrependMockHandler

func (fc *FakeClient) PrependMockHandler(operationID string, handler MockHandlerFunc)

PrependMockHandler adds a mock handler to the beginning of the chain.

func (*FakeClient) ProcessRequest

func (fc *FakeClient) ProcessRequest(request Request, defaultReturnObj interface{}) (interface{}, error)

ProcessRequest processes an API operation through the mock handler chain. This is called by the FakeTransport when API operations are executed.

func (*FakeClient) Requests

func (fc *FakeClient) Requests() []Request

Requests returns a chronologically ordered slice of requests called on the fake client.

func (*FakeClient) Reset

func (fc *FakeClient) Reset()

Reset clears all requests and mock handlers, returning the client to initial state.

func (*FakeClient) SetDefaultHandler

func (fc *FakeClient) SetDefaultHandler(handler MockHandlerFunc)

SetDefaultHandler sets a catch-all handler that matches any operation not handled by more specific handlers. It appends to the end of the handler chain.

type FakeTransport

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

FakeTransport implements runtime.ClientTransport for the fake client. It intercepts all API operations and routes them through the fake client's mock handler chain.

func (*FakeTransport) Submit

func (ft *FakeTransport) Submit(op *runtime.ClientOperation) (interface{}, error)

Submit implements runtime.ClientTransport.Submit().

type MockHandler

type MockHandler interface {
	// Handles indicates whether this MockHandler deals with a given operation.
	Handles(request Request) bool
	// Handle processes the operation and returns results. It may choose to
	// delegate by indicating handled=false.
	Handle(request Request) (handled bool, ret interface{}, err error)
}

MockHandler is an interface to allow composition of mock response functions for API operations.

type MockHandlerFunc

type MockHandlerFunc func(request Request) (handled bool, ret interface{}, err error)

MockHandlerFunc is a function that returns a response for a given Request. If "handled" is false, the fake client will continue to the next MockHandlerFunc.

type Request

type Request interface {
	// GetOperationID returns the OpenAPI operation ID (e.g., "QueryDevicesByFilter")
	GetOperationID() string
	// GetParams returns the operation parameters
	GetParams() interface{}
	// GetTimestamp returns when the request was created
	GetTimestamp() time.Time
	// DeepCopy creates a copy of the request to avoid mutation
	DeepCopy() Request
}

Request represents an API operation call with its parameters and metadata.

func NewRequest

func NewRequest(operationID string, params interface{}) Request

NewRequest creates a new Request for testing purposes.

type RequestImpl

type RequestImpl struct {
	OperationID string
	Params      interface{}
	Timestamp   time.Time
}

RequestImpl provides a basic implementation of the Request interface.

func (RequestImpl) DeepCopy

func (r RequestImpl) DeepCopy() Request

func (RequestImpl) GetOperationID

func (r RequestImpl) GetOperationID() string

func (RequestImpl) GetParams

func (r RequestImpl) GetParams() interface{}

func (RequestImpl) GetTimestamp

func (r RequestImpl) GetTimestamp() time.Time

type SimpleMockHandler

type SimpleMockHandler struct {
	OperationID string
	Handler     MockHandlerFunc
}

SimpleMockHandler provides a basic mock handler implementation that matches operations by ID.

func (*SimpleMockHandler) Handle

func (h *SimpleMockHandler) Handle(request Request) (bool, interface{}, error)

func (*SimpleMockHandler) Handles

func (h *SimpleMockHandler) Handles(request Request) bool

Jump to

Keyboard shortcuts

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