diff --git a/README.md b/README.md index 14c1b7e..90556bd 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ func main() { EventProperties: map[string]any{ "plan": "pro", }, + ContactProperties: map[string]any{ + "planName": "Pro", + }, }) if err != nil { log.Fatal(err) diff --git a/campaigns.go b/campaigns.go index 6d6285d..740232f 100644 --- a/campaigns.go +++ b/campaigns.go @@ -69,6 +69,9 @@ type LmxWarning struct { // EmailMessageFields holds the editable fields of an email message. It is // embedded in [UpdateEmailMessageRequest]; the request's Set map determines // which fields are actually written. +// +// CCEmail and BCCEmail require CC/BCC to be enabled for the team and are not +// supported for campaign emails. type EmailMessageFields struct { Subject string `json:"subject,omitempty"` PreviewText string `json:"previewText,omitempty"` diff --git a/contacts.go b/contacts.go index bd8e1eb..1748485 100644 --- a/contacts.go +++ b/contacts.go @@ -222,8 +222,9 @@ func (c *Client) UpdateContact(req UpdateContactRequest) error { return nil } -// DeleteContact deletes a contact identified by email or user ID. One of the -// two must be non-empty. +// DeleteContact deletes a contact identified by email or user ID. Set exactly +// one of the two. Only the non-empty identifier is sent; the API rejects a +// request that provides both or neither. func (c *Client) DeleteContact(email, userID string) error { body := make(map[string]any) if email != "" { diff --git a/contacts_test.go b/contacts_test.go index 355dc32..e611e15 100644 --- a/contacts_test.go +++ b/contacts_test.go @@ -219,6 +219,9 @@ func TestDeleteContact(t *testing.T) { t.Errorf("body[%q] = %v, want %v", k, gotBody[k], v) } } + if len(gotBody) != len(tt.wantBody) { + t.Errorf("body = %v, want only %v", gotBody, tt.wantBody) + } } if tt.wantAPIErr != nil { diff --git a/transactional.go b/transactional.go index 8380ca0..53229e3 100644 --- a/transactional.go +++ b/transactional.go @@ -72,7 +72,8 @@ func (c *Client) SendTransactional(req SendTransactionalRequest) error { } // ListTransactional returns a single page of transactional email templates -// along with pagination information. To iterate every page, use [Paginate]: +// from the legacy list endpoint, along with pagination information. To +// iterate every page, use [Paginate]: // // all, err := loops.Paginate(func(cursor string) ([]loops.TransactionalEmail, *loops.Pagination, error) { // return client.ListTransactional(loops.PaginationParams{Cursor: cursor}) diff --git a/version.go b/version.go index 2a2ec86..ad6fc42 100644 --- a/version.go +++ b/version.go @@ -10,7 +10,7 @@ import "runtime/debug" var Version = readVersion() // SpecVersion is the Loops OpenAPI spec version this SDK is built against. -const SpecVersion = "1.21.9" +const SpecVersion = "1.21.14" const modulePath = "github.com/loops-so/loops-go" diff --git a/workflows_test.go b/workflows_test.go index 9db6af6..92f2373 100644 --- a/workflows_test.go +++ b/workflows_test.go @@ -1325,32 +1325,53 @@ func TestDeleteWorkflow_Confirmed(t *testing.T) { } func TestDeleteWorkflow_ConfirmationRequired(t *testing.T) { - const message = "This workflow is currently sending and has 3 queued contacts. Deleting it will stop sending it and cancel those queued contacts. Confirm deletion by sending a second request with confirmDelete: true." - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusConflict) - w.Write([]byte(`{"message":` + strconv.Quote(message) + `}`)) - })) - defer server.Close() - - client := NewClient("test-key", WithBaseURL(server.URL)) - err := client.DeleteWorkflow("wf_1", DeleteWorkflowRequest{ExpectedRevisionID: ptr("rev_1")}) - if !errors.Is(err, ErrWorkflowDeleteConfirmationRequired) { - t.Fatalf("expected ErrWorkflowDeleteConfirmationRequired, got %v", err) - } - var apiErr *APIError - if !errors.As(err, &apiErr) { - t.Fatalf("expected a wrapped *APIError, got %T: %v", err, err) - } - if apiErr.StatusCode != http.StatusConflict { - t.Errorf("StatusCode = %d, want 409", apiErr.StatusCode) + tests := []struct { + name string + message string + }{ + { + name: "sending", + message: "This workflow is currently sending. Deleting it will stop sending it. Confirm deletion by sending a second request with confirmDelete: true.", + }, + { + name: "queued contacts", + message: "This workflow has 3 queued contacts. Deleting it will cancel those queued contacts. Confirm deletion by sending a second request with confirmDelete: true.", + }, + { + name: "sending and queued contacts", + message: "This workflow is currently sending and has 3 queued contacts. Deleting it will stop sending it and cancel those queued contacts. Confirm deletion by sending a second request with confirmDelete: true.", + }, } - if apiErr.Message != message { - t.Errorf("Message = %q", apiErr.Message) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusConflict) + w.Write([]byte(`{"message":` + strconv.Quote(tt.message) + `}`)) + })) + defer server.Close() + + client := NewClient("test-key", WithBaseURL(server.URL)) + err := client.DeleteWorkflow("wf_1", DeleteWorkflowRequest{ExpectedRevisionID: ptr("rev_1")}) + if !errors.Is(err, ErrWorkflowDeleteConfirmationRequired) { + t.Fatalf("expected ErrWorkflowDeleteConfirmationRequired, got %v", err) + } + var apiErr *APIError + if !errors.As(err, &apiErr) { + t.Fatalf("expected a wrapped *APIError, got %T: %v", err, err) + } + if apiErr.StatusCode != http.StatusConflict { + t.Errorf("StatusCode = %d, want 409", apiErr.StatusCode) + } + if apiErr.Message != tt.message { + t.Errorf("Message = %q", apiErr.Message) + } + }) } } func TestDeleteWorkflow_StaleRevision409(t *testing.T) { - const message = "workflowRevisionId mismatch. Latest workflowRevisionId is rev_2. Refetch and retry." + const message = "workflowRevisionId mismatch. Latest workflowRevisionId is clx7a3b5c7d9e1f3g5h7i9j1. Refetch and retry." server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusConflict) w.Write([]byte(`{"message":` + strconv.Quote(message) + `}`))