From 6989cf680b66faea455aa3bf787ae41f725bcebd Mon Sep 17 00:00:00 2001 From: Acho Arnold Date: Tue, 5 May 2026 11:42:39 +0300 Subject: [PATCH 1/3] feat: add PhoneService, PhoneAPIKeyService, and WebhookService - PhoneService: Upsert() and UpsertFCMToken() for phone management - PhoneAPIKeyService: Store() for creating phone API keys - WebhookService: Store() for creating webhooks with signing keys - Register new services on Client struct Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- client.go | 6 ++++ phone.go | 20 ++++++++++++ phone_api_key.go | 19 ++++++++++++ phone_api_key_service.go | 35 +++++++++++++++++++++ phone_service.go | 67 ++++++++++++++++++++++++++++++++++++++++ webhook.go | 18 +++++++++++ webhook_service.go | 38 +++++++++++++++++++++++ 7 files changed, 203 insertions(+) create mode 100644 phone.go create mode 100644 phone_api_key.go create mode 100644 phone_api_key_service.go create mode 100644 phone_service.go create mode 100644 webhook.go create mode 100644 webhook_service.go diff --git a/client.go b/client.go index 5029ce0..6bb59df 100644 --- a/client.go +++ b/client.go @@ -25,6 +25,9 @@ type Client struct { Heartbeats *HeartbeatService Messages *MessageService Cipher *CipherService + Phones *PhoneService + PhoneAPIKeys *PhoneAPIKeyService + Webhooks *WebhookService } // New creates and returns a new campay.Client from a slice of campay.ClientOption. @@ -46,6 +49,9 @@ func New(options ...Option) *Client { client.Heartbeats = (*HeartbeatService)(&client.common) client.MessageThreads = (*MessageThreadService)(&client.common) client.Cipher = (*CipherService)(&client.common) + client.Phones = (*PhoneService)(&client.common) + client.PhoneAPIKeys = (*PhoneAPIKeyService)(&client.common) + client.Webhooks = (*WebhookService)(&client.common) return client } diff --git a/phone.go b/phone.go new file mode 100644 index 0000000..a08d3b1 --- /dev/null +++ b/phone.go @@ -0,0 +1,20 @@ +package httpsms + +import "time" + +// Phone represents a phone registered in the httpSMS API +type Phone struct { + ID string `json:"id"` + UserID string `json:"user_id"` + PhoneNumber string `json:"phone_number"` + FcmToken *string `json:"fcm_token"` + MessagesPerMinute uint `json:"messages_per_minute"` + MaxSendAttempts uint `json:"max_send_attempts"` + MessageExpirationSeconds uint `json:"message_expiration_seconds"` + SIM string `json:"sim"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// PhoneResponse is the response gotten with a phone +type PhoneResponse ApiResponse[Phone] diff --git a/phone_api_key.go b/phone_api_key.go new file mode 100644 index 0000000..199f3ee --- /dev/null +++ b/phone_api_key.go @@ -0,0 +1,19 @@ +package httpsms + +import "time" + +// PhoneAPIKey represents a phone API key +type PhoneAPIKey struct { + ID string `json:"id"` + Name string `json:"name"` + UserID string `json:"user_id"` + UserEmail string `json:"user_email"` + PhoneNumbers []string `json:"phone_numbers"` + PhoneIDs []string `json:"phone_ids"` + APIKey string `json:"api_key"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// PhoneAPIKeyResponse is the response gotten with a phone api key +type PhoneAPIKeyResponse ApiResponse[PhoneAPIKey] diff --git a/phone_api_key_service.go b/phone_api_key_service.go new file mode 100644 index 0000000..b1265df --- /dev/null +++ b/phone_api_key_service.go @@ -0,0 +1,35 @@ +package httpsms + +import ( + "context" + "encoding/json" + "net/http" +) + +// PhoneAPIKeyService is the API client for the phone api key endpoints +type PhoneAPIKeyService service + +// PhoneAPIKeyStoreParams is the request payload for creating a phone api key +type PhoneAPIKeyStoreParams struct { + Name string `json:"name"` +} + +// Store adds a new phone api key +func (service *PhoneAPIKeyService) Store(ctx context.Context, params *PhoneAPIKeyStoreParams) (*PhoneAPIKeyResponse, *Response, error) { + request, err := service.client.newRequest(ctx, http.MethodPost, "/v1/phone-api-keys/", params) + if err != nil { + return nil, nil, err + } + + response, err := service.client.do(request) + if err != nil { + return nil, response, err + } + + phoneAPIKey := new(PhoneAPIKeyResponse) + if err = json.Unmarshal(*response.Body, phoneAPIKey); err != nil { + return nil, response, err + } + + return phoneAPIKey, response, nil +} diff --git a/phone_service.go b/phone_service.go new file mode 100644 index 0000000..c666056 --- /dev/null +++ b/phone_service.go @@ -0,0 +1,67 @@ +package httpsms + +import ( + "context" + "encoding/json" + "net/http" +) + +// PhoneService is the API client for the phone endpoints +type PhoneService service + +// PhoneUpsertParams is the request payload for creating/updating a phone +type PhoneUpsertParams struct { + PhoneNumber string `json:"phone_number"` + FcmToken string `json:"fcm_token,omitempty"` + MessagesPerMinute uint `json:"messages_per_minute,omitempty"` + MaxSendAttempts uint `json:"max_send_attempts,omitempty"` + MessageExpirationSeconds uint `json:"message_expiration_seconds,omitempty"` + SIM string `json:"sim,omitempty"` +} + +// PhoneFCMTokenParams is the request for binding FCM token to a phone via phone API key +type PhoneFCMTokenParams struct { + PhoneNumber string `json:"phone_number"` + FcmToken string `json:"fcm_token"` + SIM string `json:"sim,omitempty"` +} + +// Upsert creates or updates a phone +func (service *PhoneService) Upsert(ctx context.Context, params *PhoneUpsertParams) (*PhoneResponse, *Response, error) { + request, err := service.client.newRequest(ctx, http.MethodPut, "/v1/phones", params) + if err != nil { + return nil, nil, err + } + + response, err := service.client.do(request) + if err != nil { + return nil, response, err + } + + phone := new(PhoneResponse) + if err = json.Unmarshal(*response.Body, phone); err != nil { + return nil, response, err + } + + return phone, response, nil +} + +// UpsertFCMToken binds an FCM token to a phone via the phone API key +func (service *PhoneService) UpsertFCMToken(ctx context.Context, params *PhoneFCMTokenParams) (*PhoneResponse, *Response, error) { + request, err := service.client.newRequest(ctx, http.MethodPut, "/v1/phones/fcm-token", params) + if err != nil { + return nil, nil, err + } + + response, err := service.client.do(request) + if err != nil { + return nil, response, err + } + + phone := new(PhoneResponse) + if err = json.Unmarshal(*response.Body, phone); err != nil { + return nil, response, err + } + + return phone, response, nil +} diff --git a/webhook.go b/webhook.go new file mode 100644 index 0000000..d14f293 --- /dev/null +++ b/webhook.go @@ -0,0 +1,18 @@ +package httpsms + +import "time" + +// Webhook represents a webhook registered in the httpSMS API +type Webhook struct { + ID string `json:"id"` + UserID string `json:"user_id"` + URL string `json:"url"` + SigningKey string `json:"signing_key"` + PhoneNumbers []string `json:"phone_numbers"` + Events []string `json:"events"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// WebhookResponse is the response gotten with a webhook +type WebhookResponse ApiResponse[Webhook] diff --git a/webhook_service.go b/webhook_service.go new file mode 100644 index 0000000..31230d8 --- /dev/null +++ b/webhook_service.go @@ -0,0 +1,38 @@ +package httpsms + +import ( + "context" + "encoding/json" + "net/http" +) + +// WebhookService is the API client for the webhook endpoints +type WebhookService service + +// WebhookStoreParams is the request payload for creating a webhook +type WebhookStoreParams struct { + SigningKey string `json:"signing_key"` + URL string `json:"url"` + PhoneNumbers []string `json:"phone_numbers"` + Events []string `json:"events"` +} + +// Store adds a new webhook +func (service *WebhookService) Store(ctx context.Context, params *WebhookStoreParams) (*WebhookResponse, *Response, error) { + request, err := service.client.newRequest(ctx, http.MethodPost, "/v1/webhooks", params) + if err != nil { + return nil, nil, err + } + + response, err := service.client.do(request) + if err != nil { + return nil, response, err + } + + webhook := new(WebhookResponse) + if err = json.Unmarshal(*response.Body, webhook); err != nil { + return nil, response, err + } + + return webhook, response, nil +} From f68ae42ad206712b256e3f17006f0d8e215f1e26 Mon Sep 17 00:00:00 2001 From: Acho Arnold Date: Tue, 5 May 2026 22:40:44 +0300 Subject: [PATCH 2/3] Fix uuid --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b206555..a61a2ce 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/NdoleStudio/httpsms-go go 1.18 require ( - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index e3cc90d..776bea3 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 2830472bf894b6fd8a38230a754cdee4faab924e Mon Sep 17 00:00:00 2001 From: Acho Arnold Date: Tue, 5 May 2026 22:51:05 +0300 Subject: [PATCH 3/3] test: add tests for PhoneService, PhoneAPIKeyService, and WebhookService Add success, error, and request verification tests for all new service methods introduced in this branch: - PhoneService.Upsert and PhoneService.UpsertFCMToken - PhoneAPIKeyService.Store - WebhookService.Store Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/stubs/phone.go | 45 +++++++++ internal/stubs/phone_api_key.go | 22 ++++ internal/stubs/webhook.go | 21 ++++ phone_api_key_service_test.go | 86 ++++++++++++++++ phone_service_test.go | 172 ++++++++++++++++++++++++++++++++ webhook_service_test.go | 95 ++++++++++++++++++ 6 files changed, 441 insertions(+) create mode 100644 internal/stubs/phone.go create mode 100644 internal/stubs/phone_api_key.go create mode 100644 internal/stubs/webhook.go create mode 100644 phone_api_key_service_test.go create mode 100644 phone_service_test.go create mode 100644 webhook_service_test.go diff --git a/internal/stubs/phone.go b/internal/stubs/phone.go new file mode 100644 index 0000000..ee03a2b --- /dev/null +++ b/internal/stubs/phone.go @@ -0,0 +1,45 @@ +package stubs + +// PhoneUpsertResponse response from the PUT /v1/phones endpoint +func PhoneUpsertResponse() []byte { + return []byte(` +{ + "data": { + "id": "9d484671-cac2-41de-9171-d9d2c1835d7b", + "user_id": "hT5V2CmN5bbG81glMLmosxPV9Np2", + "phone_number": "+18005550199", + "fcm_token": "test-fcm-token", + "messages_per_minute": 5, + "max_send_attempts": 3, + "message_expiration_seconds": 3600, + "sim": "SIM1", + "created_at": "2024-01-21T13:07:56.203538Z", + "updated_at": "2024-01-21T13:07:56.203538Z" + }, + "message": "phone upserted successfully", + "status": "success" +} +`) +} + +// PhoneUpsertFCMTokenResponse response from the PUT /v1/phones/fcm-token endpoint +func PhoneUpsertFCMTokenResponse() []byte { + return []byte(` +{ + "data": { + "id": "9d484671-cac2-41de-9171-d9d2c1835d7b", + "user_id": "hT5V2CmN5bbG81glMLmosxPV9Np2", + "phone_number": "+18005550199", + "fcm_token": "new-fcm-token", + "messages_per_minute": 5, + "max_send_attempts": 3, + "message_expiration_seconds": 3600, + "sim": "SIM1", + "created_at": "2024-01-21T13:07:56.203538Z", + "updated_at": "2024-01-21T13:07:56.203538Z" + }, + "message": "phone FCM token updated successfully", + "status": "success" +} +`) +} diff --git a/internal/stubs/phone_api_key.go b/internal/stubs/phone_api_key.go new file mode 100644 index 0000000..2d9287d --- /dev/null +++ b/internal/stubs/phone_api_key.go @@ -0,0 +1,22 @@ +package stubs + +// PhoneAPIKeyStoreResponse response from the POST /v1/phone-api-keys/ endpoint +func PhoneAPIKeyStoreResponse() []byte { + return []byte(` +{ + "data": { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "name": "test-key", + "user_id": "hT5V2CmN5bbG81glMLmosxPV9Np2", + "user_email": "test@example.com", + "phone_numbers": ["+18005550199"], + "phone_ids": ["9d484671-cac2-41de-9171-d9d2c1835d7b"], + "api_key": "pk_test_1234567890abcdef", + "created_at": "2024-01-21T13:07:56.203538Z", + "updated_at": "2024-01-21T13:07:56.203538Z" + }, + "message": "phone API key created successfully", + "status": "success" +} +`) +} diff --git a/internal/stubs/webhook.go b/internal/stubs/webhook.go new file mode 100644 index 0000000..f893e3c --- /dev/null +++ b/internal/stubs/webhook.go @@ -0,0 +1,21 @@ +package stubs + +// WebhookStoreResponse response from the POST /v1/webhooks endpoint +func WebhookStoreResponse() []byte { + return []byte(` +{ + "data": { + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "user_id": "hT5V2CmN5bbG81glMLmosxPV9Np2", + "url": "https://example.com/webhook", + "signing_key": "whsec_test_signing_key", + "phone_numbers": ["+18005550199"], + "events": ["message.received", "message.sent"], + "created_at": "2024-01-21T13:07:56.203538Z", + "updated_at": "2024-01-21T13:07:56.203538Z" + }, + "message": "webhook created successfully", + "status": "success" +} +`) +} diff --git a/phone_api_key_service_test.go b/phone_api_key_service_test.go new file mode 100644 index 0000000..ceddb78 --- /dev/null +++ b/phone_api_key_service_test.go @@ -0,0 +1,86 @@ +package httpsms + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/NdoleStudio/httpsms-go/internal/helpers" + "github.com/NdoleStudio/httpsms-go/internal/stubs" + "github.com/stretchr/testify/assert" +) + +func TestPhoneAPIKeyService_Store(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusOK, stubs.PhoneAPIKeyStoreResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + phoneAPIKey, response, err := client.PhoneAPIKeys.Store(context.Background(), &PhoneAPIKeyStoreParams{ + Name: "test-key", + }) + + // Assert + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode) + + jsonContent, _ := json.Marshal(phoneAPIKey) + assert.JSONEq(t, string(stubs.PhoneAPIKeyStoreResponse()), string(jsonContent)) + + // Teardown + server.Close() +} + +func TestPhoneAPIKeyService_StoreWithError(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusInternalServerError, stubs.HttpInternalServerErrorResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, response, err := client.PhoneAPIKeys.Store(context.Background(), &PhoneAPIKeyStoreParams{ + Name: "test-key", + }) + + // Assert + assert.NotNil(t, err) + assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode) + assert.Equal(t, string(stubs.HttpInternalServerErrorResponse()), string(*response.Body)) + + // Teardown + server.Close() +} + +func TestPhoneAPIKeyService_StoreRequest(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + var capturedRequest http.Request + server := helpers.MakeRequestCapturingTestServer(http.StatusOK, stubs.PhoneAPIKeyStoreResponse(), &capturedRequest) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, _, err := client.PhoneAPIKeys.Store(context.Background(), &PhoneAPIKeyStoreParams{ + Name: "test-key", + }) + + // Assert + assert.Nil(t, err) + assert.Equal(t, http.MethodPost, capturedRequest.Method) + assert.Equal(t, "/v1/phone-api-keys/", capturedRequest.URL.Path) + assert.Equal(t, "application/json", capturedRequest.Header.Get("Content-Type")) + assert.Equal(t, apiKey, capturedRequest.Header.Get("x-api-key")) + + // Teardown + server.Close() +} diff --git a/phone_service_test.go b/phone_service_test.go new file mode 100644 index 0000000..4769005 --- /dev/null +++ b/phone_service_test.go @@ -0,0 +1,172 @@ +package httpsms + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/NdoleStudio/httpsms-go/internal/helpers" + "github.com/NdoleStudio/httpsms-go/internal/stubs" + "github.com/stretchr/testify/assert" +) + +func TestPhoneService_Upsert(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusOK, stubs.PhoneUpsertResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + phone, response, err := client.Phones.Upsert(context.Background(), &PhoneUpsertParams{ + PhoneNumber: "+18005550199", + FcmToken: "test-fcm-token", + MessagesPerMinute: 5, + MaxSendAttempts: 3, + MessageExpirationSeconds: 3600, + SIM: "SIM1", + }) + + // Assert + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode) + + jsonContent, _ := json.Marshal(phone) + assert.JSONEq(t, string(stubs.PhoneUpsertResponse()), string(jsonContent)) + + // Teardown + server.Close() +} + +func TestPhoneService_UpsertWithError(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusInternalServerError, stubs.HttpInternalServerErrorResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, response, err := client.Phones.Upsert(context.Background(), &PhoneUpsertParams{ + PhoneNumber: "+18005550199", + }) + + // Assert + assert.NotNil(t, err) + assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode) + assert.Equal(t, string(stubs.HttpInternalServerErrorResponse()), string(*response.Body)) + + // Teardown + server.Close() +} + +func TestPhoneService_UpsertRequest(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + var capturedRequest http.Request + server := helpers.MakeRequestCapturingTestServer(http.StatusOK, stubs.PhoneUpsertResponse(), &capturedRequest) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, _, err := client.Phones.Upsert(context.Background(), &PhoneUpsertParams{ + PhoneNumber: "+18005550199", + FcmToken: "test-fcm-token", + MessagesPerMinute: 5, + }) + + // Assert + assert.Nil(t, err) + assert.Equal(t, http.MethodPut, capturedRequest.Method) + assert.Equal(t, "/v1/phones", capturedRequest.URL.Path) + assert.Equal(t, "application/json", capturedRequest.Header.Get("Content-Type")) + assert.Equal(t, apiKey, capturedRequest.Header.Get("x-api-key")) + + // Teardown + server.Close() +} + +func TestPhoneService_UpsertFCMToken(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusOK, stubs.PhoneUpsertFCMTokenResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + phone, response, err := client.Phones.UpsertFCMToken(context.Background(), &PhoneFCMTokenParams{ + PhoneNumber: "+18005550199", + FcmToken: "new-fcm-token", + SIM: "SIM1", + }) + + // Assert + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode) + + jsonContent, _ := json.Marshal(phone) + assert.JSONEq(t, string(stubs.PhoneUpsertFCMTokenResponse()), string(jsonContent)) + + // Teardown + server.Close() +} + +func TestPhoneService_UpsertFCMTokenWithError(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusInternalServerError, stubs.HttpInternalServerErrorResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, response, err := client.Phones.UpsertFCMToken(context.Background(), &PhoneFCMTokenParams{ + PhoneNumber: "+18005550199", + FcmToken: "new-fcm-token", + }) + + // Assert + assert.NotNil(t, err) + assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode) + assert.Equal(t, string(stubs.HttpInternalServerErrorResponse()), string(*response.Body)) + + // Teardown + server.Close() +} + +func TestPhoneService_UpsertFCMTokenRequest(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + var capturedRequest http.Request + server := helpers.MakeRequestCapturingTestServer(http.StatusOK, stubs.PhoneUpsertFCMTokenResponse(), &capturedRequest) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, _, err := client.Phones.UpsertFCMToken(context.Background(), &PhoneFCMTokenParams{ + PhoneNumber: "+18005550199", + FcmToken: "new-fcm-token", + SIM: "SIM1", + }) + + // Assert + assert.Nil(t, err) + assert.Equal(t, http.MethodPut, capturedRequest.Method) + assert.Equal(t, "/v1/phones/fcm-token", capturedRequest.URL.Path) + assert.Equal(t, "application/json", capturedRequest.Header.Get("Content-Type")) + assert.Equal(t, apiKey, capturedRequest.Header.Get("x-api-key")) + + // Teardown + server.Close() +} diff --git a/webhook_service_test.go b/webhook_service_test.go new file mode 100644 index 0000000..5b9e855 --- /dev/null +++ b/webhook_service_test.go @@ -0,0 +1,95 @@ +package httpsms + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/NdoleStudio/httpsms-go/internal/helpers" + "github.com/NdoleStudio/httpsms-go/internal/stubs" + "github.com/stretchr/testify/assert" +) + +func TestWebhookService_Store(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusOK, stubs.WebhookStoreResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + webhook, response, err := client.Webhooks.Store(context.Background(), &WebhookStoreParams{ + SigningKey: "whsec_test_signing_key", + URL: "https://example.com/webhook", + PhoneNumbers: []string{"+18005550199"}, + Events: []string{"message.received", "message.sent"}, + }) + + // Assert + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode) + + jsonContent, _ := json.Marshal(webhook) + assert.JSONEq(t, string(stubs.WebhookStoreResponse()), string(jsonContent)) + + // Teardown + server.Close() +} + +func TestWebhookService_StoreWithError(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusInternalServerError, stubs.HttpInternalServerErrorResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, response, err := client.Webhooks.Store(context.Background(), &WebhookStoreParams{ + SigningKey: "whsec_test_signing_key", + URL: "https://example.com/webhook", + PhoneNumbers: []string{"+18005550199"}, + Events: []string{"message.received"}, + }) + + // Assert + assert.NotNil(t, err) + assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode) + assert.Equal(t, string(stubs.HttpInternalServerErrorResponse()), string(*response.Body)) + + // Teardown + server.Close() +} + +func TestWebhookService_StoreRequest(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + var capturedRequest http.Request + server := helpers.MakeRequestCapturingTestServer(http.StatusOK, stubs.WebhookStoreResponse(), &capturedRequest) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, _, err := client.Webhooks.Store(context.Background(), &WebhookStoreParams{ + SigningKey: "whsec_test_signing_key", + URL: "https://example.com/webhook", + PhoneNumbers: []string{"+18005550199"}, + Events: []string{"message.received", "message.sent"}, + }) + + // Assert + assert.Nil(t, err) + assert.Equal(t, http.MethodPost, capturedRequest.Method) + assert.Equal(t, "/v1/webhooks", capturedRequest.URL.Path) + assert.Equal(t, "application/json", capturedRequest.Header.Get("Content-Type")) + assert.Equal(t, apiKey, capturedRequest.Header.Get("x-api-key")) + + // Teardown + server.Close() +}