219 lines
5.7 KiB
Go
219 lines
5.7 KiB
Go
package models
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestAccountRole_Valid(t *testing.T) {
|
|
valid := []AccountRole{RoleMember, RoleOwner, RoleAgent, RoleAdmin, ""}
|
|
invalid := []AccountRole{"superuser", "MEMBER", "member ", "0"}
|
|
|
|
for _, role := range valid {
|
|
if got := role.Valid(); role != "" && !got {
|
|
t.Errorf("AccountRole(%q).Valid() = false, want true", role)
|
|
}
|
|
}
|
|
for _, role := range invalid {
|
|
if role.Valid() {
|
|
t.Errorf("AccountRole(%q).Valid() = true, want false", role)
|
|
}
|
|
}
|
|
if !RoleMember.Valid() {
|
|
t.Error("RoleMember.Valid() = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestAccount_SetPassword(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
password string
|
|
wantErr error
|
|
}{
|
|
{name: "合法密碼", password: "s3cret!pass"},
|
|
{name: "剛好 8 字元", password: "12345678"},
|
|
{name: "太短", password: "1234567", wantErr: ErrPasswordTooShort},
|
|
{name: "空白", password: "", wantErr: ErrPasswordTooShort},
|
|
{name: "太長", password: strings.Repeat("a", MaxPasswordLength+1), wantErr: ErrPasswordTooLong},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
acct := &Account{}
|
|
err := acct.SetPassword(tt.password)
|
|
if !errors.Is(err, tt.wantErr) {
|
|
t.Fatalf("SetPassword() error = %v, want %v", err, tt.wantErr)
|
|
}
|
|
if tt.wantErr == nil {
|
|
if acct.PasswordHash == "" {
|
|
t.Fatal("SetPassword() 後 PasswordHash 為空")
|
|
}
|
|
if acct.PasswordHash == tt.password {
|
|
t.Fatal("PasswordHash 不應儲存明文密碼")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAccount_VerifyPassword(t *testing.T) {
|
|
acct := &Account{}
|
|
if err := acct.SetPassword("s3cret!pass"); err != nil {
|
|
t.Fatalf("SetPassword() error = %v", err)
|
|
}
|
|
|
|
if !acct.VerifyPassword("s3cret!pass") {
|
|
t.Error("VerifyPassword(正確密碼) = false, want true")
|
|
}
|
|
if acct.VerifyPassword("wrong-pass") {
|
|
t.Error("VerifyPassword(錯誤密碼) = true, want false")
|
|
}
|
|
if acct.VerifyPassword("") {
|
|
t.Error("VerifyPassword(空字串) = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestAccount_VerifyPassword_每次雜湊不同(t *testing.T) {
|
|
a1, a2 := &Account{}, &Account{}
|
|
if err := a1.SetPassword("same-password"); err != nil {
|
|
t.Fatalf("a1.SetPassword() error = %v", err)
|
|
}
|
|
if err := a2.SetPassword("same-password"); err != nil {
|
|
t.Fatalf("a2.SetPassword() error = %v", err)
|
|
}
|
|
if a1.PasswordHash == a2.PasswordHash {
|
|
t.Error("相同密碼的兩次雜湊應不同(bcrypt 應加鹽)")
|
|
}
|
|
if !a1.VerifyPassword("same-password") || !a2.VerifyPassword("same-password") {
|
|
t.Error("兩個帳號都應能以原始密碼通過驗證")
|
|
}
|
|
}
|
|
|
|
func TestAccount_Validate(t *testing.T) {
|
|
valid := func() *Account {
|
|
acct := &Account{Email: "dan@example.com", Name: "Dan"}
|
|
if err := acct.SetPassword("s3cret!pass"); err != nil {
|
|
t.Fatalf("SetPassword() error = %v", err)
|
|
}
|
|
return acct
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
mutate func(*Account)
|
|
wantErr error
|
|
}{
|
|
{name: "合法帳號", mutate: func(*Account) {}},
|
|
{
|
|
name: "缺 email",
|
|
mutate: func(a *Account) { a.Email = "" },
|
|
wantErr: ErrEmailRequired,
|
|
},
|
|
{
|
|
name: "email 格式錯誤",
|
|
mutate: func(a *Account) { a.Email = "not-an-email" },
|
|
wantErr: ErrEmailInvalid,
|
|
},
|
|
{
|
|
name: "email 過長",
|
|
mutate: func(a *Account) { a.Email = strings.Repeat("a", 250) + "@example.com" },
|
|
wantErr: ErrEmailInvalid,
|
|
},
|
|
{
|
|
name: "缺 name",
|
|
mutate: func(a *Account) { a.Name = "" },
|
|
wantErr: ErrNameRequired,
|
|
},
|
|
{
|
|
name: "name 過長",
|
|
mutate: func(a *Account) { a.Name = strings.Repeat("名", 101) },
|
|
wantErr: ErrNameTooLong,
|
|
},
|
|
{
|
|
name: "role 不合法",
|
|
mutate: func(a *Account) { a.Role = "hacker" },
|
|
wantErr: ErrRoleInvalid,
|
|
},
|
|
{
|
|
name: "phone 過長",
|
|
mutate: func(a *Account) { a.Phone = strings.Repeat("0", 31) },
|
|
wantErr: ErrPhoneTooLong,
|
|
},
|
|
{
|
|
name: "avatar_url 過長",
|
|
mutate: func(a *Account) { a.AvatarURL = strings.Repeat("x", 513) },
|
|
wantErr: ErrAvatarURLTooLong,
|
|
},
|
|
{
|
|
name: "未設定密碼",
|
|
mutate: func(a *Account) { a.PasswordHash = "" },
|
|
wantErr: ErrPasswordRequired,
|
|
},
|
|
{
|
|
name: "所有合法角色可通過",
|
|
mutate: func(a *Account) {
|
|
a.Role = RoleAdmin
|
|
a.Phone = "0912345678"
|
|
a.AvatarURL = "https://example.com/a.png"
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
acct := valid()
|
|
tt.mutate(acct)
|
|
err := acct.Validate()
|
|
if !errors.Is(err, tt.wantErr) {
|
|
t.Fatalf("Validate() error = %v, want %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAccount_Normalize(t *testing.T) {
|
|
acct := &Account{
|
|
Email: " Dan@Example.COM ",
|
|
Name: " 陳大同 ",
|
|
Phone: " 0912345678 ",
|
|
AvatarURL: " https://example.com/a.png ",
|
|
}
|
|
acct.Normalize()
|
|
|
|
if acct.Email != "dan@example.com" {
|
|
t.Errorf("Email = %q, want %q", acct.Email, "dan@example.com")
|
|
}
|
|
if acct.Name != "陳大同" {
|
|
t.Errorf("Name = %q, want %q", acct.Name, "陳大同")
|
|
}
|
|
if acct.Phone != "0912345678" {
|
|
t.Errorf("Phone = %q, want %q", acct.Phone, "0912345678")
|
|
}
|
|
if acct.AvatarURL != "https://example.com/a.png" {
|
|
t.Errorf("AvatarURL = %q, want %q", acct.AvatarURL, "https://example.com/a.png")
|
|
}
|
|
if acct.Role != RoleMember {
|
|
t.Errorf("空角色應預設為 RoleMember, got %q", acct.Role)
|
|
}
|
|
}
|
|
|
|
func TestAccount_Normalize_不覆寫已設角色(t *testing.T) {
|
|
acct := &Account{Role: RoleAgent}
|
|
acct.Normalize()
|
|
if acct.Role != RoleAgent {
|
|
t.Errorf("Role = %q, want %q", acct.Role, RoleAgent)
|
|
}
|
|
}
|
|
|
|
func TestAccount_IsAdmin(t *testing.T) {
|
|
admin := &Account{Role: RoleAdmin}
|
|
member := &Account{Role: RoleMember}
|
|
if !admin.IsAdmin() {
|
|
t.Error("admin.IsAdmin() = false, want true")
|
|
}
|
|
if member.IsAdmin() {
|
|
t.Error("member.IsAdmin() = true, want false")
|
|
}
|
|
}
|