管理页商品列表原本只能按搜索、库存状态、分类、批发价过滤,缺少上下架状态 维度,无法快速定位未上架商品。 功能: - 新增 is_active 三态查询参数(缺省/all 不限、1 仅已上架、0 仅已下架),按既有 筛选分层落位:contract 增加 ListFilter.IsActive,store 拼接 is_active 条件, application 透传,transport 解析入参。 - IsActive 与公开侧的 OnlyActive 语义不同,后者是强制上架开关;两者同时存在时 不叠加 IsActive,避免组合出必然为空的查询。 - 原 parseWholesaleFilter 重命名为 parseTriStateBoolFilter,由 wholesale 与 is_active 共用,避免复制一份真/假值解析逻辑。 - 管理页筛选栏新增「上架状态」下拉,与其余四个条件按 AND 组合,重置按钮一并 复位;三语文案补齐。上架状态筛选生效时,行内切换上下架后重新拉取列表, 避免该行与筛选结果不符。 影响范围:仅后台商品列表的读取链路,未改动公开商品接口与商品写入逻辑。 测试: - 仓储层 3 个用例:三态筛选、与 search/stock_status/wholesale 的 AND 组合、 OnlyActive 优先级;集成层 1 个用例覆盖 HTTP 到持久化的完整链路。 - 补充 PostgreSQL 覆盖,验证 IsActive 与本地化搜索(ILIKE + jsonb ->>)、 批发价(jsonb_array_length)按 AND 组合的行为与 SQLite 一致。 - 顺带修复该文件的 PostgreSQL 测试夹具:setupPostgresIntegrationDB 未迁移 product_skus,而商品搜索条件含有对 product_skus 的 EXISTS 子查询,导致既有的 TestPostgresLocalizedJSONSearchRepositories 与 TestPostgresDashboardQueries 在真实 PostgreSQL 上一律报 relation "product_skus" does not exist。已在 main 上复现同样的失败,确认属既有问题,与本次功能改动无关。 验证:SQLite 下 go test ./... 187 个包全部通过;在隔离的 PostgreSQL 17.9 实例上 go test -tags integration ./... -count=1 -p 1 共 188 个包全部通过,其中 7 个 PostgreSQL 用例实际执行;gofmt 与 go vet 无告警;admin 端 vue-tsc 构建与 28 项 单测通过。
496 lines
17 KiB
Go
496 lines
17 KiB
Go
//go:build integration
|
||
// +build integration
|
||
|
||
package integrationtest
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"os"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
paymentdomain "github.com/dujiao-next/internal/modules/payment/domain"
|
||
|
||
orderdomain "github.com/dujiao-next/internal/modules/order/domain"
|
||
|
||
productcontract "github.com/dujiao-next/internal/modules/catalog/product/contract"
|
||
productdomain "github.com/dujiao-next/internal/modules/catalog/product/domain"
|
||
|
||
categorydomain "github.com/dujiao-next/internal/modules/catalog/category/domain"
|
||
|
||
"github.com/dujiao-next/internal/constants"
|
||
productgormstore "github.com/dujiao-next/internal/modules/catalog/product/store/gormstore"
|
||
contentcontract "github.com/dujiao-next/internal/modules/content/contract"
|
||
contentdomain "github.com/dujiao-next/internal/modules/content/domain"
|
||
contentgormstore "github.com/dujiao-next/internal/modules/content/infrastructure/gormstore"
|
||
dashboardgormstore "github.com/dujiao-next/internal/modules/dashboard/infrastructure/gormstore"
|
||
"github.com/dujiao-next/internal/shared/jsonmap"
|
||
"github.com/dujiao-next/internal/shared/money"
|
||
|
||
"github.com/shopspring/decimal"
|
||
"gorm.io/driver/postgres"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// setupPostgresIntegrationDB 初始化 PostgreSQL 集成测试数据库。
|
||
func setupPostgresIntegrationDB(t *testing.T) *gorm.DB {
|
||
t.Helper()
|
||
|
||
dsn := strings.TrimSpace(os.Getenv("TEST_POSTGRES_DSN"))
|
||
if dsn == "" {
|
||
t.Skip("skip postgres integration test: TEST_POSTGRES_DSN is empty")
|
||
}
|
||
|
||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||
if err != nil {
|
||
t.Fatalf("open postgres failed: %v", err)
|
||
}
|
||
|
||
cleanupModels := []interface{}{
|
||
&orderdomain.OrderItem{},
|
||
&paymentdomain.Payment{},
|
||
&orderdomain.Order{},
|
||
&contentdomain.PostProduct{},
|
||
&productdomain.ProductSKU{},
|
||
&productdomain.Product{},
|
||
&categorydomain.Category{},
|
||
&contentdomain.Banner{},
|
||
&contentdomain.Post{},
|
||
}
|
||
_ = db.Migrator().DropTable(cleanupModels...)
|
||
|
||
// product_skus 必须建表:商品搜索条件里含有对 product_skus 的 EXISTS 子查询
|
||
if err := db.AutoMigrate(
|
||
&categorydomain.Category{},
|
||
&productdomain.Product{},
|
||
&productdomain.ProductSKU{},
|
||
&contentdomain.Post{},
|
||
&contentdomain.PostProduct{},
|
||
&contentdomain.Banner{},
|
||
&orderdomain.Order{},
|
||
&orderdomain.OrderItem{},
|
||
&paymentdomain.Payment{},
|
||
); err != nil {
|
||
t.Fatalf("migrate postgres models failed: %v", err)
|
||
}
|
||
|
||
t.Cleanup(func() {
|
||
_ = db.Migrator().DropTable(cleanupModels...)
|
||
sqlDB, err := db.DB()
|
||
if err == nil {
|
||
_ = sqlDB.Close()
|
||
}
|
||
})
|
||
|
||
return db
|
||
}
|
||
|
||
func TestPostgresLocalizedJSONSearchRepositories(t *testing.T) {
|
||
db := setupPostgresIntegrationDB(t)
|
||
ctx := context.Background()
|
||
|
||
category := &categorydomain.Category{
|
||
Slug: "pg-category",
|
||
NameJSON: jsonmap.JSON{"zh-CN": "Postgres 分类"},
|
||
}
|
||
if err := db.Create(category).Error; err != nil {
|
||
t.Fatalf("create category failed: %v", err)
|
||
}
|
||
|
||
productRepo := productgormstore.NewProductStore(db)
|
||
product := &productdomain.Product{
|
||
CategoryID: category.ID,
|
||
Slug: "pg-product-rocket",
|
||
TitleJSON: jsonmap.JSON{"zh-CN": "火箭会员"},
|
||
DescriptionJSON: jsonmap.JSON{"en-US": "rocket booster package"},
|
||
PriceAmount: money.FromDecimal(decimal.NewFromInt(99)),
|
||
PurchaseType: constants.ProductPurchaseMember,
|
||
FulfillmentType: constants.FulfillmentTypeManual,
|
||
ManualStockTotal: 10,
|
||
IsActive: true,
|
||
}
|
||
if err := productRepo.Create(product); err != nil {
|
||
t.Fatalf("create product failed: %v", err)
|
||
}
|
||
|
||
productRows, productTotal, err := productRepo.List(productcontract.ListFilter{
|
||
Page: 1,
|
||
Search: "火箭",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("product list search zh-CN failed: %v", err)
|
||
}
|
||
if productTotal != 1 || len(productRows) != 1 {
|
||
t.Fatalf("product list search zh-CN want 1 got total=%d len=%d", productTotal, len(productRows))
|
||
}
|
||
|
||
productRows, productTotal, err = productRepo.List(productcontract.ListFilter{
|
||
Page: 1,
|
||
Search: "booster",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("product list search en-US failed: %v", err)
|
||
}
|
||
if productTotal != 1 || len(productRows) != 1 {
|
||
t.Fatalf("product list search en-US want 1 got total=%d len=%d", productTotal, len(productRows))
|
||
}
|
||
|
||
postStore := contentgormstore.NewPostStore(db)
|
||
post := &contentdomain.Post{
|
||
Slug: "pg-post-release",
|
||
Type: "notice",
|
||
TitleJSON: jsonmap.JSON{"en-US": "Release Notes"},
|
||
IsPublished: true,
|
||
}
|
||
if err := postStore.Create(ctx, post); err != nil {
|
||
t.Fatalf("create post failed: %v", err)
|
||
}
|
||
|
||
postRows, postTotal, err := postStore.List(ctx, contentcontract.PostQuery{
|
||
Page: 1,
|
||
Search: "Release",
|
||
Order: contentcontract.PostOrderCreatedDesc,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("post list search failed: %v", err)
|
||
}
|
||
if postTotal != 1 || len(postRows) != 1 {
|
||
t.Fatalf("post list search want 1 got total=%d len=%d", postTotal, len(postRows))
|
||
}
|
||
|
||
bannerStore := contentgormstore.NewBannerStore(db)
|
||
banner := &contentdomain.Banner{
|
||
Name: "pg-home-banner",
|
||
Position: "home",
|
||
TitleJSON: jsonmap.JSON{"zh-CN": "春季大促"},
|
||
Image: "/banner.png",
|
||
LinkType: "none",
|
||
IsActive: true,
|
||
}
|
||
if err := bannerStore.Create(ctx, banner); err != nil {
|
||
t.Fatalf("create banner failed: %v", err)
|
||
}
|
||
|
||
bannerRows, bannerTotal, err := bannerStore.List(ctx, contentcontract.BannerQuery{
|
||
Page: 1,
|
||
Search: "春季",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("banner list search failed: %v", err)
|
||
}
|
||
if bannerTotal != 1 || len(bannerRows) != 1 {
|
||
t.Fatalf("banner list search want 1 got total=%d len=%d", bannerTotal, len(bannerRows))
|
||
}
|
||
}
|
||
|
||
func TestPostgresContentGormStoresPreserveQuerySemantics(t *testing.T) {
|
||
db := setupPostgresIntegrationDB(t)
|
||
ctx := context.Background()
|
||
now := time.Now().UTC()
|
||
older := now.Add(-2 * time.Hour)
|
||
newer := now.Add(-time.Hour)
|
||
|
||
posts := []contentdomain.Post{
|
||
{Slug: "pg-content-older", Type: constants.PostTypeBlog, TitleJSON: jsonmap.JSON{"zh-CN": "模块化指南"}, IsPublished: true, PublishedAt: &older},
|
||
{Slug: "pg-content-newer", Type: constants.PostTypeBlog, TitleJSON: jsonmap.JSON{"zh-CN": "模块化指南新版"}, IsPublished: true, PublishedAt: &newer},
|
||
{Slug: "pg-content-draft", Type: constants.PostTypeBlog, TitleJSON: jsonmap.JSON{"zh-CN": "模块化指南草稿"}, IsPublished: false},
|
||
}
|
||
for index := range posts {
|
||
if err := db.Create(&posts[index]).Error; err != nil {
|
||
t.Fatalf("create postgres content post %q: %v", posts[index].Slug, err)
|
||
}
|
||
}
|
||
|
||
postStore := contentgormstore.NewPostStore(db)
|
||
modularPosts, modularTotal, err := postStore.List(ctx, contentcontract.PostQuery{
|
||
Page: 1,
|
||
PageSize: 20,
|
||
Type: constants.PostTypeBlog,
|
||
Search: "模块化",
|
||
OnlyPublished: true,
|
||
Order: contentcontract.PostOrderPublishedDesc,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("modular postgres post query: %v", err)
|
||
}
|
||
if modularTotal != 2 || len(modularPosts) != 2 {
|
||
t.Fatalf("postgres post query should return two published rows, total=%d rows=%#v", modularTotal, modularPosts)
|
||
}
|
||
if modularPosts[0].Slug != "pg-content-newer" || modularPosts[1].Slug != "pg-content-older" {
|
||
t.Fatalf("postgres post publication order mismatch: %#v", modularPosts)
|
||
}
|
||
|
||
rollbackPost := &contentdomain.Post{
|
||
Slug: "pg-content-rollback",
|
||
Type: constants.PostTypeBlog,
|
||
TitleJSON: jsonmap.JSON{"zh-CN": "事务回滚"},
|
||
}
|
||
forcedRollback := errors.New("forced content transaction rollback")
|
||
err = postStore.WithinPostWriteTransaction(ctx, func(posts contentcontract.PostStore, relations contentcontract.PostProductRelationStore) error {
|
||
if createErr := posts.Create(ctx, rollbackPost); createErr != nil {
|
||
return createErr
|
||
}
|
||
if relationErr := relations.SetRelatedProductIDs(ctx, rollbackPost.ID, []uint{42}); relationErr != nil {
|
||
return relationErr
|
||
}
|
||
return forcedRollback
|
||
})
|
||
if !errors.Is(err, forcedRollback) {
|
||
t.Fatalf("postgres content transaction should return callback error, got %v", err)
|
||
}
|
||
var rollbackPostCount int64
|
||
if err := db.Model(&contentdomain.Post{}).Where("slug = ?", rollbackPost.Slug).Count(&rollbackPostCount).Error; err != nil {
|
||
t.Fatalf("count rolled back postgres content post: %v", err)
|
||
}
|
||
if rollbackPostCount != 0 {
|
||
t.Fatalf("postgres content transaction should roll back post, count=%d", rollbackPostCount)
|
||
}
|
||
|
||
banner := contentdomain.Banner{
|
||
Name: "pg-content-banner",
|
||
Position: constants.BannerPositionHomeHero,
|
||
TitleJSON: jsonmap.JSON{"en-US": "Modular launch"},
|
||
Image: "/pg-content.png",
|
||
LinkType: constants.BannerLinkTypeNone,
|
||
IsActive: true,
|
||
}
|
||
if err := db.Create(&banner).Error; err != nil {
|
||
t.Fatalf("create postgres content banner: %v", err)
|
||
}
|
||
bannerStore := contentgormstore.NewBannerStore(db)
|
||
modularBanners, modularBannerTotal, err := bannerStore.List(ctx, contentcontract.BannerQuery{Page: 1, PageSize: 20, Search: "Modular"})
|
||
if err != nil {
|
||
t.Fatalf("modular postgres banner query: %v", err)
|
||
}
|
||
if modularBannerTotal != 1 || len(modularBanners) != 1 || modularBanners[0].ID != banner.ID {
|
||
t.Fatalf("postgres banner localized search mismatch total=%d rows=%#v", modularBannerTotal, modularBanners)
|
||
}
|
||
|
||
inactiveBanner := &contentdomain.Banner{
|
||
Name: "pg-content-inactive-banner",
|
||
Position: constants.BannerPositionHomeHero,
|
||
Image: "/pg-content-inactive.png",
|
||
LinkType: constants.BannerLinkTypeNone,
|
||
IsActive: false,
|
||
}
|
||
if err := bannerStore.Create(ctx, inactiveBanner); err != nil {
|
||
t.Fatalf("create inactive postgres content banner: %v", err)
|
||
}
|
||
var reloadedInactiveBanner contentdomain.Banner
|
||
if err := db.First(&reloadedInactiveBanner, inactiveBanner.ID).Error; err != nil {
|
||
t.Fatalf("reload inactive postgres content banner: %v", err)
|
||
}
|
||
if inactiveBanner.IsActive || reloadedInactiveBanner.IsActive {
|
||
t.Fatalf("explicit inactive postgres banner should stay inactive, returned=%t stored=%t", inactiveBanner.IsActive, reloadedInactiveBanner.IsActive)
|
||
}
|
||
}
|
||
|
||
// 后台商品列表的上架状态筛选在 PostgreSQL 上需与 SQLite 语义一致:
|
||
// IsActive 三态过滤必须与 search(PostgreSQL 走 ILIKE + jsonb ->>)、
|
||
// wholesale(PostgreSQL 走 jsonb_array_length)按 AND 正确组合。
|
||
func TestPostgresProductListActiveStateFilter(t *testing.T) {
|
||
db := setupPostgresIntegrationDB(t)
|
||
|
||
category := &categorydomain.Category{
|
||
Slug: "pg-active-state-category",
|
||
NameJSON: jsonmap.JSON{"zh-CN": "上架状态分类"},
|
||
}
|
||
if err := db.Create(category).Error; err != nil {
|
||
t.Fatalf("create category failed: %v", err)
|
||
}
|
||
|
||
productRepo := productgormstore.NewProductStore(db)
|
||
|
||
seed := func(slug string, isActive bool, withWholesale bool) *productdomain.Product {
|
||
product := &productdomain.Product{
|
||
CategoryID: category.ID,
|
||
Slug: slug,
|
||
TitleJSON: jsonmap.JSON{"zh-CN": "上架状态商品"},
|
||
PriceAmount: money.FromDecimal(decimal.NewFromInt(50)),
|
||
PurchaseType: constants.ProductPurchaseMember,
|
||
FulfillmentType: constants.FulfillmentTypeManual,
|
||
ManualStockTotal: 5,
|
||
IsActive: isActive,
|
||
}
|
||
if withWholesale {
|
||
product.WholesalePrices = productdomain.WholesalePriceTiers{
|
||
{MinQuantity: 5, UnitPrice: money.FromDecimal(decimal.NewFromInt(40))},
|
||
}
|
||
}
|
||
if err := productRepo.Create(product); err != nil {
|
||
t.Fatalf("create postgres product %q failed: %v", slug, err)
|
||
}
|
||
// is_active 带 default:false,需确认下架商品在 PostgreSQL 上确实落库为 false
|
||
var stored productdomain.Product
|
||
if err := db.First(&stored, product.ID).Error; err != nil {
|
||
t.Fatalf("reload postgres product %q failed: %v", slug, err)
|
||
}
|
||
if stored.IsActive != isActive {
|
||
t.Fatalf("postgres product %q stored is_active=%t want %t", slug, stored.IsActive, isActive)
|
||
}
|
||
return product
|
||
}
|
||
|
||
active := seed("pg-state-active", true, true)
|
||
inactive := seed("pg-state-inactive", false, true)
|
||
inactiveWithoutWholesale := seed("pg-state-inactive-plain", false, false)
|
||
|
||
listSlugs := func(filter productcontract.ListFilter) map[string]bool {
|
||
t.Helper()
|
||
filter.Page = 1
|
||
filter.PageSize = 50
|
||
rows, _, err := productRepo.List(filter)
|
||
if err != nil {
|
||
t.Fatalf("postgres product list failed: %v", err)
|
||
}
|
||
got := make(map[string]bool, len(rows))
|
||
for _, row := range rows {
|
||
got[row.Slug] = true
|
||
}
|
||
return got
|
||
}
|
||
|
||
onlyActive := true
|
||
onlyInactive := false
|
||
withWholesale := true
|
||
|
||
got := listSlugs(productcontract.ListFilter{IsActive: &onlyActive})
|
||
if !got[active.Slug] || got[inactive.Slug] {
|
||
t.Fatalf("postgres IsActive=true should return only active rows, got %+v", got)
|
||
}
|
||
|
||
got = listSlugs(productcontract.ListFilter{IsActive: &onlyInactive})
|
||
if !got[inactive.Slug] || !got[inactiveWithoutWholesale.Slug] || got[active.Slug] {
|
||
t.Fatalf("postgres IsActive=false should return only inactive rows, got %+v", got)
|
||
}
|
||
|
||
got = listSlugs(productcontract.ListFilter{IsActive: &onlyInactive, Search: "上架状态"})
|
||
if !got[inactive.Slug] || got[active.Slug] {
|
||
t.Fatalf("postgres IsActive=false + localized search mismatch, got %+v", got)
|
||
}
|
||
|
||
got = listSlugs(productcontract.ListFilter{IsActive: &onlyInactive, HasWholesalePrices: &withWholesale})
|
||
if !got[inactive.Slug] || got[inactiveWithoutWholesale.Slug] || got[active.Slug] {
|
||
t.Fatalf("postgres IsActive=false + wholesale mismatch, got %+v", got)
|
||
}
|
||
|
||
got = listSlugs(productcontract.ListFilter{IsActive: &onlyInactive, Search: "绝不匹配的关键词"})
|
||
if len(got) != 0 {
|
||
t.Fatalf("postgres IsActive=false + non-matching search should be empty, got %+v", got)
|
||
}
|
||
}
|
||
|
||
func TestPostgresDashboardQueries(t *testing.T) {
|
||
db := setupPostgresIntegrationDB(t)
|
||
repo := dashboardgormstore.New(db)
|
||
now := time.Now().UTC().Truncate(time.Second)
|
||
|
||
category := &categorydomain.Category{
|
||
Slug: "pg-dashboard-category",
|
||
NameJSON: jsonmap.JSON{"zh-CN": "仪表盘分类"},
|
||
}
|
||
if err := db.Create(category).Error; err != nil {
|
||
t.Fatalf("create category failed: %v", err)
|
||
}
|
||
|
||
product := &productdomain.Product{
|
||
CategoryID: category.ID,
|
||
Slug: "pg-dashboard-product",
|
||
TitleJSON: jsonmap.JSON{"zh-CN": "仪表盘商品"},
|
||
PriceAmount: money.FromDecimal(decimal.NewFromInt(120)),
|
||
PurchaseType: constants.ProductPurchaseMember,
|
||
FulfillmentType: constants.FulfillmentTypeManual,
|
||
IsActive: true,
|
||
}
|
||
if err := db.Create(product).Error; err != nil {
|
||
t.Fatalf("create product failed: %v", err)
|
||
}
|
||
|
||
order := &orderdomain.Order{
|
||
OrderNo: "PG-ORDER-001",
|
||
UserID: 1,
|
||
Status: constants.OrderStatusPaid,
|
||
Currency: "USD",
|
||
OriginalAmount: money.FromDecimal(decimal.NewFromInt(120)),
|
||
DiscountAmount: money.FromDecimal(decimal.Zero),
|
||
TotalAmount: money.FromDecimal(decimal.NewFromInt(120)),
|
||
CreatedAt: now,
|
||
}
|
||
if err := db.Create(order).Error; err != nil {
|
||
t.Fatalf("create order failed: %v", err)
|
||
}
|
||
|
||
orderItem := &orderdomain.OrderItem{
|
||
OrderID: order.ID,
|
||
ProductID: product.ID,
|
||
TitleJSON: jsonmap.JSON{"zh-CN": "仪表盘商品"},
|
||
UnitPrice: money.FromDecimal(decimal.NewFromInt(120)),
|
||
Quantity: 2,
|
||
TotalPrice: money.FromDecimal(decimal.NewFromInt(240)),
|
||
CouponDiscount: money.FromDecimal(decimal.NewFromInt(20)),
|
||
PromotionDiscount: money.FromDecimal(decimal.NewFromInt(10)),
|
||
FulfillmentType: constants.FulfillmentTypeManual,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
if err := db.Create(orderItem).Error; err != nil {
|
||
t.Fatalf("create order item failed: %v", err)
|
||
}
|
||
|
||
payment := &paymentdomain.Payment{
|
||
OrderID: order.ID,
|
||
ChannelID: 1,
|
||
ProviderType: constants.PaymentProviderOfficial,
|
||
ChannelType: constants.PaymentChannelTypeAlipay,
|
||
InteractionMode: constants.PaymentInteractionRedirect,
|
||
Amount: money.FromDecimal(decimal.NewFromInt(120)),
|
||
FeeRate: money.FromDecimal(decimal.Zero),
|
||
FeeAmount: money.FromDecimal(decimal.Zero),
|
||
Currency: "USD",
|
||
Status: constants.PaymentStatusSuccess,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
if err := db.Create(payment).Error; err != nil {
|
||
t.Fatalf("create payment failed: %v", err)
|
||
}
|
||
|
||
startAt := now.Add(-time.Hour)
|
||
endAt := now.Add(time.Hour)
|
||
|
||
topProducts, err := repo.GetTopProducts(startAt, endAt, 5)
|
||
if err != nil {
|
||
t.Fatalf("get top products failed: %v", err)
|
||
}
|
||
if len(topProducts) != 1 {
|
||
t.Fatalf("top products len want 1 got %d", len(topProducts))
|
||
}
|
||
if topProducts[0].Title != "仪表盘商品" {
|
||
t.Fatalf("top product title want 仪表盘商品 got %s", topProducts[0].Title)
|
||
}
|
||
|
||
orderTrends, err := repo.GetOrderTrends(startAt, endAt)
|
||
if err != nil {
|
||
t.Fatalf("get order trends failed: %v", err)
|
||
}
|
||
if len(orderTrends) == 0 {
|
||
t.Fatalf("order trends should not be empty")
|
||
}
|
||
if strings.TrimSpace(orderTrends[0].Day) == "" {
|
||
t.Fatalf("order trend day should not be empty")
|
||
}
|
||
|
||
paymentTrends, err := repo.GetPaymentTrends(startAt, endAt)
|
||
if err != nil {
|
||
t.Fatalf("get payment trends failed: %v", err)
|
||
}
|
||
if len(paymentTrends) == 0 {
|
||
t.Fatalf("payment trends should not be empty")
|
||
}
|
||
if strings.TrimSpace(paymentTrends[0].Day) == "" {
|
||
t.Fatalf("payment trend day should not be empty")
|
||
}
|
||
}
|