init push
This commit is contained in:
967
internal/repository/mysql/contact_repo_test.go
Normal file
967
internal/repository/mysql/contact_repo_test.go
Normal file
@@ -0,0 +1,967 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"wucher/internal/domain/auth"
|
||||
"wucher/internal/domain/contact"
|
||||
"wucher/internal/shared/pkg/appctx"
|
||||
"wucher/internal/shared/pkg/uuidv7"
|
||||
)
|
||||
|
||||
func TestContactDetailSelectSQL_IncludesProfileAttachmentID(t *testing.T) {
|
||||
sql := contactDetailSelectSQL()
|
||||
if !strings.Contains(sql, "users.profile_attachment_id AS profile_attachment_id") {
|
||||
t.Fatalf("expected detail select to include users.profile_attachment_id alias")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactListSelectSQL_IncludesProfileAttachmentID(t *testing.T) {
|
||||
sql := contactListSelectSQL()
|
||||
if !strings.Contains(sql, "users.profile_attachment_id AS profile_attachment_id") {
|
||||
t.Fatalf("expected list select to include users.profile_attachment_id alias")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContactClearSSOEmailUnlinksMicrosoftIdentity(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
repo := NewContactRepository(db)
|
||||
|
||||
adminRoleID := uuidv7.MustBytes()
|
||||
pilotRoleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: adminRoleID, Code: "admin", Name: "Admin"}).Error; err != nil {
|
||||
t.Fatalf("create admin role: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.Role{ID: pilotRoleID, Code: auth.RoleCodePilot, Name: "Pilot"}).Error; err != nil {
|
||||
t.Fatalf("create pilot role: %v", err)
|
||||
}
|
||||
|
||||
adminUser := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "admin3@example.com",
|
||||
FirstName: "Admin",
|
||||
LastName: "User",
|
||||
Timezone: "UTC",
|
||||
RoleID: adminRoleID,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(adminUser).Error; err != nil {
|
||||
t.Fatalf("create admin user: %v", err)
|
||||
}
|
||||
ctx := appctx.WithUserID(context.Background(), adminUser.ID)
|
||||
|
||||
sso := "a@microsoft.com"
|
||||
target := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "target@example.com",
|
||||
SSOEmail: &sso,
|
||||
FirstName: "Target",
|
||||
LastName: "User",
|
||||
Timezone: "UTC",
|
||||
RoleID: pilotRoleID,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(target).Error; err != nil {
|
||||
t.Fatalf("create target user: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.UserIdentity{
|
||||
ID: uuidv7.MustBytes(),
|
||||
UserID: target.ID,
|
||||
Provider: "microsoft",
|
||||
ProviderSubject: "subject-a",
|
||||
Email: "a@microsoft.com",
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create identity: %v", err)
|
||||
}
|
||||
|
||||
empty := " "
|
||||
if err := repo.UpdateContact(ctx, contact.UpdateInput{
|
||||
UserID: target.ID,
|
||||
User: contact.UserPatch{
|
||||
SSOEmail: &empty,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("update contact clear sso email: %v", err)
|
||||
}
|
||||
|
||||
var refreshed auth.User
|
||||
if err := db.Where("id = ?", target.ID).Take(&refreshed).Error; err != nil {
|
||||
t.Fatalf("reload user: %v", err)
|
||||
}
|
||||
if refreshed.SSOEmail != nil {
|
||||
t.Fatalf("expected user sso_email NULL, got %v", *refreshed.SSOEmail)
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := db.Table("user_identities").
|
||||
Where("user_id = ? AND provider = ?", target.ID, "microsoft").
|
||||
Count(&count).Error; err != nil {
|
||||
t.Fatalf("count identities: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("expected microsoft identity unlinked, got %d rows", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryDeleteContactRemovesUserAndProfiles(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodePilot, Name: "Pilot"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
username := "pilot.delete"
|
||||
user := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "pilot-delete@example.com",
|
||||
Username: &username,
|
||||
FirstName: "Pilot",
|
||||
LastName: "Delete",
|
||||
Timezone: "Asia/Jakarta",
|
||||
RoleID: roleID,
|
||||
IsActive: true,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.PilotProfile{
|
||||
UserID: user.ID,
|
||||
PilotCategory: auth.PilotCategoryRegular,
|
||||
ShortName: "PD",
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create pilot profile: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.DoctorProfile{
|
||||
UserID: user.ID,
|
||||
ShortName: "Dr PD",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create extra profile: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.DeleteContact(context.Background(), user.ID); err != nil {
|
||||
t.Fatalf("delete contact: %v", err)
|
||||
}
|
||||
|
||||
var userCount int64
|
||||
if err := db.Table("users").Where("id = ?", user.ID).Count(&userCount).Error; err != nil {
|
||||
t.Fatalf("count users: %v", err)
|
||||
}
|
||||
if userCount != 0 {
|
||||
t.Fatalf("expected user deleted, got %d rows", userCount)
|
||||
}
|
||||
|
||||
for _, table := range contactProfileTables {
|
||||
var count int64
|
||||
if err := db.Table(table).Where("user_id = ?", user.ID).Count(&count).Error; err != nil {
|
||||
t.Fatalf("count %s: %v", table, err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("expected %s deleted, got %d rows", table, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryDeleteContactBlockedWhenUsedByBase(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodePilot, Name: "Pilot"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
user := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "pilot-base-ref@example.com",
|
||||
FirstName: "Pilot",
|
||||
LastName: "Ref",
|
||||
Timezone: "UTC",
|
||||
RoleID: roleID,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
|
||||
if err := db.Exec(`CREATE TABLE IF NOT EXISTS base_contact_roles (
|
||||
id BLOB PRIMARY KEY,
|
||||
base_id BLOB NOT NULL,
|
||||
contact_id BLOB NOT NULL,
|
||||
role_code TEXT NOT NULL
|
||||
)`).Error; err != nil {
|
||||
t.Fatalf("create base_contact_roles: %v", err)
|
||||
}
|
||||
repo := NewContactRepository(db)
|
||||
if err := db.Table("base_contact_roles").Create(map[string]any{
|
||||
"id": uuidv7.MustBytes(),
|
||||
"base_id": uuidv7.MustBytes(),
|
||||
"contact_id": user.ID,
|
||||
"role_code": "responsible_flight_rescuer",
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed base_contact_roles: %v", err)
|
||||
}
|
||||
|
||||
err := repo.DeleteContact(context.Background(), user.ID)
|
||||
if err == nil {
|
||||
t.Fatalf("expected delete to be blocked")
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "bases") {
|
||||
t.Fatalf("expected bases dependency in error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContactSameValuesDoesNotReturnNotFound(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodePilot, Name: "Pilot"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
issuedAt := time.Date(2004, 10, 3, 0, 0, 0, 0, time.UTC)
|
||||
expiredAt := time.Date(2029, 3, 27, 0, 0, 0, 0, time.UTC)
|
||||
weight := 0.0
|
||||
username := "pilot_test_2"
|
||||
user := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "pilot2@test.com",
|
||||
Username: &username,
|
||||
FirstName: "a",
|
||||
LastName: "a",
|
||||
MobilePhone: "2",
|
||||
Timezone: "Asia/Jakarta",
|
||||
RoleID: roleID,
|
||||
IsActive: true,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.PilotProfile{
|
||||
UserID: user.ID,
|
||||
PilotCategory: auth.PilotCategoryRegular,
|
||||
ShortName: "a",
|
||||
LicenseNo: "a",
|
||||
LicenseIssuedAt: &issuedAt,
|
||||
LicenseExpiredAt: &expiredAt,
|
||||
TechnicianLicenseNo: "",
|
||||
HasIFRQualification: true,
|
||||
IsChiefPilot: false,
|
||||
IsDUL: true,
|
||||
TotalFlightMinutes: 2,
|
||||
ResponsiblePilotMinutes: 2,
|
||||
SecondPilotMinutes: 2,
|
||||
DoubleCommandMinutes: 2,
|
||||
FlightInstructorMinutes: 0,
|
||||
NightFlightMinutes: 2,
|
||||
IFRFlightMinutes: 2,
|
||||
Location: "string",
|
||||
Postcode: "2",
|
||||
StreetLine: "2",
|
||||
WeightKG: &weight,
|
||||
PhotoFileID: "a",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create pilot profile: %v", err)
|
||||
}
|
||||
|
||||
email := "pilot2@test.com"
|
||||
firstName := "a"
|
||||
lastName := "a"
|
||||
mobilePhone := "2"
|
||||
timezone := "Asia/Jakarta"
|
||||
isActive := true
|
||||
pilotCategory := auth.PilotCategoryRegular
|
||||
shortName := "a"
|
||||
licenseNo := "a"
|
||||
technicianLicenseNo := ""
|
||||
hasIFRQualification := true
|
||||
isChiefPilot := false
|
||||
isDUL := true
|
||||
totalFlightMinutes := 2
|
||||
responsiblePilotMinutes := 2
|
||||
secondPilotMinutes := 2
|
||||
doubleCommandMinutes := 2
|
||||
flightInstructorMinutes := 0
|
||||
nightFlightMinutes := 2
|
||||
ifrFlightMinutes := 2
|
||||
location := "string"
|
||||
postcode := "2"
|
||||
streetLine := "2"
|
||||
photoFileID := "a"
|
||||
|
||||
err := repo.UpdateContact(context.Background(), contact.UpdateInput{
|
||||
UserID: user.ID,
|
||||
User: contact.UserPatch{
|
||||
Email: &email,
|
||||
Username: &username,
|
||||
FirstName: &firstName,
|
||||
LastName: &lastName,
|
||||
MobilePhone: &mobilePhone,
|
||||
Timezone: &timezone,
|
||||
IsActive: &isActive,
|
||||
},
|
||||
Profile: contact.ProfilePatch{
|
||||
PilotCategory: &pilotCategory,
|
||||
ShortName: &shortName,
|
||||
LicenseNo: &licenseNo,
|
||||
LicenseIssuedAt: &issuedAt,
|
||||
LicenseExpiredAt: &expiredAt,
|
||||
TechnicianLicenseNo: &technicianLicenseNo,
|
||||
HasIFRQualification: &hasIFRQualification,
|
||||
IsChiefPilot: &isChiefPilot,
|
||||
IsDUL: &isDUL,
|
||||
TotalFlightMinutes: &totalFlightMinutes,
|
||||
ResponsiblePilotMinutes: &responsiblePilotMinutes,
|
||||
SecondPilotMinutes: &secondPilotMinutes,
|
||||
DoubleCommandMinutes: &doubleCommandMinutes,
|
||||
FlightInstructorMinutes: &flightInstructorMinutes,
|
||||
NightFlightMinutes: &nightFlightMinutes,
|
||||
IFRFlightMinutes: &ifrFlightMinutes,
|
||||
Location: &location,
|
||||
Postcode: &postcode,
|
||||
StreetLine: &streetLine,
|
||||
WeightKG: &weight,
|
||||
PhotoFileID: &photoFileID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("update contact with same values: %v", err)
|
||||
}
|
||||
|
||||
var pilotProfileCount int64
|
||||
if err := db.Table("pilot_profiles").Where("user_id = ?", user.ID).Count(&pilotProfileCount).Error; err != nil {
|
||||
t.Fatalf("count pilot profiles: %v", err)
|
||||
}
|
||||
if pilotProfileCount != 1 {
|
||||
t.Fatalf("expected single pilot profile row after update, got %d", pilotProfileCount)
|
||||
}
|
||||
|
||||
got, err := repo.GetContactByID(context.Background(), user.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get contact after update: %v", err)
|
||||
}
|
||||
if got == nil || got.RoleCode != auth.RoleCodePilot {
|
||||
t.Fatalf("expected pilot contact after update, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContactClearsAirRescuerProfileStringFields(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodeAirRescuer, Name: "Air Rescuer"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
user := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "air-rescuer@test.com",
|
||||
FirstName: "Air",
|
||||
LastName: "Rescuer",
|
||||
Timezone: "UTC",
|
||||
RoleID: roleID,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.AirRescuerProfile{
|
||||
UserID: user.ID,
|
||||
ShortName: "CINDY",
|
||||
Location: "12",
|
||||
Postcode: "12",
|
||||
StreetLine: "12",
|
||||
Note: "12",
|
||||
ResponsibleFlightRescuer: false,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create air rescuer profile: %v", err)
|
||||
}
|
||||
|
||||
empty := ""
|
||||
if err := repo.UpdateContact(context.Background(), contact.UpdateInput{
|
||||
UserID: user.ID,
|
||||
Profile: contact.ProfilePatch{
|
||||
ShortName: &empty,
|
||||
Location: &empty,
|
||||
Postcode: &empty,
|
||||
StreetLine: &empty,
|
||||
Note: &empty,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("update contact clear profile strings: %v", err)
|
||||
}
|
||||
|
||||
var profile auth.AirRescuerProfile
|
||||
if err := db.Where("user_id = ?", user.ID).Take(&profile).Error; err != nil {
|
||||
t.Fatalf("reload air rescuer profile: %v", err)
|
||||
}
|
||||
if profile.ShortName != "" || profile.Location != "" || profile.Postcode != "" || profile.StreetLine != "" || profile.Note != "" {
|
||||
t.Fatalf("expected cleared string fields, got %#v", profile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryCreateContactRejectsDuplicateUsername(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodePilot, Name: "Pilot"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
username := "pilot.duplicate"
|
||||
first, err := repo.CreateContact(context.Background(), contact.CreateInput{
|
||||
RoleID: roleID,
|
||||
User: contact.UserPatch{
|
||||
Email: strPtrContactRepo("first@example.com"),
|
||||
Username: &username,
|
||||
FirstName: strPtrContactRepo("First"),
|
||||
LastName: strPtrContactRepo("User"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create first contact: %v", err)
|
||||
}
|
||||
if len(first) != 16 {
|
||||
t.Fatalf("expected first user id, got len=%d", len(first))
|
||||
}
|
||||
|
||||
secondUsername := "pilot.duplicate"
|
||||
_, err = repo.CreateContact(context.Background(), contact.CreateInput{
|
||||
RoleID: roleID,
|
||||
User: contact.UserPatch{
|
||||
Email: strPtrContactRepo("second@example.com"),
|
||||
Username: &secondUsername,
|
||||
FirstName: strPtrContactRepo("Second"),
|
||||
LastName: strPtrContactRepo("User"),
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, contact.ErrUsernameAlreadyExists) {
|
||||
t.Fatalf("expected duplicate username error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContactRejectsDuplicateUsername(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodePilot, Name: "Pilot"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
firstUsername := "pilot.update.1"
|
||||
firstID, err := repo.CreateContact(context.Background(), contact.CreateInput{
|
||||
RoleID: roleID,
|
||||
User: contact.UserPatch{
|
||||
Email: strPtrContactRepo("first-update@example.com"),
|
||||
Username: &firstUsername,
|
||||
FirstName: strPtrContactRepo("First"),
|
||||
LastName: strPtrContactRepo("User"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create first contact: %v", err)
|
||||
}
|
||||
|
||||
secondUsername := "pilot.update.2"
|
||||
secondID, err := repo.CreateContact(context.Background(), contact.CreateInput{
|
||||
RoleID: roleID,
|
||||
User: contact.UserPatch{
|
||||
Email: strPtrContactRepo("second-update@example.com"),
|
||||
Username: &secondUsername,
|
||||
FirstName: strPtrContactRepo("Second"),
|
||||
LastName: strPtrContactRepo("User"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create second contact: %v", err)
|
||||
}
|
||||
|
||||
newUsername := firstUsername
|
||||
err = repo.UpdateContact(context.Background(), contact.UpdateInput{
|
||||
UserID: secondID,
|
||||
User: contact.UserPatch{
|
||||
Username: &newUsername,
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, contact.ErrUsernameAlreadyExists) {
|
||||
t.Fatalf("expected duplicate username error on update, got %v", err)
|
||||
}
|
||||
|
||||
got, err := repo.GetContactByID(context.Background(), firstID)
|
||||
if err != nil {
|
||||
t.Fatalf("get first contact: %v", err)
|
||||
}
|
||||
if got == nil || got.Username != firstUsername {
|
||||
t.Fatalf("unexpected first contact after duplicate update: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryListContactsDefaultSortByActiveAndSortKey(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodeStaff, Name: "Staff"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
seedContactStaffUser(t, db, roleID, "Gamma", nil, true, "g-note")
|
||||
seedContactStaffUser(t, db, roleID, "Beta", intPtrContactRepo(0), true, "b-note")
|
||||
seedContactStaffUser(t, db, roleID, "Charlie", intPtrContactRepo(2), true, "c-note")
|
||||
seedContactStaffUser(t, db, roleID, "Alpha", intPtrContactRepo(1), true, "a-note")
|
||||
seedContactStaffUser(t, db, roleID, "Zulu", nil, false, "z-note")
|
||||
seedContactStaffUser(t, db, roleID, "Bravo", intPtrContactRepo(9), false, "br-note")
|
||||
|
||||
rows, total, err := repo.ListContacts(context.Background(), contact.ListFilter{})
|
||||
if err != nil {
|
||||
t.Fatalf("list contacts: %v", err)
|
||||
}
|
||||
if total != 6 || len(rows) != 6 {
|
||||
t.Fatalf("unexpected total/len total=%d len=%d", total, len(rows))
|
||||
}
|
||||
|
||||
got := []string{rows[0].FirstName, rows[1].FirstName, rows[2].FirstName, rows[3].FirstName, rows[4].FirstName, rows[5].FirstName}
|
||||
want := []string{"Beta", "Alpha", "Charlie", "Gamma", "Bravo", "Zulu"}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("unexpected order got=%v want=%v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryListContactsSearchByNote(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodeStaff, Name: "Staff"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
seedContactStaffUser(t, db, roleID, "Alpha", nil, true, "dispatcher")
|
||||
seedContactStaffUser(t, db, roleID, "Bravo", nil, true, "maintenance")
|
||||
|
||||
rows, total, err := repo.ListContacts(context.Background(), contact.ListFilter{Search: "dispatch"})
|
||||
if err != nil {
|
||||
t.Fatalf("list contacts: %v", err)
|
||||
}
|
||||
if total != 1 || len(rows) != 1 {
|
||||
t.Fatalf("unexpected total/len total=%d len=%d", total, len(rows))
|
||||
}
|
||||
if rows[0].FirstName != "Alpha" || rows[0].Note != "dispatcher" {
|
||||
t.Fatalf("unexpected row %+v", rows[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryListContactsIncludesPilotProfileFields(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodePilot, Name: "Pilot"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
userID := uuidv7.MustBytes()
|
||||
username := "pilot_list_detail"
|
||||
issuedAt := time.Date(2026, 4, 20, 0, 0, 0, 0, time.UTC)
|
||||
expiredAt := time.Date(2029, 4, 20, 0, 0, 0, 0, time.UTC)
|
||||
user := &auth.User{
|
||||
ID: userID,
|
||||
Email: "pilot.list@example.com",
|
||||
Username: &username,
|
||||
FirstName: "Pilot",
|
||||
LastName: "List",
|
||||
Timezone: "UTC",
|
||||
RoleID: roleID,
|
||||
IsActive: true,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.PilotProfile{
|
||||
UserID: userID,
|
||||
PilotCategory: auth.PilotCategoryFreelance,
|
||||
LicenseIssuedAt: &issuedAt,
|
||||
LicenseExpiredAt: &expiredAt,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create pilot profile: %v", err)
|
||||
}
|
||||
|
||||
rows, total, err := repo.ListContacts(context.Background(), contact.ListFilter{Role: auth.RoleCodePilot})
|
||||
if err != nil {
|
||||
t.Fatalf("list contacts: %v", err)
|
||||
}
|
||||
if total != 1 || len(rows) != 1 {
|
||||
t.Fatalf("unexpected total/len total=%d len=%d", total, len(rows))
|
||||
}
|
||||
if rows[0].PilotLicenseIssuedAt == nil || !rows[0].PilotLicenseIssuedAt.Equal(issuedAt) {
|
||||
t.Fatalf("expected pilot license_issued_at returned, got %#v", rows[0].PilotLicenseIssuedAt)
|
||||
}
|
||||
if rows[0].PilotLicenseExpiredAt == nil || !rows[0].PilotLicenseExpiredAt.Equal(expiredAt) {
|
||||
t.Fatalf("expected pilot license_expired_at returned, got %#v", rows[0].PilotLicenseExpiredAt)
|
||||
}
|
||||
if rows[0].PilotCategory != auth.PilotCategoryFreelance {
|
||||
t.Fatalf("expected pilot category returned, got %q", rows[0].PilotCategory)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContactRoleAssignsRoleWhenCurrentRoleIsEmpty(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
staffRoleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: staffRoleID, Code: auth.RoleCodeStaff, Name: "Staff"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
userID := uuidv7.MustBytes()
|
||||
user := &auth.User{
|
||||
ID: userID,
|
||||
Email: "no-role@example.com",
|
||||
FirstName: "No",
|
||||
LastName: "Role",
|
||||
Timezone: "UTC",
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.UpdateContactRole(context.Background(), userID, auth.RoleCodeStaff); err != nil {
|
||||
t.Fatalf("update contact role: %v", err)
|
||||
}
|
||||
|
||||
var refreshed auth.User
|
||||
if err := db.Where("id = ?", userID).Take(&refreshed).Error; err != nil {
|
||||
t.Fatalf("reload user: %v", err)
|
||||
}
|
||||
if len(refreshed.RoleID) != 16 || string(refreshed.RoleID) != string(staffRoleID) {
|
||||
t.Fatalf("expected role_id updated to staff, got %x", refreshed.RoleID)
|
||||
}
|
||||
|
||||
roleIDs, err := listUserRoleIDsTx(db, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("list user roles: %v", err)
|
||||
}
|
||||
if len(roleIDs) != 1 || string(roleIDs[0]) != string(staffRoleID) {
|
||||
t.Fatalf("expected user_roles to contain only staff role, got %d rows", len(roleIDs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContact_IsAdminTruePersistsAdminRoleWhenUpdatingRoles(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
adminRoleID := uuidv7.MustBytes()
|
||||
pilotRoleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: adminRoleID, Code: "admin", Name: "Admin"}).Error; err != nil {
|
||||
t.Fatalf("create admin role: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.Role{ID: pilotRoleID, Code: auth.RoleCodePilot, Name: "Pilot"}).Error; err != nil {
|
||||
t.Fatalf("create pilot role: %v", err)
|
||||
}
|
||||
|
||||
user := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "admin-sync@example.com",
|
||||
FirstName: "Admin",
|
||||
LastName: "Sync",
|
||||
Timezone: "UTC",
|
||||
RoleID: pilotRoleID,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
if err := replaceUserRolesTx(context.Background(), db, user.ID, pilotRoleID, [][]byte{pilotRoleID}); err != nil {
|
||||
t.Fatalf("seed user roles: %v", err)
|
||||
}
|
||||
|
||||
trueVal := true
|
||||
if err := repo.UpdateContact(context.Background(), contact.UpdateInput{
|
||||
UserID: user.ID,
|
||||
UpdateRoles: true,
|
||||
RoleID: pilotRoleID,
|
||||
RoleIDs: [][]byte{pilotRoleID},
|
||||
User: contact.UserPatch{
|
||||
IsAdmin: &trueVal,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("update contact: %v", err)
|
||||
}
|
||||
|
||||
roleIDs, err := listUserRoleIDsTx(db, user.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("list user roles: %v", err)
|
||||
}
|
||||
if len(roleIDs) != 2 {
|
||||
t.Fatalf("expected 2 roles (pilot + admin), got %d", len(roleIDs))
|
||||
}
|
||||
foundAdmin := false
|
||||
for i := range roleIDs {
|
||||
if string(roleIDs[i]) == string(adminRoleID) {
|
||||
foundAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundAdmin {
|
||||
t.Fatalf("expected admin role assigned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContact_StaffCanBeAdmin(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
adminRoleID := uuidv7.MustBytes()
|
||||
staffRoleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: adminRoleID, Code: "admin", Name: "Admin"}).Error; err != nil {
|
||||
t.Fatalf("create admin role: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.Role{ID: staffRoleID, Code: auth.RoleCodeStaff, Name: "Staff"}).Error; err != nil {
|
||||
t.Fatalf("create staff role: %v", err)
|
||||
}
|
||||
|
||||
user := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "staff-admin@example.com",
|
||||
FirstName: "Staff",
|
||||
LastName: "Admin",
|
||||
Timezone: "UTC",
|
||||
RoleID: staffRoleID,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
if err := replaceUserRolesTx(context.Background(), db, user.ID, staffRoleID, [][]byte{staffRoleID}); err != nil {
|
||||
t.Fatalf("seed user roles: %v", err)
|
||||
}
|
||||
|
||||
trueVal := true
|
||||
if err := repo.UpdateContact(context.Background(), contact.UpdateInput{
|
||||
UserID: user.ID,
|
||||
User: contact.UserPatch{
|
||||
IsAdmin: &trueVal,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("update contact: %v", err)
|
||||
}
|
||||
|
||||
roleIDs, err := listUserRoleIDsTx(db, user.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("list user roles: %v", err)
|
||||
}
|
||||
if len(roleIDs) != 2 {
|
||||
t.Fatalf("expected 2 roles (staff + admin), got %d", len(roleIDs))
|
||||
}
|
||||
foundAdmin := false
|
||||
for i := range roleIDs {
|
||||
if string(roleIDs[i]) == string(adminRoleID) {
|
||||
foundAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundAdmin {
|
||||
t.Fatalf("expected admin role assigned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContactNilSortKeyDoesNotClearWithoutSetFlag(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodeStaff, Name: "Staff"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
sortKey := 7
|
||||
user := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "sortkey-clear@example.com",
|
||||
FirstName: "Sort",
|
||||
LastName: "Key",
|
||||
Timezone: "UTC",
|
||||
RoleID: roleID,
|
||||
SortKey: &sortKey,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
|
||||
firstName := "Sorted"
|
||||
if err := repo.UpdateContact(context.Background(), contact.UpdateInput{
|
||||
UserID: user.ID,
|
||||
User: contact.UserPatch{
|
||||
FirstName: &firstName,
|
||||
SortKey: nil,
|
||||
SortKeySet: false,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("update contact: %v", err)
|
||||
}
|
||||
|
||||
var refreshed auth.User
|
||||
if err := db.Where("id = ?", user.ID).Take(&refreshed).Error; err != nil {
|
||||
t.Fatalf("reload user: %v", err)
|
||||
}
|
||||
if refreshed.SortKey == nil || *refreshed.SortKey != 7 {
|
||||
t.Fatalf("expected sortkey to remain 7, got %#v", refreshed.SortKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactRepositoryUpdateContactNullSortKeyClearsValue(t *testing.T) {
|
||||
db := openDBTestSQLite(t)
|
||||
if err := AutoMigrate(db); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
repo := NewContactRepository(db)
|
||||
roleID := uuidv7.MustBytes()
|
||||
if err := db.Create(&auth.Role{ID: roleID, Code: auth.RoleCodeStaff, Name: "Staff"}).Error; err != nil {
|
||||
t.Fatalf("create role: %v", err)
|
||||
}
|
||||
|
||||
sortKey := 7
|
||||
user := &auth.User{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Email: "sortkey-null@example.com",
|
||||
FirstName: "Sort",
|
||||
LastName: "Key",
|
||||
Timezone: "UTC",
|
||||
RoleID: roleID,
|
||||
SortKey: &sortKey,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.UpdateContact(context.Background(), contact.UpdateInput{
|
||||
UserID: user.ID,
|
||||
User: contact.UserPatch{
|
||||
SortKey: nil,
|
||||
SortKeySet: true,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("update contact: %v", err)
|
||||
}
|
||||
|
||||
var refreshed auth.User
|
||||
if err := db.Where("id = ?", user.ID).Take(&refreshed).Error; err != nil {
|
||||
t.Fatalf("reload user: %v", err)
|
||||
}
|
||||
if refreshed.SortKey != nil {
|
||||
t.Fatalf("expected sortkey NULL, got %d", *refreshed.SortKey)
|
||||
}
|
||||
}
|
||||
|
||||
func seedContactStaffUser(t *testing.T, db *gorm.DB, roleID []byte, firstName string, sortKey *int, isActive bool, note string) []byte {
|
||||
t.Helper()
|
||||
|
||||
userID := uuidv7.MustBytes()
|
||||
username := fmt.Sprintf("%s_user_%d", firstName, time.Now().UnixNano())
|
||||
user := &auth.User{
|
||||
ID: userID,
|
||||
Email: fmt.Sprintf("%s@example.com", username),
|
||||
Username: &username,
|
||||
FirstName: firstName,
|
||||
LastName: "Contact",
|
||||
Timezone: "UTC",
|
||||
RoleID: roleID,
|
||||
SortKey: sortKey,
|
||||
IsActive: isActive,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
if err := db.Model(&auth.User{}).Where("id = ?", userID).UpdateColumn("is_active", isActive).Error; err != nil {
|
||||
t.Fatalf("sync user is_active: %v", err)
|
||||
}
|
||||
if err := db.Create(&auth.StaffProfile{
|
||||
UserID: userID,
|
||||
ShortName: firstName,
|
||||
Note: note,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create staff profile: %v", err)
|
||||
}
|
||||
|
||||
return userID
|
||||
}
|
||||
|
||||
func intPtrContactRepo(v int) *int { return &v }
|
||||
|
||||
func strPtrContactRepo(v string) *string { return &v }
|
||||
Reference in New Issue
Block a user