feat: authsession service
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
// Package userresolution defines the domain result returned by the user
|
||||
// resolution boundary before session creation.
|
||||
package userresolution
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"galaxy/authsession/internal/domain/common"
|
||||
)
|
||||
|
||||
// Kind identifies the coarse user-resolution result for one normalized e-mail.
|
||||
type Kind string
|
||||
|
||||
const (
|
||||
// KindExisting reports that the e-mail belongs to an existing user.
|
||||
KindExisting Kind = "existing"
|
||||
|
||||
// KindCreatable reports that the e-mail is free and user creation is
|
||||
// allowed.
|
||||
KindCreatable Kind = "creatable"
|
||||
|
||||
// KindBlocked reports that the e-mail or subject is blocked from login or
|
||||
// registration.
|
||||
KindBlocked Kind = "blocked"
|
||||
)
|
||||
|
||||
// IsKnown reports whether Kind is one of the user-resolution kinds supported
|
||||
// by the current domain model.
|
||||
func (k Kind) IsKnown() bool {
|
||||
switch k {
|
||||
case KindExisting, KindCreatable, KindBlocked:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// BlockReasonCode stores one machine-readable user-block reason.
|
||||
type BlockReasonCode string
|
||||
|
||||
// String returns BlockReasonCode as its stored code value.
|
||||
func (code BlockReasonCode) String() string {
|
||||
return string(code)
|
||||
}
|
||||
|
||||
// IsZero reports whether BlockReasonCode is empty.
|
||||
func (code BlockReasonCode) IsZero() bool {
|
||||
return strings.TrimSpace(string(code)) == ""
|
||||
}
|
||||
|
||||
// Validate reports whether BlockReasonCode is non-empty and normalized for
|
||||
// domain use.
|
||||
func (code BlockReasonCode) Validate() error {
|
||||
switch {
|
||||
case code.IsZero():
|
||||
return errors.New("block reason code must not be empty")
|
||||
case strings.TrimSpace(string(code)) != string(code):
|
||||
return errors.New("block reason code must not contain surrounding whitespace")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Result stores the coarse user-resolution outcome consumed by later auth
|
||||
// workflow stages.
|
||||
type Result struct {
|
||||
// Kind reports the coarse resolution outcome.
|
||||
Kind Kind
|
||||
|
||||
// UserID is set only when Kind is KindExisting.
|
||||
UserID common.UserID
|
||||
|
||||
// BlockReasonCode is set only when Kind is KindBlocked.
|
||||
BlockReasonCode BlockReasonCode
|
||||
}
|
||||
|
||||
// Validate reports whether Result satisfies the Stage-2 structural invariants.
|
||||
func (r Result) Validate() error {
|
||||
if !r.Kind.IsKnown() {
|
||||
return fmt.Errorf("user resolution kind %q is unsupported", r.Kind)
|
||||
}
|
||||
|
||||
switch r.Kind {
|
||||
case KindExisting:
|
||||
if err := r.UserID.Validate(); err != nil {
|
||||
return fmt.Errorf("user resolution user id: %w", err)
|
||||
}
|
||||
if !r.BlockReasonCode.IsZero() {
|
||||
return errors.New("existing user resolution must not contain block reason code")
|
||||
}
|
||||
case KindCreatable:
|
||||
if !r.UserID.IsZero() {
|
||||
return errors.New("creatable user resolution must not contain user id")
|
||||
}
|
||||
if !r.BlockReasonCode.IsZero() {
|
||||
return errors.New("creatable user resolution must not contain block reason code")
|
||||
}
|
||||
case KindBlocked:
|
||||
if !r.UserID.IsZero() {
|
||||
return errors.New("blocked user resolution must not contain user id")
|
||||
}
|
||||
if err := r.BlockReasonCode.Validate(); err != nil {
|
||||
return fmt.Errorf("user resolution block reason code: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package userresolution
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
"galaxy/authsession/internal/domain/common"
|
||||
)
|
||||
|
||||
func TestKindIsKnown(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value Kind
|
||||
want bool
|
||||
}{
|
||||
{name: "existing", value: KindExisting, want: true},
|
||||
{name: "creatable", value: KindCreatable, want: true},
|
||||
{name: "blocked", value: KindBlocked, want: true},
|
||||
{name: "unknown", value: Kind("unknown"), want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := tt.value.IsKnown(); got != tt.want {
|
||||
require.Failf(t, "test failed", "IsKnown() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value Result
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "existing valid",
|
||||
value: Result{
|
||||
Kind: KindExisting,
|
||||
UserID: common.UserID("user-123"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "creatable valid",
|
||||
value: Result{
|
||||
Kind: KindCreatable,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "blocked valid",
|
||||
value: Result{
|
||||
Kind: KindBlocked,
|
||||
BlockReasonCode: BlockReasonCode("policy_blocked"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing requires user id",
|
||||
value: Result{
|
||||
Kind: KindExisting,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "creatable rejects user id",
|
||||
value: Result{
|
||||
Kind: KindCreatable,
|
||||
UserID: common.UserID("user-123"),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "blocked requires reason",
|
||||
value: Result{
|
||||
Kind: KindBlocked,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "blocked rejects user id",
|
||||
value: Result{
|
||||
Kind: KindBlocked,
|
||||
UserID: common.UserID("user-123"),
|
||||
BlockReasonCode: BlockReasonCode("policy_blocked"),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := tt.value.Validate()
|
||||
if tt.wantErr && err == nil {
|
||||
require.FailNow(t, "Validate() returned nil error")
|
||||
}
|
||||
if !tt.wantErr && err != nil {
|
||||
require.Failf(t, "test failed", "Validate() returned error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user