diff --git a/Makefile b/Makefile index b584b5a..8abe343 100644 --- a/Makefile +++ b/Makefile @@ -81,11 +81,12 @@ clean: qtest: @echo "Perform quick tests ..." - @time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewConfig ./*.go + @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewConfig ./*.go @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewServer ./*.go @#time richgo test -v -coverprofile=.coverage/coverage.out internal/tag/*.go @### time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestAuthorize ./pkg/authz/validator/... @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestAddProviders ./pkg/messaging/... + @time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/messaging/... @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/credentials/... @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/errors/... @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/requests/... diff --git a/pkg/errors/messaging.go b/pkg/errors/messaging.go index 27b6e49..cfb6123 100644 --- a/pkg/errors/messaging.go +++ b/pkg/errors/messaging.go @@ -23,4 +23,7 @@ const ( ErrMessagingProviderCredentialsWithPasswordless StandardError = "messaging provider config is both passwordless and has credentials" ErrMessagingProviderAuthUnsupported StandardError = "messaging provider does not support AUTH extension" + + ErrMessagingProviderSend StandardError = "messaging provider send error: %v" + ErrMessagingProviderDir StandardError = "messaging provider file dir error: %v" ) diff --git a/pkg/messaging/config.go b/pkg/messaging/config.go index dcb35ce..6ae126c 100644 --- a/pkg/messaging/config.go +++ b/pkg/messaging/config.go @@ -21,6 +21,7 @@ import ( // Config represents a collection of various messaging providers. type Config struct { EmailProviders []*EmailProvider `json:"email_providers,omitempty" xml:"email_providers,omitempty" yaml:"email_providers,omitempty"` + FileProviders []*FileProvider `json:"file_providers,omitempty" xml:"file_providers,omitempty" yaml:"file_providers,omitempty"` } // Provider is an interface to work with messaging providers. @@ -32,6 +33,7 @@ type Provider interface { func (cfg *Config) Add(c Provider) error { switch v := c.(type) { case *EmailProvider: + case *FileProvider: default: return errors.ErrMessagingAddProviderConfigType.WithArgs(v) } @@ -43,6 +45,8 @@ func (cfg *Config) Add(c Provider) error { switch v := c.(type) { case *EmailProvider: cfg.EmailProviders = append(cfg.EmailProviders, v) + case *FileProvider: + cfg.FileProviders = append(cfg.FileProviders, v) } return nil } @@ -54,6 +58,11 @@ func (cfg *Config) FindProvider(s string) bool { return true } } + for _, p := range cfg.FileProviders { + if p.Name == s { + return true + } + } return false } @@ -80,3 +89,13 @@ func (cfg *Config) ExtractEmailProvider(s string) *EmailProvider { } return nil } + +// ExtractFileProvider returns FileProvider by name. +func (cfg *Config) ExtractFileProvider(s string) *FileProvider { + for _, p := range cfg.FileProviders { + if p.Name == s { + return p + } + } + return nil +} diff --git a/pkg/messaging/config_test.go b/pkg/messaging/config_test.go index 03a50a9..efa2f77 100644 --- a/pkg/messaging/config_test.go +++ b/pkg/messaging/config_test.go @@ -15,23 +15,36 @@ package messaging import ( - // "fmt" "github.com/google/go-cmp/cmp" "github.com/greenpau/go-authcrunch/internal/tests" - // "github.com/greenpau/go-authcrunch/pkg/errors" + "github.com/greenpau/go-authcrunch/pkg/errors" "testing" ) +type dummyProvider struct { +} + +func (p *dummyProvider) Validate() error { + return nil +} + func TestAddProviders(t *testing.T) { + tmpDir, err := tests.TempDir("TestAddMessagingProviders") + if err != nil { + t.Fatal(err) + } + testcases := []struct { - name string - entry Provider - want string - shouldErr bool - err error + name string + providerName string + entry Provider + want string + shouldErr bool + err error }{ { - name: "test valid email", + name: "test valid email provider config", + providerName: "default", entry: &EmailProvider{ Name: "default", Address: "localhost", @@ -51,6 +64,58 @@ func TestAddProviders(t *testing.T) { ] }`, }, + { + name: "test valid email provider passwordless config", + providerName: "default", + entry: &EmailProvider{ + Name: "default", + Address: "localhost", + Protocol: "smtp", + Passwordless: true, + SenderEmail: "root@localhost", + }, + want: `{ + "email_providers": [ + { + "address": "localhost", + "name": "default", + "protocol": "smtp", + "passwordless": true, + "sender_email": "root@localhost" + } + ] + }`, + }, + { + name: "test valid file provider config", + providerName: "default", + entry: &FileProvider{ + Name: "default", + RootDir: tmpDir, + }, + want: `{ + "file_providers": [ + { + "name": "default", + "root_dir": "` + tmpDir + `" + } + ] + }`, + }, + { + name: "test invalid messaging provider config", + entry: &dummyProvider{}, + shouldErr: true, + err: errors.ErrMessagingAddProviderConfigType.WithArgs(&dummyProvider{}), + }, + { + name: "test file provider config without root directory", + entry: &FileProvider{ + Name: "default", + }, + shouldErr: true, + err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("root_dir"), + }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { @@ -71,6 +136,50 @@ func TestAddProviders(t *testing.T) { got := tests.Unpack(t, cfg) want := tests.Unpack(t, tc.want) + if !cfg.FindProvider(tc.providerName) { + t.Fatalf("failed FindProvider with %q", tc.providerName) + } + + switch tc.entry.(type) { + case *EmailProvider: + p := cfg.ExtractEmailProvider(tc.providerName) + if p == nil { + t.Fatalf("failed to extract %q file provider", tc.providerName) + } + providerCreds := cfg.FindProviderCredentials(tc.providerName) + switch providerCreds { + case "passwordless": + if !p.Passwordless { + t.Fatalf("provider credentials mismatch: %v, %v", providerCreds, p.Credentials) + } + case p.Credentials: + default: + t.Fatalf("provider credentials mismatch: %v, %v", providerCreds, p.Credentials) + } + case *FileProvider: + p := cfg.ExtractFileProvider(tc.providerName) + if p == nil { + t.Fatalf("failed to extract %q file provider", tc.providerName) + } + } + + if tc.name == "test valid email provider config" { + if cfg.FindProvider("foobar") { + t.Fatal("unexpected success with FindProvider") + } + + if cfg.ExtractEmailProvider("foo") != nil { + t.Fatal("unexpected success with ExtractEmailProvider") + } + + if cfg.ExtractFileProvider("foo") != nil { + t.Fatal("unexpected success with ExtractEmailProvider") + } + if cfg.FindProviderCredentials("foo") != "" { + t.Fatal("unexpected success with FindProviderCredentials") + } + } + if diff := cmp.Diff(want, got); diff != "" { t.Logf("JSON: %s", tests.UnpackJSON(t, got)) t.Errorf("Add() mismatch (-want +got):\n%s", diff) diff --git a/pkg/messaging/email.go b/pkg/messaging/email.go index 5e544de..5f89689 100644 --- a/pkg/messaging/email.go +++ b/pkg/messaging/email.go @@ -15,14 +15,7 @@ package messaging import ( - "fmt" - "github.com/emersion/go-sasl" - "github.com/emersion/go-smtp" - "github.com/greenpau/go-authcrunch/pkg/credentials" "github.com/greenpau/go-authcrunch/pkg/errors" - "github.com/greenpau/go-authcrunch/pkg/util" - "strings" - "time" ) // EmailProvider represents email messaging provider. @@ -38,94 +31,6 @@ type EmailProvider struct { BlindCarbonCopy []string `json:"blind_carbon_copy,omitempty" xml:"blind_carbon_copy,omitempty" yaml:"blind_carbon_copy,omitempty"` } -// Send sends an email message. -func (e *EmailProvider) Send(creds *credentials.Generic, rcpts []string, subj, body string) error { - dial := smtp.Dial - if e.Protocol == "smtps" { - dial = func(addr string) (*smtp.Client, error) { - return smtp.DialTLS(addr, nil) - } - } - - c, err := dial(e.Address) - if err != nil { - return err - } - defer c.Close() - - if found, _ := c.Extension("STARTTLS"); found { - if err := c.StartTLS(nil); err != nil { - return err - } - } - - if !e.Passwordless && creds != nil { - if found, _ := c.Extension("AUTH"); !found { - return errors.ErrMessagingProviderAuthUnsupported - } - auth := sasl.NewPlainClient("", creds.Username, creds.Password) - if err := c.Auth(auth); err != nil { - return err - } - } - - if err := c.Mail(e.SenderEmail, nil); err != nil { - return err - } - - for _, rcpt := range rcpts { - if err := c.Rcpt(rcpt); err != nil { - return err - } - } - - sender := e.SenderEmail - if e.SenderName != "" { - sender = `"` + e.SenderName + `" <` + e.SenderEmail + ">" - } - - msg := "MIME-Version: 1.0\n" - msg += "Date: " + time.Now().Format(time.RFC1123Z) + "\n" - msg += "From: " + sender + "\n" - msg += "Subject: " + subj + "\n" - msg += "Thread-Topic: Account Registration." + "\n" - msg += "Message-ID: <" + util.GetRandomString(64) + "." + e.SenderEmail + ">" + "\n" - msg += `To: ` + strings.Join(rcpts, ", ") + "\n" - - if len(e.BlindCarbonCopy) > 0 { - bccRcpts := dedupRcpt(rcpts, e.BlindCarbonCopy) - if len(bccRcpts) > 0 { - msg += "Bcc: " + strings.Join(bccRcpts, ", ") + "\n" - } - } - - msg += "Content-Transfer-Encoding: quoted-printable" + "\n" - msg += `Content-Type: text/html; charset="utf-8"` + "\n" - - msg += "\r\n" + body - - // Write email subject body. - wc, err := c.Data() - if err != nil { - return err - } - _, err = fmt.Fprintf(wc, msg) - if err != nil { - return err - } - - if err := wc.Close(); err != nil { - return err - } - - // Close connection. - if err := c.Quit(); err != nil { - return err - } - - return nil -} - // Validate validates EmailProvider configuration. func (e *EmailProvider) Validate() error { if e.Name == "" { @@ -170,19 +75,3 @@ func (e *EmailProvider) Validate() error { } return nil } - -func dedupRcpt(arr1, arr2 []string) []string { - var output []string - m := make(map[string]interface{}) - for _, s := range arr1 { - m[s] = true - } - - for _, s := range arr2 { - if _, exists := m[s]; exists { - continue - } - output = append(output, s) - } - return output -} diff --git a/pkg/messaging/email_send.go b/pkg/messaging/email_send.go new file mode 100644 index 0000000..e5cf15a --- /dev/null +++ b/pkg/messaging/email_send.go @@ -0,0 +1,137 @@ +// Copyright 2022 Paul Greenberg greenpau@outlook.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messaging + +import ( + "fmt" + "github.com/emersion/go-sasl" + "github.com/emersion/go-smtp" + "github.com/greenpau/go-authcrunch/pkg/credentials" + "github.com/greenpau/go-authcrunch/pkg/errors" + "github.com/greenpau/go-authcrunch/pkg/util" + "strings" + "time" +) + +type EmailProviderSendInput struct { + Subject string + Body string + Recipients []string + Credentials *credentials.Generic +} + +// Send sends an email message. +func (e *EmailProvider) Send(req *EmailProviderSendInput) error { + dial := smtp.Dial + if e.Protocol == "smtps" { + dial = func(addr string) (*smtp.Client, error) { + return smtp.DialTLS(addr, nil) + } + } + + c, err := dial(e.Address) + if err != nil { + return err + } + defer c.Close() + + if found, _ := c.Extension("STARTTLS"); found { + if err := c.StartTLS(nil); err != nil { + return err + } + } + + if !e.Passwordless && req.Credentials != nil { + if found, _ := c.Extension("AUTH"); !found { + return errors.ErrMessagingProviderAuthUnsupported + } + auth := sasl.NewPlainClient("", req.Credentials.Username, req.Credentials.Password) + if err := c.Auth(auth); err != nil { + return err + } + } + + if err := c.Mail(e.SenderEmail, nil); err != nil { + return err + } + + for _, rcpt := range req.Recipients { + if err := c.Rcpt(rcpt); err != nil { + return err + } + } + + sender := e.SenderEmail + if e.SenderName != "" { + sender = `"` + e.SenderName + `" <` + e.SenderEmail + ">" + } + + msg := "MIME-Version: 1.0\n" + msg += "Date: " + time.Now().Format(time.RFC1123Z) + "\n" + msg += "From: " + sender + "\n" + msg += "Subject: " + req.Subject + "\n" + msg += "Thread-Topic: Account Registration." + "\n" + msg += "Message-ID: <" + util.GetRandomString(64) + "." + e.SenderEmail + ">" + "\n" + msg += `To: ` + strings.Join(req.Recipients, ", ") + "\n" + + if len(e.BlindCarbonCopy) > 0 { + bccRcpts := dedupRcpt(req.Recipients, e.BlindCarbonCopy) + if len(bccRcpts) > 0 { + msg += "Bcc: " + strings.Join(bccRcpts, ", ") + "\n" + } + } + + msg += "Content-Transfer-Encoding: quoted-printable" + "\n" + msg += `Content-Type: text/html; charset="utf-8"` + "\n" + + msg += "\r\n" + req.Body + + // Write email subject body. + wc, err := c.Data() + if err != nil { + return err + } + _, err = fmt.Fprintf(wc, msg) + if err != nil { + return err + } + + if err := wc.Close(); err != nil { + return err + } + + // Close connection. + if err := c.Quit(); err != nil { + return err + } + + return nil +} + +func dedupRcpt(arr1, arr2 []string) []string { + var output []string + m := make(map[string]interface{}) + for _, s := range arr1 { + m[s] = true + } + + for _, s := range arr2 { + if _, exists := m[s]; exists { + continue + } + output = append(output, s) + } + return output +} diff --git a/pkg/messaging/email_test.go b/pkg/messaging/email_test.go new file mode 100644 index 0000000..980c024 --- /dev/null +++ b/pkg/messaging/email_test.go @@ -0,0 +1,152 @@ +// Copyright 2022 Paul Greenberg greenpau@outlook.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messaging + +import ( + "github.com/google/go-cmp/cmp" + "github.com/greenpau/go-authcrunch/pkg/errors" + "testing" +) + +func TestValidateEmailProvider(t *testing.T) { + testcases := []struct { + name string + entry *EmailProvider + want string + shouldErr bool + err error + }{ + { + name: "test valid email provider config", + entry: &EmailProvider{ + Name: "default", + Address: "localhost", + Protocol: "smtp", + Credentials: "default_email_creds", + SenderEmail: "root@localhost", + }, + }, + { + name: "test email provider config with credentials and passwordless", + entry: &EmailProvider{ + Name: "default", + Address: "localhost", + Protocol: "smtp", + Credentials: "default_email_creds", + Passwordless: true, + SenderEmail: "root@localhost", + }, + shouldErr: true, + err: errors.ErrMessagingProviderCredentialsWithPasswordless, + }, + { + name: "test email provider config without credentials and passwordless", + entry: &EmailProvider{ + Name: "default", + Address: "localhost", + Protocol: "smtp", + SenderEmail: "root@localhost", + }, + shouldErr: true, + err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("credentials"), + }, + { + name: "test email provider config without address", + entry: &EmailProvider{ + Name: "default", + // Address: "localhost", + Protocol: "smtp", + Credentials: "default_email_creds", + SenderEmail: "root@localhost", + }, + shouldErr: true, + err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("address"), + }, + { + name: "test email provider config without protocol", + entry: &EmailProvider{ + Name: "default", + Address: "localhost", + // Protocol: "smtp", + Credentials: "default_email_creds", + SenderEmail: "root@localhost", + }, + shouldErr: true, + err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("protocol"), + }, + { + name: "test email provider config with unsupported protocol", + entry: &EmailProvider{ + Name: "default", + Address: "localhost", + Protocol: "foobar", + Credentials: "default_email_creds", + SenderEmail: "root@localhost", + }, + shouldErr: true, + err: errors.ErrMessagingProviderProtocolUnsupported.WithArgs("foobar"), + }, + { + name: "test email provider config without sender email", + entry: &EmailProvider{ + Name: "default", + Address: "localhost", + Protocol: "smtp", + Credentials: "default_email_creds", + // SenderEmail: "root@localhost", + }, + shouldErr: true, + err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("sender_email"), + }, + { + name: "test email provider config without name", + entry: &EmailProvider{}, + shouldErr: true, + err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("name"), + }, + { + name: "test email provider config with invalid template", + entry: &EmailProvider{ + Name: "default", + Address: "localhost", + Protocol: "smtp", + Credentials: "default_email_creds", + SenderEmail: "root@localhost", + Templates: map[string]string{ + "foo": "bar", + }, + }, + shouldErr: true, + err: errors.ErrMessagingProviderInvalidTemplate.WithArgs("foo"), + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + err := tc.entry.Validate() + if err != nil { + if !tc.shouldErr { + t.Fatalf("expected success, got: %v", err) + } + if diff := cmp.Diff(err.Error(), tc.err.Error()); diff != "" { + t.Fatalf("unexpected error: %v, want: %v", err, tc.err) + } + return + } + if tc.shouldErr { + t.Fatalf("unexpected success, want: %v", tc.err) + } + }) + } +} diff --git a/pkg/messaging/file.go b/pkg/messaging/file.go new file mode 100644 index 0000000..0c0813d --- /dev/null +++ b/pkg/messaging/file.go @@ -0,0 +1,53 @@ +// Copyright 2022 Paul Greenberg greenpau@outlook.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messaging + +import ( + "github.com/greenpau/go-authcrunch/pkg/errors" +) + +// FileProvider represents file messaging provider which writes messages +// to a local file system, +type FileProvider struct { + Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"` + RootDir string `json:"root_dir,omitempty" xml:"root_dir,omitempty" yaml:"root_dir,omitempty"` + Templates map[string]string `json:"templates,omitempty" xml:"templates,omitempty" yaml:"templates,omitempty"` +} + +// Validate validates FileProvider configuration. +func (e *FileProvider) Validate() error { + if e.Name == "" { + return errors.ErrMessagingProviderKeyValueEmpty.WithArgs("name") + } + + if e.RootDir == "" { + return errors.ErrMessagingProviderKeyValueEmpty.WithArgs("root_dir") + } + + if e.Templates != nil { + for k := range e.Templates { + switch k { + case "password_recovery": + case "registration_confirmation": + case "registration_ready": + case "registration_verdict": + case "mfa_otp": + default: + return errors.ErrMessagingProviderInvalidTemplate.WithArgs(k) + } + } + } + return nil +} diff --git a/pkg/messaging/file_send.go b/pkg/messaging/file_send.go new file mode 100644 index 0000000..339bf04 --- /dev/null +++ b/pkg/messaging/file_send.go @@ -0,0 +1,67 @@ +// Copyright 2022 Paul Greenberg greenpau@outlook.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messaging + +import ( + "github.com/greenpau/go-authcrunch/pkg/errors" + "github.com/greenpau/go-authcrunch/pkg/util" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +type FileProviderSendInput struct { + Subject string + Body string + Recipients []string +} + +// Send writes a message to a file system. +func (p *FileProvider) Send(req *FileProviderSendInput) error { + fileInfo, err := os.Stat(p.RootDir) + if err != nil { + if !os.IsNotExist(err) { + return errors.ErrMessagingProviderDir.WithArgs(err) + } + if err := os.MkdirAll(p.RootDir, 0700); err != nil { + return errors.ErrMessagingProviderDir.WithArgs(err) + } + } + if fileInfo != nil && !fileInfo.IsDir() { + return errors.ErrMessagingProviderDir.WithArgs(p.RootDir + "is not a directory") + } + + msgID := util.GetRandomString(64) + fp := filepath.Join(p.RootDir, msgID[:32]+".eml") + + msg := "MIME-Version: 1.0\n" + msg += "Date: " + time.Now().Format(time.RFC1123Z) + "\n" + msg += "Subject: " + req.Subject + "\n" + msg += "Thread-Topic: Account Registration." + "\n" + msg += "Message-ID: <" + msgID + ">" + "\n" + msg += `To: ` + strings.Join(req.Recipients, ", ") + "\n" + + msg += "Content-Transfer-Encoding: quoted-printable" + "\n" + msg += `Content-Type: text/html; charset="utf-8"` + "\n" + + msg += "\r\n" + req.Body + + if err := ioutil.WriteFile(fp, []byte(msg), 0600); err != nil { + return errors.ErrMessagingProviderSend.WithArgs(err) + } + return nil +} diff --git a/pkg/messaging/file_send_test.go b/pkg/messaging/file_send_test.go new file mode 100644 index 0000000..2effe93 --- /dev/null +++ b/pkg/messaging/file_send_test.go @@ -0,0 +1,74 @@ +// Copyright 2022 Paul Greenberg greenpau@outlook.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messaging + +import ( + "github.com/google/go-cmp/cmp" + "github.com/greenpau/go-authcrunch/internal/tests" + "testing" +) + +func TestFileProviderSend(t *testing.T) { + tmpDir, err := tests.TempDir("TestFileProviderSend") + if err != nil { + t.Fatal(err) + } + + tmpDir += "/inbox" + t.Logf("Temp dir: %s", tmpDir) + + testcases := []struct { + name string + provider *FileProvider + want string + shouldErr bool + err error + }{ + { + name: "test sending notification", + provider: &FileProvider{ + Name: "default", + RootDir: tmpDir, + }, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + err := tc.provider.Validate() + if err != nil { + t.Fatalf("unexpected validation error: %v", err) + } + + err = tc.provider.Send(&FileProviderSendInput{ + Subject: "foo", + Body: "foobar", + Recipients: []string{"root@localhost"}, + }) + + if err != nil { + if !tc.shouldErr { + t.Fatalf("expected success, got: %v", err) + } + if diff := cmp.Diff(err.Error(), tc.err.Error()); diff != "" { + t.Fatalf("unexpected error: %v, want: %v", err, tc.err) + } + return + } + if tc.shouldErr { + t.Fatalf("unexpected success, want: %v", tc.err) + } + }) + } +} diff --git a/pkg/messaging/file_test.go b/pkg/messaging/file_test.go new file mode 100644 index 0000000..af6e87b --- /dev/null +++ b/pkg/messaging/file_test.go @@ -0,0 +1,82 @@ +// Copyright 2022 Paul Greenberg greenpau@outlook.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messaging + +import ( + "github.com/google/go-cmp/cmp" + "github.com/greenpau/go-authcrunch/pkg/errors" + "testing" +) + +func TestValidateFileProvider(t *testing.T) { + testcases := []struct { + name string + entry *FileProvider + want string + shouldErr bool + err error + }{ + { + name: "test valid file provider config", + entry: &FileProvider{ + Name: "default", + RootDir: "foobar", + }, + }, + { + name: "test file provider config without root directory", + entry: &FileProvider{ + Name: "default", + }, + shouldErr: true, + err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("root_dir"), + }, + { + name: "test file provider config without name", + entry: &FileProvider{}, + shouldErr: true, + err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("name"), + }, + { + name: "test file provider config with invalid template", + entry: &FileProvider{ + Name: "default", + RootDir: "foobar", + Templates: map[string]string{ + "foo": "bar", + }, + }, + shouldErr: true, + err: errors.ErrMessagingProviderInvalidTemplate.WithArgs("foo"), + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + err := tc.entry.Validate() + if err != nil { + if !tc.shouldErr { + t.Fatalf("expected success, got: %v", err) + } + if diff := cmp.Diff(err.Error(), tc.err.Error()); diff != "" { + t.Fatalf("unexpected error: %v, want: %v", err, tc.err) + } + return + } + if tc.shouldErr { + t.Fatalf("unexpected success, want: %v", tc.err) + } + }) + } +}