185 lines
5.6 KiB
Go
185 lines
5.6 KiB
Go
package internalhttp
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"galaxy/mail/internal/domain/common"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestDecodeLoginCodeDeliveryCommandSuccess(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
request := httptest.NewRequest(http.MethodPost, LoginCodeDeliveriesPath, strings.NewReader(`{"email":" pilot@example.com ","code":"123456","locale":" en "}`))
|
|
request.Header.Set("Content-Type", "application/json")
|
|
request.Header.Set(IdempotencyKeyHeader, "challenge-1")
|
|
|
|
command, err := DecodeLoginCodeDeliveryCommand(request)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, LoginCodeDeliveryCommand{
|
|
IdempotencyKey: common.IdempotencyKey("challenge-1"),
|
|
Email: common.Email("pilot@example.com"),
|
|
Code: "123456",
|
|
Locale: common.Locale("en"),
|
|
}, command)
|
|
}
|
|
|
|
func TestDecodeLoginCodeDeliveryCommandRejectsInvalidRequests(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
contentType string
|
|
headerValue string
|
|
body string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "missing content type",
|
|
headerValue: "challenge-1",
|
|
body: `{"email":"pilot@example.com","code":"123456","locale":"en"}`,
|
|
wantErr: "Content-Type must be application/json",
|
|
},
|
|
{
|
|
name: "missing idempotency key",
|
|
contentType: "application/json",
|
|
body: `{"email":"pilot@example.com","code":"123456","locale":"en"}`,
|
|
wantErr: "Idempotency-Key header must not be empty",
|
|
},
|
|
{
|
|
name: "idempotency key surrounding whitespace",
|
|
contentType: "application/json",
|
|
headerValue: " challenge-1 ",
|
|
body: `{"email":"pilot@example.com","code":"123456","locale":"en"}`,
|
|
wantErr: "Idempotency-Key header must not contain surrounding whitespace",
|
|
},
|
|
{
|
|
name: "unknown field",
|
|
contentType: "application/json",
|
|
headerValue: "challenge-1",
|
|
body: `{"email":"pilot@example.com","code":"123456","locale":"en","extra":true}`,
|
|
wantErr: "decode request body",
|
|
},
|
|
{
|
|
name: "trailing json",
|
|
contentType: "application/json",
|
|
headerValue: "challenge-1",
|
|
body: `{"email":"pilot@example.com","code":"123456","locale":"en"}{}`,
|
|
wantErr: "unexpected trailing JSON input",
|
|
},
|
|
{
|
|
name: "code surrounding whitespace",
|
|
contentType: "application/json",
|
|
headerValue: "challenge-1",
|
|
body: `{"email":"pilot@example.com","code":" 123456 ","locale":"en"}`,
|
|
wantErr: "code must not contain surrounding whitespace",
|
|
},
|
|
{
|
|
name: "invalid locale",
|
|
contentType: "application/json",
|
|
headerValue: "challenge-1",
|
|
body: `{"email":"pilot@example.com","code":"123456","locale":"english"}`,
|
|
wantErr: "locale:",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
request := httptest.NewRequest(http.MethodPost, LoginCodeDeliveriesPath, strings.NewReader(tt.body))
|
|
if tt.contentType != "" {
|
|
request.Header.Set("Content-Type", tt.contentType)
|
|
}
|
|
if tt.headerValue != "" {
|
|
request.Header.Set(IdempotencyKeyHeader, tt.headerValue)
|
|
}
|
|
|
|
_, err := DecodeLoginCodeDeliveryCommand(request)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDecodeLoginCodeDeliveryCommandRepeatedEquivalentRequestsMatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
first := httptest.NewRequest(http.MethodPost, LoginCodeDeliveriesPath, strings.NewReader(`{"email":"pilot@example.com","code":"123456","locale":"en"}`))
|
|
first.Header.Set("Content-Type", "application/json")
|
|
first.Header.Set(IdempotencyKeyHeader, "challenge-1")
|
|
|
|
second := httptest.NewRequest(http.MethodPost, LoginCodeDeliveriesPath, strings.NewReader(`{"email":" pilot@example.com ","code":"123456","locale":" en "}`))
|
|
second.Header.Set("Content-Type", "application/json")
|
|
second.Header.Set(IdempotencyKeyHeader, "challenge-1")
|
|
|
|
firstCommand, err := DecodeLoginCodeDeliveryCommand(first)
|
|
require.NoError(t, err)
|
|
secondCommand, err := DecodeLoginCodeDeliveryCommand(second)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, firstCommand, secondCommand)
|
|
}
|
|
|
|
func TestLoginCodeDeliveryCommandFingerprintStableForEquivalentRequests(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
first := LoginCodeDeliveryCommand{
|
|
IdempotencyKey: common.IdempotencyKey("challenge-1"),
|
|
Email: common.Email("pilot@example.com"),
|
|
Code: "123456",
|
|
Locale: common.Locale("en"),
|
|
}
|
|
second := LoginCodeDeliveryCommand{
|
|
IdempotencyKey: common.IdempotencyKey("challenge-1"),
|
|
Email: common.Email("pilot@example.com"),
|
|
Code: "123456",
|
|
Locale: common.Locale("en"),
|
|
}
|
|
|
|
firstFingerprint, err := first.Fingerprint()
|
|
require.NoError(t, err)
|
|
secondFingerprint, err := second.Fingerprint()
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, firstFingerprint, secondFingerprint)
|
|
}
|
|
|
|
func TestLoginCodeDeliveryResponseValidate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require.NoError(t, LoginCodeDeliveryResponse{Outcome: LoginCodeDeliveryOutcomeSent}.Validate())
|
|
require.NoError(t, LoginCodeDeliveryResponse{Outcome: LoginCodeDeliveryOutcomeSuppressed}.Validate())
|
|
|
|
err := LoginCodeDeliveryResponse{Outcome: LoginCodeDeliveryOutcome("queued")}.Validate()
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, "unsupported")
|
|
}
|
|
|
|
func TestErrorResponseValidate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require.NoError(t, ErrorResponse{
|
|
Error: ErrorBody{
|
|
Code: ErrorCodeInvalidRequest,
|
|
Message: "field-specific validation detail",
|
|
},
|
|
}.Validate())
|
|
|
|
err := ErrorResponse{
|
|
Error: ErrorBody{
|
|
Code: " invalid_request ",
|
|
Message: "",
|
|
},
|
|
}.Validate()
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, "error code")
|
|
}
|