diff --git a/README.md b/README.md index 127769b..50a88e0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # onecli -CLI tool for OneLogin +A CLI tool for interacting with OneLogin API ## Description @@ -41,14 +41,75 @@ Download the latest release from the [releases page](https://github.com/pepabo/o ## Usage +### User Management + +```bash +# List all users +onecli user list + +# List users with filters +onecli user list --email user@example.com +onecli user list --username username +onecli user list --firstname John +onecli user list --lastname Doe +onecli user list --user-id 123 + +# Add a new user +onecli user add "John" "Doe" "john.doe@example.com" + +# Modify user email +onecli user modify email "newemail@example.com" --email "oldemail@example.com" +``` + +### App Management + ```bash -# Check version -onecli --version +# List all apps +onecli app list + +# List apps with user details +onecli app list --details +``` + +### Event Management + +```bash +# List all events +onecli event list + +# List events with filters +onecli event list --event-type-id 1 +onecli event list --user-id 123 +onecli event list --since 2023-01-01 +onecli event list --until 2023-12-31 + +# List all event types +onecli event types -# Run commands -onecli [command] [options] +# List event types in JSON format +onecli event types --output json ``` +## Output Formats + +All list commands support multiple output formats: + +- `yaml` (default) +- `json` + +Example: +```bash +onecli user list --output json +``` + +## Configuration + +Set the following environment variables: + +- `ONELOGIN_CLIENT_ID`: Your OneLogin client ID +- `ONELOGIN_CLIENT_SECRET`: Your OneLogin client secret +- `ONELOGIN_SUBDOMAIN`: Your OneLogin subdomain + ## Development ### Requirements @@ -61,6 +122,16 @@ onecli [command] [options] go build ``` +### Running Tests + +```bash +# Run tests +go test ./... + +# Run specific tests +go test ./onelogin -v -run TestGetEventTypes +``` + ## License This project is licensed under the terms of the included LICENSE file. diff --git a/cmd/event.go b/cmd/event.go new file mode 100644 index 0000000..250bebe --- /dev/null +++ b/cmd/event.go @@ -0,0 +1,178 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/pepabo/onecli/onelogin" + "github.com/pepabo/onecli/utils" + "github.com/spf13/cobra" +) + +var eventCmd = &cobra.Command{ + Use: "event", + Aliases: []string{"ev"}, + Short: "Event management commands", + Long: `Commands for managing OneLogin events in your organization`, +} + +var ( + eventQueryClientID string + eventQueryCreatedAt string + eventQueryDirectoryID string + eventQueryEventTypeID string + eventQueryEventType string + eventQueryResolution string + eventQueryID string + eventQuerySince string + eventQueryUntil string + eventQueryUserID string + eventOutput string +) + +var eventListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"l", "ls"}, + Short: "List all events", + Long: `List all events in your OneLogin organization`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := initClient() + if err != nil { + return err + } + query, err := getEventQuery(client) + if err != nil { + return err + } + events, err := client.ListEvents(query) + if err != nil { + return fmt.Errorf("error getting events: %v", err) + } + if err := utils.PrintOutput(events, utils.OutputFormat(eventOutput), os.Stdout); err != nil { + return fmt.Errorf("error printing output: %v", err) + } + return nil + }, +} + +var eventTypesCmd = &cobra.Command{ + Use: "types", + Aliases: []string{"t", "type"}, + Short: "List all event types", + Long: `List all event types in your OneLogin organization`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := initClient() + if err != nil { + return err + } + + eventTypes, err := client.GetEventTypes() + if err != nil { + return fmt.Errorf("error getting event types: %v", err) + } + + if err := utils.PrintOutput(eventTypes, utils.OutputFormat(eventOutput), os.Stdout); err != nil { + return fmt.Errorf("error printing output: %v", err) + } + return nil + }, +} + +func getEventQuery(client *onelogin.Onelogin) (onelogin.EventsQuery, error) { + query := onelogin.EventsQuery{} + + if eventQueryClientID != "" { + query.ClientID = &eventQueryClientID + } + + if eventQueryCreatedAt != "" { + query.CreatedAt = &eventQueryCreatedAt + } + + if eventQueryDirectoryID != "" { + query.DirectoryID = &eventQueryDirectoryID + } + + // --typeと--type-idの排他チェック + if eventQueryEventTypeID != "" && eventQueryEventType != "" { + return query, fmt.Errorf("--type and --type-id cannot be used together") + } + + if eventQueryEventTypeID != "" { + query.EventTypeID = &eventQueryEventTypeID + } else if eventQueryEventType != "" { + eventTypes, err := client.GetEventTypes() + if err != nil { + return query, fmt.Errorf("error getting event types: %v", err) + } + nameToID := onelogin.EventTypeNameIDMap(eventTypes) + typeNames := strings.Split(eventQueryEventType, ",") + var typeIDs []string + var invalidNames []string + for _, name := range typeNames { + name = strings.TrimSpace(name) + if name == "" { + continue + } + if id, exists := nameToID[name]; exists { + typeIDs = append(typeIDs, fmt.Sprintf("%d", id)) + } else { + invalidNames = append(invalidNames, name) + } + } + if len(invalidNames) > 0 { + return query, fmt.Errorf("invalid event type name(s): %s. Use 'onecli event types' to see available event types", strings.Join(invalidNames, ", ")) + } + if len(typeIDs) > 0 { + eventTypeIDs := strings.Join(typeIDs, ",") + query.EventTypeID = &eventTypeIDs + } + } + + if eventQueryResolution != "" { + query.Resolution = &eventQueryResolution + } + + if eventQueryID != "" { + query.ID = &eventQueryID + } + + if eventQuerySince != "" { + query.Since = &eventQuerySince + } + + if eventQueryUntil != "" { + query.Until = &eventQueryUntil + } + + if eventQueryUserID != "" { + query.UserID = &eventQueryUserID + } + + return query, nil +} + +func init() { + eventCmd.AddCommand(eventListCmd) + eventCmd.AddCommand(eventTypesCmd) + + eventListCmd.Flags().StringVarP(&eventOutput, "output", "o", "yaml", "Output format (yaml, json)") + eventListCmd.Flags().StringVar(&eventQueryClientID, "client-id", "", "Filter events by client ID") + eventListCmd.Flags().StringVar(&eventQueryCreatedAt, "created-at", "", "Filter events by created at") + eventListCmd.Flags().StringVar(&eventQueryDirectoryID, "directory-id", "", "Filter events by directory ID") + eventListCmd.Flags().StringVar(&eventQueryEventTypeID, "type-id", "", "Filter events by event type ID (comma-separated for multiple values)") + eventListCmd.Flags().StringVar(&eventQueryEventType, "type", "", "Filter events by event type name (comma-separated for multiple values)") + eventListCmd.Flags().StringVar(&eventQueryResolution, "resolution", "", "Filter events by resolution") + eventListCmd.Flags().StringVar(&eventQueryID, "id", "", "Filter events by ID") + eventListCmd.Flags().StringVar(&eventQuerySince, "since", "", "Filter events from date (YYYY-MM-DD)") + eventListCmd.Flags().StringVar(&eventQueryUntil, "until", "", "Filter events to date (YYYY-MM-DD)") + eventListCmd.Flags().StringVar(&eventQueryUserID, "user-id", "", "Filter events by user ID") + + // Make --type and --type-id mutually exclusive + eventListCmd.MarkFlagsMutuallyExclusive("type", "type-id") + + eventTypesCmd.Flags().StringVarP(&eventOutput, "output", "o", "yaml", "Output format (yaml, json)") +} diff --git a/cmd/event_test.go b/cmd/event_test.go new file mode 100644 index 0000000..c1f06a5 --- /dev/null +++ b/cmd/event_test.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetEventQueryWithTypeNames(t *testing.T) { + // Test cases + tests := []struct { + name string + eventTypeNames string + expectedIDs string + }{ + { + name: "single event type name", + eventTypeNames: "User Login", + expectedIDs: "1", + }, + { + name: "multiple event type names", + eventTypeNames: "User Login,User Logout", + expectedIDs: "1,2", + }, + { + name: "multiple event type names with spaces", + eventTypeNames: "User Login, User Logout, Password Reset", + expectedIDs: "1,2,3", + }, + { + name: "empty string", + eventTypeNames: "", + expectedIDs: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set the global variable + eventQueryEventType = tt.eventTypeNames + eventQueryEventTypeID = "" // Ensure type-id is not set + + // Create a mock client that returns our test data + // Note: This is a simplified test since we can't easily mock the client + // in the current structure. In a real scenario, you'd want to inject + // the client dependency. + + // For now, we'll just test the string parsing logic + if tt.eventTypeNames != "" { + // This would normally call the client, but for testing we'll just verify + // that the flag is set correctly + assert.Equal(t, tt.eventTypeNames, eventQueryEventType) + } + }) + } +} + +func TestEventTypeNameValidation(t *testing.T) { + // Test that invalid event type names are properly handled + invalidNames := []string{"Invalid Event Type", "NonExistentEvent", "Unknown Event"} + + // This would be tested in the actual command execution + // where the client.GetEventTypes() would be called and validation would occur + for _, name := range invalidNames { + // In a real scenario, this would cause an error when the command runs + assert.NotEmpty(t, name) + } +} diff --git a/cmd/root.go b/cmd/root.go index db8c16b..4ea196e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,7 +16,6 @@ var rootCmd = &cobra.Command{ Short: "OneLogin CLI tool", Long: `A CLI tool for interacting with OneLogin API`, PersistentPreRun: func(cmd *cobra.Command, args []string) { - // ログ出力の制御 if !verbose { log.SetOutput(io.Discard) } @@ -39,6 +38,7 @@ func Execute() error { func init() { rootCmd.AddCommand(userCmd) rootCmd.AddCommand(appCmd) + rootCmd.AddCommand(eventCmd) rootCmd.AddCommand(versionCmd) rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output") } diff --git a/cmd/user.go b/cmd/user.go index 322ff72..1e8e82c 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -142,13 +142,29 @@ var addCmd = &cobra.Command{ } func getUserQuery() onelogin.UserQuery { - return onelogin.UserQuery{ - Email: &userQueryEmail, - Username: &userQueryUsername, - Firstname: &userQueryFirstname, - Lastname: &userQueryLastname, - UserIDs: &userQueryUserID, + query := onelogin.UserQuery{} + + if userQueryEmail != "" { + query.Email = &userQueryEmail + } + + if userQueryUsername != "" { + query.Username = &userQueryUsername + } + + if userQueryFirstname != "" { + query.Firstname = &userQueryFirstname } + + if userQueryLastname != "" { + query.Lastname = &userQueryLastname + } + + if userQueryUserID != "" { + query.UserIDs = &userQueryUserID + } + + return query } // isQueryParamsEmpty checks if all query parameters are empty diff --git a/onelogin/client.go b/onelogin/client.go index 50b3c49..3679e8b 100644 --- a/onelogin/client.go +++ b/onelogin/client.go @@ -1,11 +1,12 @@ package onelogin import ( + "sync" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" ) const ( - // DefaultPageSize は1ページあたりのデフォルトの取得件数です DefaultPageSize = 1000 ) @@ -25,10 +26,16 @@ type Client interface { CreateUser(user models.User) (any, error) GetApps(query models.Queryable) (any, error) GetAppUsers(appID int, query models.Queryable) (any, error) + ListEvents(query models.Queryable) (any, error) + GetEventTypes(query models.Queryable) (any, error) } type Onelogin struct { client Client + + eventTypesCache []EventType + eventTypesCacheErr error + eventTypesCacheOnce sync.Once } // New creates a new Onelogin client diff --git a/onelogin/event_types.go b/onelogin/event_types.go new file mode 100644 index 0000000..a483921 --- /dev/null +++ b/onelogin/event_types.go @@ -0,0 +1,76 @@ +package onelogin + +import ( + "encoding/json" +) + +// EventType represents an OneLogin event type +type EventType struct { + ID int32 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} + +type EventTypesResponse struct { + Status struct { + Error bool `json:"error"` + Code int `json:"code"` + Message string `json:"message"` + Type string `json:"type"` + } `json:"status"` + Data []EventType `json:"data"` +} + +// GetEventTypes retrieves event types from OneLogin (with caching) +func (o *Onelogin) GetEventTypes() ([]EventType, error) { + o.eventTypesCacheOnce.Do(func() { + result, err := o.client.GetEventTypes(nil) + if err != nil { + o.eventTypesCacheErr = err + return + } + + // Convert the result to EventTypesResponse + response, err := convertToEventTypesResponse(result.(map[string]any)) + if err != nil { + o.eventTypesCacheErr = err + return + } + + o.eventTypesCache = response.Data + }) + return o.eventTypesCache, o.eventTypesCacheErr +} + +func convertToEventTypesResponse(data map[string]any) (*EventTypesResponse, error) { + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + + var response EventTypesResponse + err = json.Unmarshal(jsonData, &response) + if err != nil { + return nil, err + } + + return &response, nil +} + +// EventTypeIDNameMap returns a map[id]name +func EventTypeIDNameMap(eventTypes []EventType) map[int32]string { + m := make(map[int32]string) + for _, et := range eventTypes { + m[et.ID] = et.Name + } + return m +} + +// EventTypeNameIDMap returns a map[name]id +func EventTypeNameIDMap(eventTypes []EventType) map[string]int32 { + m := make(map[string]int32) + for _, et := range eventTypes { + m[et.Name] = et.ID + } + return m +} diff --git a/onelogin/event_types_test.go b/onelogin/event_types_test.go new file mode 100644 index 0000000..7835a56 --- /dev/null +++ b/onelogin/event_types_test.go @@ -0,0 +1,236 @@ +package onelogin + +import ( + "testing" + + "github.com/pepabo/onecli/utils" + "github.com/stretchr/testify/assert" +) + +func TestGetEventTypes(t *testing.T) { + tests := []struct { + name string + mockResponse any + expectedEventTypes []EventType + expectedError error + }{ + { + name: "successful event types retrieval", + mockResponse: map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "name": "User Login", + "description": "User login event", + }, + map[string]any{ + "id": float64(2), + "name": "User Logout", + "description": "User logout event", + }, + map[string]any{ + "id": float64(3), + "name": "Password Reset", + "description": "Password reset event", + }, + }, + }, + expectedEventTypes: []EventType{ + { + ID: 1, + Name: "User Login", + Description: "User login event", + }, + { + ID: 2, + Name: "User Logout", + Description: "User logout event", + }, + { + ID: 3, + Name: "Password Reset", + Description: "Password reset event", + }, + }, + }, + { + name: "successful event types retrieval with empty result", + mockResponse: map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "data": []any{}, + }, + expectedEventTypes: []EventType{}, + }, + { + name: "error from client", + mockResponse: nil, + expectedError: assert.AnError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := new(utils.MockClient) + o := &Onelogin{ + client: mockClient, + } + + // Set up mock expectations + if tt.expectedError != nil { + mockClient.On("GetEventTypes", nil).Return(nil, tt.expectedError) + } else { + mockClient.On("GetEventTypes", nil).Return(tt.mockResponse, nil) + } + + eventTypes, err := o.GetEventTypes() + + if tt.expectedError != nil { + assert.Error(t, err) + assert.Equal(t, tt.expectedError, err) + } else { + assert.NoError(t, err) + if eventTypes == nil { + eventTypes = []EventType{} + } + assert.Equal(t, tt.expectedEventTypes, eventTypes) + } + + mockClient.AssertExpectations(t) + }) + } +} + +func TestConvertToEventTypesResponse(t *testing.T) { + tests := []struct { + name string + input map[string]any + expectedResult *EventTypesResponse + expectedError bool + }{ + { + name: "valid event types response", + input: map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "name": "User Login", + "description": "User login event", + }, + map[string]any{ + "id": float64(2), + "name": "User Logout", + "description": "User logout event", + }, + }, + }, + expectedResult: &EventTypesResponse{ + Status: struct { + Error bool `json:"error"` + Code int `json:"code"` + Message string `json:"message"` + Type string `json:"type"` + }{ + Error: false, + Code: 200, + Message: "Success", + Type: "success", + }, + Data: []EventType{ + { + ID: 1, + Name: "User Login", + Description: "User login event", + }, + { + ID: 2, + Name: "User Logout", + Description: "User logout event", + }, + }, + }, + expectedError: false, + }, + { + name: "empty data response", + input: map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "data": []any{}, + }, + expectedResult: &EventTypesResponse{ + Status: struct { + Error bool `json:"error"` + Code int `json:"code"` + Message string `json:"message"` + Type string `json:"type"` + }{ + Error: false, + Code: 200, + Message: "Success", + Type: "success", + }, + Data: []EventType{}, + }, + expectedError: false, + }, + { + name: "invalid json data", + input: map[string]any{ + "invalid": make(chan int), // This will cause JSON marshaling to fail + }, + expectedResult: nil, + expectedError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := convertToEventTypesResponse(tt.input) + + if tt.expectedError { + assert.Error(t, err) + assert.Nil(t, result) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, tt.expectedResult.Status, result.Status) + assert.Equal(t, tt.expectedResult.Data, result.Data) + } + }) + } +} + +func TestEventTypeStruct(t *testing.T) { + // Test EventType struct field mapping + eventType := EventType{ + ID: 1, + Name: "Test Event Type", + Description: "Test event type description", + } + + // Test that all fields are properly set + assert.Equal(t, int32(1), eventType.ID) + assert.Equal(t, "Test Event Type", eventType.Name) + assert.Equal(t, "Test event type description", eventType.Description) +} diff --git a/onelogin/events.go b/onelogin/events.go new file mode 100644 index 0000000..88c6ded --- /dev/null +++ b/onelogin/events.go @@ -0,0 +1,166 @@ +package onelogin + +import ( + "encoding/json" + "strconv" + "time" +) + +// Event represents an OneLogin event +type Event struct { + AccountID int32 `json:"account_id,omitempty"` + ActorSystem string `json:"actor_system,omitempty"` + ActorUserID int32 `json:"actor_user_id,omitempty"` + ActorUserName string `json:"actor_user_name,omitempty"` + AppID int32 `json:"app_id,omitempty"` + AppName string `json:"app_name,omitempty"` + AssumingActingUserID int32 `json:"assuming_acting_user_id,omitempty"` + BrowserFingerprint string `json:"browser_fingerprint,omitempty"` + ClientID string `json:"client_id,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + CustomMessage string `json:"custom_message,omitempty"` + DirectoryID int32 `json:"directory_id,omitempty"` + DirectorySyncRunID int32 `json:"directory_sync_run_id,omitempty"` + ErrorDescription string `json:"error_description,omitempty"` + EventTypeID int32 `json:"event_type_id,omitempty"` + EventType string `json:"event_type,omitempty"` + GroupID int32 `json:"group_id,omitempty"` + GroupName string `json:"group_name,omitempty"` + ID uint64 `json:"id,omitempty"` + IPAddr string `json:"ipaddr,omitempty"` + Notes string `json:"notes,omitempty"` + OperationName string `json:"operation_name,omitempty"` + OTPDeviceID int32 `json:"otp_device_id,omitempty"` + OTPDeviceName string `json:"otp_device_name,omitempty"` + PolicyID int32 `json:"policy_id,omitempty"` + PolicyName string `json:"policy_name,omitempty"` + ProxyIP string `json:"proxy_ip,omitempty"` + Resolution int32 `json:"resolution,omitempty"` + ResourceTypeID int32 `json:"resource_type_id,omitempty"` + RiskCookieID string `json:"risk_cookie_id,omitempty"` + RiskReasons string `json:"risk_reasons,omitempty"` + RiskScore int32 `json:"risk_score,omitempty"` + RoleID int32 `json:"role_id,omitempty"` + RoleName string `json:"role_name,omitempty"` + Since string `json:"since,omitempty"` + Until string `json:"until,omitempty"` + UserID int32 `json:"user_id,omitempty"` + UserName string `json:"user_name,omitempty"` +} + +type EventsResponse struct { + Status struct { + Error bool `json:"error"` + Code int `json:"code"` + Message string `json:"message"` + Type string `json:"type"` + } `json:"status"` + Pagination struct { + BeforeCursor *string `json:"before_cursor"` + AfterCursor *string `json:"after_cursor"` + PreviousLink *string `json:"previous_link"` + NextLink *string `json:"next_link"` + } `json:"pagination"` + Data []Event `json:"data"` +} + +// EventsQuery represents query parameters for events +type EventsQuery struct { + Limit string `json:"limit,omitempty"` + Cursor string `json:"after_cursor,omitempty"` + ClientID *string `json:"client_id,omitempty"` + CreatedAt *string `json:"created_at,omitempty"` + DirectoryID *string `json:"directory_id,omitempty"` + EventTypeID *string `json:"event_type_id,omitempty"` + Resolution *string `json:"resolution,omitempty"` + ID *string `json:"id,omitempty"` + Since *string `json:"since,omitempty"` + Until *string `json:"until,omitempty"` + UserID *string `json:"user_id,omitempty"` +} + +// GetKeyValidators returns the validators for the query parameters +func (q EventsQuery) GetKeyValidators() map[string]func(any) bool { + return map[string]func(any) bool{ + "limit": validateString, + "cursor": validateString, + "client_id": validateString, + "created_at": validateString, + "directory_id": validateString, + "event_type_id": validateString, + "resolution": validateString, + "id": validateString, + "since": validateString, + "until": validateString, + "user_id": validateString, + } +} + +// ListEvents retrieves events from OneLogin +func (o *Onelogin) ListEvents(query EventsQuery) ([]Event, error) { + query.Limit = strconv.Itoa(DefaultPageSize) + nextCursor := "" + events := []Event{} + + // Get event types mapping for efficient lookup + eventTypes, err := o.GetEventTypes() + if err != nil { + return nil, err + } + + // Create a map for quick lookup of event type names by ID + eventTypeMap := EventTypeIDNameMap(eventTypes) + + for { + if nextCursor != "" { + query.Cursor = nextCursor + } + + result, err := o.client.ListEvents(&query) + if err != nil { + return nil, err + } + + // TODO: Inefficient workaround - using JSON marshaling/unmarshaling as a shortcut for complex type conversion + response, err := convertToEventsResponse(result.(map[string]any)) + if err != nil { + return nil, err + } + + // Set event type names for each event + for i := range response.Data { + if eventTypeName, exists := eventTypeMap[response.Data[i].EventTypeID]; exists { + response.Data[i].EventType = eventTypeName + } + } + + events = append(events, response.Data...) + + // Check if AfterCursor is nil before dereferencing + if response.Pagination.AfterCursor == nil { + break + } + nextCursor = *response.Pagination.AfterCursor + + if nextCursor == "" { + break + } + } + + return events, nil +} + +func convertToEventsResponse(data map[string]any) (*EventsResponse, error) { + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + + var response EventsResponse + err = json.Unmarshal(jsonData, &response) + if err != nil { + return nil, err + } + + return &response, nil +} diff --git a/onelogin/events_test.go b/onelogin/events_test.go new file mode 100644 index 0000000..cc7e1a9 --- /dev/null +++ b/onelogin/events_test.go @@ -0,0 +1,743 @@ +package onelogin + +import ( + "strconv" + "testing" + "time" + + "github.com/pepabo/onecli/utils" + "github.com/stretchr/testify/assert" +) + +func TestEventsQuery_GetKeyValidators(t *testing.T) { + query := EventsQuery{} + validators := query.GetKeyValidators() + + // Test that all expected validators are present + expectedKeys := []string{"event_type_id", "user_id", "since", "until", "limit", "cursor"} + for _, key := range expectedKeys { + assert.Contains(t, validators, key, "Validator for key %s should be present", key) + } + + // Test that validators are functions + for key, validator := range validators { + assert.NotNil(t, validator, "Validator for key %s should not be nil", key) + } +} + +func TestListEvents(t *testing.T) { + tests := []struct { + name string + query EventsQuery + mockResponse []any + expectedEvents []Event + expectedError error + }{ + { + name: "successful events retrieval", + query: EventsQuery{ + EventTypeID: func() *string { v := "1"; return &v }(), + UserID: func() *string { v := "123"; return &v }(), + }, + mockResponse: []any{ + map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "pagination": map[string]any{ + "before_cursor": nil, + "after_cursor": nil, + "previous_link": nil, + "next_link": nil, + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "account_id": float64(12345), + "event_type_id": float64(1), + "user_id": float64(123), + "user_name": "testuser", + "created_at": "2023-01-01T00:00:00Z", + "ipaddr": "192.168.1.1", + }, + map[string]any{ + "id": float64(2), + "account_id": float64(12345), + "event_type_id": float64(2), + "user_id": float64(456), + "user_name": "testuser2", + "created_at": "2023-01-02T00:00:00Z", + "ipaddr": "192.168.1.2", + }, + }, + }, + }, + expectedEvents: []Event{ + { + ID: 1, + AccountID: 12345, + EventTypeID: 1, + EventType: "User Login", + UserID: 123, + UserName: "testuser", + IPAddr: "192.168.1.1", + }, + { + ID: 2, + AccountID: 12345, + EventTypeID: 2, + EventType: "User Logout", + UserID: 456, + UserName: "testuser2", + IPAddr: "192.168.1.2", + }, + }, + }, + { + name: "successful events retrieval with pagination", + query: EventsQuery{ + EventTypeID: func() *string { v := "1"; return &v }(), + }, + mockResponse: []any{ + // First page + map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "pagination": map[string]any{ + "before_cursor": nil, + "after_cursor": func() *string { v := "cursor123"; return &v }(), + "previous_link": nil, + "next_link": func() *string { v := "https://api.onelogin.com/events?after_cursor=cursor123"; return &v }(), + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "account_id": float64(12345), + "event_type_id": float64(1), + "user_id": float64(123), + "user_name": "testuser", + "created_at": "2023-01-01T00:00:00Z", + }, + }, + }, + // Second page + map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "pagination": map[string]any{ + "before_cursor": func() *string { v := "cursor123"; return &v }(), + "after_cursor": nil, + "previous_link": func() *string { v := "https://api.onelogin.com/events?before_cursor=cursor123"; return &v }(), + "next_link": nil, + }, + "data": []any{ + map[string]any{ + "id": float64(2), + "account_id": float64(12345), + "event_type_id": float64(1), + "user_id": float64(456), + "user_name": "testuser2", + "created_at": "2023-01-02T00:00:00Z", + }, + }, + }, + }, + expectedEvents: []Event{ + { + ID: 1, + AccountID: 12345, + EventTypeID: 1, + EventType: "User Login", + UserID: 123, + UserName: "testuser", + }, + { + ID: 2, + AccountID: 12345, + EventTypeID: 1, + EventType: "User Login", + UserID: 456, + UserName: "testuser2", + }, + }, + }, + { + name: "successful events retrieval with empty result", + query: EventsQuery{ + EventTypeID: func() *string { v := "999"; return &v }(), + }, + mockResponse: []any{ + map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "pagination": map[string]any{ + "before_cursor": nil, + "after_cursor": nil, + "previous_link": nil, + "next_link": nil, + }, + "data": []any{}, + }, + }, + expectedEvents: []Event{}, + }, + { + name: "error from client", + query: EventsQuery{ + EventTypeID: func() *string { v := "1"; return &v }(), + }, + mockResponse: []any{}, + expectedError: assert.AnError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := new(utils.MockClient) + o := &Onelogin{ + client: mockClient, + } + + // Set up mock expectations + if tt.expectedError != nil { + // Mock GetEventTypes for event type name lookup (this will be called before the error) + mockEventTypesResponse := map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "name": "User Login", + "description": "User login event", + }, + map[string]any{ + "id": float64(2), + "name": "User Logout", + "description": "User logout event", + }, + }, + } + mockClient.On("GetEventTypes", nil).Return(mockEventTypesResponse, nil) + + // Create expected query with Limit set to DefaultPageSize + expectedQuery := tt.query + expectedQuery.Limit = strconv.Itoa(DefaultPageSize) + mockClient.On("ListEvents", &expectedQuery).Return(nil, tt.expectedError) + } else { + // Mock GetEventTypes for event type name lookup + mockEventTypesResponse := map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "name": "User Login", + "description": "User login event", + }, + map[string]any{ + "id": float64(2), + "name": "User Logout", + "description": "User logout event", + }, + }, + } + mockClient.On("GetEventTypes", nil).Return(mockEventTypesResponse, nil) + + // Set up pagination expectations + for i, response := range tt.mockResponse { + if i == 0 { + // Create expected query with Limit set to DefaultPageSize + expectedQuery := tt.query + expectedQuery.Limit = strconv.Itoa(DefaultPageSize) + mockClient.On("ListEvents", &expectedQuery).Return(response, nil) + } else { + // For subsequent pages, we need to create a new query with cursor + paginatedQuery := tt.query + paginatedQuery.Limit = strconv.Itoa(DefaultPageSize) + if i > 0 && i-1 < len(tt.mockResponse) { + // Get the after_cursor from the previous response + if prevResponse, ok := tt.mockResponse[i-1].(map[string]any); ok { + if pagination, ok := prevResponse["pagination"].(map[string]any); ok { + if afterCursor, ok := pagination["after_cursor"].(*string); ok && afterCursor != nil { + paginatedQuery.Cursor = *afterCursor + } + } + } + } + mockClient.On("ListEvents", &paginatedQuery).Return(response, nil) + } + } + } + + events, err := o.ListEvents(tt.query) + + if tt.expectedError != nil { + assert.Error(t, err) + assert.Equal(t, tt.expectedError, err) + } else { + assert.NoError(t, err) + if events == nil { + events = []Event{} + } + // Compare all fields except CreatedAt + for i := range tt.expectedEvents { + actual := events[i] + expected := tt.expectedEvents[i] + assert.Equal(t, expected.AccountID, actual.AccountID) + assert.Equal(t, expected.ActorSystem, actual.ActorSystem) + assert.Equal(t, expected.ActorUserID, actual.ActorUserID) + assert.Equal(t, expected.ActorUserName, actual.ActorUserName) + assert.Equal(t, expected.AppID, actual.AppID) + assert.Equal(t, expected.AppName, actual.AppName) + assert.Equal(t, expected.AssumingActingUserID, actual.AssumingActingUserID) + assert.Equal(t, expected.BrowserFingerprint, actual.BrowserFingerprint) + assert.Equal(t, expected.ClientID, actual.ClientID) + assert.Equal(t, expected.CustomMessage, actual.CustomMessage) + assert.Equal(t, expected.DirectoryID, actual.DirectoryID) + assert.Equal(t, expected.DirectorySyncRunID, actual.DirectorySyncRunID) + assert.Equal(t, expected.ErrorDescription, actual.ErrorDescription) + assert.Equal(t, expected.EventTypeID, actual.EventTypeID) + assert.Equal(t, expected.EventType, actual.EventType) + assert.Equal(t, expected.GroupID, actual.GroupID) + assert.Equal(t, expected.GroupName, actual.GroupName) + assert.Equal(t, expected.ID, actual.ID) + assert.Equal(t, expected.IPAddr, actual.IPAddr) + assert.Equal(t, expected.Notes, actual.Notes) + assert.Equal(t, expected.OperationName, actual.OperationName) + assert.Equal(t, expected.OTPDeviceID, actual.OTPDeviceID) + assert.Equal(t, expected.OTPDeviceName, actual.OTPDeviceName) + assert.Equal(t, expected.PolicyID, actual.PolicyID) + assert.Equal(t, expected.PolicyName, actual.PolicyName) + assert.Equal(t, expected.ProxyIP, actual.ProxyIP) + assert.Equal(t, expected.Resolution, actual.Resolution) + assert.Equal(t, expected.ResourceTypeID, actual.ResourceTypeID) + assert.Equal(t, expected.RiskCookieID, actual.RiskCookieID) + assert.Equal(t, expected.RiskReasons, actual.RiskReasons) + assert.Equal(t, expected.RiskScore, actual.RiskScore) + assert.Equal(t, expected.RoleID, actual.RoleID) + assert.Equal(t, expected.RoleName, actual.RoleName) + assert.Equal(t, expected.Since, actual.Since) + assert.Equal(t, expected.Until, actual.Until) + assert.Equal(t, expected.UserID, actual.UserID) + assert.Equal(t, expected.UserName, actual.UserName) + } + } + + mockClient.AssertExpectations(t) + }) + } +} + +func TestListEventsWithComplexQuery(t *testing.T) { + tests := []struct { + name string + query EventsQuery + mockResponse any + expectedEvents []Event + expectedError error + }{ + { + name: "events with all query parameters", + query: EventsQuery{ + EventTypeID: func() *string { v := "1"; return &v }(), + UserID: func() *string { v := "123"; return &v }(), + Since: func() *string { v := "2023-01-01T00:00:00Z"; return &v }(), + Until: func() *string { v := "2023-01-31T23:59:59Z"; return &v }(), + }, + mockResponse: map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "pagination": map[string]any{ + "before_cursor": nil, + "after_cursor": nil, + "previous_link": nil, + "next_link": nil, + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "account_id": float64(12345), + "event_type_id": float64(1), + "user_id": float64(123), + "app_id": float64(456), + "user_name": "testuser", + "app_name": "Test App", + "created_at": "2023-01-15T12:00:00Z", + "ipaddr": "192.168.1.1", + "risk_score": float64(50), + }, + }, + }, + expectedEvents: []Event{ + { + ID: 1, + AccountID: 12345, + EventTypeID: 1, + EventType: "User Login", + UserID: 123, + AppID: 456, + UserName: "testuser", + AppName: "Test App", + IPAddr: "192.168.1.1", + RiskScore: 50, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := new(utils.MockClient) + o := &Onelogin{ + client: mockClient, + } + + // Set up mock expectations + if tt.expectedError != nil { + // Mock GetEventTypes for event type name lookup (this will be called before the error) + mockEventTypesResponse := map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "name": "User Login", + "description": "User login event", + }, + }, + } + mockClient.On("GetEventTypes", nil).Return(mockEventTypesResponse, nil) + + // Create expected query with Limit set to DefaultPageSize + expectedQuery := tt.query + expectedQuery.Limit = strconv.Itoa(DefaultPageSize) + mockClient.On("ListEvents", &expectedQuery).Return(nil, tt.expectedError) + } else { + // Mock GetEventTypes for event type name lookup + mockEventTypesResponse := map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "name": "User Login", + "description": "User login event", + }, + map[string]any{ + "id": float64(2), + "name": "User Logout", + "description": "User logout event", + }, + }, + } + mockClient.On("GetEventTypes", nil).Return(mockEventTypesResponse, nil) + + // Create expected query with Limit set to DefaultPageSize + expectedQuery := tt.query + expectedQuery.Limit = strconv.Itoa(DefaultPageSize) + mockClient.On("ListEvents", &expectedQuery).Return(tt.mockResponse, nil) + } + + events, err := o.ListEvents(tt.query) + + if tt.expectedError != nil { + assert.Error(t, err) + assert.Equal(t, tt.expectedError, err) + } else { + assert.NoError(t, err) + if events == nil { + events = []Event{} + } + for i := range tt.expectedEvents { + actual := events[i] + expected := tt.expectedEvents[i] + assert.Equal(t, expected.AccountID, actual.AccountID) + assert.Equal(t, expected.AppID, actual.AppID) + assert.Equal(t, expected.AppName, actual.AppName) + assert.Equal(t, expected.EventTypeID, actual.EventTypeID) + assert.Equal(t, expected.EventType, actual.EventType) + assert.Equal(t, expected.UserID, actual.UserID) + assert.Equal(t, expected.UserName, actual.UserName) + assert.Equal(t, expected.RiskScore, actual.RiskScore) + assert.Equal(t, expected.ID, actual.ID) + assert.Equal(t, expected.IPAddr, actual.IPAddr) + } + } + + mockClient.AssertExpectations(t) + }) + } +} + +func TestConvertToEventsResponse(t *testing.T) { + tests := []struct { + name string + input map[string]any + expectedResult *EventsResponse + expectedError bool + }{ + { + name: "valid events response", + input: map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "pagination": map[string]any{ + "before_cursor": nil, + "after_cursor": func() *string { v := "cursor123"; return &v }(), + "previous_link": nil, + "next_link": func() *string { v := "https://api.onelogin.com/events?after_cursor=cursor123"; return &v }(), + }, + "data": []any{ + map[string]any{ + "id": float64(1), + "account_id": float64(12345), + "event_type_id": float64(1), + "user_id": float64(123), + "user_name": "testuser", + "created_at": "2023-01-01T00:00:00Z", + "ipaddr": "192.168.1.1", + }, + }, + }, + expectedResult: &EventsResponse{ + Status: struct { + Error bool `json:"error"` + Code int `json:"code"` + Message string `json:"message"` + Type string `json:"type"` + }{ + Error: false, + Code: 200, + Message: "Success", + Type: "success", + }, + Pagination: struct { + BeforeCursor *string `json:"before_cursor"` + AfterCursor *string `json:"after_cursor"` + PreviousLink *string `json:"previous_link"` + NextLink *string `json:"next_link"` + }{ + BeforeCursor: nil, + AfterCursor: func() *string { v := "cursor123"; return &v }(), + PreviousLink: nil, + NextLink: func() *string { v := "https://api.onelogin.com/events?after_cursor=cursor123"; return &v }(), + }, + Data: []Event{ + { + ID: 1, + AccountID: 12345, + EventTypeID: 1, + EventType: "", + UserID: 123, + UserName: "testuser", + IPAddr: "192.168.1.1", + }, + }, + }, + expectedError: false, + }, + { + name: "empty data response", + input: map[string]any{ + "status": map[string]any{ + "error": false, + "code": float64(200), + "message": "Success", + "type": "success", + }, + "pagination": map[string]any{ + "before_cursor": nil, + "after_cursor": nil, + "previous_link": nil, + "next_link": nil, + }, + "data": []any{}, + }, + expectedResult: &EventsResponse{ + Status: struct { + Error bool `json:"error"` + Code int `json:"code"` + Message string `json:"message"` + Type string `json:"type"` + }{ + Error: false, + Code: 200, + Message: "Success", + Type: "success", + }, + Pagination: struct { + BeforeCursor *string `json:"before_cursor"` + AfterCursor *string `json:"after_cursor"` + PreviousLink *string `json:"previous_link"` + NextLink *string `json:"next_link"` + }{ + BeforeCursor: nil, + AfterCursor: nil, + PreviousLink: nil, + NextLink: nil, + }, + Data: []Event{}, + }, + expectedError: false, + }, + { + name: "invalid json data", + input: map[string]any{ + "invalid": make(chan int), // This will cause JSON marshaling to fail + }, + expectedResult: nil, + expectedError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := convertToEventsResponse(tt.input) + + if tt.expectedError { + assert.Error(t, err) + assert.Nil(t, result) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + for i := range tt.expectedResult.Data { + actual := result.Data[i] + expected := tt.expectedResult.Data[i] + assert.Equal(t, expected.AccountID, actual.AccountID) + assert.Equal(t, expected.AppID, actual.AppID) + assert.Equal(t, expected.AppName, actual.AppName) + assert.Equal(t, expected.EventTypeID, actual.EventTypeID) + assert.Equal(t, expected.EventType, actual.EventType) + assert.Equal(t, expected.UserID, actual.UserID) + assert.Equal(t, expected.UserName, actual.UserName) + assert.Equal(t, expected.RiskScore, actual.RiskScore) + assert.Equal(t, expected.ID, actual.ID) + assert.Equal(t, expected.IPAddr, actual.IPAddr) + } + } + }) + } +} + +func TestEventStruct(t *testing.T) { + // Test Event struct field mapping + event := Event{ + AccountID: 12345, + ActorSystem: "system1", + ActorUserID: 123, + ActorUserName: "actoruser", + AppID: 456, + AppName: "Test App", + AssumingActingUserID: 789, + BrowserFingerprint: "fingerprint123", + ClientID: "client123", + CreatedAt: func() *time.Time { t := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC); return &t }(), + CustomMessage: "Custom message", + DirectoryID: 101, + DirectorySyncRunID: 202, + ErrorDescription: "Error description", + EventTypeID: 1, + EventType: "User Login", + GroupID: 303, + GroupName: "Test Group", + ID: 1, + IPAddr: "192.168.1.1", + Notes: "Test notes", + OperationName: "test_operation", + OTPDeviceID: 404, + OTPDeviceName: "OTP Device", + PolicyID: 505, + PolicyName: "Test Policy", + ProxyIP: "10.0.0.1", + Resolution: 1, + ResourceTypeID: 606, + RiskCookieID: "cookie123", + RiskReasons: "risk reasons", + RiskScore: 50, + RoleID: 707, + RoleName: "Test Role", + Since: "2023-01-01T00:00:00Z", + Until: "2023-01-31T23:59:59Z", + UserID: 123, + UserName: "testuser", + } + + // Test that all fields are properly set + assert.Equal(t, int32(12345), event.AccountID) + assert.Equal(t, "system1", event.ActorSystem) + assert.Equal(t, int32(123), event.ActorUserID) + assert.Equal(t, "actoruser", event.ActorUserName) + assert.Equal(t, int32(456), event.AppID) + assert.Equal(t, "Test App", event.AppName) + assert.Equal(t, int32(789), event.AssumingActingUserID) + assert.Equal(t, "fingerprint123", event.BrowserFingerprint) + assert.Equal(t, "client123", event.ClientID) + assert.NotNil(t, event.CreatedAt) + assert.Equal(t, "Custom message", event.CustomMessage) + assert.Equal(t, int32(101), event.DirectoryID) + assert.Equal(t, int32(202), event.DirectorySyncRunID) + assert.Equal(t, "Error description", event.ErrorDescription) + assert.Equal(t, int32(1), event.EventTypeID) + assert.Equal(t, "User Login", event.EventType) + assert.Equal(t, int32(303), event.GroupID) + assert.Equal(t, "Test Group", event.GroupName) + assert.Equal(t, uint64(1), event.ID) + assert.Equal(t, "192.168.1.1", event.IPAddr) + assert.Equal(t, "Test notes", event.Notes) + assert.Equal(t, "test_operation", event.OperationName) + assert.Equal(t, int32(404), event.OTPDeviceID) + assert.Equal(t, "OTP Device", event.OTPDeviceName) + assert.Equal(t, int32(505), event.PolicyID) + assert.Equal(t, "Test Policy", event.PolicyName) + assert.Equal(t, "10.0.0.1", event.ProxyIP) + assert.Equal(t, int32(1), event.Resolution) + assert.Equal(t, int32(606), event.ResourceTypeID) + assert.Equal(t, "cookie123", event.RiskCookieID) + assert.Equal(t, "risk reasons", event.RiskReasons) + assert.Equal(t, int32(50), event.RiskScore) + assert.Equal(t, int32(707), event.RoleID) + assert.Equal(t, "Test Role", event.RoleName) + assert.Equal(t, "2023-01-01T00:00:00Z", event.Since) + assert.Equal(t, "2023-01-31T23:59:59Z", event.Until) + assert.Equal(t, int32(123), event.UserID) + assert.Equal(t, "testuser", event.UserName) +} diff --git a/onelogin/validation.go b/onelogin/validation.go new file mode 100644 index 0000000..7e1de38 --- /dev/null +++ b/onelogin/validation.go @@ -0,0 +1,13 @@ +package onelogin + +// validateString checks if the provided value is a string. +func validateString(val any) bool { + switch v := val.(type) { + case string: + return true + case *string: + return v != nil + default: + return false + } +} diff --git a/onelogin/wrapper.go b/onelogin/wrapper.go index 08cb535..4f495dd 100644 --- a/onelogin/wrapper.go +++ b/onelogin/wrapper.go @@ -1,7 +1,12 @@ package onelogin import ( + "log/slog" + "net/http" + "os" + o "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/api" "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) @@ -15,6 +20,11 @@ func NewOneloginSDKWrapper() (*OneloginSDK, error) { if err != nil { return nil, err } + + if os.Getenv("ONECLI_DEBUG") != "" { + sdk.Client.HttpClient = NewHTTPDebuggerClient(sdk.Client.HttpClient) + } + return &OneloginSDK{sdk: sdk}, nil } @@ -45,6 +55,14 @@ func (s *OneloginSDK) GetAppUsers(appID int, query models.Queryable) (any, error return s.get(p, query) } +func (s *OneloginSDK) ListEvents(query models.Queryable) (any, error) { + return s.sdk.ListEvents(query) +} + +func (s *OneloginSDK) GetEventTypes(query models.Queryable) (any, error) { + return s.sdk.GetEventTypes(query) +} + func (s *OneloginSDK) get(path string, query models.Queryable) (any, error) { r, err := s.sdk.Client.Get(&path, query) if err != nil { @@ -53,3 +71,22 @@ func (s *OneloginSDK) get(path string, query models.Queryable) (any, error) { return utl.CheckHTTPResponse(r) } + +type HTTPDebuggerClient struct { + client api.HTTPClient + logger *slog.Logger +} + +func NewHTTPDebuggerClient(client api.HTTPClient) *HTTPDebuggerClient { + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})) + return &HTTPDebuggerClient{client: client, logger: logger} +} + +func (c *HTTPDebuggerClient) Do(req *http.Request) (*http.Response, error) { + method := req.Method + url := req.URL.String() + + c.logger.Debug("Request", "method", method, "url", url) + + return c.client.Do(req) +} diff --git a/utils/mock.go b/utils/mock.go index 7a984de..143a1e1 100644 --- a/utils/mock.go +++ b/utils/mock.go @@ -39,3 +39,15 @@ func (m *MockClient) GetAppUsers(appID int, query models.Queryable) (any, error) args := m.Called(appID, query) return args.Get(0), args.Error(1) } + +// ListEvents mocks the ListEvents method +func (m *MockClient) ListEvents(query models.Queryable) (any, error) { + args := m.Called(query) + return args.Get(0), args.Error(1) +} + +// GetEventTypes mocks the GetEventTypes method +func (m *MockClient) GetEventTypes(query models.Queryable) (any, error) { + args := m.Called(query) + return args.Get(0), args.Error(1) +}