123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- package publish_test
- import (
- "context"
- "errors"
- "fmt"
- "io"
- "os"
- "sort"
- "strings"
- "testing"
- "time"
- "github.com/qor/oss"
- "github.com/qor5/admin/publish"
- "github.com/theplant/sliceutils"
- "gorm.io/driver/postgres"
- "gorm.io/gorm"
- "gorm.io/gorm/clause"
- )
- type Product struct {
- gorm.Model
- Name string
- Code string
- publish.Version
- publish.Schedule
- publish.Status
- publish.List
- }
- func (p *Product) getContent() string {
- return p.Code + p.Name + p.VersionName
- }
- func (p *Product) getUrl() string {
- return fmt.Sprintf("test/product/%s/index.html", p.Code)
- }
- func (p *Product) getListUrl() string {
- return "test/product/list/index.html"
- }
- func (p *Product) getListContent() string {
- return fmt.Sprintf("list page %s", p.Code)
- }
- func (p *Product) GetPublishActions(db *gorm.DB, ctx context.Context, storage oss.StorageInterface) (objs []*publish.PublishAction, err error) {
- objs = append(objs, &publish.PublishAction{
- Url: p.getUrl(),
- Content: p.getContent(),
- IsDelete: false,
- })
- p.SetOnlineUrl(p.getUrl())
- var liveRecord Product
- db.Where("id = ? AND status = ?", p.ID, publish.StatusOnline).First(&liveRecord)
- if liveRecord.ID == 0 {
- return
- }
- if liveRecord.GetOnlineUrl() != p.GetOnlineUrl() {
- objs = append(objs, &publish.PublishAction{
- Url: liveRecord.getUrl(),
- IsDelete: true,
- })
- }
- if val, ok := ctx.Value("skip_list").(bool); ok && val {
- return
- }
- objs = append(objs, &publish.PublishAction{
- Url: p.getListUrl(),
- Content: p.getListContent(),
- IsDelete: false,
- })
- return
- }
- func (p *Product) GetUnPublishActions(db *gorm.DB, ctx context.Context, storage oss.StorageInterface) (objs []*publish.PublishAction, err error) {
- objs = append(objs, &publish.PublishAction{
- Url: p.GetOnlineUrl(),
- IsDelete: true,
- })
- if val, ok := ctx.Value("skip_list").(bool); ok && val {
- return
- }
- objs = append(objs, &publish.PublishAction{
- Url: p.getListUrl(),
- IsDelete: true,
- })
- return
- }
- type ProductWithoutVersion struct {
- gorm.Model
- Name string
- Code string
- publish.Status
- publish.List
- }
- func (p *ProductWithoutVersion) getContent() string {
- return p.Code + p.Name
- }
- func (p *ProductWithoutVersion) getUrl() string {
- return fmt.Sprintf("test/product_no_version/%s/index.html", p.Code)
- }
- func (p *ProductWithoutVersion) GetPublishActions(db *gorm.DB, ctx context.Context, storage oss.StorageInterface) (objs []*publish.PublishAction, err error) {
- objs = append(objs, &publish.PublishAction{
- Url: p.getUrl(),
- Content: p.getContent(),
- IsDelete: false,
- })
- if p.GetStatus() == publish.StatusOnline && p.GetOnlineUrl() != p.getUrl() {
- objs = append(objs, &publish.PublishAction{
- Url: p.GetOnlineUrl(),
- IsDelete: true,
- })
- }
- p.SetOnlineUrl(p.getUrl())
- return
- }
- func (p *ProductWithoutVersion) GetUnPublishActions(db *gorm.DB, ctx context.Context, storage oss.StorageInterface) (objs []*publish.PublishAction, err error) {
- objs = append(objs, &publish.PublishAction{
- Url: p.GetOnlineUrl(),
- IsDelete: true,
- })
- return
- }
- func (this ProductWithoutVersion) GetListUrl(pageNumber string) string {
- return fmt.Sprintf("/product_without_version/list/%v.html", pageNumber)
- }
- func (this ProductWithoutVersion) GetListContent(db *gorm.DB, onePageItems *publish.OnePageItems) string {
- pageNumber := onePageItems.PageNumber
- var result string
- for _, item := range onePageItems.Items {
- record := item.(*ProductWithoutVersion)
- result = result + fmt.Sprintf("product:%v ", record.Name)
- }
- result = result + fmt.Sprintf("pageNumber:%v", pageNumber)
- return result
- }
- func (this ProductWithoutVersion) Sort(array []interface{}) {
- var temp []*ProductWithoutVersion
- sliceutils.Unwrap(array, &temp)
- sort.Sort(SliceProductWithoutVersion(temp))
- for k, v := range temp {
- array[k] = v
- }
- return
- }
- type SliceProductWithoutVersion []*ProductWithoutVersion
- func (x SliceProductWithoutVersion) Len() int { return len(x) }
- func (x SliceProductWithoutVersion) Less(i, j int) bool { return x[i].Name < x[j].Name }
- func (x SliceProductWithoutVersion) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
- type MockStorage struct {
- oss.StorageInterface
- Objects map[string]string
- }
- func (m *MockStorage) Get(path string) (f *os.File, err error) {
- var content, exist = m.Objects[path]
- if !exist {
- err = errors.New("NoSuchKey: The specified key does not exist")
- return
- }
- pattern := fmt.Sprintf("s3*%d", time.Now().Unix())
- if err == nil {
- if f, err = os.CreateTemp("/tmp", pattern); err == nil {
- f.WriteString(content)
- f.Seek(0, 0)
- }
- }
- return
- }
- func (m *MockStorage) Put(path string, r io.Reader) (*oss.Object, error) {
- fmt.Println("Calling mock s3 client - Put: ", path)
- b, err := io.ReadAll(r)
- if err != nil {
- panic(err)
- }
- if m.Objects == nil {
- m.Objects = make(map[string]string)
- }
- m.Objects[path] = string(b)
- return &oss.Object{}, nil
- }
- func (m *MockStorage) Delete(path string) error {
- fmt.Println("Calling mock s3 client - Delete: ", path)
- delete(m.Objects, path)
- return nil
- }
- func ConnectDB() *gorm.DB {
- db, err := gorm.Open(postgres.Open(os.Getenv("DBURL")), &gorm.Config{})
- if err != nil {
- panic(err)
- }
- return db.Debug()
- }
- func TestPublishVersionContentToS3(t *testing.T) {
- db := ConnectDB()
- db.AutoMigrate(&Product{})
- storage := &MockStorage{}
- productV1 := Product{
- Model: gorm.Model{ID: 1},
- Code: "0001",
- Name: "coffee",
- Status: publish.Status{Status: publish.StatusDraft},
- Version: publish.Version{Version: "v1"},
- }
- productV2 := Product{
- Model: gorm.Model{ID: 1},
- Code: "0002",
- Name: "coffee",
- Status: publish.Status{Status: publish.StatusDraft},
- Version: publish.Version{Version: "v2"},
- }
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV1)
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV2)
- p := publish.New(db, storage)
- // publish v1
- if err := p.WithValue("skip_list", true).Publish(&productV1); err != nil {
- t.Error(err)
- }
- if err := assertUpdateStatus(db, &productV1, publish.StatusOnline, productV1.getUrl()); err != nil {
- t.Error(err)
- }
- if err := assertUploadFile(&productV1, storage); err != nil {
- t.Error(err)
- }
- if err := assertUploadListFile(&productV1, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
- } else {
- t.Error(errors.New("skip_list failed"))
- }
- // publish v2
- if err := p.WithValue("skip_list", false).Publish(&productV2); err != nil {
- t.Error(err)
- }
- if err := assertUpdateStatus(db, &productV2, publish.StatusOnline, productV2.getUrl()); err != nil {
- t.Error(err)
- }
- if err := assertUploadFile(&productV2, storage); err != nil {
- t.Error(err)
- }
- if err := assertUploadListFile(&productV2, storage); err != nil {
- t.Error(err)
- }
- // if delete v1 file
- if err := assertUploadFile(&productV1, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
- } else {
- t.Error(errors.New(fmt.Sprintf("delete file %s failed", productV1.getUrl())))
- }
- // if update v1 status to offline
- if err := assertUpdateStatus(db, &productV1, publish.StatusOffline, productV1.getUrl()); err != nil {
- t.Error(err)
- }
- // unpublish v2
- if err := p.UnPublish(&productV2); err != nil {
- t.Error(err)
- }
- if err := assertUpdateStatus(db, &productV2, publish.StatusOffline, productV2.getUrl()); err != nil {
- t.Error(err)
- }
- if err := assertUploadFile(&productV2, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
- } else {
- t.Error(errors.New(fmt.Sprintf("delete file %s failed", productV2.getUrl())))
- }
- if err := assertUploadListFile(&productV2, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
- } else {
- t.Error(errors.New("delete list file %s failed"), productV2.getListUrl())
- }
- }
- func TestPublishList(t *testing.T) {
- db := ConnectDB()
- db.AutoMigrate(&ProductWithoutVersion{})
- storage := &MockStorage{}
- productV1 := ProductWithoutVersion{
- Model: gorm.Model{ID: 1},
- Code: "1",
- Name: "1",
- Status: publish.Status{Status: publish.StatusDraft},
- }
- productV2 := ProductWithoutVersion{
- Model: gorm.Model{ID: 2},
- Code: "2",
- Name: "2",
- Status: publish.Status{Status: publish.StatusDraft},
- }
- productV3 := ProductWithoutVersion{
- Model: gorm.Model{ID: 3},
- Code: "3",
- Name: "3",
- Status: publish.Status{Status: publish.StatusDraft},
- }
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV1)
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV2)
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV3)
- publisher := publish.New(db, storage)
- listPublisher := publish.NewListPublishBuilder(db, storage)
- publisher.Publish(&productV1)
- publisher.Publish(&productV3)
- if err := listPublisher.Run(ProductWithoutVersion{}); err != nil {
- panic(err)
- }
- var expected string
- expected = "product:1 product:3 pageNumber:1"
- if storage.Objects["/product_without_version/list/1.html"] != expected {
- t.Error(errors.New(fmt.Sprintf(`
- want: %v
- get: %v
- `, expected, storage.Objects["/product_without_version/list/1.html"])))
- }
- publisher.Publish(&productV2)
- if err := listPublisher.Run(ProductWithoutVersion{}); err != nil {
- panic(err)
- }
- expected = "product:1 product:2 product:3 pageNumber:1"
- if storage.Objects["/product_without_version/list/1.html"] != expected {
- t.Error(errors.New(fmt.Sprintf(`
- want: %v
- get: %v
- `, expected, storage.Objects["/product_without_version/list/1.html"])))
- }
- publisher.UnPublish(&productV2)
- if err := listPublisher.Run(ProductWithoutVersion{}); err != nil {
- panic(err)
- }
- expected = "product:1 product:3 pageNumber:1"
- if storage.Objects["/product_without_version/list/1.html"] != expected {
- t.Error(errors.New(fmt.Sprintf(`
- want: %v
- get: %v
- `, expected, storage.Objects["/product_without_version/list/1.html"])))
- }
- publisher.UnPublish(&productV3)
- if err := listPublisher.Run(ProductWithoutVersion{}); err != nil {
- panic(err)
- }
- expected = "product:1 pageNumber:1"
- if storage.Objects["/product_without_version/list/1.html"] != expected {
- t.Error(errors.New(fmt.Sprintf(`
- want: %v
- get: %v
- `, expected, storage.Objects["/product_without_version/list/1.html"])))
- }
- }
- func TestSchedulePublish(t *testing.T) {
- db := ConnectDB()
- db.Migrator().DropTable(&Product{})
- db.AutoMigrate(&Product{})
- storage := &MockStorage{}
- productV1 := Product{
- Model: gorm.Model{ID: 1},
- Version: publish.Version{Version: "2021-12-19-v01"},
- Code: "1",
- Name: "1",
- Status: publish.Status{Status: publish.StatusDraft},
- }
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV1)
- publisher := publish.New(db, storage)
- publisher.Publish(&productV1)
- var expected string
- expected = "11"
- if storage.Objects["test/product/1/index.html"] != expected {
- t.Error(errors.New(fmt.Sprintf(`
- want: %v
- get: %v
- `, expected, storage.Objects["test/product/1/index.html"])))
- }
- productV1.Name = "2"
- var startAt = db.NowFunc().Add(-24 * time.Hour)
- productV1.SetScheduledStartAt(&startAt)
- if err := db.Save(&productV1).Error; err != nil {
- panic(err)
- }
- schedulePublisher := publish.NewSchedulePublishBuilder(publisher)
- if err := schedulePublisher.Run(productV1); err != nil {
- panic(err)
- }
- expected = "12"
- if storage.Objects["test/product/1/index.html"] != expected {
- t.Error(errors.New(fmt.Sprintf(`
- want: %v
- get: %v
- `, expected, storage.Objects["test/product/1/index.html"])))
- }
- var endAt = startAt.Add(time.Second * 2)
- productV1.SetScheduledEndAt(&endAt)
- if err := db.Save(&productV1).Error; err != nil {
- panic(err)
- }
- if err := schedulePublisher.Run(productV1); err != nil {
- panic(err)
- }
- expected = ""
- if storage.Objects["test/product/1/index.html"] != expected {
- t.Error(errors.New(fmt.Sprintf(`
- want: %v
- get: %v
- `, expected, storage.Objects["test/product/1/index.html"])))
- }
- }
- func TestPublishContentWithoutVersionToS3(t *testing.T) {
- db := ConnectDB()
- db.AutoMigrate(&ProductWithoutVersion{})
- storage := &MockStorage{}
- product1 := ProductWithoutVersion{
- Model: gorm.Model{ID: 1},
- Code: "0001",
- Name: "tea",
- Status: publish.Status{Status: publish.StatusDraft},
- }
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&product1)
- p := publish.New(db, storage)
- // publish product1
- if err := p.Publish(&product1); err != nil {
- t.Error(err)
- }
- if err := assertNoVersionUpdateStatus(db, &product1, publish.StatusOnline, product1.getUrl()); err != nil {
- t.Error(err)
- }
- if err := assertNoVersionUploadFile(&product1, storage); err != nil {
- t.Error(err)
- }
- product1Clone := product1
- product1Clone.Code = "0002"
- // publish product1 again
- if err := p.Publish(&product1Clone); err != nil {
- t.Error(err)
- }
- if err := assertNoVersionUpdateStatus(db, &product1Clone, publish.StatusOnline, product1Clone.getUrl()); err != nil {
- t.Error(err)
- }
- if err := assertNoVersionUploadFile(&product1Clone, storage); err != nil {
- t.Error(err)
- }
- // if delete product1 old file
- if err := assertNoVersionUploadFile(&product1, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
- } else {
- t.Error(errors.New(fmt.Sprintf("delete file %s failed", product1.getUrl())))
- }
- // unpublish product1
- if err := p.UnPublish(&product1Clone); err != nil {
- t.Error(err)
- }
- if err := assertNoVersionUpdateStatus(db, &product1Clone, publish.StatusOffline, product1Clone.getUrl()); err != nil {
- t.Error(err)
- }
- // if delete product1 file
- if err := assertNoVersionUploadFile(&product1Clone, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
- } else {
- t.Error(errors.New(fmt.Sprintf("delete file %s failed", product1Clone.getUrl())))
- }
- }
- func assertUpdateStatus(db *gorm.DB, p *Product, assertStatus string, asserOnlineUrl string) (err error) {
- var pindb Product
- err = db.Model(&Product{}).Where("id = ? AND version = ?", p.ID, p.GetVersion()).First(&pindb).Error
- if err != nil {
- return err
- }
- if pindb.GetStatus() != assertStatus || pindb.GetOnlineUrl() != asserOnlineUrl {
- return errors.New("update status failed")
- }
- return
- }
- func assertNoVersionUpdateStatus(db *gorm.DB, p *ProductWithoutVersion, assertStatus string, asserOnlineUrl string) (err error) {
- var pindb ProductWithoutVersion
- err = db.Model(&ProductWithoutVersion{}).Where("id = ?", p.ID).First(&pindb).Error
- if err != nil {
- return err
- }
- if pindb.GetStatus() != assertStatus || pindb.GetOnlineUrl() != asserOnlineUrl {
- return errors.New("update status failed")
- }
- return
- }
- func assertUploadFile(p *Product, storage oss.StorageInterface) error {
- f, err := storage.Get(p.getUrl())
- if err != nil {
- return err
- }
- c, err := io.ReadAll(f)
- if string(c) != p.getContent() {
- return errors.New("wrong content")
- }
- return nil
- }
- func assertUploadListFile(p *Product, storage oss.StorageInterface) error {
- f, err := storage.Get(p.getListUrl())
- if err != nil {
- return err
- }
- c, err := io.ReadAll(f)
- if string(c) != p.getListContent() {
- return errors.New("wrong content")
- }
- return nil
- }
- func assertNoVersionUploadFile(p *ProductWithoutVersion, storage oss.StorageInterface) error {
- f, err := storage.Get(p.getUrl())
- if err != nil {
- return err
- }
- c, err := io.ReadAll(f)
- if string(c) != p.getContent() {
- return errors.New("wrong content")
- }
- return nil
- }
|