Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions application/article/getArticlesByAuthor/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/gofrs/uuid/v5"

"github.com/khanzadimahdi/testproject/domain"
"github.com/khanzadimahdi/testproject/domain/user"
"github.com/khanzadimahdi/testproject/infrastructure/validator/rules"
)

type Request struct {
Expand All @@ -29,7 +29,7 @@ func (r *Request) Validate() domain.ValidationErrors {
}
}

if len(r.Username) > 0 && !user.IsValidUsername(r.Username) {
if len(r.Username) > 0 && !rules.IsValidUsername(r.Username) {
validationErrors["username"] = "invalid_value"
}

Expand Down
4 changes: 2 additions & 2 deletions application/auth/register/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package register

import (
"github.com/khanzadimahdi/testproject/domain"
"github.com/khanzadimahdi/testproject/domain/user"
"github.com/khanzadimahdi/testproject/infrastructure/validator/rules"
)

type Request struct {
Expand All @@ -18,7 +18,7 @@ var _ domain.Validatable = &Request{}
func (r *Request) Validate() domain.ValidationErrors {
validationErrors := make(domain.ValidationErrors)

if !user.IsValidEmail(r.Identity) {
if !rules.IsValidEmail(r.Identity) {
validationErrors["identity"] = "invalid_email"
}

Expand Down
4 changes: 2 additions & 2 deletions application/auth/verify/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package verify

import (
"github.com/khanzadimahdi/testproject/domain"
"github.com/khanzadimahdi/testproject/domain/user"
"github.com/khanzadimahdi/testproject/infrastructure/validator/rules"
)

type Request struct {
Expand All @@ -29,7 +29,7 @@ func (r *Request) Validate() domain.ValidationErrors {

if len(r.Username) == 0 {
validationErrors["username"] = "required_field"
} else if !user.IsValidUsername(r.Username) {
} else if !rules.IsValidUsername(r.Username) {
validationErrors["username"] = "invalid_value"
}

Expand Down
48 changes: 48 additions & 0 deletions application/contact/createMessage/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package createMessage

import (
"strings"

"github.com/khanzadimahdi/testproject/domain"
"github.com/khanzadimahdi/testproject/infrastructure/validator/rules"
)

type Request struct {
Subject string `json:"subject"`
Body string `json:"body"`
Email string `json:"email"`
Phone string `json:"phone"`
}

var _ domain.Validatable = &Request{}

func (r *Request) Validate() domain.ValidationErrors {
validationErrors := make(domain.ValidationErrors)

if len(strings.TrimSpace(r.Subject)) == 0 {
validationErrors["subject"] = "required_field"
}

if len(strings.TrimSpace(r.Body)) == 0 {
validationErrors["body"] = "required_field"
}

// The sender is anonymous, so at least one way of reaching them back is
// required; whichever one they filled in still has to be well-formed.
if len(r.Email) == 0 && len(r.Phone) == 0 {
validationErrors["email"] = "email_or_phone_required"
validationErrors["phone"] = "email_or_phone_required"

return validationErrors
}

if len(r.Email) > 0 && !rules.IsValidEmail(r.Email) {
validationErrors["email"] = "invalid_email"
}

if len(r.Phone) > 0 && !rules.IsValidPhoneNumber(r.Phone) {
validationErrors["phone"] = "invalid_phone_number"
}

return validationErrors
}
118 changes: 118 additions & 0 deletions application/contact/createMessage/request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package createMessage

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/khanzadimahdi/testproject/domain"
)

func TestRequest_Validate(t *testing.T) {
tests := []struct {
name string
request Request
want domain.ValidationErrors
}{
{
name: "valid request with an email",
request: Request{
Subject: "a subject",
Body: "a body",
Email: "user@example.com",
},
want: domain.ValidationErrors{},
},
{
name: "valid request with a phone number",
request: Request{
Subject: "a subject",
Body: "a body",
Phone: "09123456789",
},
want: domain.ValidationErrors{},
},
{
name: "valid request with both",
request: Request{
Subject: "a subject",
Body: "a body",
Email: "user@example.com",
Phone: "1234",
},
want: domain.ValidationErrors{},
},
{
name: "missing subject and body",
request: Request{
Email: "user@example.com",
},
want: domain.ValidationErrors{
"subject": "required_field",
"body": "required_field",
},
},
{
name: "blank subject and body",
request: Request{
Subject: " ",
Body: "\n\t",
Email: "user@example.com",
},
want: domain.ValidationErrors{
"subject": "required_field",
"body": "required_field",
},
},
{
name: "neither an email nor a phone number",
request: Request{
Subject: "a subject",
Body: "a body",
},
want: domain.ValidationErrors{
"email": "email_or_phone_required",
"phone": "email_or_phone_required",
},
},
{
name: "malformed email",
request: Request{
Subject: "a subject",
Body: "a body",
Email: "not-an-email",
},
want: domain.ValidationErrors{
"email": "invalid_email",
},
},
{
name: "malformed phone number",
request: Request{
Subject: "a subject",
Body: "a body",
Phone: "12-3",
},
want: domain.ValidationErrors{
"phone": "invalid_phone_number",
},
},
{
name: "too short phone number",
request: Request{
Subject: "a subject",
Body: "a body",
Phone: "123",
},
want: domain.ValidationErrors{
"phone": "invalid_phone_number",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tt.request.Validate())
})
}
}
7 changes: 7 additions & 0 deletions application/contact/createMessage/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package createMessage

import "github.com/khanzadimahdi/testproject/domain"

type Response struct {
ValidationErrors domain.ValidationErrors `json:"errors,omitempty"`
}
42 changes: 42 additions & 0 deletions application/contact/createMessage/usecase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package createMessage

import (
"context"

"github.com/khanzadimahdi/testproject/domain"
"github.com/khanzadimahdi/testproject/domain/contact"
)

type UseCase struct {
contactRepository contact.Repository
validator domain.Validator
}

func NewUseCase(
contactRepository contact.Repository,
validator domain.Validator,
) *UseCase {
return &UseCase{
contactRepository: contactRepository,
validator: validator,
}
}

func (uc *UseCase) Execute(ctx context.Context, request *Request) (*Response, error) {
if validationErrors := uc.validator.Validate(request); len(validationErrors) > 0 {
return &Response{
ValidationErrors: validationErrors,
}, nil
}

m := contact.Message{
Subject: request.Subject,
Body: request.Body,
Email: request.Email,
Phone: request.Phone,
}

_, err := uc.contactRepository.Save(ctx, &m)

return nil, err
}
115 changes: 115 additions & 0 deletions application/contact/createMessage/usecase_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package createMessage

import (
"context"
"errors"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

"github.com/khanzadimahdi/testproject/domain/contact"
"github.com/khanzadimahdi/testproject/infrastructure/repository/mocks/contacts"
"github.com/khanzadimahdi/testproject/infrastructure/validator"
)

func TestUseCase_Execute(t *testing.T) {
t.Parallel()

t.Run("sends a message", func(t *testing.T) {
t.Parallel()

var (
contactRepository contacts.MockContactsRepository
validator validator.MockValidator

r = Request{
Subject: "a subject",
Body: "a body",
Email: "user@example.com",
Phone: "09123456789",
}

m = contact.Message{
Subject: r.Subject,
Body: r.Body,
Email: r.Email,
Phone: r.Phone,
}
)

validator.On("Validate", &r).Once().Return(nil)
defer validator.AssertExpectations(t)

contactRepository.On("Save", mock.Anything, &m).Once().Return("message-uuid", nil)
defer contactRepository.AssertExpectations(t)

response, err := NewUseCase(&contactRepository, &validator).Execute(context.Background(), &r)

assert.NoError(t, err)
assert.Nil(t, response)
})

t.Run("validation fails", func(t *testing.T) {
t.Parallel()

var (
contactRepository contacts.MockContactsRepository
validator validator.MockValidator

r = Request{}
expectedResponse = Response{
ValidationErrors: map[string]string{
"subject": "this field is required",
"body": "this field is required",
"email": "either an email address or a phone number is required",
"phone": "either an email address or a phone number is required",
},
}
)

validator.On("Validate", &r).Once().Return(expectedResponse.ValidationErrors)
defer validator.AssertExpectations(t)

response, err := NewUseCase(&contactRepository, &validator).Execute(context.Background(), &r)

contactRepository.AssertNotCalled(t, "Save")

assert.NoError(t, err)
assert.Equal(t, &expectedResponse, response)
})

t.Run("saving the message fails", func(t *testing.T) {
t.Parallel()

var (
contactRepository contacts.MockContactsRepository
validator validator.MockValidator

r = Request{
Subject: "a subject",
Body: "a body",
Email: "user@example.com",
}

m = contact.Message{
Subject: r.Subject,
Body: r.Body,
Email: r.Email,
}

expectedErr = errors.New("saving the message failed")
)

validator.On("Validate", &r).Once().Return(nil)
defer validator.AssertExpectations(t)

contactRepository.On("Save", mock.Anything, &m).Once().Return("", expectedErr)
defer contactRepository.AssertExpectations(t)

response, err := NewUseCase(&contactRepository, &validator).Execute(context.Background(), &r)

assert.ErrorIs(t, err, expectedErr)
assert.Nil(t, response)
})
}
5 changes: 5 additions & 0 deletions application/dashboard/contact/deleteMessage/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package deleteMessage

type Request struct {
MessageUUID string `json:"uuid"`
}
Loading
Loading