Skip to content

[webhooks] add client methods#9

Merged
capcom6 merged 1 commit intomasterfrom
webhooks/add-client-methods
Jan 30, 2025
Merged

[webhooks] add client methods#9
capcom6 merged 1 commit intomasterfrom
webhooks/add-client-methods

Conversation

@capcom6
Copy link
Copy Markdown
Member

@capcom6 capcom6 commented Jan 29, 2025

Summary by CodeRabbit

  • New Features
    • Added webhook management capabilities to the SMS gateway client.
    • Users can now list, register, and delete webhooks through the API client.
    • Introduced a function to retrieve all supported webhook event types.
  • Bug Fixes
    • Updated JSON serialization for the ID field in the Webhook struct to omit empty values.
  • Tests
    • Introduced new unit tests for webhook management functionalities and event type validation, ensuring proper response handling.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 29, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Files with missing lines Coverage Δ
smsgateway/client.go 82.08% <100.00%> (+6.08%) ⬆️
smsgateway/domain_webhooks.go 100.00% <100.00%> (ø)

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 29, 2025

Walkthrough

The changes introduce new methods to the SMS Gateway client for managing webhooks. These methods provide functionality to list existing webhooks, register new webhooks, and delete specific webhooks through API interactions. The implementation adds three new methods to the Client struct, enabling comprehensive webhook management within the SMS gateway service. Additionally, corresponding unit tests have been created to validate these new functionalities, and a modification to the Webhook struct's JSON serialization behavior has been made.

Changes

File Change Summary
smsgateway/client.go Added three new methods to Client struct:
- ListWebhooks(ctx context.Context) ([]Webhook, error): Retrieves list of webhooks.
- RegisterWebhook(ctx context.Context, webhook Webhook) (Webhook, error): Registers a new webhook.
- DeleteWebhook(ctx context.Context, webhookID string) error: Deletes a specific webhook by ID.
smsgateway/client_test.go Added three new test functions:
- TestClient_ListWebhooks(t *testing.T): Tests ListWebhooks method.
- TestClient_RegisterWebhook(t *testing.T): Tests RegisterWebhook method.
- TestClient_DeleteWebhook(t *testing.T): Tests DeleteWebhook method.
smsgateway/domain_webhooks.go Added new function:
- WebhookEventTypes() []WebhookEvent: Returns all supported webhook event types.
Modified ID field in Webhook struct:
- Changed JSON tag from json:"id" to json:"id,omitempty" to omit empty ID from JSON output.
smsgateway/domain_webhooks_test.go Added new test function:
- TestWebhookEventTypes(t *testing.T): Validates event types returned by WebhookEventTypes().

Sequence Diagram

sequenceDiagram
    participant Client
    participant SMSGatewayAPI
    
    alt List Webhooks
        Client->>SMSGatewayAPI: GET /webhooks
        SMSGatewayAPI-->>Client: Return []Webhook
    end
    
    alt Register Webhook
        Client->>SMSGatewayAPI: POST /webhooks (Webhook data)
        SMSGatewayAPI-->>Client: Return registered Webhook
    end
    
    alt Delete Webhook
        Client->>SMSGatewayAPI: DELETE /webhooks/{webhookID}
        SMSGatewayAPI-->>Client: Confirm deletion
    end
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
smsgateway/client.go (3)

54-59: Add godoc comment for the ListWebhooks method.

While the implementation is correct, adding documentation would improve the API's usability. Consider adding a comment describing the method's purpose, return values, and possible errors.

+// ListWebhooks retrieves all registered webhooks.
+// Returns a slice of Webhook objects or an error if the request fails.
 func (c *Client) ListWebhooks(ctx context.Context) ([]Webhook, error) {

61-66: Add input validation and documentation.

The implementation looks good, but consider these improvements:

  1. Add godoc comment describing the method
  2. Validate the webhook parameter before making the request
+// RegisterWebhook registers a new webhook.
+// Returns the registered webhook with server-assigned fields or an error if the request fails.
 func (c *Client) RegisterWebhook(ctx context.Context, webhook Webhook) (Webhook, error) {
+    if webhook.URL == "" {
+        return Webhook{}, fmt.Errorf("webhook URL is required")
+    }
     path := "/webhooks"

68-72: Add input validation and documentation.

The implementation correctly uses URL escaping, but consider these improvements:

  1. Add godoc comment describing the method
  2. Validate the webhookID parameter is not empty
+// DeleteWebhook removes a webhook with the specified ID.
+// Returns an error if the deletion fails or if the webhook doesn't exist.
 func (c *Client) DeleteWebhook(ctx context.Context, webhookID string) error {
+    if webhookID == "" {
+        return fmt.Errorf("webhookID is required")
+    }
     path := fmt.Sprintf("/webhooks/%s", url.PathEscape(webhookID))
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64912d4 and 1451a45.

📒 Files selected for processing (1)
  • smsgateway/client.go (2 hunks)
🔇 Additional comments (2)
smsgateway/client.go (2)

9-9: LGTM! Import addition is appropriate.

The net/url import is correctly added and necessary for safe URL path escaping in the webhook management methods.


54-72: Verify the Webhook type definition.

The implementation looks solid overall, but I notice the Webhook type is used but not visible in this file. Please ensure it's properly defined and documented in the package.

✅ Verification successful

✓ Webhook type is properly defined and documented

The Webhook type is well-defined in domain_webhooks.go with appropriate fields, validation rules, and comprehensive documentation. The implementation in client.go correctly uses this type.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Webhook type definition
rg -t go "type\s+Webhook\s+struct" .

Length of output: 92


Script:

#!/bin/bash
# View the Webhook struct definition with context
rg -B 2 -A 10 "type\s+Webhook\s+struct" ./smsgateway/domain_webhooks.go

Length of output: 521

@android-sms-gateway android-sms-gateway deleted a comment from coderabbitai Bot Jan 29, 2025
@capcom6
Copy link
Copy Markdown
Member Author

capcom6 commented Jan 29, 2025

@CodeRabbit, please generate unit tests for the new code

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 29, 2025

Tip

For best results, initiate chat on the files or code changes.

@capcom6 Here are the unit tests for the new webhook methods:

func TestClient_ListWebhooks(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/webhooks" {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		if r.Method != http.MethodGet {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`[{"id":"123","url":"https://example.com","events":["sms:delivered"]}]`))
	}))
	defer server.Close()

	client := NewClient(Config{
		BaseURL: server.URL,
	})

	tests := []struct {
		name    string
		c       *Client
		want    []Webhook
		wantErr bool
	}{
		{
			name: "Success",
			c:    client,
			want: []Webhook{
				{
					ID:     "123",
					URL:    "https://example.com",
					Events: []WebhookEvent{"sms:delivered"},
				},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := tt.c.ListWebhooks(context.Background())
			if (err != nil) != tt.wantErr {
				t.Errorf("Client.ListWebhooks() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Client.ListWebhooks() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestClient_RegisterWebhook(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/webhooks" {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		if r.Method != http.MethodPost {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		if r.Header.Get("Content-Type") != "application/json" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		body, _ := io.ReadAll(r.Body)
		defer r.Body.Close()

		if string(body) != `{"url":"https://example.com","events":["sms:delivered"]}` {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		w.WriteHeader(http.StatusCreated)
		_, _ = w.Write([]byte(`{"id":"123","url":"https://example.com","events":["sms:delivered"]}`))
	}))
	defer server.Close()

	client := NewClient(Config{
		BaseURL: server.URL,
	})

	type args struct {
		webhook Webhook
	}
	tests := []struct {
		name    string
		c       *Client
		args    args
		want    Webhook
		wantErr bool
	}{
		{
			name: "Success",
			c:    client,
			args: args{
				webhook: Webhook{
					URL:    "https://example.com",
					Events: []WebhookEvent{"sms:delivered"},
				},
			},
			want: Webhook{
				ID:     "123",
				URL:    "https://example.com",
				Events: []WebhookEvent{"sms:delivered"},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := tt.c.RegisterWebhook(context.Background(), tt.args.webhook)
			if (err != nil) != tt.wantErr {
				t.Errorf("Client.RegisterWebhook() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Client.RegisterWebhook() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestClient_DeleteWebhook(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/webhooks/123" {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		if r.Method != http.MethodDelete {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	client := NewClient(Config{
		BaseURL: server.URL,
	})

	type args struct {
		webhookID string
	}
	tests := []struct {
		name    string
		c       *Client
		args    args
		wantErr bool
	}{
		{
			name: "Success",
			c:    client,
			args: args{
				webhookID: "123",
			},
			wantErr: false,
		},
		{
			name: "Not Found",
			c:    client,
			args: args{
				webhookID: "456",
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := tt.c.DeleteWebhook(context.Background(), tt.args.webhookID); (err != nil) != tt.wantErr {
				t.Errorf("Client.DeleteWebhook() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

These tests cover:

  1. Successful webhook listing with proper response parsing
  2. Successful webhook registration with request body validation
  3. Successful webhook deletion
  4. Error cases for invalid webhook IDs
  5. HTTP method validation
  6. Content-Type header validation for POST requests

The tests follow the same patterns as the existing tests in the codebase, using table-driven tests and httptest.NewServer for HTTP mocking.

🔗 Analysis chain
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Get the implementation details
cat smsgateway/client.go

# Check for existing test files
fd ".*_test.go"

Length of output: 3069


Script:

#!/bin/bash
# Get the existing test files content
cat smsgateway/client_test.go
cat smsgateway/domain_webhooks_test.go

Length of output: 4429

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
smsgateway/domain_webhooks.go (1)

38-38: Document ID field requirements in struct comments.

While the omitempty tag is appropriate for the ID field, it would be helpful to document when this field is expected to be populated vs. when it should be empty. For example:

  • Required when: retrieving existing webhooks
  • Optional when: registering new webhooks

Add this documentation above the ID field:

 // The unique identifier of the webhook.
+// This field is populated by the server when retrieving existing webhooks.
+// When registering new webhooks, this field should be omitted.
 ID string `json:"id,omitempty" validate:"max=36" example:"123e4567-e89b-12d3-a456-426614174000"`
smsgateway/client.go (2)

74-78: Add validation for webhookID parameter.

The webhookID parameter should be validated before making the request to prevent unnecessary API calls.

 func (c *Client) DeleteWebhook(ctx context.Context, webhookID string) error {
+    if webhookID == "" {
+        return fmt.Errorf("webhookID cannot be empty")
+    }
     path := fmt.Sprintf("/webhooks/%s", url.PathEscape(webhookID))
     return c.doRequest(ctx, http.MethodDelete, path, map[string]string{}, nil, nil)
 }

54-61: Add context timeout examples in method documentation.

The methods accept a context parameter but don't provide examples of how to use it for timeouts.

Add usage examples to the documentation:

 // ListWebhooks retrieves all registered webhooks.
 // Returns a slice of Webhook objects or an error if the request fails.
+//
+// Example with timeout:
+//  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+//  defer cancel()
+//  webhooks, err := client.ListWebhooks(ctx)
 func (c *Client) ListWebhooks(ctx context.Context) ([]Webhook, error) {

Also applies to: 63-70, 72-78

smsgateway/client_test.go (2)

172-202: Add more test cases for ListWebhooks.

The test coverage could be improved by adding cases for:

  • Empty webhook list
  • Server errors (500, 503)
  • Context cancellation
 tests := []struct {
     name    string
     c       *Client
+    ctx     context.Context
     want    []Webhook
     wantErr bool
+    errMsg  string
 }{
     {
         name: "Success",
         c:    client,
+        ctx:  context.Background(),
         want: []Webhook{
             {
                 ID:    "123",
                 URL:   "https://example.com",
                 Event: WebhookEventSmsDelivered,
             },
         },
         wantErr: false,
     },
+    {
+        name: "Empty List",
+        c:    client,
+        ctx:  context.Background(),
+        want: []Webhook{},
+        wantErr: false,
+    },
+    {
+        name: "Context Cancelled",
+        c:    client,
+        ctx:  func() context.Context {
+            ctx, cancel := context.WithCancel(context.Background())
+            cancel()
+            return ctx
+        }(),
+        want: nil,
+        wantErr: true,
+        errMsg: "context canceled",
+    },
 }

205-276: Add test cases for invalid webhook events in RegisterWebhook.

The test should verify that the server validates webhook events.

 tests := []struct {
     name    string
     c       *Client
     args    args
     want    Webhook
     wantErr bool
+    errMsg  string
 }{
+    {
+        name: "Invalid Event",
+        c:    client,
+        args: args{
+            webhook: Webhook{
+                URL:   "https://example.com",
+                Event: "invalid:event",
+            },
+        },
+        want:    Webhook{},
+        wantErr: true,
+        errMsg:  "invalid webhook event",
+    },
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1451a45 and 562dd69.

📒 Files selected for processing (3)
  • smsgateway/client.go (3 hunks)
  • smsgateway/client_test.go (1 hunks)
  • smsgateway/domain_webhooks.go (1 hunks)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
smsgateway/domain_webhooks.go (1)

26-28: Remove redundant documentation.

The second comment block repeats the first one without adding new information.

 // WebhookEventTypes returns a slice of all supported webhook event types.
-//
-// Returns a slice of all supported webhook event types.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 562dd69 and 7614de9.

📒 Files selected for processing (1)
  • smsgateway/domain_webhooks.go (2 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
smsgateway/domain_webhooks.go

[warning] 29-36: smsgateway/domain_webhooks.go#L29-L36
Added lines #L29 - L36 were not covered by tests

🔇 Additional comments (2)
smsgateway/domain_webhooks.go (2)

51-51: LGTM! Good use of omitempty tag.

Adding omitempty to the ID field is a good practice as it prevents empty IDs from being included in JSON output, which is particularly useful for webhook registration requests where the ID is typically assigned by the server.


29-37: 🛠️ Refactor suggestion

Refactor to use existing allEventTypes map.

The function currently maintains a separate list of event types that duplicates what's already in allEventTypes. This creates a maintenance burden as both lists need to be kept in sync.

 func WebhookEventTypes() []WebhookEvent {
-    return []WebhookEvent{
-        WebhookEventSmsReceived,
-        WebhookEventSmsSent,
-        WebhookEventSmsDelivered,
-        WebhookEventSmsFailed,
-        WebhookEventSystemPing,
-    }
+    events := make([]WebhookEvent, 0, len(allEventTypes))
+    for event := range allEventTypes {
+        events = append(events, event)
+    }
+    return events
 }

Also, please add unit tests for this function to ensure proper coverage.

✅ Verification successful

Refactoring suggestion and test coverage concerns are valid

The review is correct on both points:

  1. The WebhookEventTypes() function duplicates the events already defined in allEventTypes map, creating a maintenance burden.
  2. There are no unit tests for the WebhookEventTypes() function, while other webhook-related functionality is tested.

The suggested refactoring will help maintain a single source of truth, and adding tests will ensure the function works as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any existing tests for WebhookEventTypes
rg -l "TestWebhookEventTypes" .

Length of output: 31


Script:

#!/bin/bash
# Find allEventTypes declaration and WebhookEvent type
ast-grep --pattern 'var allEventTypes'
ast-grep --pattern 'type WebhookEvent'

# Find webhook-related test files and their content
fd -e go -x grep -l "webhook" {} \;

Length of output: 274


Script:

#!/bin/bash
# Check domain_webhooks.go content
cat smsgateway/domain_webhooks.go

# Check webhook test content
cat smsgateway/webhooks/webhook_test.go

Length of output: 2513

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 29-36: smsgateway/domain_webhooks.go#L29-L36
Added lines #L29 - L36 were not covered by tests

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
smsgateway/domain_webhooks_test.go (1)

38-44: Enhance test coverage with additional validations.

While the current implementation is functional, consider enhancing it with additional validations:

 func TestWebhookEventTypes(t *testing.T) {
+    eventTypes := smsgateway.WebhookEventTypes()
+    
+    // Verify non-empty return
+    if len(eventTypes) == 0 {
+        t.Error("WebhookEventTypes() returned empty slice")
+    }
+
+    // Verify required event types are present
+    requiredEvents := []smsgateway.WebhookEvent{
+        smsgateway.WebhookEventSmsDelivered,
+        // Add other required event types
+    }
+    
+    eventSet := make(map[smsgateway.WebhookEvent]struct{})
+    for _, v := range eventTypes {
+        eventSet[v] = struct{}{}
         if !smsgateway.IsValidWebhookEvent(v) {
-            t.Errorf("event type %s is not valid", v)
+            t.Errorf("event type %q is not valid", v)
         }
     }
+    
+    for _, required := range requiredEvents {
+        if _, exists := eventSet[required]; !exists {
+            t.Errorf("required event type %q is missing", required)
+        }
+    }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7614de9 and 59c34aa.

📒 Files selected for processing (2)
  • smsgateway/domain_webhooks.go (2 hunks)
  • smsgateway/domain_webhooks_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • smsgateway/domain_webhooks.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
smsgateway/domain_webhooks_test.go (2)

36-38: LGTM! Clear documentation and purpose.

The test function is well-documented and has a clear purpose.


Line range hint 1-44: Verify complete test coverage for webhook functionality.

While this file adequately tests webhook event type validation, please ensure that all new webhook client methods (listing, registration, and deletion) are covered by the tests mentioned in the PR comments.

✅ Verification successful

Test coverage for webhook functionality is complete

The codebase includes comprehensive test coverage across all webhook-related functionality:

  • Domain-level tests for event validation
  • Client-level tests for listing, registering, and deleting webhooks
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check test coverage for webhook-related functionality

# Search for webhook-related test files
echo "Searching for webhook-related test files..."
fd -e go -e _test.go webhook

# Search for webhook client method tests
echo -e "\nSearching for webhook client method tests..."
rg -l "TestClient.*Webhook" 

# Check test coverage patterns
echo -e "\nChecking test patterns for webhook operations..."
rg -A 5 "func TestClient_(List|Register|Delete)Webhook"

Length of output: 1745

@capcom6 capcom6 force-pushed the webhooks/add-client-methods branch from 59c34aa to 10ce8e1 Compare January 30, 2025 09:55
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
smsgateway/client.go (1)

54-61: Consider adding pagination and filtering support.

While the current implementation is functional, consider:

  1. Adding pagination support to handle large numbers of webhooks efficiently
  2. Supporting query parameters for filtering webhooks by event type or URL

This would improve scalability and usability of the API.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59c34aa and 10ce8e1.

📒 Files selected for processing (4)
  • smsgateway/client.go (3 hunks)
  • smsgateway/client_test.go (1 hunks)
  • smsgateway/domain_webhooks.go (2 hunks)
  • smsgateway/domain_webhooks_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • smsgateway/domain_webhooks_test.go
  • smsgateway/domain_webhooks.go
🔇 Additional comments (3)
smsgateway/client.go (2)

9-9: LGTM! Import addition is appropriate.

The addition of net/url import is necessary for proper URL escaping in the DeleteWebhook method.


72-78: LGTM! Proper URL escaping and status code handling.

The implementation correctly:

  1. Escapes webhook ID to prevent path traversal
  2. Handles HTTP 204 No Content response appropriately

Also applies to: 117-119

smsgateway/client_test.go (1)

278-329: LGTM! Comprehensive test coverage for DeleteWebhook.

The test cases appropriately cover:

  1. Successful webhook deletion
  2. Non-existent webhook handling
  3. HTTP method validation
  4. Status code verification

Comment thread smsgateway/client.go
Comment on lines +63 to +70
// RegisterWebhook registers a new webhook.
// Returns the registered webhook with server-assigned fields or an error if the request fails.
func (c *Client) RegisterWebhook(ctx context.Context, webhook Webhook) (Webhook, error) {
path := "/webhooks"
resp := Webhook{}

return resp, c.doRequest(ctx, http.MethodPost, path, map[string]string{}, &webhook, &resp)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation for webhook registration.

Consider adding:

  1. URL format validation
  2. Event type validation against allowed values
  3. Rate limiting considerations to prevent abuse

Example validation implementation:

 func (c *Client) RegisterWebhook(ctx context.Context, webhook Webhook) (Webhook, error) {
+    // Validate URL format
+    if _, err := url.Parse(webhook.URL); err != nil {
+        return Webhook{}, fmt.Errorf("invalid webhook URL: %w", err)
+    }
+
+    // Validate event type
+    if !webhook.Event.IsValid() {
+        return Webhook{}, fmt.Errorf("invalid event type: %s", webhook.Event)
+    }
+
     path := "/webhooks"
     resp := Webhook{}

     return resp, c.doRequest(ctx, http.MethodPost, path, map[string]string{}, &webhook, &resp)
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment thread smsgateway/client_test.go
Comment on lines +205 to +276
func TestClient_RegisterWebhook(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/webhooks" {
w.WriteHeader(http.StatusNotFound)
return
}
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
if r.Header.Get("Content-Type") != "application/json" {
w.WriteHeader(http.StatusBadRequest)
return
}

body, _ := io.ReadAll(r.Body)
defer r.Body.Close()

if string(body) != `{"url":"https://example.com","event":"sms:delivered"}` {
w.WriteHeader(http.StatusBadRequest)
return
}

w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`{"id":"123","url":"https://example.com","event":"sms:delivered"}`))
}))
defer server.Close()

client := NewClient(Config{
BaseURL: server.URL,
})

type args struct {
webhook Webhook
}
tests := []struct {
name string
c *Client
args args
want Webhook
wantErr bool
}{
{
name: "Success",
c: client,
args: args{
webhook: Webhook{
URL: "https://example.com",
Event: WebhookEventSmsDelivered,
},
},
want: Webhook{
ID: "123",
URL: "https://example.com",
Event: WebhookEventSmsDelivered,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.c.RegisterWebhook(context.Background(), tt.args.webhook)
if (err != nil) != tt.wantErr {
t.Errorf("Client.RegisterWebhook() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.RegisterWebhook() = %v, want %v", got, tt.want)
}
})
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error test cases for RegisterWebhook.

Consider adding test cases for:

  1. Invalid webhook URL format
  2. Invalid event type
  3. Server validation errors (400)
  4. Duplicate webhook registration

Example additional test cases:

 tests := []struct {
     name    string
     c       *Client
     args    args
     want    Webhook
     wantErr bool
 }{
     {
         name: "Success",
         c:    client,
         args: args{webhook: Webhook{
             URL:   "https://example.com",
             Event: WebhookEventSmsDelivered,
         }},
         want: Webhook{
             ID:    "123",
             URL:   "https://example.com",
             Event: WebhookEventSmsDelivered,
         },
         wantErr: false,
     },
+    {
+        name: "Invalid URL",
+        c:    client,
+        args: args{webhook: Webhook{
+            URL:   "not-a-url",
+            Event: WebhookEventSmsDelivered,
+        }},
+        want: Webhook{},
+        wantErr: true,
+    },
+    {
+        name: "Invalid Event",
+        c:    client,
+        args: args{webhook: Webhook{
+            URL:   "https://example.com",
+            Event: "invalid",
+        }},
+        want: Webhook{},
+        wantErr: true,
+    },
 },

Committable suggestion skipped: line range outside the PR's diff.

Comment thread smsgateway/client_test.go
Comment on lines +153 to +203
func TestClient_ListWebhooks(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/webhooks" {
w.WriteHeader(http.StatusNotFound)
return
}
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`[{"id":"123","url":"https://example.com","event":"sms:delivered"}]`))
}))
defer server.Close()

client := NewClient(Config{
BaseURL: server.URL,
})

tests := []struct {
name string
c *Client
want []Webhook
wantErr bool
}{
{
name: "Success",
c: client,
want: []Webhook{
{
ID: "123",
URL: "https://example.com",
Event: WebhookEventSmsDelivered,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.c.ListWebhooks(context.Background())
if (err != nil) != tt.wantErr {
t.Errorf("Client.ListWebhooks() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.ListWebhooks() = %v, want %v", got, tt.want)
}
})
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add more test cases for ListWebhooks.

Consider adding test cases for:

  1. Empty webhook list response
  2. Server errors (500, 503)
  3. Invalid response format
  4. Authentication failures (401, 403)

Example additional test cases:

 tests := []struct {
     name    string
     c       *Client
     want    []Webhook
     wantErr bool
 }{
     {
         name: "Success",
         c:    client,
         want: []Webhook{{
             ID:    "123",
             URL:   "https://example.com",
             Event: WebhookEventSmsDelivered,
         }},
         wantErr: false,
     },
+    {
+        name: "Empty List",
+        c:    client,
+        want: []Webhook{},
+        wantErr: false,
+    },
+    {
+        name: "Server Error",
+        c:    client,
+        want: nil,
+        wantErr: true,
+    },
 },

Committable suggestion skipped: line range outside the PR's diff.

@capcom6 capcom6 merged commit 4b19250 into master Jan 30, 2025
@capcom6 capcom6 deleted the webhooks/add-client-methods branch January 30, 2025 10:00
@coderabbitai coderabbitai Bot mentioned this pull request Jul 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant