Files
Xor290 919c807004
ci-api / test (push) Failing after 8m7s
ci-web / test (push) Failing after 5m5s
chore: build
2026-09-20 12:18:33 +02:00

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)
}
}