Files
quyun-v2/backend/app/services/content_test.go
2026-01-08 14:07:58 +08:00

379 lines
12 KiB
Go

package services
import (
"context"
"database/sql"
"testing"
"quyun/v2/app/commands/testx"
content_dto "quyun/v2/app/http/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/database"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/suite"
"go.ipao.vip/atom/contracts"
"go.uber.org/dig"
)
type ContentTestSuiteInjectParams struct {
dig.In
DB *sql.DB
Initials []contracts.Initial `group:"initials"`
}
type ContentTestSuite struct {
suite.Suite
ContentTestSuiteInjectParams
}
func Test_Content(t *testing.T) {
providers := testx.Default().With(Provide)
testx.Serve(providers, t, func(p ContentTestSuiteInjectParams) {
suite.Run(t, &ContentTestSuite{ContentTestSuiteInjectParams: p})
})
}
func (s *ContentTestSuite) Test_List() {
Convey("List", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameUser)
// Create Author
author := &models.User{Nickname: "Author1", Username: "author1", Phone: "13800000001"}
models.UserQuery.WithContext(ctx).Create(author)
// Create Contents
c1 := &models.Content{
TenantID: 1,
UserID: author.ID,
Title: "Content A",
Status: consts.ContentStatusPublished,
Genre: "video",
}
c2 := &models.Content{
TenantID: 1,
UserID: author.ID,
Title: "Content B",
Status: consts.ContentStatusDraft, // Draft
Genre: "video",
}
models.ContentQuery.WithContext(ctx).Create(c1, c2)
Convey("should list only published contents", func() {
tid := int64(1)
filter := &content_dto.ContentListFilter{
TenantID: &tid,
Pagination: requests.Pagination{
Page: 1,
Limit: 10,
},
}
res, err := Content.List(ctx, filter)
So(err, ShouldBeNil)
So(res.Total, ShouldEqual, 1)
items := res.Items.([]content_dto.ContentItem)
So(items[0].Title, ShouldEqual, "Content A")
So(items[0].AuthorName, ShouldEqual, "Author1")
})
})
}
func (s *ContentTestSuite) Test_Get() {
Convey("Get", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameMediaAsset, models.TableNameContentAsset, models.TableNameUser)
// Author
author := &models.User{Nickname: "Author1", Username: "author1", Phone: "13800000002"}
models.UserQuery.WithContext(ctx).Create(author)
// Asset
asset := &models.MediaAsset{
TenantID: 1,
UserID: author.ID,
ObjectKey: "test.mp4",
Type: consts.MediaAssetTypeVideo,
}
models.MediaAssetQuery.WithContext(ctx).Create(asset)
// Content
content := &models.Content{
TenantID: 1,
UserID: author.ID,
Title: "Detail Content",
Status: consts.ContentStatusPublished,
}
models.ContentQuery.WithContext(ctx).Create(content)
// Link Asset
ca := &models.ContentAsset{
TenantID: 1,
UserID: author.ID,
ContentID: content.ID,
AssetID: asset.ID,
Sort: 1,
Role: consts.ContentAssetRoleMain, // Explicitly set role
}
models.ContentAssetQuery.WithContext(ctx).Create(ca)
// Set context to author
ctx = context.WithValue(ctx, consts.CtxKeyUser, author.ID)
Convey("should get detail with assets", func() {
detail, err := Content.Get(ctx, author.ID, content.ID)
So(err, ShouldBeNil)
So(detail.Title, ShouldEqual, "Detail Content")
So(detail.AuthorName, ShouldEqual, "Author1")
So(len(detail.MediaUrls), ShouldEqual, 1)
So(detail.MediaUrls[0].URL, ShouldContainSubstring, "test.mp4")
})
})
}
func (s *ContentTestSuite) Test_CreateComment() {
Convey("CreateComment", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameComment, models.TableNameUser)
// User & Content
u := &models.User{Username: "user1", Phone: "13900000001"}
models.UserQuery.WithContext(ctx).Create(u)
c := &models.Content{TenantID: 1, UserID: u.ID, Title: "C"}
models.ContentQuery.WithContext(ctx).Create(c)
// Auth context
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
Convey("should create comment", func() {
form := &content_dto.CommentCreateForm{
Content: "Nice!",
}
err := Content.CreateComment(ctx, u.ID, c.ID, form)
So(err, ShouldBeNil)
count, _ := models.CommentQuery.WithContext(ctx).Where(models.CommentQuery.ContentID.Eq(c.ID)).Count()
So(count, ShouldEqual, 1)
})
})
}
func (s *ContentTestSuite) Test_Library() {
Convey("Library", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameContentAccess, models.TableNameUser, models.TableNameContentAsset, models.TableNameMediaAsset)
// User
u := &models.User{Username: "user_lib", Phone: "13900000002"}
models.UserQuery.WithContext(ctx).Create(u)
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
// Content
c := &models.Content{TenantID: 1, UserID: u.ID, Title: "Paid Content", Genre: "video"}
models.ContentQuery.WithContext(ctx).Create(c)
// Asset (Video & Cover)
assetVid := &models.MediaAsset{TenantID: 1, UserID: u.ID, Type: consts.MediaAssetTypeVideo, ObjectKey: "video.mp4"}
assetImg := &models.MediaAsset{TenantID: 1, UserID: u.ID, Type: consts.MediaAssetTypeImage, ObjectKey: "cover.jpg"}
models.MediaAssetQuery.WithContext(ctx).Create(assetVid, assetImg)
models.ContentAssetQuery.WithContext(ctx).Create(
&models.ContentAsset{ContentID: c.ID, AssetID: assetVid.ID, Role: consts.ContentAssetRoleMain},
&models.ContentAsset{ContentID: c.ID, AssetID: assetImg.ID, Role: consts.ContentAssetRoleCover},
)
// Access
models.ContentAccessQuery.WithContext(ctx).Create(&models.ContentAccess{
TenantID: 1, UserID: u.ID, ContentID: c.ID, Status: "active",
})
Convey("should get library content with details", func() {
list, err := Content.GetLibrary(ctx, u.ID)
So(err, ShouldBeNil)
So(len(list), ShouldEqual, 1)
So(list[0].Title, ShouldEqual, "Paid Content")
So(list[0].Type, ShouldEqual, "video")
So(list[0].Cover, ShouldContainSubstring, "cover.jpg")
So(list[0].IsPurchased, ShouldBeTrue)
})
})
}
func (s *ContentTestSuite) Test_Interact() {
Convey("Interact", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameUserContentAction, models.TableNameUser)
// User & Content
u := &models.User{Username: "user_act", Phone: "13900000003"}
models.UserQuery.WithContext(ctx).Create(u)
c := &models.Content{TenantID: 1, UserID: u.ID, Title: "Liked Content", Likes: 0}
models.ContentQuery.WithContext(ctx).Create(c)
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
Convey("Like flow", func() {
// Add Like
err := Content.AddLike(ctx, u.ID, c.ID)
So(err, ShouldBeNil)
// Verify count
cReload, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(c.ID)).First()
So(cReload.Likes, ShouldEqual, 1)
// Get Likes
likes, err := Content.GetLikes(ctx, u.ID)
So(err, ShouldBeNil)
So(len(likes), ShouldEqual, 1)
So(likes[0].ID, ShouldEqual, c.ID)
// Remove Like
err = Content.RemoveLike(ctx, u.ID, c.ID)
So(err, ShouldBeNil)
// Verify count
cReload, _ = models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(c.ID)).First()
So(cReload.Likes, ShouldEqual, 0)
})
Convey("Favorite flow", func() {
// Add Favorite
err := Content.AddFavorite(ctx, u.ID, c.ID)
So(err, ShouldBeNil)
// Get Favorites
favs, err := Content.GetFavorites(ctx, u.ID)
So(err, ShouldBeNil)
So(len(favs), ShouldEqual, 1)
So(favs[0].ID, ShouldEqual, c.ID)
// Remove Favorite
err = Content.RemoveFavorite(ctx, u.ID, c.ID)
So(err, ShouldBeNil)
// Get Favorites
favs, err = Content.GetFavorites(ctx, u.ID)
So(err, ShouldBeNil)
So(len(favs), ShouldEqual, 0)
})
})
}
func (s *ContentTestSuite) Test_ListTopics() {
Convey("ListTopics", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameUser)
u := &models.User{Username: "user_t", Phone: "13900000005"}
models.UserQuery.WithContext(ctx).Create(u)
// Create Contents: 2 video, 1 audio
models.ContentQuery.WithContext(ctx).Create(
&models.Content{TenantID: 1, UserID: u.ID, Title: "V1", Genre: "video", Status: consts.ContentStatusPublished},
&models.Content{TenantID: 1, UserID: u.ID, Title: "V2", Genre: "video", Status: consts.ContentStatusPublished},
&models.Content{TenantID: 1, UserID: u.ID, Title: "A1", Genre: "audio", Status: consts.ContentStatusPublished},
&models.Content{TenantID: 1, UserID: u.ID, Title: "D1", Genre: "draft", Status: consts.ContentStatusDraft}, // Should ignore
)
Convey("should aggregate topics", func() {
topics, err := Content.ListTopics(ctx)
So(err, ShouldBeNil)
So(len(topics), ShouldBeGreaterThanOrEqualTo, 2)
var videoCount, audioCount int
for _, t := range topics {
if t.Tag == "video" {
videoCount = t.Count
}
if t.Tag == "audio" {
audioCount = t.Count
}
}
So(videoCount, ShouldEqual, 2)
So(audioCount, ShouldEqual, 1)
})
})
}
func (s *ContentTestSuite) Test_PreviewLogic() {
Convey("Preview Logic", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameContentAsset, models.TableNameContentAccess, models.TableNameUser, models.TableNameMediaAsset)
author := &models.User{Username: "author_p", Phone: "13900000006"}
models.UserQuery.WithContext(ctx).Create(author)
c := &models.Content{TenantID: 1, UserID: author.ID, Title: "Premium", Status: consts.ContentStatusPublished}
models.ContentQuery.WithContext(ctx).Create(c)
assetMain := &models.MediaAsset{ObjectKey: "main.mp4", Type: consts.MediaAssetTypeVideo}
assetPrev := &models.MediaAsset{ObjectKey: "preview.mp4", Type: consts.MediaAssetTypeVideo}
models.MediaAssetQuery.WithContext(ctx).Create(assetMain, assetPrev)
models.ContentAssetQuery.WithContext(ctx).Create(
&models.ContentAsset{ContentID: c.ID, AssetID: assetMain.ID, Role: consts.ContentAssetRoleMain},
&models.ContentAsset{ContentID: c.ID, AssetID: assetPrev.ID, Role: consts.ContentAssetRolePreview},
)
Convey("guest should see preview only", func() {
guest := &models.User{Username: "guest", Phone: "13900000007"}
models.UserQuery.WithContext(ctx).Create(guest)
guestCtx := context.WithValue(ctx, consts.CtxKeyUser, guest.ID)
detail, err := Content.Get(guestCtx, 0, c.ID)
So(err, ShouldBeNil)
So(len(detail.MediaUrls), ShouldEqual, 1)
So(detail.MediaUrls[0].URL, ShouldContainSubstring, "preview.mp4")
So(detail.IsPurchased, ShouldBeFalse)
})
Convey("owner should see all", func() {
ownerCtx := context.WithValue(ctx, consts.CtxKeyUser, author.ID)
detail, err := Content.Get(ownerCtx, author.ID, c.ID)
So(err, ShouldBeNil)
So(len(detail.MediaUrls), ShouldEqual, 2)
So(detail.IsPurchased, ShouldBeTrue)
})
Convey("buyer should see all", func() {
buyer := &models.User{Username: "buyer_p", Phone: "13900000008"}
models.UserQuery.WithContext(ctx).Create(buyer)
buyerCtx := context.WithValue(ctx, consts.CtxKeyUser, buyer.ID)
models.ContentAccessQuery.WithContext(ctx).Create(&models.ContentAccess{
UserID: buyer.ID, ContentID: c.ID, Status: consts.ContentAccessStatusActive,
})
detail, err := Content.Get(buyerCtx, buyer.ID, c.ID)
So(err, ShouldBeNil)
So(len(detail.MediaUrls), ShouldEqual, 2)
So(detail.IsPurchased, ShouldBeTrue)
})
})
}
func (s *ContentTestSuite) Test_ViewCounting() {
Convey("ViewCounting", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameUser)
author := &models.User{Username: "author_v", Phone: "13900000009"}
models.UserQuery.WithContext(ctx).Create(author)
c := &models.Content{TenantID: 1, UserID: author.ID, Title: "View Me", Views: 0, Status: consts.ContentStatusPublished}
models.ContentQuery.WithContext(ctx).Create(c)
Convey("should increment views", func() {
_, err := Content.Get(ctx, 0, c.ID)
So(err, ShouldBeNil)
cReload, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(c.ID)).First()
So(cReload.Views, ShouldEqual, 1)
})
})
}