package service import ( "context" "strings" "testing" "fashionapi/internal/pkg/hashid" "fashionapi/internal/repository" ) // stubBrandRepo 只实现删除链路上用到的两个方法;其余方法通过嵌入的接口转发 // (本测试不会调用它们,因此不必为 7 个无关方法写样板)。 type stubBrandRepo struct { repository.BrandRepository runways int64 deleted uint8 setCalled bool } func (s *stubBrandRepo) CountRunwaysByBrand(_ context.Context, _ uint32) (int64, error) { return s.runways, nil } func (s *stubBrandRepo) SetDeleted(_ context.Context, _ uint32, deleted uint8) error { s.setCalled = true s.deleted = deleted return nil } // TestBrandDeleteBlockedByRunways 品牌下还有图集时必须拒绝删除,且绝不能写 is_deleted。 // 这是删除功能唯一的护栏:brand_runways.brand_id 没有外键级联,误删会留下指不到品牌的图集。 func TestBrandDeleteBlockedByRunways(t *testing.T) { repo := &stubBrandRepo{runways: 3} svc := NewBrandService(repo) err := svc.Delete(context.Background(), hashid.Encode(1)) if err == nil { t.Fatal("品牌下还有图集时应拒绝删除") } if repo.setCalled { t.Fatal("被拒绝时不得写入 is_deleted") } if !strings.Contains(err.Error(), "3") { t.Fatalf("错误信息应带上图集数量,便于运营处理,实际: %v", err) } } // TestBrandDeleteAllowedWhenNoRunways 没有图集时可以软删,且写入的必须是 is_deleted=1。 func TestBrandDeleteAllowedWhenNoRunways(t *testing.T) { repo := &stubBrandRepo{} svc := NewBrandService(repo) if err := svc.Delete(context.Background(), hashid.Encode(1)); err != nil { t.Fatalf("无图集时应允许删除,实际: %v", err) } if !repo.setCalled || repo.deleted != 1 { t.Fatalf("应写入 is_deleted=1,实际 called=%v deleted=%d", repo.setCalled, repo.deleted) } } // TestBrandDeleteInvalidUID 非法 id 直接报错,不得触达数据库。 func TestBrandDeleteInvalidUID(t *testing.T) { repo := &stubBrandRepo{} svc := NewBrandService(repo) if err := svc.Delete(context.Background(), "!!!not-a-hashid"); err == nil { t.Fatal("非法 id 应返回错误") } if repo.setCalled { t.Fatal("非法 id 不应写库") } }