snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
36
server/pkg/mail/check.go
Normal file
36
server/pkg/mail/check.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
gomail "gopkg.in/mail.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
hostCheckRE = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
|
||||
)
|
||||
|
||||
// ConfigCheck dials and authenticates to an SMTP server using the same
|
||||
// gomail dialer used for the regular send path. Returns "" on success
|
||||
// or a human-readable error string on failure.
|
||||
func ConfigCheck(host string, port uint, username, password string, tlsConfig *tls.Config) (checkRes string) {
|
||||
d := gomail.NewDialer(host, int(port), username, password)
|
||||
d.Timeout = 10 * time.Second
|
||||
if tlsConfig != nil {
|
||||
d.TLSConfig = tlsConfig
|
||||
}
|
||||
|
||||
sc, err := d.Dial()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
_ = sc.Close()
|
||||
return ""
|
||||
}
|
||||
|
||||
func IsValidHost(host string) bool {
|
||||
hostCheck := regexp.MustCompile(hostCheckRE)
|
||||
return hostCheck.MatchString(host)
|
||||
}
|
||||
30
server/pkg/mail/check_test.go
Normal file
30
server/pkg/mail/check_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHostValidator(t *testing.T) {
|
||||
ttc := []struct {
|
||||
host string
|
||||
ok bool
|
||||
}{
|
||||
{"ç$€§az.com", false},
|
||||
{"@sendyy.com", false},
|
||||
{"qwertyuiop.com", true},
|
||||
{"test.foo.bar", true},
|
||||
{"10.10.10", true},
|
||||
{"192.10.10345", true},
|
||||
{"1rg.10ui.10", true},
|
||||
{"info .crust tech", false},
|
||||
{"info.crust.tech", true},
|
||||
{"crust-tech?", false},
|
||||
{"crust-tech", true},
|
||||
{"crust/tech", false},
|
||||
}
|
||||
|
||||
for _, tc := range ttc {
|
||||
require.True(t, IsValidHost(tc.host) == tc.ok, "Validation of %s should return %v", tc.host, tc.ok)
|
||||
}
|
||||
}
|
||||
19
server/pkg/mail/healthcheck.go
Normal file
19
server/pkg/mail/healthcheck.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Healtcheck for (global) scheduler
|
||||
func Healthcheck(ctx context.Context) error {
|
||||
if defaultDialerError == nil {
|
||||
return defaultDialerError
|
||||
}
|
||||
|
||||
if defaultDialer == nil {
|
||||
return fmt.Errorf("uninitialized")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
110
server/pkg/mail/mail.go
Normal file
110
server/pkg/mail/mail.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
gomail "gopkg.in/mail.v2"
|
||||
)
|
||||
|
||||
type (
|
||||
Dialer interface {
|
||||
DialAndSend(...*gomail.Message) error
|
||||
}
|
||||
|
||||
applyCfg func(*gomail.Dialer)
|
||||
)
|
||||
|
||||
const (
|
||||
addressCheckRE = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
|
||||
)
|
||||
|
||||
var (
|
||||
addressCheck *regexp.Regexp
|
||||
defaultDialer Dialer
|
||||
defaultFrom string
|
||||
defaultDialerError error
|
||||
)
|
||||
|
||||
func init() {
|
||||
addressCheck = regexp.MustCompile(addressCheckRE)
|
||||
}
|
||||
|
||||
// SetupDialer setups SMTP dialer
|
||||
//
|
||||
// Host variable can contain "<host>:<port>" that will override port value
|
||||
func SetupDialer(host string, port int, user, pass, from string, ff ...applyCfg) {
|
||||
if host == "" {
|
||||
defaultDialerError = fmt.Errorf("no hostname provided for SMTP")
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(host, ":") {
|
||||
parts := strings.SplitN(host, ":", 2)
|
||||
port, _ = strconv.Atoi(parts[1])
|
||||
host = parts[0]
|
||||
}
|
||||
|
||||
if port == 0 {
|
||||
defaultDialerError = fmt.Errorf("no port provided for SMTP")
|
||||
return
|
||||
}
|
||||
|
||||
if from == "" {
|
||||
from = "placeholder@example.net"
|
||||
}
|
||||
|
||||
if defaultDialerError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defaultFrom = from
|
||||
dialer := gomail.NewDialer(
|
||||
host,
|
||||
port,
|
||||
user,
|
||||
pass,
|
||||
)
|
||||
|
||||
for _, fn := range ff {
|
||||
fn(dialer)
|
||||
}
|
||||
|
||||
defaultDialer = dialer
|
||||
}
|
||||
|
||||
func New() *gomail.Message {
|
||||
message := gomail.NewMessage()
|
||||
message.SetHeader("From", defaultFrom)
|
||||
return message
|
||||
}
|
||||
|
||||
// Send message with SMTP dialer
|
||||
func Send(message *gomail.Message, dd ...Dialer) (err error) {
|
||||
for _, d := range append(dd, defaultDialer) {
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = d.DialAndSend(message); err != nil {
|
||||
return fmt.Errorf("could not send email: %w", err)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, none of the dialer could be used,
|
||||
// is there an error with default dialer?
|
||||
if defaultDialerError != nil {
|
||||
return fmt.Errorf("could not send email: %w", defaultDialerError)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to find configured and working SMTP dialer")
|
||||
|
||||
}
|
||||
|
||||
func IsValidAddress(addr string) bool {
|
||||
return addressCheck.MatchString(addr)
|
||||
}
|
||||
52
server/pkg/mail/mail_mock_test.go
Normal file
52
server/pkg/mail/mail_mock_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: pkg/mail/mail.go
|
||||
|
||||
// Package mail is a generated GoMock package.
|
||||
package mail
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
mail_v2 "gopkg.in/mail.v2"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockDialer is a mock of Dialer interface
|
||||
type MockDialer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDialerMockRecorder
|
||||
}
|
||||
|
||||
// MockDialerMockRecorder is the mock recorder for MockDialer
|
||||
type MockDialerMockRecorder struct {
|
||||
mock *MockDialer
|
||||
}
|
||||
|
||||
// NewMockDialer creates a new mock instance
|
||||
func NewMockDialer(ctrl *gomock.Controller) *MockDialer {
|
||||
mock := &MockDialer{ctrl: ctrl}
|
||||
mock.recorder = &MockDialerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockDialer) EXPECT() *MockDialerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// DialAndSend mocks base method
|
||||
func (m *MockDialer) DialAndSend(arg0 ...*mail_v2.Message) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
for _, a := range arg0 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "DialAndSend", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DialAndSend indicates an expected call of DialAndSend
|
||||
func (mr *MockDialerMockRecorder) DialAndSend(arg0 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialAndSend", reflect.TypeOf((*MockDialer)(nil).DialAndSend), arg0...)
|
||||
}
|
||||
112
server/pkg/mail/mail_test.go
Normal file
112
server/pkg/mail/mail_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDialerInvalidSetup(t *testing.T) {
|
||||
defaultDialer = nil
|
||||
defaultDialerError = nil
|
||||
|
||||
SetupDialer("", 0, "", "", "")
|
||||
require.True(t, defaultDialerError != nil, "'Missing SMTP configuration' error should be set, got: %v", defaultDialerError)
|
||||
require.True(t, defaultDialer == nil, "defaultDialer should n be set, got: %v", defaultDialer)
|
||||
}
|
||||
|
||||
func TestDialerValidSetup(t *testing.T) {
|
||||
defaultDialer = nil
|
||||
defaultDialerError = nil
|
||||
|
||||
SetupDialer("localhost:321", 0, "", "", "some@email.tld")
|
||||
require.True(t, defaultDialerError == nil, "defaultDialerError should be nil, got %v", defaultDialerError)
|
||||
require.True(t, defaultDialer != nil, "defaultDialer should be set, got %v", defaultDialer)
|
||||
|
||||
}
|
||||
|
||||
func TestMailSendWithoutDialer(t *testing.T) {
|
||||
msg := New()
|
||||
defaultDialer = nil
|
||||
defaultDialerError = nil
|
||||
{
|
||||
err := Send(msg)
|
||||
require.True(t, err != nil, "Send() should return an error, got %v", err)
|
||||
}
|
||||
|
||||
defaultDialer = nil
|
||||
defaultDialerError = fmt.Errorf("Default dialer init error")
|
||||
{
|
||||
err := Send(msg)
|
||||
require.True(t, err != nil, "Send() should return an error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMailSendWithDefaultDialer(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
msg := New()
|
||||
|
||||
dDialer := NewMockDialer(mockCtrl)
|
||||
dDialer.EXPECT().DialAndSend(msg).Times(1).Return(nil)
|
||||
|
||||
defaultDialerError = nil
|
||||
defaultDialer = dDialer
|
||||
|
||||
require.NoError(t, Send(msg))
|
||||
defaultDialer = nil
|
||||
}
|
||||
|
||||
func TestMailSendWithSpecificDialer(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
msg := New()
|
||||
|
||||
sDailer := NewMockDialer(mockCtrl)
|
||||
sDailer.EXPECT().DialAndSend(msg).Times(1).Return(nil)
|
||||
|
||||
defaultDialerError = nil
|
||||
dDialer := NewMockDialer(mockCtrl)
|
||||
dDialer.EXPECT().DialAndSend(msg).Times(0)
|
||||
|
||||
defaultDialer = dDialer
|
||||
require.NoError(t, Send(msg, sDailer))
|
||||
defaultDialer = nil
|
||||
}
|
||||
|
||||
func TestMailSendErrors(t *testing.T) {
|
||||
defaultDialerError = nil
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
msg := New()
|
||||
|
||||
sDailer := NewMockDialer(mockCtrl)
|
||||
sDailer.EXPECT().DialAndSend(msg).Times(1).Return(fmt.Errorf("some-error"))
|
||||
|
||||
err := Send(msg, sDailer)
|
||||
require.True(t, err != nil, "Send() should return an error, got: %v", err)
|
||||
}
|
||||
|
||||
func TestMailValidator(t *testing.T) {
|
||||
ttc := []struct {
|
||||
addr string
|
||||
ok bool
|
||||
}{
|
||||
{"ç$€§/az@gmail.com", false},
|
||||
{"abcd@gmail_yahoo.com", false},
|
||||
{"abcd@gmail-yahoo.com", true},
|
||||
{"abcd@gmailyahoo", true},
|
||||
{"abcd@gmail.yahoo", true},
|
||||
{"info @ crust tech", false},
|
||||
{"info@crust.tech", true},
|
||||
}
|
||||
|
||||
for _, tc := range ttc {
|
||||
require.True(t, IsValidAddress(tc.addr) == tc.ok, "Validation of %s should return %v", tc.addr, tc.ok)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user