246 lines
8.4 KiB
Go
246 lines
8.4 KiB
Go
package site_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"backend/internal/modules/site"
|
|
)
|
|
|
|
// fakeRepository is an in-memory site.Repository backing the single
|
|
// site_settings row, used to unit-test site.Service without a real database.
|
|
type fakeRepository struct {
|
|
settings site.Settings
|
|
}
|
|
|
|
func newFakeRepository() *fakeRepository {
|
|
return &fakeRepository{settings: site.Settings{ID: 1}}
|
|
}
|
|
|
|
func strPtr(s string) *string { return &s }
|
|
func boolPtr(b bool) *bool { return &b }
|
|
|
|
func (r *fakeRepository) Get(_ context.Context) (*site.Settings, error) {
|
|
cp := r.settings
|
|
return &cp, nil
|
|
}
|
|
|
|
func (r *fakeRepository) Update(_ context.Context, s *site.Settings) error {
|
|
r.settings.Name = s.Name
|
|
r.settings.Description = s.Description
|
|
r.settings.OrdersEnabled = s.OrdersEnabled
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeRepository) UpdateAppearance(_ context.Context, s *site.Settings) error {
|
|
r.settings.HeaderBgColor = s.HeaderBgColor
|
|
r.settings.HeaderTextColor = s.HeaderTextColor
|
|
r.settings.BodyBgColor = s.BodyBgColor
|
|
r.settings.BodyTextColor = s.BodyTextColor
|
|
r.settings.FooterBgColor = s.FooterBgColor
|
|
r.settings.FooterTextColor = s.FooterTextColor
|
|
r.settings.AccentColor = s.AccentColor
|
|
r.settings.ProductLayout = s.ProductLayout
|
|
r.settings.ProductColumns = s.ProductColumns
|
|
r.settings.ProductScroll = s.ProductScroll
|
|
r.settings.ContactCardTransparent = s.ContactCardTransparent
|
|
r.settings.ContactCardBgColor = s.ContactCardBgColor
|
|
r.settings.LogoMediaID = s.LogoMediaID
|
|
r.settings.HeroMediaID = s.HeroMediaID
|
|
r.settings.HeroPages = s.HeroPages
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeRepository) UpdateCustomerAuth(_ context.Context, s *site.Settings) error {
|
|
r.settings.CustomerLoginEnabled = s.CustomerLoginEnabled
|
|
r.settings.CustomerRegistrationEnabled = s.CustomerRegistrationEnabled
|
|
r.settings.CustomerVerificationRequired = s.CustomerVerificationRequired
|
|
r.settings.VerificationContactLinkID = s.VerificationContactLinkID
|
|
return nil
|
|
}
|
|
|
|
func TestService_Update_PersistsFields(t *testing.T) {
|
|
svc := site.NewService(newFakeRepository())
|
|
ctx := context.Background()
|
|
|
|
updated, err := svc.Update(ctx, "My Shop", "A small shop", true)
|
|
if err != nil {
|
|
t.Fatalf("Update() error = %v", err)
|
|
}
|
|
if updated.Name != "My Shop" || updated.Description != "A small shop" || !updated.OrdersEnabled {
|
|
t.Fatalf("Update() = %+v, fields not persisted as expected", updated)
|
|
}
|
|
|
|
fetched, err := svc.Get(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Get() error = %v", err)
|
|
}
|
|
if fetched.Name != "My Shop" {
|
|
t.Fatalf("Get() after Update() = %+v, want name %q", fetched, "My Shop")
|
|
}
|
|
}
|
|
|
|
func TestService_UpdateAppearance_DoesNotRequireIdentityFields(t *testing.T) {
|
|
svc := site.NewService(newFakeRepository())
|
|
ctx := context.Background()
|
|
|
|
// The Appearance admin page never touches name/description, so it must
|
|
// be able to save even when the site identity was never configured
|
|
// (name still empty) -- this used to fail because both concerns shared
|
|
// one PUT that validated identity fields on every save.
|
|
updated, err := svc.UpdateAppearance(ctx, site.AppearanceInput{
|
|
HeaderBgColor: strPtr("#111111"),
|
|
HeaderTextColor: strPtr("#ffffff"),
|
|
BodyBgColor: strPtr("#222222"),
|
|
BodyTextColor: strPtr("#eeeeee"),
|
|
FooterBgColor: strPtr("#333333"),
|
|
FooterTextColor: strPtr("#dddddd"),
|
|
AccentColor: strPtr("#ff00ff"),
|
|
ProductLayout: strPtr("list"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdateAppearance() error = %v", err)
|
|
}
|
|
if updated.AccentColor != "#ff00ff" || updated.ProductLayout != "list" {
|
|
t.Fatalf("UpdateAppearance() = %+v, fields not persisted as expected", updated)
|
|
}
|
|
if updated.Name != "" {
|
|
t.Fatalf("UpdateAppearance() = %+v, should not touch identity fields", updated)
|
|
}
|
|
}
|
|
|
|
func TestService_UpdateAppearance_PartialUpdateKeepsOtherFields(t *testing.T) {
|
|
svc := site.NewService(newFakeRepository())
|
|
ctx := context.Background()
|
|
|
|
_, err := svc.UpdateAppearance(ctx, site.AppearanceInput{
|
|
HeaderBgColor: strPtr("#111111"),
|
|
HeaderTextColor: strPtr("#ffffff"),
|
|
BodyBgColor: strPtr("#222222"),
|
|
BodyTextColor: strPtr("#eeeeee"),
|
|
FooterBgColor: strPtr("#333333"),
|
|
FooterTextColor: strPtr("#dddddd"),
|
|
AccentColor: strPtr("#ff00ff"),
|
|
ProductLayout: strPtr("grid"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdateAppearance() error = %v", err)
|
|
}
|
|
|
|
// The admin only changes the product layout; every color must keep its
|
|
// previously saved value, not be wiped out.
|
|
updated, err := svc.UpdateAppearance(ctx, site.AppearanceInput{ProductLayout: strPtr("list")})
|
|
if err != nil {
|
|
t.Fatalf("UpdateAppearance() partial error = %v", err)
|
|
}
|
|
if updated.ProductLayout != "list" {
|
|
t.Fatalf("UpdateAppearance() partial = %+v, want product_layout list", updated)
|
|
}
|
|
if updated.AccentColor != "#ff00ff" || updated.HeaderBgColor != "#111111" {
|
|
t.Fatalf("UpdateAppearance() partial = %+v, unrelated fields should be unchanged", updated)
|
|
}
|
|
}
|
|
|
|
func TestService_UpdateAppearance_LogoAndHeroClearedOnlyWhenExplicit(t *testing.T) {
|
|
svc := site.NewService(newFakeRepository())
|
|
ctx := context.Background()
|
|
|
|
logoID := uuid.New()
|
|
updated, err := svc.UpdateAppearance(ctx, site.AppearanceInput{
|
|
LogoMediaIDSet: true,
|
|
LogoMediaID: &logoID,
|
|
HeroPages: strPtr("home,contact"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdateAppearance() error = %v", err)
|
|
}
|
|
if updated.LogoMediaID == nil || *updated.LogoMediaID != logoID {
|
|
t.Fatalf("UpdateAppearance() = %+v, want logo set", updated)
|
|
}
|
|
if updated.HeroPages != "home,contact" {
|
|
t.Fatalf("UpdateAppearance() = %+v, want hero_pages persisted", updated)
|
|
}
|
|
|
|
// Saving an unrelated field (no LogoMediaIDSet) must not wipe the logo.
|
|
updated, err = svc.UpdateAppearance(ctx, site.AppearanceInput{ProductLayout: strPtr("list")})
|
|
if err != nil {
|
|
t.Fatalf("UpdateAppearance() second call error = %v", err)
|
|
}
|
|
if updated.LogoMediaID == nil || *updated.LogoMediaID != logoID {
|
|
t.Fatalf("UpdateAppearance() = %+v, logo should survive an unrelated save", updated)
|
|
}
|
|
|
|
// Explicitly clearing the logo (Set=true, ID=nil) must remove it.
|
|
updated, err = svc.UpdateAppearance(ctx, site.AppearanceInput{LogoMediaIDSet: true, LogoMediaID: nil})
|
|
if err != nil {
|
|
t.Fatalf("UpdateAppearance() clear error = %v", err)
|
|
}
|
|
if updated.LogoMediaID != nil {
|
|
t.Fatalf("UpdateAppearance() = %+v, want logo cleared", updated)
|
|
}
|
|
}
|
|
|
|
func TestService_UpdateCustomerAuth_PartialUpdateKeepsOtherFields(t *testing.T) {
|
|
svc := site.NewService(newFakeRepository())
|
|
ctx := context.Background()
|
|
|
|
linkID := uuid.New()
|
|
_, err := svc.UpdateCustomerAuth(ctx, site.CustomerAuthInput{
|
|
LoginEnabled: boolPtr(true),
|
|
VerificationRequired: boolPtr(true),
|
|
VerificationContactLinkSet: true,
|
|
VerificationContactLinkID: &linkID,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdateCustomerAuth() error = %v", err)
|
|
}
|
|
|
|
// Flipping just login_enabled must not disturb the verification
|
|
// requirement or the chosen contact link.
|
|
updated, err := svc.UpdateCustomerAuth(ctx, site.CustomerAuthInput{LoginEnabled: boolPtr(false)})
|
|
if err != nil {
|
|
t.Fatalf("UpdateCustomerAuth() partial error = %v", err)
|
|
}
|
|
if updated.CustomerLoginEnabled {
|
|
t.Fatalf("UpdateCustomerAuth() partial = %+v, want login disabled", updated)
|
|
}
|
|
if !updated.CustomerVerificationRequired {
|
|
t.Fatalf("UpdateCustomerAuth() partial = %+v, verification_required should be unchanged", updated)
|
|
}
|
|
if updated.VerificationContactLinkID == nil || *updated.VerificationContactLinkID != linkID {
|
|
t.Fatalf("UpdateCustomerAuth() partial = %+v, contact link should be unchanged", updated)
|
|
}
|
|
}
|
|
|
|
func TestService_UpdateCustomerAuth_CanClearContactLink(t *testing.T) {
|
|
svc := site.NewService(newFakeRepository())
|
|
ctx := context.Background()
|
|
|
|
linkID := uuid.New()
|
|
_, err := svc.UpdateCustomerAuth(ctx, site.CustomerAuthInput{VerificationContactLinkSet: true, VerificationContactLinkID: &linkID})
|
|
if err != nil {
|
|
t.Fatalf("UpdateCustomerAuth() error = %v", err)
|
|
}
|
|
|
|
updated, err := svc.UpdateCustomerAuth(ctx, site.CustomerAuthInput{VerificationContactLinkSet: true, VerificationContactLinkID: nil})
|
|
if err != nil {
|
|
t.Fatalf("UpdateCustomerAuth() clear error = %v", err)
|
|
}
|
|
if updated.VerificationContactLinkID != nil {
|
|
t.Fatalf("UpdateCustomerAuth() clear = %+v, want nil contact link", updated)
|
|
}
|
|
}
|
|
|
|
func TestService_Get_DefaultsBeforeAnyUpdate(t *testing.T) {
|
|
svc := site.NewService(newFakeRepository())
|
|
settings, err := svc.Get(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Get() error = %v", err)
|
|
}
|
|
if settings.Name != "" {
|
|
t.Fatalf("Get() before any update = %+v, want empty defaults", settings)
|
|
}
|
|
}
|