package services import ( "context" "database/sql" "testing" "quyun/v2/app/commands/testx" order_dto "quyun/v2/app/http/v1/dto" "quyun/v2/database" "quyun/v2/database/models" "quyun/v2/pkg/consts" . "github.com/smartystreets/goconvey/convey" "github.com/spf13/cast" "github.com/stretchr/testify/suite" "go.ipao.vip/atom/contracts" "go.uber.org/dig" ) type OrderTestSuiteInjectParams struct { dig.In DB *sql.DB Initials []contracts.Initial `group:"initials"` } type OrderTestSuite struct { suite.Suite OrderTestSuiteInjectParams } func Test_Order(t *testing.T) { providers := testx.Default().With(Provide) testx.Serve(providers, t, func(p OrderTestSuiteInjectParams) { suite.Run(t, &OrderTestSuite{OrderTestSuiteInjectParams: p}) }) } func (s *OrderTestSuite) Test_PurchaseFlow() { Convey("Purchase Flow", s.T(), func() { ctx := s.T().Context() database.Truncate(ctx, s.DB, models.TableNameOrder, models.TableNameOrderItem, models.TableNameUser, models.TableNameContent, models.TableNameContentPrice, models.TableNameTenant, models.TableNameContentAccess, models.TableNameTenantLedger, ) // 1. Setup Data // Creator creator := &models.User{Username: "creator", Phone: "13800000001"} models.UserQuery.WithContext(ctx).Create(creator) // Tenant tenant := &models.Tenant{UserID: creator.ID, Name: "Music Shop", Code: "shop1", Status: consts.TenantStatusVerified} models.TenantQuery.WithContext(ctx).Create(tenant) // Content content := &models.Content{TenantID: tenant.ID, UserID: creator.ID, Title: "Song A", Status: consts.ContentStatusPublished} models.ContentQuery.WithContext(ctx).Create(content) // Price (10.00 CNY = 1000 cents) price := &models.ContentPrice{TenantID: tenant.ID, ContentID: content.ID, PriceAmount: 1000, Currency: consts.CurrencyCNY} models.ContentPriceQuery.WithContext(ctx).Create(price) // Buyer buyer := &models.User{Username: "buyer", Phone: "13900000001", Balance: 2000} // Has 20.00 models.UserQuery.WithContext(ctx).Create(buyer) buyerCtx := context.WithValue(ctx, consts.CtxKeyUser, buyer.ID) Convey("should create and pay order successfully", func() { // Step 1: Create Order form := &order_dto.OrderCreateForm{ContentID: cast.ToString(content.ID)} createRes, err := Order.Create(buyerCtx, form) So(err, ShouldBeNil) So(createRes.OrderID, ShouldNotBeEmpty) // Verify created status oid := cast.ToInt64(createRes.OrderID) o, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First() So(o.Status, ShouldEqual, consts.OrderStatusCreated) So(o.AmountPaid, ShouldEqual, 1000) // Step 2: Pay Order payForm := &order_dto.OrderPayForm{Method: "balance"} _, err = Order.Pay(buyerCtx, createRes.OrderID, payForm) So(err, ShouldBeNil) // Verify Order Paid o, _ = models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First() So(o.Status, ShouldEqual, consts.OrderStatusPaid) So(o.PaidAt, ShouldNotBeZeroValue) // Verify Balance Deducted b, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(buyer.ID)).First() So(b.Balance, ShouldEqual, 1000) // 2000 - 1000 // Verify Access Granted access, _ := models.ContentAccessQuery.WithContext(ctx).Where(models.ContentAccessQuery.UserID.Eq(buyer.ID), models.ContentAccessQuery.ContentID.Eq(content.ID)).First() So(access, ShouldNotBeNil) So(access.Status, ShouldEqual, consts.ContentAccessStatusActive) // Verify Ledger Created (Creator received money logic?) // Note: My implementation credits the TENANT OWNER (creator.ID). l, _ := models.TenantLedgerQuery.WithContext(ctx).Where(models.TenantLedgerQuery.OrderID.Eq(o.ID)).First() So(l, ShouldNotBeNil) So(l.UserID, ShouldEqual, creator.ID) So(l.Amount, ShouldEqual, 1000) So(l.Type, ShouldEqual, consts.TenantLedgerTypeDebitPurchase) }) Convey("should fail pay if insufficient balance", func() { // Set balance to 5.00 models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(buyer.ID)).Update(models.UserQuery.Balance, 500) form := &order_dto.OrderCreateForm{ContentID: cast.ToString(content.ID)} createRes, err := Order.Create(buyerCtx, form) So(err, ShouldBeNil) payForm := &order_dto.OrderPayForm{Method: "balance"} _, err = Order.Pay(buyerCtx, createRes.OrderID, payForm) So(err, ShouldNotBeNil) // Error should be QuotaExceeded or similar }) }) }