162 lines
4.9 KiB
Go
162 lines
4.9 KiB
Go
package system
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type securityUpdateRepo struct {
|
|
current *SecurityConfig
|
|
backfillErr error
|
|
callOrder []string
|
|
backfillAt time.Time
|
|
persistedCopy *SecurityConfig
|
|
}
|
|
|
|
type bootstrapSecurityRepo struct {
|
|
initialized bool
|
|
calls int
|
|
}
|
|
|
|
func (r *bootstrapSecurityRepo) SecurityConfig(context.Context) (*SecurityConfig, error) {
|
|
r.calls++
|
|
if !r.initialized {
|
|
return nil, ErrDatabaseNotInitialized
|
|
}
|
|
return &SecurityConfig{ID: 1, LimitCount: 42}, nil
|
|
}
|
|
|
|
func (*bootstrapSecurityRepo) SaveSecurityConfig(context.Context, *SecurityConfig) error {
|
|
return nil
|
|
}
|
|
|
|
func (*bootstrapSecurityRepo) BackfillPasswordUpdatedAt(context.Context, time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (r *securityUpdateRepo) SecurityConfig(context.Context) (*SecurityConfig, error) {
|
|
r.callOrder = append(r.callOrder, "get")
|
|
copy := *r.current
|
|
return ©, nil
|
|
}
|
|
|
|
func (r *securityUpdateRepo) SaveSecurityConfig(_ context.Context, value *SecurityConfig) error {
|
|
r.callOrder = append(r.callOrder, "save")
|
|
value.UpdatedAt = value.UpdatedAt.Add(time.Second)
|
|
copy := *value
|
|
r.persistedCopy = ©
|
|
return nil
|
|
}
|
|
|
|
func (r *securityUpdateRepo) BackfillPasswordUpdatedAt(_ context.Context, at time.Time) error {
|
|
r.callOrder = append(r.callOrder, "backfill")
|
|
r.backfillAt = at
|
|
return r.backfillErr
|
|
}
|
|
|
|
func TestUpdateSecurityKeepsSavedConfigWhenPasswordBackfillFails(t *testing.T) {
|
|
createdAt := time.Date(2026, time.August, 16, 12, 0, 0, 0, time.Local)
|
|
backfillErr := errors.New("backfill failed")
|
|
repo := &securityUpdateRepo{
|
|
current: &SecurityConfig{ID: 1, CreatedAt: createdAt, UpdatedAt: createdAt, PwdExpireEnable: false},
|
|
backfillErr: backfillErr,
|
|
}
|
|
uc := NewSecurityUsecase(repo, nil, nil, nil)
|
|
next := &SecurityConfig{ID: 99, PwdExpireEnable: true, PwdExpireDays: 30}
|
|
|
|
err := uc.UpdateSecurity(context.Background(), next)
|
|
if !errors.Is(err, backfillErr) {
|
|
t.Fatalf("UpdateSecurity() error = %v, want %v", err, backfillErr)
|
|
}
|
|
if got := repo.callOrder; len(got) != 3 || got[0] != "get" || got[1] != "save" || got[2] != "backfill" {
|
|
t.Fatalf("call order = %v, want [get save backfill]", got)
|
|
}
|
|
if repo.persistedCopy == nil || repo.persistedCopy.ID != 1 || !repo.persistedCopy.PwdExpireEnable {
|
|
t.Fatalf("persisted config = %+v", repo.persistedCopy)
|
|
}
|
|
if repo.backfillAt.IsZero() {
|
|
t.Fatal("password timestamp backfill was not attempted")
|
|
}
|
|
cached, currentErr := uc.Current(context.Background())
|
|
if currentErr != nil {
|
|
t.Fatalf("Current() error = %v", currentErr)
|
|
}
|
|
if !cached.PwdExpireEnable || cached.ID != 1 {
|
|
t.Fatalf("effective config = %+v, want newly saved config", cached)
|
|
}
|
|
if got := repo.callOrder; len(got) != 3 {
|
|
t.Fatalf("Current() unexpectedly reloaded repository: %v", got)
|
|
}
|
|
}
|
|
|
|
func TestUpdateSecurityDoesNotBackfillWithoutDisabledToEnabledTransition(t *testing.T) {
|
|
repo := &securityUpdateRepo{current: &SecurityConfig{ID: 1, PwdExpireEnable: true}}
|
|
uc := NewSecurityUsecase(repo, nil, nil, nil)
|
|
|
|
if err := uc.UpdateSecurity(context.Background(), &SecurityConfig{PwdExpireEnable: true}); err != nil {
|
|
t.Fatalf("UpdateSecurity() error = %v", err)
|
|
}
|
|
if got := repo.callOrder; len(got) != 2 || got[0] != "get" || got[1] != "save" {
|
|
t.Fatalf("call order = %v, want [get save]", got)
|
|
}
|
|
}
|
|
|
|
func TestSecurityReadsPersistedValueWhileCurrentUsesCache(t *testing.T) {
|
|
repo := &securityUpdateRepo{current: &SecurityConfig{ID: 1, LimitCount: 10}}
|
|
uc := NewSecurityUsecase(repo, nil, nil, nil)
|
|
|
|
cached, err := uc.Current(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cached.LimitCount != 10 {
|
|
t.Fatalf("initial cached limit = %d", cached.LimitCount)
|
|
}
|
|
repo.current.LimitCount = 20
|
|
|
|
current, err := uc.Current(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if current.LimitCount != 10 {
|
|
t.Fatalf("Current() bypassed cache: %d", current.LimitCount)
|
|
}
|
|
fresh, err := uc.Security(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fresh.LimitCount != 20 {
|
|
t.Fatalf("Security() returned stale value: %d", fresh.LimitCount)
|
|
}
|
|
}
|
|
|
|
func TestCurrentUsesBootstrapDefaultsWithoutCachingThem(t *testing.T) {
|
|
repo := &bootstrapSecurityRepo{}
|
|
uc := NewSecurityUsecase(repo, nil, nil, nil)
|
|
|
|
bootstrap, err := uc.Current(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Current() during bootstrap error = %v", err)
|
|
}
|
|
if bootstrap.ID != 1 || bootstrap.KeyLong != 6 || bootstrap.ImgWidth != 240 || bootstrap.ImgHeight != 80 || bootstrap.LimitEnable {
|
|
t.Fatalf("bootstrap config = %+v", bootstrap)
|
|
}
|
|
if repo.calls != 1 {
|
|
t.Fatalf("bootstrap repository calls = %d, want 1", repo.calls)
|
|
}
|
|
|
|
repo.initialized = true
|
|
active, err := uc.Current(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Current() after initialization error = %v", err)
|
|
}
|
|
if active.LimitCount != 42 {
|
|
t.Fatalf("active config = %+v, want persisted config", active)
|
|
}
|
|
if repo.calls != 2 {
|
|
t.Fatalf("repository calls = %d, want fallback to be uncached", repo.calls)
|
|
}
|
|
}
|