115 lines
3.8 KiB
Go
115 lines
3.8 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/api/handlers/auth"
|
|
validator "RenewCMS/api/pkg/validator"
|
|
"RenewCMS/internal/domain/user"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"bytes"
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
type resetMailFailureHasher struct{}
|
|
|
|
func (resetMailFailureHasher) Hash(value string) (string, error) {
|
|
return "hash-of-" + value, nil
|
|
}
|
|
|
|
func (resetMailFailureHasher) Compare(hashed string, value string) error {
|
|
return nil
|
|
}
|
|
|
|
func captureStandardLog(t *testing.T) *bytes.Buffer {
|
|
t.Helper()
|
|
var buffer bytes.Buffer
|
|
previousOutput, previousFlags := log.Writer(), log.Flags()
|
|
log.SetOutput(&buffer)
|
|
t.Cleanup(func() {
|
|
log.SetOutput(previousOutput)
|
|
log.SetFlags(previousFlags)
|
|
})
|
|
return &buffer
|
|
}
|
|
|
|
func resetRequestWith(mailRepo *mockMailRepo, email string) (*httptest.ResponseRecorder, string) {
|
|
userRepo := newMockUserRepo()
|
|
userRepo.users[1] = user.User{ID: 1, Username: "alice", Email: "alice@example.com", IsVerified: true}
|
|
userRepo.nextID = 1
|
|
hasher := resetMailFailureHasher{}
|
|
handler := auth.NewPasswordResetHandler(
|
|
useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"),
|
|
useCases.NewResetPasswordUseCase(userRepo, hasher),
|
|
)
|
|
router := chi.NewRouter()
|
|
router.Post("/v1/auth/password-reset", handler.RequestPasswordReset)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/auth/password-reset", bytes.NewBufferString(`{"email": "`+email+`"}`))
|
|
recorder := httptest.NewRecorder()
|
|
router.ServeHTTP(recorder, req)
|
|
return recorder, userRepo.lastUpdatePasswordResetCodeCode
|
|
}
|
|
|
|
func TestRequestPasswordResetMailFailure(t *testing.T) {
|
|
validator.InitValidator()
|
|
sendFailure := errors.New("smtp: connection refused")
|
|
|
|
failingMail := func() *mockMailRepo {
|
|
mailRepo := newMockMailRepo()
|
|
mailRepo.fail.failNextWith("Send", sendFailure)
|
|
return mailRepo
|
|
}
|
|
|
|
t.Run("A failed send is logged and the response is unchanged", func(t *testing.T) {
|
|
logged := captureStandardLog(t)
|
|
|
|
recorder, _ := resetRequestWith(failingMail(), "alice@example.com")
|
|
|
|
if recorder.Code != http.StatusOK || strings.TrimSpace(recorder.Body.String()) != requestPasswordResetGenericBody {
|
|
t.Errorf("expected the generic 200, got %d %s", recorder.Code, recorder.Body.String())
|
|
}
|
|
if jwtCookieOf(recorder) != nil {
|
|
t.Errorf("no jwt cookie may be set")
|
|
}
|
|
line := logged.String()
|
|
if !strings.Contains(line, "Send") || !strings.Contains(line, "smtp: connection refused") {
|
|
t.Errorf("log must name the failed send and its error, got %q", line)
|
|
}
|
|
})
|
|
|
|
t.Run("The response is identical for a failed send, a successful send and an unknown email", func(t *testing.T) {
|
|
captureStandardLog(t)
|
|
|
|
failed, _ := resetRequestWith(failingMail(), "alice@example.com")
|
|
sent, _ := resetRequestWith(newMockMailRepo(), "alice@example.com")
|
|
unknown, _ := resetRequestWith(newMockMailRepo(), "nobody@example.com")
|
|
|
|
for name, other := range map[string]*httptest.ResponseRecorder{"successful send": sent, "unknown email": unknown} {
|
|
if failed.Code != other.Code || failed.Body.String() != other.Body.String() {
|
|
t.Errorf("%s differs: %d %q vs %d %q", name, failed.Code, failed.Body.String(), other.Code, other.Body.String())
|
|
}
|
|
if !reflect.DeepEqual(failed.Header(), other.Header()) {
|
|
t.Errorf("%s headers differ: %v vs %v", name, failed.Header(), other.Header())
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("The log does not contain the raw reset code", func(t *testing.T) {
|
|
logged := captureStandardLog(t)
|
|
|
|
_, storedHash := resetRequestWith(failingMail(), "alice@example.com")
|
|
|
|
rawCode := strings.TrimPrefix(storedHash, "hash-of-")
|
|
if rawCode == "" || strings.Contains(logged.String(), rawCode) || strings.Contains(logged.String(), storedHash) {
|
|
t.Errorf("log must contain neither the raw code nor its hash, got %q", logged.String())
|
|
}
|
|
})
|
|
}
|