From 4bdbceb3e4dd108fdb947ef3d97367bc587bc486 Mon Sep 17 00:00:00 2001 From: Tim Heckman Date: Sat, 13 Nov 2021 15:50:50 -0800 Subject: [PATCH] Add support for adding email filters for Generic Email Integrations This change adds support for adding different email filter rules to a generic inbound email integration associated with a specific Service. This is done by adding two new fields to the Integration struct. While implementing this functionality I found that the API documentation from PagerDuty lacked details about what the correct values are for specific fields, and specifically what each value means. In fact, I actually had to go into the PagerDuty UI to try and set up email filters, so that I could guess what the different API values actually meant. Because of that, I chose to create an enum-like type for each thing that wasn't well documented to make it easier to consume via this client. That's resulted in quite a lot of extra code, but the result is that anyone who wishes to consume this area of the Go SDK should be able to figure it out how to use it without much trouble. Because of how the code grew, I also chose to move the Service Integration-specific code into a new file (service_integration.go). Closes #315 --- service.go | 77 -------- service_integration.go | 354 +++++++++++++++++++++++++++++++++++ service_integration_test.go | 355 ++++++++++++++++++++++++++++++++++++ service_test.go | 115 ------------ 4 files changed, 709 insertions(+), 192 deletions(-) create mode 100644 service_integration.go create mode 100644 service_integration_test.go diff --git a/service.go b/service.go index 72582c1d..81b91382 100644 --- a/service.go +++ b/service.go @@ -8,17 +8,6 @@ import ( "github.com/google/go-querystring/query" ) -// Integration is an endpoint (like Nagios, email, or an API call) that generates events, which are normalized and de-duplicated by PagerDuty to create incidents. -type Integration struct { - APIObject - Name string `json:"name,omitempty"` - Service *APIObject `json:"service,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - Vendor *APIObject `json:"vendor,omitempty"` - IntegrationKey string `json:"integration_key,omitempty"` - IntegrationEmail string `json:"integration_email,omitempty"` -} - // InlineModel represents when a scheduled action will occur. type InlineModel struct { Type string `json:"type,omitempty"` @@ -265,72 +254,6 @@ func (c *Client) DeleteServiceWithContext(ctx context.Context, id string) error return err } -// CreateIntegration creates a new integration belonging to a service. -// -// Deprecated: Use CreateIntegrationWithContext instead. -func (c *Client) CreateIntegration(id string, i Integration) (*Integration, error) { - return c.CreateIntegrationWithContext(context.Background(), id, i) -} - -// CreateIntegrationWithContext creates a new integration belonging to a service. -func (c *Client) CreateIntegrationWithContext(ctx context.Context, id string, i Integration) (*Integration, error) { - d := map[string]Integration{ - "integration": i, - } - - resp, err := c.post(ctx, "/services/"+id+"/integrations", d, nil) - return getIntegrationFromResponse(c, resp, err) -} - -// GetIntegrationOptions is the data structure used when calling the GetIntegration API endpoint. -type GetIntegrationOptions struct { - Includes []string `url:"include,omitempty,brackets"` -} - -// GetIntegration gets details about an integration belonging to a service. -// -// Deprecated: Use GetIntegrationWithContext instead. -func (c *Client) GetIntegration(serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) { - return c.GetIntegrationWithContext(context.Background(), serviceID, integrationID, o) -} - -// GetIntegrationWithContext gets details about an integration belonging to a service. -func (c *Client) GetIntegrationWithContext(ctx context.Context, serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) { - v, err := query.Values(o) - if err != nil { - return nil, err - } - - resp, err := c.get(ctx, "/services/"+serviceID+"/integrations/"+integrationID+"?"+v.Encode()) - return getIntegrationFromResponse(c, resp, err) -} - -// UpdateIntegration updates an integration belonging to a service. -// -// Deprecated: Use UpdateIntegrationWithContext instead. -func (c *Client) UpdateIntegration(serviceID string, i Integration) (*Integration, error) { - return c.UpdateIntegrationWithContext(context.Background(), serviceID, i) -} - -// UpdateIntegrationWithContext updates an integration belonging to a service. -func (c *Client) UpdateIntegrationWithContext(ctx context.Context, serviceID string, i Integration) (*Integration, error) { - resp, err := c.put(ctx, "/services/"+serviceID+"/integrations/"+i.ID, i, nil) - return getIntegrationFromResponse(c, resp, err) -} - -// DeleteIntegration deletes an existing integration. -// -// Deprecated: Use DeleteIntegrationWithContext instead. -func (c *Client) DeleteIntegration(serviceID string, integrationID string) error { - return c.DeleteIntegrationWithContext(context.Background(), serviceID, integrationID) -} - -// DeleteIntegrationWithContext deletes an existing integration. -func (c *Client) DeleteIntegrationWithContext(ctx context.Context, serviceID string, integrationID string) error { - _, err := c.delete(ctx, "/services/"+serviceID+"/integrations/"+integrationID) - return err -} - // ListServiceRulesPaginated gets all rules for a service. func (c *Client) ListServiceRulesPaginated(ctx context.Context, serviceID string) ([]ServiceRule, error) { var rules []ServiceRule diff --git a/service_integration.go b/service_integration.go new file mode 100644 index 00000000..c1068b4a --- /dev/null +++ b/service_integration.go @@ -0,0 +1,354 @@ +package pagerduty + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/google/go-querystring/query" +) + +// IntegrationEmailFilterMode is a type to respresent the different filter modes +// for a Generic Email Integration. This defines how the email filter rules +// (IntegrationEmailFilterRuleMode) are used when emails are ingested. +type IntegrationEmailFilterMode uint8 + +const ( + // EmailFilterModeInvalid only exists to make it harder to use values of + // this type incorrectly. Please instead use one of EmailFilterModeAll, + // EmailFilterModeOr, EmailFilterModeAnd + // + // This value should not get marshaled to JSON by the encoding/json package. + EmailFilterModeInvalid IntegrationEmailFilterMode = iota + + // EmailFilterModeAll means that all incoming email will be be accepted, and + // no email rules will be considered. + EmailFilterModeAll + + // EmailFilterModeOr instructs the email filtering system to accept the + // email if one or more rules match the message. + EmailFilterModeOr + + // EmailFilterModeAnd instructs the email filtering system to accept the + // email only if all of the rules match the message. + EmailFilterModeAnd +) + +// string values for each IntegrationEmailFilterMode value +const ( + efmAll = "all-email" // EmailFilterModeAll + efmOr = "or-rules-email" // EmailFilterModeOr + efmAnd = "and-rules-email" // EmailFilterModeAnd +) + +func (i IntegrationEmailFilterMode) String() string { + switch i { + case EmailFilterModeAll: + return efmAll + + case EmailFilterModeOr: + return efmOr + + case EmailFilterModeAnd: + return efmAnd + + default: + return "invalid" + } +} + +// compile time encoding/json interface satisfaction assertions +var ( + _ json.Marshaler = IntegrationEmailFilterMode(0) + _ json.Unmarshaler = (*IntegrationEmailFilterMode)(nil) +) + +// MarshalJSON satisfies json.Marshaler +func (i IntegrationEmailFilterMode) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", i.String())), nil +} + +// UnmarshalJSON satisfies json.Unmarshaler +func (i *IntegrationEmailFilterMode) UnmarshalJSON(b []byte) error { + if b[0] != '"' { + if bytes.Equal(b, []byte(`null`)) { + return errors.New("value cannot be null") + } + + // just return json.Unmarshal error + var s string + + err := json.Unmarshal(b, &s) + if err == nil { + panic("this should not be possible...") + } + + return err + } + + v := string(b[1 : len(b)-1]) + + switch v { + case efmAll: + *i = EmailFilterModeAll + + case efmOr: + *i = EmailFilterModeOr + + case efmAnd: + *i = EmailFilterModeAnd + + default: + return fmt.Errorf("unknown value %q", v) + } + + return nil +} + +// IntegrationEmailFilterRuleMode is a type to represent the different matching +// modes of Generic Email Integration Filer Rules without consumers of this +// package needing to be intimately familiar with the specifics of the REST API. +type IntegrationEmailFilterRuleMode uint8 + +const ( + // EmailFilterRuleModeInvalid only exists to make it harder to use values of this + // type incorrectly. Please instead use one of EmailFilterRuleModeAlways, + // EmailFilterRuleModeMatch, or EmailFilterRuleModeNoMatch. + // + // This value should not get marshaled to JSON by the encoding/json package. + EmailFilterRuleModeInvalid IntegrationEmailFilterRuleMode = iota + + // EmailFilterRuleModeAlways means that the specific value can be anything. Any + // associated regular expression will be ignored. + EmailFilterRuleModeAlways + + // EmailFilterRuleModeMatch means that the associated regular expression must + // match the associated value. + EmailFilterRuleModeMatch + + // EmailFilterRuleModeNoMatch means that the associated regular expression must NOT + // match the associated value. + EmailFilterRuleModeNoMatch +) + +// string values for each IntegrationEmailFilterRuleMode value +const ( + efrmAlways = "always" // EmailFilterRuleModeAlways + efrmMatch = "match" // EmailFilterRuleModeMatch + efrmNoMatch = "no-match" // EmailFilterRuleModeNoMatch +) + +func (i IntegrationEmailFilterRuleMode) String() string { + switch i { + case EmailFilterRuleModeMatch: + return efrmMatch + + case EmailFilterRuleModeNoMatch: + return efrmNoMatch + + case EmailFilterRuleModeAlways: + return efrmAlways + + default: + return "invalid" + } +} + +// compile time encoding/json interface satisfaction assertions +var ( + _ json.Marshaler = IntegrationEmailFilterRuleMode(0) + _ json.Unmarshaler = (*IntegrationEmailFilterRuleMode)(nil) +) + +// MarshalJSON satisfies json.Marshaler +func (i IntegrationEmailFilterRuleMode) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", i.String())), nil +} + +// UnmarshalJSON satisfies json.Unmarshaler +func (i *IntegrationEmailFilterRuleMode) UnmarshalJSON(b []byte) error { + if b[0] != '"' { + if bytes.Equal(b, []byte(`null`)) { + return errors.New("value cannot be null") + } + + // just return json.Unmarshal error + var s string + + err := json.Unmarshal(b, &s) + if err == nil { + panic("this should not be possible...") + } + + return err + } + + v := string(b[1 : len(b)-1]) + + switch v { + case efrmMatch: + *i = EmailFilterRuleModeMatch + + case efrmNoMatch: + *i = EmailFilterRuleModeNoMatch + + case efrmAlways: + *i = EmailFilterRuleModeAlways + + default: + return fmt.Errorf("unknown value %q", v) + } + + return nil +} + +// IntegrationEmailFilterRule represents a single email filter rule for an +// integration of type generic_email_inbound_integration. Information about how +// to configure email rules can be found here: +// https://support.pagerduty.com/docs/email-management-filters-and-rules. +type IntegrationEmailFilterRule struct { + // SubjectMode and SubjectRegex control the behaviors of how this filter + // matches the subject of an inbound email. + SubjectMode IntegrationEmailFilterRuleMode `json:"subject_mode,omitempty"` + SubjectRegex *string `json:"subject_regex,omitempty"` + + // BodyMode and BodyRegex control the behaviors of how this filter matches + // the body of an inbound email. + BodyMode IntegrationEmailFilterRuleMode `json:"body_mode,omitempty"` + BodyRegex *string `json:"body_regex,omitempty"` + + FromEmailMode IntegrationEmailFilterRuleMode `json:"from_email_mode,omitempty"` + FromEmailRegex *string `json:"from_email_regex,omitempty"` +} + +// UnmarshalJSON satisfies json.Unmarshaler. +func (i *IntegrationEmailFilterRule) UnmarshalJSON(b []byte) error { + // the purpose of this function is to ensure that when unmarshaling, the + // different *string values are never nil pointers. + // + // this is not a communicated feature of the API, so if it chnages + // it's not a breaking change -- doesn't mean we can't try. + var ief integrationEmailFilterRule + if err := json.Unmarshal(b, &ief); err != nil { + return err + } + + i.BodyMode = ief.BodyMode + i.SubjectMode = ief.SubjectMode + i.FromEmailMode = ief.FromEmailMode + + // if the *string is nil, set it to a *string with value "" + if ief.SubjectRegex == nil { + i.SubjectRegex = strPtr("") + } else { + i.SubjectRegex = ief.SubjectRegex + } + + if ief.BodyRegex == nil { + i.BodyRegex = strPtr("") + } else { + i.BodyRegex = ief.BodyRegex + } + + if ief.FromEmailRegex == nil { + i.FromEmailRegex = strPtr("") + } else { + i.FromEmailRegex = ief.FromEmailRegex + } + + return nil +} + +func strPtr(s string) *string { return &s } + +type integrationEmailFilterRule struct { + SubjectMode IntegrationEmailFilterRuleMode `json:"subject_mode"` + SubjectRegex *string `json:"subject_regex,omitempty"` + BodyMode IntegrationEmailFilterRuleMode `json:"body_mode"` + BodyRegex *string `json:"body_regex,omitempty"` + FromEmailMode IntegrationEmailFilterRuleMode `json:"from_email_mode"` + FromEmailRegex *string `json:"from_email_regex,omitempty"` +} + +// Integration is an endpoint (like Nagios, email, or an API call) that +// generates events, which are normalized and de-duplicated by PagerDuty to +// create incidents. +type Integration struct { + APIObject + Name string `json:"name,omitempty"` + Service *APIObject `json:"service,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + Vendor *APIObject `json:"vendor,omitempty"` + IntegrationKey string `json:"integration_key,omitempty"` + IntegrationEmail string `json:"integration_email,omitempty"` + EmailFilterMode IntegrationEmailFilterMode `json:"email_filter_mode,omitempty"` + EmailFilters []IntegrationEmailFilterRule `json:"email_filters,omitempty"` +} + +// CreateIntegration creates a new integration belonging to a service. +// +// Deprecated: Use CreateIntegrationWithContext instead. +func (c *Client) CreateIntegration(id string, i Integration) (*Integration, error) { + return c.CreateIntegrationWithContext(context.Background(), id, i) +} + +// CreateIntegrationWithContext creates a new integration belonging to a service. +func (c *Client) CreateIntegrationWithContext(ctx context.Context, id string, i Integration) (*Integration, error) { + d := map[string]Integration{ + "integration": i, + } + + resp, err := c.post(ctx, "/services/"+id+"/integrations", d, nil) + return getIntegrationFromResponse(c, resp, err) +} + +// GetIntegrationOptions is the data structure used when calling the GetIntegration API endpoint. +type GetIntegrationOptions struct { + Includes []string `url:"include,omitempty,brackets"` +} + +// GetIntegration gets details about an integration belonging to a service. +// +// Deprecated: Use GetIntegrationWithContext instead. +func (c *Client) GetIntegration(serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) { + return c.GetIntegrationWithContext(context.Background(), serviceID, integrationID, o) +} + +// GetIntegrationWithContext gets details about an integration belonging to a service. +func (c *Client) GetIntegrationWithContext(ctx context.Context, serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/services/"+serviceID+"/integrations/"+integrationID+"?"+v.Encode()) + return getIntegrationFromResponse(c, resp, err) +} + +// UpdateIntegration updates an integration belonging to a service. +// +// Deprecated: Use UpdateIntegrationWithContext instead. +func (c *Client) UpdateIntegration(serviceID string, i Integration) (*Integration, error) { + return c.UpdateIntegrationWithContext(context.Background(), serviceID, i) +} + +// UpdateIntegrationWithContext updates an integration belonging to a service. +func (c *Client) UpdateIntegrationWithContext(ctx context.Context, serviceID string, i Integration) (*Integration, error) { + resp, err := c.put(ctx, "/services/"+serviceID+"/integrations/"+i.ID, i, nil) + return getIntegrationFromResponse(c, resp, err) +} + +// DeleteIntegration deletes an existing integration. +// +// Deprecated: Use DeleteIntegrationWithContext instead. +func (c *Client) DeleteIntegration(serviceID string, integrationID string) error { + return c.DeleteIntegrationWithContext(context.Background(), serviceID, integrationID) +} + +// DeleteIntegrationWithContext deletes an existing integration. +func (c *Client) DeleteIntegrationWithContext(ctx context.Context, serviceID string, integrationID string) error { + _, err := c.delete(ctx, "/services/"+serviceID+"/integrations/"+integrationID) + return err +} diff --git a/service_integration_test.go b/service_integration_test.go new file mode 100644 index 00000000..2d4bce12 --- /dev/null +++ b/service_integration_test.go @@ -0,0 +1,355 @@ +package pagerduty + +import ( + "encoding/json" + "fmt" + "net/http" + "testing" +) + +// Create Integration +func TestClient_CreateIntegration(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/1/integrations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`)) + }) + + client := defaultTestClient(server.URL, "foo") + input := Integration{ + Name: "foo", + } + servID := "1" + + res, err := client.CreateIntegration(servID, input) + + want := &Integration{ + APIObject: APIObject{ + ID: "1", + }, + Name: "foo", + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +// Get Integration +func TestClient_GetIntegration(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`)) + }) + + client := defaultTestClient(server.URL, "foo") + input := GetIntegrationOptions{ + Includes: []string{}, + } + servID := "1" + intID := "1" + + res, err := client.GetIntegration(servID, intID, input) + + want := &Integration{ + APIObject: APIObject{ + ID: "1", + }, + Name: "foo", + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +// Update Integration +func TestClient_UpdateIntegration(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`)) + }) + + client := defaultTestClient(server.URL, "foo") + input := Integration{ + APIObject: APIObject{ + ID: "1", + }, + Name: "foo", + } + servID := "1" + + res, err := client.UpdateIntegration(servID, input) + + want := &Integration{ + APIObject: APIObject{ + ID: "1", + }, + Name: "foo", + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +// Delete Integration +func TestClient_DeleteIntegration(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + client := defaultTestClient(server.URL, "foo") + servID := "1" + intID := "1" + err := client.DeleteIntegration(servID, intID) + if err != nil { + t.Fatal(err) + } +} + +func TestIntegrationEmailFilterMode_String(t *testing.T) { + tests := []struct { + name string + input IntegrationEmailFilterMode + want string + }{ + {name: "unknown", input: ^IntegrationEmailFilterMode(0), want: "invalid"}, + {name: "invalid", input: EmailFilterModeInvalid, want: "invalid"}, + {name: "all", input: EmailFilterModeAll, want: "all-email"}, + {name: "or", input: EmailFilterModeOr, want: "or-rules-email"}, + {name: "and", input: EmailFilterModeAnd, want: "and-rules-email"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.input.String(); got != tt.want { + t.Fatalf("String() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestIntegrationEmailFilterMode_MarshalJSON(t *testing.T) { + tests := []struct { + name string + input IntegrationEmailFilterMode + want string + }{ + {name: "unknown", input: ^IntegrationEmailFilterMode(0), want: "invalid"}, + {name: "invalid", input: EmailFilterModeInvalid, want: "invalid"}, + {name: "all", input: EmailFilterModeAll, want: "all-email"}, + {name: "or", input: EmailFilterModeOr, want: "or-rules-email"}, + {name: "and", input: EmailFilterModeAnd, want: "and-rules-email"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + json, err := tt.input.MarshalJSON() + testErrCheck(t, "tt.input.MarshalJSON()", "", err) + + want := fmt.Sprintf("%q", tt.want) + + if got := string(json); got != want { + t.Fatalf("MarshalJSON() = `%s`, want `%s`", got, want) + } + }) + } +} + +func TestIntegrationEmailFilterMode_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want IntegrationEmailFilterMode + err string + }{ + {name: "invalid", input: `"invalid"`, err: `unknown value "invalid"`}, + {name: "null", input: `null`, err: `value cannot be null`}, + {name: "not_string", input: `42`, err: `json: cannot unmarshal number into Go value of type string`}, + {name: "all", input: `"all-email"`, want: EmailFilterModeAll}, + {name: "or", input: `"or-rules-email"`, want: EmailFilterModeOr}, + {name: "and", input: `"and-rules-email"`, want: EmailFilterModeAnd}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := new(IntegrationEmailFilterMode) + + if cont := testErrCheck(t, "got.UnmarshalJSON()", tt.err, got.UnmarshalJSON([]byte(tt.input))); !cont { + return + } + + if *got != tt.want { + t.Fatalf("got = %d (%s), want = %d (%s)", *got, got.String(), tt.want, tt.want.String()) + } + }) + } +} + +func TestIntegrationEmailFilterRuleMode_String(t *testing.T) { + tests := []struct { + name string + input IntegrationEmailFilterRuleMode + want string + }{ + {name: "unknown", input: ^IntegrationEmailFilterRuleMode(0), want: "invalid"}, + {name: "invalid", input: EmailFilterRuleModeInvalid, want: "invalid"}, + {name: "always", input: EmailFilterRuleModeAlways, want: "always"}, + {name: "match", input: EmailFilterRuleModeMatch, want: "match"}, + {name: "no_match", input: EmailFilterRuleModeNoMatch, want: "no-match"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.input.String(); got != tt.want { + t.Fatalf("String() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestIntegrationEmailFilterRuleMode_MarshalJSON(t *testing.T) { + tests := []struct { + name string + input IntegrationEmailFilterRuleMode + want string + }{ + {name: "unknown", input: ^IntegrationEmailFilterRuleMode(0), want: "invalid"}, + {name: "invalid", input: EmailFilterRuleModeInvalid, want: "invalid"}, + {name: "always", input: EmailFilterRuleModeAlways, want: "always"}, + {name: "match", input: EmailFilterRuleModeMatch, want: "match"}, + {name: "no_match", input: EmailFilterRuleModeNoMatch, want: "no-match"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + json, err := tt.input.MarshalJSON() + testErrCheck(t, "tt.input.MarshalJSON()", "", err) + + want := fmt.Sprintf("%q", tt.want) + + if got := string(json); got != want { + t.Fatalf("MarshalJSON() = `%s`, want `%s`", got, want) + } + }) + } +} + +func TestIntegrationEmailFilterRuleMode_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want IntegrationEmailFilterRuleMode + err string + }{ + {name: "invalid", input: `"invalid"`, err: `unknown value "invalid"`}, + {name: "null", input: `null`, err: `value cannot be null`}, + {name: "not_string", input: `42`, err: `json: cannot unmarshal number into Go value of type string`}, + {name: "always", input: `"always"`, want: EmailFilterRuleModeAlways}, + {name: "match", input: `"match"`, want: EmailFilterRuleModeMatch}, + {name: "no_match", input: `"no-match"`, want: EmailFilterRuleModeNoMatch}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := new(IntegrationEmailFilterRuleMode) + + if cont := testErrCheck(t, "got.UnmarshalJSON()", tt.err, got.UnmarshalJSON([]byte(tt.input))); !cont { + return + } + + if *got != tt.want { + t.Fatalf("got = %d (%s), want = %d (%s)", *got, got.String(), tt.want, tt.want.String()) + } + }) + } +} + +func TestIntegrationEmailFilterRule(t *testing.T) { + t.Run("zero_value/omitempty", func(t *testing.T) { + var iefr IntegrationEmailFilterRule + + j, err := json.Marshal(iefr) + testErrCheck(t, "json.Marshal()", "", err) + + if string(j) != "{}" { + t.Fatalf("expected empty object, got %q", string(j)) + } + }) +} + +func TestIntegrationEmailFilterRule_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want IntegrationEmailFilterRule + err string + }{ + { + name: "full", + input: `{"subject_mode":"always", "subject_regex":"", "body_mode":"match", "body_regex":"testbody", "from_email_mode":"no-match", "from_email_regex":"testfrom"}`, + want: IntegrationEmailFilterRule{ + SubjectMode: EmailFilterRuleModeAlways, + SubjectRegex: strPtr(""), + BodyMode: EmailFilterRuleModeMatch, + BodyRegex: strPtr("testbody"), + FromEmailMode: EmailFilterRuleModeNoMatch, + FromEmailRegex: strPtr("testfrom"), + }, + }, + + { + name: "empty", + input: `{}`, + want: IntegrationEmailFilterRule{ + SubjectMode: EmailFilterRuleModeInvalid, + SubjectRegex: strPtr(""), + BodyMode: EmailFilterRuleModeInvalid, + BodyRegex: strPtr(""), + FromEmailMode: EmailFilterRuleModeInvalid, + FromEmailRegex: strPtr(""), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := new(IntegrationEmailFilterRule) + + err := got.UnmarshalJSON([]byte(tt.input)) + + if !testErrCheck(t, "got.UnmarshalJSON()", tt.err, err) { + return + } + + if got.SubjectRegex == nil { + t.Error("got.SubjectRegex = ") + } + + if got.BodyRegex == nil { + t.Error("got.BodyRegex = ") + } + + if got.FromEmailRegex == nil { + t.Error("got.FromEmailRegex = ") + } + + testEqual(t, tt.want, *got) + }) + } +} diff --git a/service_test.go b/service_test.go index 6f05a0fd..dbcc5303 100644 --- a/service_test.go +++ b/service_test.go @@ -314,121 +314,6 @@ func TestService_Delete(t *testing.T) { } } -// Create Integration -func TestService_CreateIntegration(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/services/1/integrations", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") - _, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`)) - }) - - client := defaultTestClient(server.URL, "foo") - input := Integration{ - Name: "foo", - } - servID := "1" - - res, err := client.CreateIntegration(servID, input) - - want := &Integration{ - APIObject: APIObject{ - ID: "1", - }, - Name: "foo", - } - - if err != nil { - t.Fatal(err) - } - testEqual(t, want, res) -} - -// Get Integration -func TestService_GetIntegration(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - _, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`)) - }) - - client := defaultTestClient(server.URL, "foo") - input := GetIntegrationOptions{ - Includes: []string{}, - } - servID := "1" - intID := "1" - - res, err := client.GetIntegration(servID, intID, input) - - want := &Integration{ - APIObject: APIObject{ - ID: "1", - }, - Name: "foo", - } - - if err != nil { - t.Fatal(err) - } - testEqual(t, want, res) -} - -// Update Integration -func TestService_UpdateIntegration(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") - _, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`)) - }) - - client := defaultTestClient(server.URL, "foo") - input := Integration{ - APIObject: APIObject{ - ID: "1", - }, - Name: "foo", - } - servID := "1" - - res, err := client.UpdateIntegration(servID, input) - - want := &Integration{ - APIObject: APIObject{ - ID: "1", - }, - Name: "foo", - } - - if err != nil { - t.Fatal(err) - } - testEqual(t, want, res) -} - -// Delete Integration -func TestService_DeleteIntegration(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") - }) - - client := defaultTestClient(server.URL, "foo") - servID := "1" - intID := "1" - err := client.DeleteIntegration(servID, intID) - if err != nil { - t.Fatal(err) - } -} - // List Service Rules func TestService_ListRules(t *testing.T) { setup()