publish_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. package publish_test
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "os"
  8. "sort"
  9. "strings"
  10. "testing"
  11. "time"
  12. "github.com/qor/oss"
  13. "github.com/qor5/admin/publish"
  14. "github.com/theplant/sliceutils"
  15. "gorm.io/driver/postgres"
  16. "gorm.io/gorm"
  17. "gorm.io/gorm/clause"
  18. )
  19. type Product struct {
  20. gorm.Model
  21. Name string
  22. Code string
  23. publish.Version
  24. publish.Schedule
  25. publish.Status
  26. publish.List
  27. }
  28. func (p *Product) getContent() string {
  29. return p.Code + p.Name + p.VersionName
  30. }
  31. func (p *Product) getUrl() string {
  32. return fmt.Sprintf("test/product/%s/index.html", p.Code)
  33. }
  34. func (p *Product) getListUrl() string {
  35. return "test/product/list/index.html"
  36. }
  37. func (p *Product) getListContent() string {
  38. return fmt.Sprintf("list page %s", p.Code)
  39. }
  40. func (p *Product) GetPublishActions(db *gorm.DB, ctx context.Context, storage oss.StorageInterface) (objs []*publish.PublishAction, err error) {
  41. objs = append(objs, &publish.PublishAction{
  42. Url: p.getUrl(),
  43. Content: p.getContent(),
  44. IsDelete: false,
  45. })
  46. p.SetOnlineUrl(p.getUrl())
  47. var liveRecord Product
  48. db.Where("id = ? AND status = ?", p.ID, publish.StatusOnline).First(&liveRecord)
  49. if liveRecord.ID == 0 {
  50. return
  51. }
  52. if liveRecord.GetOnlineUrl() != p.GetOnlineUrl() {
  53. objs = append(objs, &publish.PublishAction{
  54. Url: liveRecord.getUrl(),
  55. IsDelete: true,
  56. })
  57. }
  58. if val, ok := ctx.Value("skip_list").(bool); ok && val {
  59. return
  60. }
  61. objs = append(objs, &publish.PublishAction{
  62. Url: p.getListUrl(),
  63. Content: p.getListContent(),
  64. IsDelete: false,
  65. })
  66. return
  67. }
  68. func (p *Product) GetUnPublishActions(db *gorm.DB, ctx context.Context, storage oss.StorageInterface) (objs []*publish.PublishAction, err error) {
  69. objs = append(objs, &publish.PublishAction{
  70. Url: p.GetOnlineUrl(),
  71. IsDelete: true,
  72. })
  73. if val, ok := ctx.Value("skip_list").(bool); ok && val {
  74. return
  75. }
  76. objs = append(objs, &publish.PublishAction{
  77. Url: p.getListUrl(),
  78. IsDelete: true,
  79. })
  80. return
  81. }
  82. type ProductWithoutVersion struct {
  83. gorm.Model
  84. Name string
  85. Code string
  86. publish.Status
  87. publish.List
  88. }
  89. func (p *ProductWithoutVersion) getContent() string {
  90. return p.Code + p.Name
  91. }
  92. func (p *ProductWithoutVersion) getUrl() string {
  93. return fmt.Sprintf("test/product_no_version/%s/index.html", p.Code)
  94. }
  95. func (p *ProductWithoutVersion) GetPublishActions(db *gorm.DB, ctx context.Context, storage oss.StorageInterface) (objs []*publish.PublishAction, err error) {
  96. objs = append(objs, &publish.PublishAction{
  97. Url: p.getUrl(),
  98. Content: p.getContent(),
  99. IsDelete: false,
  100. })
  101. if p.GetStatus() == publish.StatusOnline && p.GetOnlineUrl() != p.getUrl() {
  102. objs = append(objs, &publish.PublishAction{
  103. Url: p.GetOnlineUrl(),
  104. IsDelete: true,
  105. })
  106. }
  107. p.SetOnlineUrl(p.getUrl())
  108. return
  109. }
  110. func (p *ProductWithoutVersion) GetUnPublishActions(db *gorm.DB, ctx context.Context, storage oss.StorageInterface) (objs []*publish.PublishAction, err error) {
  111. objs = append(objs, &publish.PublishAction{
  112. Url: p.GetOnlineUrl(),
  113. IsDelete: true,
  114. })
  115. return
  116. }
  117. func (this ProductWithoutVersion) GetListUrl(pageNumber string) string {
  118. return fmt.Sprintf("/product_without_version/list/%v.html", pageNumber)
  119. }
  120. func (this ProductWithoutVersion) GetListContent(db *gorm.DB, onePageItems *publish.OnePageItems) string {
  121. pageNumber := onePageItems.PageNumber
  122. var result string
  123. for _, item := range onePageItems.Items {
  124. record := item.(*ProductWithoutVersion)
  125. result = result + fmt.Sprintf("product:%v ", record.Name)
  126. }
  127. result = result + fmt.Sprintf("pageNumber:%v", pageNumber)
  128. return result
  129. }
  130. func (this ProductWithoutVersion) Sort(array []interface{}) {
  131. var temp []*ProductWithoutVersion
  132. sliceutils.Unwrap(array, &temp)
  133. sort.Sort(SliceProductWithoutVersion(temp))
  134. for k, v := range temp {
  135. array[k] = v
  136. }
  137. return
  138. }
  139. type SliceProductWithoutVersion []*ProductWithoutVersion
  140. func (x SliceProductWithoutVersion) Len() int { return len(x) }
  141. func (x SliceProductWithoutVersion) Less(i, j int) bool { return x[i].Name < x[j].Name }
  142. func (x SliceProductWithoutVersion) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
  143. type MockStorage struct {
  144. oss.StorageInterface
  145. Objects map[string]string
  146. }
  147. func (m *MockStorage) Get(path string) (f *os.File, err error) {
  148. var content, exist = m.Objects[path]
  149. if !exist {
  150. err = errors.New("NoSuchKey: The specified key does not exist")
  151. return
  152. }
  153. pattern := fmt.Sprintf("s3*%d", time.Now().Unix())
  154. if err == nil {
  155. if f, err = os.CreateTemp("/tmp", pattern); err == nil {
  156. f.WriteString(content)
  157. f.Seek(0, 0)
  158. }
  159. }
  160. return
  161. }
  162. func (m *MockStorage) Put(path string, r io.Reader) (*oss.Object, error) {
  163. fmt.Println("Calling mock s3 client - Put: ", path)
  164. b, err := io.ReadAll(r)
  165. if err != nil {
  166. panic(err)
  167. }
  168. if m.Objects == nil {
  169. m.Objects = make(map[string]string)
  170. }
  171. m.Objects[path] = string(b)
  172. return &oss.Object{}, nil
  173. }
  174. func (m *MockStorage) Delete(path string) error {
  175. fmt.Println("Calling mock s3 client - Delete: ", path)
  176. delete(m.Objects, path)
  177. return nil
  178. }
  179. func ConnectDB() *gorm.DB {
  180. db, err := gorm.Open(postgres.Open(os.Getenv("DBURL")), &gorm.Config{})
  181. if err != nil {
  182. panic(err)
  183. }
  184. return db.Debug()
  185. }
  186. func TestPublishVersionContentToS3(t *testing.T) {
  187. db := ConnectDB()
  188. db.AutoMigrate(&Product{})
  189. storage := &MockStorage{}
  190. productV1 := Product{
  191. Model: gorm.Model{ID: 1},
  192. Code: "0001",
  193. Name: "coffee",
  194. Status: publish.Status{Status: publish.StatusDraft},
  195. Version: publish.Version{Version: "v1"},
  196. }
  197. productV2 := Product{
  198. Model: gorm.Model{ID: 1},
  199. Code: "0002",
  200. Name: "coffee",
  201. Status: publish.Status{Status: publish.StatusDraft},
  202. Version: publish.Version{Version: "v2"},
  203. }
  204. db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV1)
  205. db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV2)
  206. p := publish.New(db, storage)
  207. // publish v1
  208. if err := p.WithValue("skip_list", true).Publish(&productV1); err != nil {
  209. t.Error(err)
  210. }
  211. if err := assertUpdateStatus(db, &productV1, publish.StatusOnline, productV1.getUrl()); err != nil {
  212. t.Error(err)
  213. }
  214. if err := assertUploadFile(&productV1, storage); err != nil {
  215. t.Error(err)
  216. }
  217. if err := assertUploadListFile(&productV1, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
  218. } else {
  219. t.Error(errors.New("skip_list failed"))
  220. }
  221. // publish v2
  222. if err := p.WithValue("skip_list", false).Publish(&productV2); err != nil {
  223. t.Error(err)
  224. }
  225. if err := assertUpdateStatus(db, &productV2, publish.StatusOnline, productV2.getUrl()); err != nil {
  226. t.Error(err)
  227. }
  228. if err := assertUploadFile(&productV2, storage); err != nil {
  229. t.Error(err)
  230. }
  231. if err := assertUploadListFile(&productV2, storage); err != nil {
  232. t.Error(err)
  233. }
  234. // if delete v1 file
  235. if err := assertUploadFile(&productV1, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
  236. } else {
  237. t.Error(errors.New(fmt.Sprintf("delete file %s failed", productV1.getUrl())))
  238. }
  239. // if update v1 status to offline
  240. if err := assertUpdateStatus(db, &productV1, publish.StatusOffline, productV1.getUrl()); err != nil {
  241. t.Error(err)
  242. }
  243. // unpublish v2
  244. if err := p.UnPublish(&productV2); err != nil {
  245. t.Error(err)
  246. }
  247. if err := assertUpdateStatus(db, &productV2, publish.StatusOffline, productV2.getUrl()); err != nil {
  248. t.Error(err)
  249. }
  250. if err := assertUploadFile(&productV2, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
  251. } else {
  252. t.Error(errors.New(fmt.Sprintf("delete file %s failed", productV2.getUrl())))
  253. }
  254. if err := assertUploadListFile(&productV2, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
  255. } else {
  256. t.Error(errors.New("delete list file %s failed"), productV2.getListUrl())
  257. }
  258. }
  259. func TestPublishList(t *testing.T) {
  260. db := ConnectDB()
  261. db.AutoMigrate(&ProductWithoutVersion{})
  262. storage := &MockStorage{}
  263. productV1 := ProductWithoutVersion{
  264. Model: gorm.Model{ID: 1},
  265. Code: "1",
  266. Name: "1",
  267. Status: publish.Status{Status: publish.StatusDraft},
  268. }
  269. productV2 := ProductWithoutVersion{
  270. Model: gorm.Model{ID: 2},
  271. Code: "2",
  272. Name: "2",
  273. Status: publish.Status{Status: publish.StatusDraft},
  274. }
  275. productV3 := ProductWithoutVersion{
  276. Model: gorm.Model{ID: 3},
  277. Code: "3",
  278. Name: "3",
  279. Status: publish.Status{Status: publish.StatusDraft},
  280. }
  281. db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV1)
  282. db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV2)
  283. db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV3)
  284. publisher := publish.New(db, storage)
  285. listPublisher := publish.NewListPublishBuilder(db, storage)
  286. publisher.Publish(&productV1)
  287. publisher.Publish(&productV3)
  288. if err := listPublisher.Run(ProductWithoutVersion{}); err != nil {
  289. panic(err)
  290. }
  291. var expected string
  292. expected = "product:1 product:3 pageNumber:1"
  293. if storage.Objects["/product_without_version/list/1.html"] != expected {
  294. t.Error(errors.New(fmt.Sprintf(`
  295. want: %v
  296. get: %v
  297. `, expected, storage.Objects["/product_without_version/list/1.html"])))
  298. }
  299. publisher.Publish(&productV2)
  300. if err := listPublisher.Run(ProductWithoutVersion{}); err != nil {
  301. panic(err)
  302. }
  303. expected = "product:1 product:2 product:3 pageNumber:1"
  304. if storage.Objects["/product_without_version/list/1.html"] != expected {
  305. t.Error(errors.New(fmt.Sprintf(`
  306. want: %v
  307. get: %v
  308. `, expected, storage.Objects["/product_without_version/list/1.html"])))
  309. }
  310. publisher.UnPublish(&productV2)
  311. if err := listPublisher.Run(ProductWithoutVersion{}); err != nil {
  312. panic(err)
  313. }
  314. expected = "product:1 product:3 pageNumber:1"
  315. if storage.Objects["/product_without_version/list/1.html"] != expected {
  316. t.Error(errors.New(fmt.Sprintf(`
  317. want: %v
  318. get: %v
  319. `, expected, storage.Objects["/product_without_version/list/1.html"])))
  320. }
  321. publisher.UnPublish(&productV3)
  322. if err := listPublisher.Run(ProductWithoutVersion{}); err != nil {
  323. panic(err)
  324. }
  325. expected = "product:1 pageNumber:1"
  326. if storage.Objects["/product_without_version/list/1.html"] != expected {
  327. t.Error(errors.New(fmt.Sprintf(`
  328. want: %v
  329. get: %v
  330. `, expected, storage.Objects["/product_without_version/list/1.html"])))
  331. }
  332. }
  333. func TestSchedulePublish(t *testing.T) {
  334. db := ConnectDB()
  335. db.Migrator().DropTable(&Product{})
  336. db.AutoMigrate(&Product{})
  337. storage := &MockStorage{}
  338. productV1 := Product{
  339. Model: gorm.Model{ID: 1},
  340. Version: publish.Version{Version: "2021-12-19-v01"},
  341. Code: "1",
  342. Name: "1",
  343. Status: publish.Status{Status: publish.StatusDraft},
  344. }
  345. db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&productV1)
  346. publisher := publish.New(db, storage)
  347. publisher.Publish(&productV1)
  348. var expected string
  349. expected = "11"
  350. if storage.Objects["test/product/1/index.html"] != expected {
  351. t.Error(errors.New(fmt.Sprintf(`
  352. want: %v
  353. get: %v
  354. `, expected, storage.Objects["test/product/1/index.html"])))
  355. }
  356. productV1.Name = "2"
  357. var startAt = db.NowFunc().Add(-24 * time.Hour)
  358. productV1.SetScheduledStartAt(&startAt)
  359. if err := db.Save(&productV1).Error; err != nil {
  360. panic(err)
  361. }
  362. schedulePublisher := publish.NewSchedulePublishBuilder(publisher)
  363. if err := schedulePublisher.Run(productV1); err != nil {
  364. panic(err)
  365. }
  366. expected = "12"
  367. if storage.Objects["test/product/1/index.html"] != expected {
  368. t.Error(errors.New(fmt.Sprintf(`
  369. want: %v
  370. get: %v
  371. `, expected, storage.Objects["test/product/1/index.html"])))
  372. }
  373. var endAt = startAt.Add(time.Second * 2)
  374. productV1.SetScheduledEndAt(&endAt)
  375. if err := db.Save(&productV1).Error; err != nil {
  376. panic(err)
  377. }
  378. if err := schedulePublisher.Run(productV1); err != nil {
  379. panic(err)
  380. }
  381. expected = ""
  382. if storage.Objects["test/product/1/index.html"] != expected {
  383. t.Error(errors.New(fmt.Sprintf(`
  384. want: %v
  385. get: %v
  386. `, expected, storage.Objects["test/product/1/index.html"])))
  387. }
  388. }
  389. func TestPublishContentWithoutVersionToS3(t *testing.T) {
  390. db := ConnectDB()
  391. db.AutoMigrate(&ProductWithoutVersion{})
  392. storage := &MockStorage{}
  393. product1 := ProductWithoutVersion{
  394. Model: gorm.Model{ID: 1},
  395. Code: "0001",
  396. Name: "tea",
  397. Status: publish.Status{Status: publish.StatusDraft},
  398. }
  399. db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&product1)
  400. p := publish.New(db, storage)
  401. // publish product1
  402. if err := p.Publish(&product1); err != nil {
  403. t.Error(err)
  404. }
  405. if err := assertNoVersionUpdateStatus(db, &product1, publish.StatusOnline, product1.getUrl()); err != nil {
  406. t.Error(err)
  407. }
  408. if err := assertNoVersionUploadFile(&product1, storage); err != nil {
  409. t.Error(err)
  410. }
  411. product1Clone := product1
  412. product1Clone.Code = "0002"
  413. // publish product1 again
  414. if err := p.Publish(&product1Clone); err != nil {
  415. t.Error(err)
  416. }
  417. if err := assertNoVersionUpdateStatus(db, &product1Clone, publish.StatusOnline, product1Clone.getUrl()); err != nil {
  418. t.Error(err)
  419. }
  420. if err := assertNoVersionUploadFile(&product1Clone, storage); err != nil {
  421. t.Error(err)
  422. }
  423. // if delete product1 old file
  424. if err := assertNoVersionUploadFile(&product1, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
  425. } else {
  426. t.Error(errors.New(fmt.Sprintf("delete file %s failed", product1.getUrl())))
  427. }
  428. // unpublish product1
  429. if err := p.UnPublish(&product1Clone); err != nil {
  430. t.Error(err)
  431. }
  432. if err := assertNoVersionUpdateStatus(db, &product1Clone, publish.StatusOffline, product1Clone.getUrl()); err != nil {
  433. t.Error(err)
  434. }
  435. // if delete product1 file
  436. if err := assertNoVersionUploadFile(&product1Clone, storage); err != nil && strings.HasPrefix(err.Error(), "NoSuchKey: The specified key does not exist") {
  437. } else {
  438. t.Error(errors.New(fmt.Sprintf("delete file %s failed", product1Clone.getUrl())))
  439. }
  440. }
  441. func assertUpdateStatus(db *gorm.DB, p *Product, assertStatus string, asserOnlineUrl string) (err error) {
  442. var pindb Product
  443. err = db.Model(&Product{}).Where("id = ? AND version = ?", p.ID, p.GetVersion()).First(&pindb).Error
  444. if err != nil {
  445. return err
  446. }
  447. if pindb.GetStatus() != assertStatus || pindb.GetOnlineUrl() != asserOnlineUrl {
  448. return errors.New("update status failed")
  449. }
  450. return
  451. }
  452. func assertNoVersionUpdateStatus(db *gorm.DB, p *ProductWithoutVersion, assertStatus string, asserOnlineUrl string) (err error) {
  453. var pindb ProductWithoutVersion
  454. err = db.Model(&ProductWithoutVersion{}).Where("id = ?", p.ID).First(&pindb).Error
  455. if err != nil {
  456. return err
  457. }
  458. if pindb.GetStatus() != assertStatus || pindb.GetOnlineUrl() != asserOnlineUrl {
  459. return errors.New("update status failed")
  460. }
  461. return
  462. }
  463. func assertUploadFile(p *Product, storage oss.StorageInterface) error {
  464. f, err := storage.Get(p.getUrl())
  465. if err != nil {
  466. return err
  467. }
  468. c, err := io.ReadAll(f)
  469. if string(c) != p.getContent() {
  470. return errors.New("wrong content")
  471. }
  472. return nil
  473. }
  474. func assertUploadListFile(p *Product, storage oss.StorageInterface) error {
  475. f, err := storage.Get(p.getListUrl())
  476. if err != nil {
  477. return err
  478. }
  479. c, err := io.ReadAll(f)
  480. if string(c) != p.getListContent() {
  481. return errors.New("wrong content")
  482. }
  483. return nil
  484. }
  485. func assertNoVersionUploadFile(p *ProductWithoutVersion, storage oss.StorageInterface) error {
  486. f, err := storage.Get(p.getUrl())
  487. if err != nil {
  488. return err
  489. }
  490. c, err := io.ReadAll(f)
  491. if string(c) != p.getContent() {
  492. return errors.New("wrong content")
  493. }
  494. return nil
  495. }