From 8e753d6c84b4edecab3ec9bbbba3169df718e395 Mon Sep 17 00:00:00 2001 From: Akhil Date: Tue, 11 Apr 2023 23:57:33 +0530 Subject: [PATCH 1/2] feat: bulk, broadcast and cancel Trigger Events --- lib/event.go | 71 +++++++++ lib/event_test.go | 136 ++++++++++++++++++ lib/model.go | 21 +++ testdata/novu_broadcast_event_to_all.json | 9 ++ testdata/novu_send_trigger_bulk.json | 28 ++++ testdata/novu_send_trigger_bulk_response.json | 16 +++ 6 files changed, 281 insertions(+) create mode 100644 testdata/novu_broadcast_event_to_all.json create mode 100644 testdata/novu_send_trigger_bulk.json create mode 100644 testdata/novu_send_trigger_bulk_response.json diff --git a/lib/event.go b/lib/event.go index bb16aa7..e314ed6 100644 --- a/lib/event.go +++ b/lib/event.go @@ -9,6 +9,9 @@ import ( type IEvent interface { Trigger(ctx context.Context, eventId string, data ITriggerPayloadOptions) (EventResponse, error) + TriggerBulk(ctx context.Context, data []BulkTriggerOptions) ([]EventResponse, error) + BroadcastToAll(ctx context.Context, data BroadcastEventToAll) (EventResponse, error) + CancelTrigger(ctx context.Context, transactionId string) (bool, error) } type EventService service @@ -41,4 +44,72 @@ func (e *EventService) Trigger(ctx context.Context, eventId string, data ITrigge return resp, nil } +func (e *EventService) TriggerBulk(ctx context.Context, data []BulkTriggerOptions) ([]EventResponse, error) { + var resp []EventResponse + URL := e.client.config.BackendURL.JoinPath("events/trigger/bulk") + + reqBody := BulkTriggerEvent{ + Events: data, + } + + jsonBody, _ := json.Marshal(reqBody) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, URL.String(), bytes.NewBuffer(jsonBody)) + if err != nil { + return resp, err + } + + _, err = e.client.sendRequest(req, &resp) + if err != nil { + return resp, err + } + + return resp, nil + +} + +func (e *EventService) BroadcastToAll(ctx context.Context, data BroadcastEventToAll) (EventResponse, error) { + var resp EventResponse + URL := e.client.config.BackendURL.JoinPath("events/trigger/broadcast") + + reqBody := BroadcastEventToAll{ + Name: data.Name, + Payload: data.Payload, + Overrides: data.Overrides, + TransactionId: data.TransactionId, + Actor: data.Actor, + } + + jsonBody, _ := json.Marshal(reqBody) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, URL.String(), bytes.NewBuffer(jsonBody)) + if err != nil { + return resp, err + } + + _, err = e.client.sendRequest(req, &resp) + if err != nil { + return resp, err + } + + return resp, nil +} + +func (e *EventService) CancelTrigger(ctx context.Context, transactionId string) (bool, error) { + var resp bool + URL := e.client.config.BackendURL.JoinPath("events/trigger/" + transactionId) + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, URL.String(), http.NoBody) + if err != nil { + return resp, err + } + + _, err = e.client.sendRequest(req, &resp) + if err != nil { + return resp, err + } + + return resp, nil +} + var _ IEvent = &EventService{} diff --git a/lib/event_test.go b/lib/event_test.go index 76daf95..3af07ef 100644 --- a/lib/event_test.go +++ b/lib/event_test.go @@ -129,3 +129,139 @@ func TestEventServiceTriggerForTopic_Success(t *testing.T) { require.Nil(t, err) } + +func TestBulkTriggerEvent_Success(t *testing.T) { + var ( + receivedBody lib.BulkTriggerEvent + expectedTokenRequest lib.BulkTriggerEvent + triggerPayload lib.BulkTriggerEvent + ) + + eventService := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if err := json.NewDecoder(req.Body).Decode(&receivedBody); err != nil { + log.Printf("error in unmarshalling %+v", err) + w.WriteHeader(http.StatusBadRequest) + return + } + + t.Run("Header must contain ApiKey", func(t *testing.T) { + authKey := req.Header.Get("Authorization") + assert.True(t, strings.Contains(authKey, novuApiKey)) + assert.True(t, strings.HasPrefix(authKey, "ApiKey")) + }) + + t.Run("URL and request method is as expected", func(t *testing.T) { + expectedURL := "/v1/events/trigger/bulk" + assert.Equal(t, http.MethodPost, req.Method) + assert.Equal(t, expectedURL, req.RequestURI) + }) + + t.Run("Request is as expected", func(t *testing.T) { + fileToStruct(filepath.Join("../testdata", "novu_send_trigger_bulk.json"), &expectedTokenRequest) + assert.Equal(t, expectedTokenRequest, receivedBody) + }) + + var resp []lib.EventResponse + fileToStruct(filepath.Join("../testdata", "novu_send_trigger_bulk_response.json"), &resp) + + w.WriteHeader(http.StatusOK) + bb, _ := json.Marshal(resp) + w.Write(bb) + })) + + defer eventService.Close() + + ctx := context.Background() + fileToStruct(filepath.Join("../testdata", "novu_send_trigger_bulk.json"), &triggerPayload) + + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(eventService.URL)}) + _, err := c.EventApi.TriggerBulk(ctx, triggerPayload.Events) + + require.Nil(t, err) +} + +func TestBroadcastEventToAll_Success(t *testing.T) { + var ( + receivedBody lib.BroadcastEventToAll + expectedTokenRequest lib.BroadcastEventToAll + triggerPayload lib.BroadcastEventToAll + ) + + eventService := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if err := json.NewDecoder(req.Body).Decode(&receivedBody); err != nil { + log.Printf("error in unmarshalling %+v", err) + w.WriteHeader(http.StatusBadRequest) + return + } + + t.Run("Header must contain ApiKey", func(t *testing.T) { + authKey := req.Header.Get("Authorization") + assert.True(t, strings.Contains(authKey, novuApiKey)) + assert.True(t, strings.HasPrefix(authKey, "ApiKey")) + }) + + t.Run("URL and request method is as expected", func(t *testing.T) { + expectedURL := "/v1/events/trigger/broadcast" + assert.Equal(t, http.MethodPost, req.Method) + assert.Equal(t, expectedURL, req.RequestURI) + }) + + t.Run("Request is as expected", func(t *testing.T) { + fileToStruct(filepath.Join("../testdata", "novu_broadcast_event_to_all.json"), &expectedTokenRequest) + assert.Equal(t, expectedTokenRequest, receivedBody) + }) + + var resp lib.EventResponse + fileToStruct(filepath.Join("../testdata", "novu_send_trigger_response.json"), &resp) + + w.WriteHeader(http.StatusOK) + bb, _ := json.Marshal(resp) + w.Write(bb) + })) + + defer eventService.Close() + + ctx := context.Background() + fileToStruct(filepath.Join("../testdata", "novu_broadcast_event_to_all.json"), &triggerPayload) + + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(eventService.URL)}) + _, err := c.EventApi.BroadcastToAll(ctx, triggerPayload) + + require.Nil(t, err) +} + +func TestCancelTriggeredEvent_Success(t *testing.T) { + const transactionId = "d2239acb-e879-4bdb-ab6f-365b43278d8f" + var expectedResponse bool = true + + ctx := context.Background() + + eventService := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + t.Run("Header must contain ApiKey", func(t *testing.T) { + authKey := req.Header.Get("Authorization") + assert.True(t, strings.Contains(authKey, novuApiKey)) + assert.True(t, strings.HasPrefix(authKey, "ApiKey")) + }) + + t.Run("URL and request method is as expected", func(t *testing.T) { + expectedURL := "/v1/events/trigger/" + transactionId + assert.Equal(t, http.MethodDelete, req.Method) + assert.Equal(t, expectedURL, req.RequestURI) + }) + + var resp bool = true + w.WriteHeader(http.StatusOK) + bb, _ := json.Marshal(resp) + w.Write(bb) + })) + + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(eventService.URL)}) + + resp, err := c.EventApi.CancelTrigger(ctx, transactionId) + require.Nil(t, err) + assert.NotNil(t, resp) + + t.Run("Response is as expected", func(t *testing.T) { + assert.Equal(t, expectedResponse, resp) + }) +} diff --git a/lib/model.go b/lib/model.go index 363e0e8..dd8f6ea 100644 --- a/lib/model.go +++ b/lib/model.go @@ -304,3 +304,24 @@ type IntegrationResponse struct { type GetIntegrationsResponse struct { Data []Integration `json:"data"` } + +type BulkTriggerOptions struct { + Name interface{} `json:"name,omitempty"` + To interface{} `json:"to,omitempty"` + Payload interface{} `json:"payload,omitempty"` + Overrides interface{} `json:"overrides,omitempty"` + TransactionId string `json:"transactionId,omitempty"` + Actor interface{} `json:"actor,omitempty"` +} + +type BulkTriggerEvent struct { + Events []BulkTriggerOptions `json:"events"` +} + +type BroadcastEventToAll struct { + Name interface{} `json:"name,omitempty"` + Payload interface{} `json:"payload,omitempty"` + Overrides interface{} `json:"overrides,omitempty"` + TransactionId string `json:"transactionId,omitempty"` + Actor interface{} `json:"actor,omitempty"` +} diff --git a/testdata/novu_broadcast_event_to_all.json b/testdata/novu_broadcast_event_to_all.json new file mode 100644 index 0000000..4e2d4d2 --- /dev/null +++ b/testdata/novu_broadcast_event_to_all.json @@ -0,0 +1,9 @@ +{ + "payload": { + "name": "Hello World", + "organization": { + "logo": "https://happycorp.com/logo.png" + } + }, + "transactionId": "7425cb40d22507199a000009" +} \ No newline at end of file diff --git a/testdata/novu_send_trigger_bulk.json b/testdata/novu_send_trigger_bulk.json new file mode 100644 index 0000000..d7319d7 --- /dev/null +++ b/testdata/novu_send_trigger_bulk.json @@ -0,0 +1,28 @@ +{ + "events": [ + { + "name": "event1", + "to": { + "subscriberId": "john@doemail.com", + "lastName": "Doe", + "firstName": "John", + "email": "john@doemail.com" + }, + "payload": { + "name": "Hello World" + } + }, + { + "name": "event2", + "to": { + "subscriberId": "jack@google.com", + "lastName": "Jones", + "firstName": "Jack", + "email": "jack@google.com" + }, + "payload": { + "name": "Hello World" + } + } + ] +} \ No newline at end of file diff --git a/testdata/novu_send_trigger_bulk_response.json b/testdata/novu_send_trigger_bulk_response.json new file mode 100644 index 0000000..5960500 --- /dev/null +++ b/testdata/novu_send_trigger_bulk_response.json @@ -0,0 +1,16 @@ +[ + { + "data": { + "acknowledged": true, + "status": "processed", + "transactionId": "d2239acb-e879-4bdb-ab6f-365b43278d8f" + } + }, + { + "data": { + "acknowledged": true, + "status": "processed", + "transactionId": "r2239acb-e879-kjd7-ab6f-365b43278d8e" + } + } +] \ No newline at end of file From 10f36443d400d0b39540edfa19a625f032d3e7f7 Mon Sep 17 00:00:00 2001 From: Akhil Date: Sat, 13 May 2023 00:32:22 +0530 Subject: [PATCH 2/2] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3068b9a..59dba39 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ Check the `cmd` directory to see a sample implementation and test files to see s Class | Method | HTTP request | Description ------------ |----------------------------------------------------------------------------------|-----------------------------------------| ------------- *EventApi* | [**Trigger**](https://docs.novu.co/platform/subscribers#removing-a-subscriber) | **Post** /events/trigger | Trigger +*EventApi* | [**TriggerBulk**](https://docs.novu.co/api/trigger-event/) | **Post** /v1/events/trigger/bulk | Bulk trigger event +*EventApi* | [**BroadcastToAll**](https://docs.novu.co/api/broadcast-event-to-all/) | **Post** /v1/events/trigger/broadcast | Broadcast event to all +*EventApi* | [**CancelTrigger**](https://docs.novu.co/api/cancel-triggered-event/) | **Delete** /v1/events/trigger/:transactionId | Cancel triggered event *SubscriberApi* | [**Get**](https://docs.novu.co/api/get-subscriber/) | **Get** /subscribers/:subscriberId | Get a subscriber *SubscriberApi* | [**Identify**](https://docs.novu.co/platform/subscribers#creating-a-subscriber) | **Post** /subscribers | Create a subscriber *SubscriberApi* | [**Update**](https://docs.novu.co/platform/subscribers#updating-subscriber-data) | **Put** /subscribers/:subscriberID | Update subscriber data @@ -103,4 +106,4 @@ As always, if you need additional assistance, join our Discord us a note [here]( Name | ------------ | [Oyewole Samuel](https://github.com/samsoft00) | -[Dima Grossman](https://github.com/scopsy) | \ No newline at end of file +[Dima Grossman](https://github.com/scopsy) |