database_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. // Copyright 2021 gorse Project Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package data
  15. import (
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "reflect"
  20. "strconv"
  21. "testing"
  22. "time"
  23. "github.com/jaswdr/faker"
  24. "github.com/juju/errors"
  25. "github.com/samber/lo"
  26. "github.com/stretchr/testify/assert"
  27. "github.com/stretchr/testify/suite"
  28. "google.golang.org/protobuf/proto"
  29. )
  30. var (
  31. positiveFeedbackType = "positiveFeedbackType"
  32. negativeFeedbackType = "negativeFeedbackType"
  33. duplicateFeedbackType = "duplicateFeedbackType"
  34. )
  35. type baseTestSuite struct {
  36. suite.Suite
  37. Database
  38. }
  39. func (suite *baseTestSuite) getUsers(ctx context.Context, batchSize int) []User {
  40. users := make([]User, 0)
  41. var err error
  42. var data []User
  43. cursor := ""
  44. for {
  45. cursor, data, err = suite.Database.GetUsers(ctx, cursor, batchSize)
  46. suite.NoError(err)
  47. users = append(users, data...)
  48. if cursor == "" {
  49. suite.LessOrEqual(len(data), batchSize)
  50. return users
  51. } else {
  52. suite.Equal(batchSize, len(data))
  53. }
  54. }
  55. }
  56. func (suite *baseTestSuite) getUsersStream(ctx context.Context, batchSize int) []User {
  57. var users []User
  58. userChan, errChan := suite.Database.GetUserStream(ctx, batchSize)
  59. for batchUsers := range userChan {
  60. users = append(users, batchUsers...)
  61. }
  62. suite.NoError(<-errChan)
  63. return users
  64. }
  65. func (suite *baseTestSuite) getItems(ctx context.Context, batchSize int) []Item {
  66. items := make([]Item, 0)
  67. var err error
  68. var data []Item
  69. cursor := ""
  70. for {
  71. cursor, data, err = suite.Database.GetItems(ctx, cursor, batchSize, nil)
  72. suite.NoError(err)
  73. items = append(items, data...)
  74. if cursor == "" {
  75. suite.LessOrEqual(len(data), batchSize)
  76. return items
  77. } else {
  78. suite.Equal(batchSize, len(data))
  79. }
  80. }
  81. }
  82. func (suite *baseTestSuite) getItemStream(ctx context.Context, batchSize int) []Item {
  83. var items []Item
  84. itemChan, errChan := suite.Database.GetItemStream(ctx, batchSize, nil)
  85. for batchUsers := range itemChan {
  86. items = append(items, batchUsers...)
  87. }
  88. suite.NoError(<-errChan)
  89. return items
  90. }
  91. func (suite *baseTestSuite) getFeedback(ctx context.Context, batchSize int, beginTime, endTime *time.Time, feedbackTypes ...string) []Feedback {
  92. feedback := make([]Feedback, 0)
  93. var err error
  94. var data []Feedback
  95. cursor := ""
  96. for {
  97. cursor, data, err = suite.Database.GetFeedback(ctx, cursor, batchSize, beginTime, endTime, feedbackTypes...)
  98. suite.NoError(err)
  99. feedback = append(feedback, data...)
  100. if cursor == "" {
  101. suite.LessOrEqual(len(data), batchSize)
  102. return feedback
  103. } else {
  104. suite.Equal(batchSize, len(data))
  105. }
  106. }
  107. }
  108. func (suite *baseTestSuite) getFeedbackStream(ctx context.Context, batchSize int, scanOptions ...ScanOption) []Feedback {
  109. var feedbacks []Feedback
  110. feedbackChan, errChan := suite.Database.GetFeedbackStream(ctx, batchSize, scanOptions...)
  111. for batchFeedback := range feedbackChan {
  112. feedbacks = append(feedbacks, batchFeedback...)
  113. }
  114. suite.NoError(<-errChan)
  115. return feedbacks
  116. }
  117. func (suite *baseTestSuite) TearDownSuite() {
  118. err := suite.Database.Close()
  119. suite.NoError(err)
  120. }
  121. func (suite *baseTestSuite) SetupTest() {
  122. err := suite.Database.Ping()
  123. suite.NoError(err)
  124. err = suite.Database.Purge()
  125. suite.NoError(err)
  126. }
  127. func (suite *baseTestSuite) TearDownTest() {
  128. err := suite.Database.Purge()
  129. suite.NoError(err)
  130. }
  131. func (suite *baseTestSuite) TestUsers() {
  132. ctx := context.Background()
  133. // Insert users
  134. var insertedUsers []User
  135. fake := faker.New()
  136. for i := 9; i >= 0; i-- {
  137. insertedUsers = append(insertedUsers, User{
  138. UserId: strconv.Itoa(i),
  139. Labels: map[string]any{
  140. "color": fake.Color().ColorName(),
  141. "company": lo.Map(lo.Range(3), func(_, _ int) any {
  142. return fake.Genre().Name()
  143. }),
  144. },
  145. Comment: fmt.Sprintf("comment %d", i),
  146. })
  147. }
  148. err := suite.Database.BatchInsertUsers(ctx, insertedUsers)
  149. suite.NoError(err)
  150. // Get users
  151. users := suite.getUsers(ctx, 3)
  152. suite.Equal(10, len(users))
  153. for i, user := range users {
  154. suite.Equal(insertedUsers[9-i], user)
  155. }
  156. // Get user stream
  157. usersFromStream := suite.getUsersStream(ctx, 3)
  158. suite.ElementsMatch(insertedUsers, usersFromStream)
  159. // Get this user
  160. user, err := suite.Database.GetUser(ctx, "0")
  161. suite.NoError(err)
  162. suite.Equal("0", user.UserId)
  163. // Delete this user
  164. err = suite.Database.DeleteUser(ctx, "0")
  165. suite.NoError(err)
  166. _, err = suite.Database.GetUser(ctx, "0")
  167. suite.True(errors.Is(err, errors.NotFound), err)
  168. // test override
  169. err = suite.Database.BatchInsertUsers(ctx, []User{{UserId: "1", Comment: "override"}})
  170. suite.NoError(err)
  171. user, err = suite.Database.GetUser(ctx, "1")
  172. suite.NoError(err)
  173. suite.Equal("override", user.Comment)
  174. // test modify
  175. err = suite.Database.ModifyUser(ctx, "1", UserPatch{Comment: proto.String("modify")})
  176. suite.NoError(err)
  177. err = suite.Database.ModifyUser(ctx, "1", UserPatch{Labels: []string{"a", "b", "c"}})
  178. suite.NoError(err)
  179. err = suite.Database.ModifyUser(ctx, "1", UserPatch{Subscribe: []string{"d", "e", "f"}})
  180. suite.NoError(err)
  181. user, err = suite.Database.GetUser(ctx, "1")
  182. suite.NoError(err)
  183. suite.Equal("modify", user.Comment)
  184. suite.Equal([]any{"a", "b", "c"}, user.Labels)
  185. suite.Equal([]string{"d", "e", "f"}, user.Subscribe)
  186. // test insert empty
  187. err = suite.Database.BatchInsertUsers(ctx, nil)
  188. suite.NoError(err)
  189. // insert duplicate users
  190. err = suite.Database.BatchInsertUsers(ctx, []User{{UserId: "1"}, {UserId: "1"}})
  191. suite.NoError(err)
  192. }
  193. func (suite *baseTestSuite) TestFeedback() {
  194. ctx := context.Background()
  195. // users that already exists
  196. err := suite.Database.BatchInsertUsers(ctx, []User{{"0", []string{"a"}, []string{"x"}, "comment"}})
  197. suite.NoError(err)
  198. // items that already exists
  199. err = suite.Database.BatchInsertItems(ctx, []Item{{ItemId: "0", Labels: []string{"b"}, Timestamp: time.Date(1996, 4, 8, 10, 0, 0, 0, time.UTC)}})
  200. suite.NoError(err)
  201. // insert feedbacks
  202. timestamp := time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC)
  203. feedback := []Feedback{
  204. {FeedbackKey{positiveFeedbackType, "0", "8"}, timestamp, "comment"},
  205. {FeedbackKey{positiveFeedbackType, "1", "6"}, timestamp, "comment"},
  206. {FeedbackKey{positiveFeedbackType, "2", "4"}, timestamp, "comment"},
  207. {FeedbackKey{positiveFeedbackType, "3", "2"}, timestamp, "comment"},
  208. {FeedbackKey{positiveFeedbackType, "4", "0"}, timestamp, "comment"},
  209. }
  210. err = suite.Database.BatchInsertFeedback(ctx, feedback, true, true, true)
  211. suite.NoError(err)
  212. // other type
  213. err = suite.Database.BatchInsertFeedback(ctx, []Feedback{{FeedbackKey: FeedbackKey{negativeFeedbackType, "0", "2"}}}, true, true, true)
  214. suite.NoError(err)
  215. err = suite.Database.BatchInsertFeedback(ctx, []Feedback{{FeedbackKey: FeedbackKey{negativeFeedbackType, "2", "4"}}}, true, true, true)
  216. suite.NoError(err)
  217. // future feedback
  218. futureFeedback := []Feedback{
  219. {FeedbackKey{duplicateFeedbackType, "0", "0"}, time.Now().Add(time.Hour), "comment"},
  220. {FeedbackKey{duplicateFeedbackType, "1", "2"}, time.Now().Add(time.Hour), "comment"},
  221. {FeedbackKey{duplicateFeedbackType, "2", "4"}, time.Now().Add(time.Hour), "comment"},
  222. {FeedbackKey{duplicateFeedbackType, "3", "6"}, time.Now().Add(time.Hour), "comment"},
  223. {FeedbackKey{duplicateFeedbackType, "4", "8"}, time.Now().Add(time.Hour), "comment"},
  224. }
  225. err = suite.Database.BatchInsertFeedback(ctx, futureFeedback, true, true, true)
  226. suite.NoError(err)
  227. // Get feedback
  228. ret := suite.getFeedback(ctx, 3, nil, lo.ToPtr(time.Now()), positiveFeedbackType)
  229. suite.Equal(feedback, ret)
  230. ret = suite.getFeedback(ctx, 2, nil, lo.ToPtr(time.Now()))
  231. suite.Equal(len(feedback)+2, len(ret))
  232. ret = suite.getFeedback(ctx, 2, lo.ToPtr(timestamp.Add(time.Second)), lo.ToPtr(time.Now()))
  233. suite.Empty(ret)
  234. // Get feedback stream
  235. feedbackFromStream := suite.getFeedbackStream(ctx, 3, WithEndTime(time.Now()), WithFeedbackTypes(positiveFeedbackType))
  236. suite.ElementsMatch(feedback, feedbackFromStream)
  237. feedbackFromStream = suite.getFeedbackStream(ctx, 3, WithEndTime(time.Now()))
  238. suite.Equal(len(feedback)+2, len(feedbackFromStream))
  239. feedbackFromStream = suite.getFeedbackStream(ctx, 3, WithBeginTime(timestamp.Add(time.Second)), WithEndTime(time.Now()))
  240. suite.Empty(feedbackFromStream)
  241. feedbackFromStream = suite.getFeedbackStream(ctx, 3, WithBeginUserId("1"), WithEndUserId("3"), WithEndTime(time.Now()), WithFeedbackTypes(positiveFeedbackType))
  242. suite.Equal(feedback[1:4], feedbackFromStream)
  243. // Get items
  244. items := suite.getItems(ctx, 3)
  245. suite.Equal(5, len(items))
  246. for i, item := range items {
  247. suite.Equal(strconv.Itoa(i*2), item.ItemId)
  248. if item.ItemId != "0" {
  249. suite.Zero(item.Timestamp)
  250. suite.Empty(item.Labels)
  251. suite.Empty(item.Comment)
  252. }
  253. }
  254. // Get users
  255. users := suite.getUsers(ctx, 2)
  256. suite.Equal(5, len(users))
  257. for i, user := range users {
  258. suite.Equal(strconv.Itoa(i), user.UserId)
  259. if user.UserId != "0" {
  260. suite.Empty(user.Labels)
  261. suite.Empty(user.Subscribe)
  262. suite.Empty(user.Comment)
  263. }
  264. }
  265. // check users that already exists
  266. user, err := suite.Database.GetUser(ctx, "0")
  267. suite.NoError(err)
  268. suite.Equal(User{"0", []any{"a"}, []string{"x"}, "comment"}, user)
  269. // check items that already exists
  270. item, err := suite.Database.GetItem(ctx, "0")
  271. suite.NoError(err)
  272. suite.Equal(Item{ItemId: "0", Labels: []any{"b"}, Timestamp: time.Date(1996, 4, 8, 10, 0, 0, 0, time.UTC)}, item)
  273. // Get typed feedback by user
  274. ret, err = suite.Database.GetUserFeedback(ctx, "2", lo.ToPtr(time.Now()), positiveFeedbackType)
  275. suite.NoError(err)
  276. suite.Equal(1, len(ret))
  277. suite.Equal("2", ret[0].UserId)
  278. suite.Equal("4", ret[0].ItemId)
  279. // Get all feedback by user
  280. ret, err = suite.Database.GetUserFeedback(ctx, "2", lo.ToPtr(time.Now()))
  281. suite.NoError(err)
  282. suite.Equal(2, len(ret))
  283. // Get typed feedback by item
  284. ret, err = suite.Database.GetItemFeedback(ctx, "4", positiveFeedbackType)
  285. suite.NoError(err)
  286. suite.Equal(1, len(ret))
  287. suite.Equal("2", ret[0].UserId)
  288. suite.Equal("4", ret[0].ItemId)
  289. // Get all feedback by item
  290. ret, err = suite.Database.GetItemFeedback(ctx, "4")
  291. suite.NoError(err)
  292. suite.Equal(2, len(ret))
  293. // test override
  294. err = suite.Database.BatchInsertFeedback(ctx, []Feedback{{
  295. FeedbackKey: FeedbackKey{positiveFeedbackType, "0", "8"},
  296. Comment: "override",
  297. }}, true, true, true)
  298. suite.NoError(err)
  299. ret, err = suite.Database.GetUserFeedback(ctx, "0", lo.ToPtr(time.Now()), positiveFeedbackType)
  300. suite.NoError(err)
  301. suite.Equal(1, len(ret))
  302. suite.Equal("override", ret[0].Comment)
  303. // test not overwrite
  304. err = suite.Database.BatchInsertFeedback(ctx, []Feedback{{
  305. FeedbackKey: FeedbackKey{positiveFeedbackType, "0", "8"},
  306. Comment: "not_override",
  307. }}, true, true, false)
  308. suite.NoError(err)
  309. ret, err = suite.Database.GetUserFeedback(ctx, "0", lo.ToPtr(time.Now()), positiveFeedbackType)
  310. suite.NoError(err)
  311. suite.Equal(1, len(ret))
  312. suite.Equal("override", ret[0].Comment)
  313. // insert no feedback
  314. err = suite.Database.BatchInsertFeedback(ctx, nil, true, true, true)
  315. suite.NoError(err)
  316. // not insert users or items
  317. err = suite.Database.BatchInsertFeedback(ctx, []Feedback{
  318. {FeedbackKey: FeedbackKey{"a", "100", "200"}},
  319. {FeedbackKey: FeedbackKey{"a", "0", "200"}},
  320. {FeedbackKey: FeedbackKey{"a", "100", "8"}},
  321. }, false, false, false)
  322. suite.NoError(err)
  323. result, err := suite.Database.GetUserItemFeedback(ctx, "100", "200")
  324. suite.NoError(err)
  325. suite.Empty(result)
  326. result, err = suite.Database.GetUserItemFeedback(ctx, "0", "200")
  327. suite.NoError(err)
  328. suite.Empty(result)
  329. result, err = suite.Database.GetUserItemFeedback(ctx, "100", "8")
  330. suite.NoError(err)
  331. suite.Empty(result)
  332. // insert valid feedback and invalid feedback at the same time
  333. err = suite.Database.BatchInsertFeedback(ctx, []Feedback{
  334. {FeedbackKey: FeedbackKey{"a", "0", "8"}},
  335. {FeedbackKey: FeedbackKey{"a", "100", "200"}},
  336. }, false, false, false)
  337. suite.NoError(err)
  338. // insert duplicate feedback
  339. err = suite.Database.BatchInsertFeedback(ctx, []Feedback{
  340. {FeedbackKey: FeedbackKey{"a", "0", "0"}},
  341. {FeedbackKey: FeedbackKey{"a", "0", "0"}},
  342. }, true, true, true)
  343. suite.NoError(err)
  344. }
  345. func (suite *baseTestSuite) TestItems() {
  346. ctx := context.Background()
  347. // Items
  348. items := []Item{
  349. {
  350. ItemId: "0",
  351. IsHidden: true,
  352. Categories: []string{"a"},
  353. Timestamp: time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC),
  354. Labels: []any{"a"},
  355. Comment: "comment 0",
  356. },
  357. {
  358. ItemId: "2",
  359. Categories: []string{"b"},
  360. Timestamp: time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC),
  361. Labels: []any{"a"},
  362. Comment: "comment 2",
  363. },
  364. {
  365. ItemId: "4",
  366. IsHidden: true,
  367. Categories: []string{"a"},
  368. Timestamp: time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC),
  369. Labels: []any{"a", "b"},
  370. Comment: "comment 4",
  371. },
  372. {
  373. ItemId: "6",
  374. Categories: []string{"b"},
  375. Timestamp: time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC),
  376. Labels: []any{"b"},
  377. Comment: "comment 6",
  378. },
  379. {
  380. ItemId: "8",
  381. IsHidden: true,
  382. Categories: []string{"a"},
  383. Timestamp: time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC),
  384. Labels: []any{"b"},
  385. Comment: "comment 8",
  386. },
  387. }
  388. // Insert item
  389. err := suite.Database.BatchInsertItems(ctx, items)
  390. suite.NoError(err)
  391. // Get items
  392. totalItems := suite.getItems(ctx, 3)
  393. suite.Equal(items, totalItems)
  394. // Get item stream
  395. itemsFromStream := suite.getItemStream(ctx, 3)
  396. suite.ElementsMatch(items, itemsFromStream)
  397. // Get item
  398. for _, item := range items {
  399. ret, err := suite.Database.GetItem(ctx, item.ItemId)
  400. suite.NoError(err)
  401. suite.Equal(item, ret)
  402. }
  403. // batch get items
  404. batchItem, err := suite.Database.BatchGetItems(ctx, []string{"2", "6"})
  405. suite.NoError(err)
  406. suite.Equal([]Item{items[1], items[3]}, batchItem)
  407. // Delete item
  408. err = suite.Database.DeleteItem(ctx, "0")
  409. suite.NoError(err)
  410. _, err = suite.Database.GetItem(ctx, "0")
  411. suite.True(errors.Is(err, errors.NotFound), err)
  412. // test override
  413. err = suite.Database.BatchInsertItems(ctx, []Item{{ItemId: "4", IsHidden: false, Categories: []string{"b"}, Labels: []string{"o"}, Comment: "override"}})
  414. suite.NoError(err)
  415. item, err := suite.Database.GetItem(ctx, "4")
  416. suite.NoError(err)
  417. suite.False(item.IsHidden)
  418. suite.Equal([]string{"b"}, item.Categories)
  419. suite.Equal([]any{"o"}, item.Labels)
  420. suite.Equal("override", item.Comment)
  421. // test modify
  422. timestamp := time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)
  423. err = suite.Database.ModifyItem(ctx, "2", ItemPatch{IsHidden: proto.Bool(true)})
  424. suite.NoError(err)
  425. err = suite.Database.ModifyItem(ctx, "2", ItemPatch{Categories: []string{"a"}})
  426. suite.NoError(err)
  427. err = suite.Database.ModifyItem(ctx, "2", ItemPatch{Comment: proto.String("modify")})
  428. suite.NoError(err)
  429. err = suite.Database.ModifyItem(ctx, "2", ItemPatch{Labels: []string{"a", "b", "c"}})
  430. suite.NoError(err)
  431. err = suite.Database.ModifyItem(ctx, "2", ItemPatch{Timestamp: &timestamp})
  432. suite.NoError(err)
  433. item, err = suite.Database.GetItem(ctx, "2")
  434. suite.NoError(err)
  435. suite.True(item.IsHidden)
  436. suite.Equal([]string{"a"}, item.Categories)
  437. suite.Equal("modify", item.Comment)
  438. suite.Equal([]any{"a", "b", "c"}, item.Labels)
  439. suite.Equal(timestamp, item.Timestamp)
  440. // test insert empty
  441. err = suite.Database.BatchInsertItems(ctx, nil)
  442. suite.NoError(err)
  443. // test get empty
  444. items, err = suite.Database.BatchGetItems(ctx, nil)
  445. suite.NoError(err)
  446. suite.Empty(items)
  447. // test insert duplicate items
  448. err = suite.Database.BatchInsertItems(ctx, []Item{{ItemId: "1"}, {ItemId: "1"}})
  449. suite.NoError(err)
  450. }
  451. func (suite *baseTestSuite) TestDeleteUser() {
  452. ctx := context.Background()
  453. // Insert ret
  454. feedback := []Feedback{
  455. {FeedbackKey{positiveFeedbackType, "a", "0"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  456. {FeedbackKey{positiveFeedbackType, "a", "2"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  457. {FeedbackKey{positiveFeedbackType, "a", "4"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  458. {FeedbackKey{positiveFeedbackType, "a", "6"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  459. {FeedbackKey{positiveFeedbackType, "a", "8"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  460. }
  461. err := suite.Database.BatchInsertFeedback(ctx, feedback, true, true, true)
  462. suite.NoError(err)
  463. // Delete user
  464. err = suite.Database.DeleteUser(ctx, "a")
  465. suite.NoError(err)
  466. _, err = suite.Database.GetUser(ctx, "a")
  467. suite.NotNil(err, "failed to delete user")
  468. ret, err := suite.Database.GetUserFeedback(ctx, "a", lo.ToPtr(time.Now()), positiveFeedbackType)
  469. suite.NoError(err)
  470. suite.Equal(0, len(ret))
  471. _, ret, err = suite.Database.GetFeedback(ctx, "", 100, nil, lo.ToPtr(time.Now()), positiveFeedbackType)
  472. suite.NoError(err)
  473. suite.Empty(ret)
  474. }
  475. func (suite *baseTestSuite) TestDeleteItem() {
  476. ctx := context.Background()
  477. // Insert ret
  478. feedbacks := []Feedback{
  479. {FeedbackKey{positiveFeedbackType, "0", "b"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  480. {FeedbackKey{positiveFeedbackType, "1", "b"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  481. {FeedbackKey{positiveFeedbackType, "2", "b"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  482. {FeedbackKey{positiveFeedbackType, "3", "b"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  483. {FeedbackKey{positiveFeedbackType, "4", "b"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  484. }
  485. err := suite.Database.BatchInsertFeedback(ctx, feedbacks, true, true, true)
  486. suite.NoError(err)
  487. // Delete item
  488. err = suite.Database.DeleteItem(ctx, "b")
  489. suite.NoError(err)
  490. _, err = suite.Database.GetItem(ctx, "b")
  491. suite.Error(err, "failed to delete item")
  492. ret, err := suite.Database.GetItemFeedback(ctx, "b", positiveFeedbackType)
  493. suite.NoError(err)
  494. suite.Equal(0, len(ret))
  495. _, ret, err = suite.Database.GetFeedback(ctx, "", 100, nil, lo.ToPtr(time.Now()), positiveFeedbackType)
  496. suite.NoError(err)
  497. suite.Empty(ret)
  498. }
  499. func (suite *baseTestSuite) TestDeleteFeedback() {
  500. ctx := context.Background()
  501. feedbacks := []Feedback{
  502. {FeedbackKey{"type1", "2", "3"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  503. {FeedbackKey{"type2", "2", "3"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  504. {FeedbackKey{"type3", "2", "3"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  505. {FeedbackKey{"type1", "2", "4"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  506. {FeedbackKey{"type1", "1", "3"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  507. }
  508. err := suite.Database.BatchInsertFeedback(ctx, feedbacks, true, true, true)
  509. suite.NoError(err)
  510. // get user-item feedback
  511. ret, err := suite.Database.GetUserItemFeedback(ctx, "2", "3")
  512. suite.NoError(err)
  513. suite.ElementsMatch([]Feedback{feedbacks[0], feedbacks[1], feedbacks[2]}, ret)
  514. feedbackType2 := "type2"
  515. ret, err = suite.Database.GetUserItemFeedback(ctx, "2", "3", feedbackType2)
  516. suite.NoError(err)
  517. suite.Equal([]Feedback{feedbacks[1]}, ret)
  518. // delete user-item feedback
  519. deleteCount, err := suite.Database.DeleteUserItemFeedback(ctx, "2", "3")
  520. suite.NoError(err)
  521. suite.Equal(3, deleteCount)
  522. ret, err = suite.Database.GetUserItemFeedback(ctx, "2", "3")
  523. suite.NoError(err)
  524. suite.Empty(ret)
  525. feedbackType1 := "type1"
  526. deleteCount, err = suite.Database.DeleteUserItemFeedback(ctx, "1", "3", feedbackType1)
  527. suite.NoError(err)
  528. suite.Equal(1, deleteCount)
  529. ret, err = suite.Database.GetUserItemFeedback(ctx, "1", "3", feedbackType2)
  530. suite.NoError(err)
  531. suite.Empty(ret)
  532. }
  533. func (suite *baseTestSuite) TestTimeLimit() {
  534. ctx := context.Background()
  535. // insert items
  536. items := []Item{
  537. {
  538. ItemId: "0",
  539. Timestamp: time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC),
  540. Labels: []any{"a"},
  541. Comment: "comment 0",
  542. },
  543. {
  544. ItemId: "2",
  545. Timestamp: time.Date(1997, 3, 15, 0, 0, 0, 0, time.UTC),
  546. Labels: []any{"a"},
  547. Comment: "comment 2",
  548. },
  549. {
  550. ItemId: "4",
  551. Timestamp: time.Date(1998, 3, 15, 0, 0, 0, 0, time.UTC),
  552. Labels: []any{"a", "b"},
  553. Comment: "comment 4",
  554. },
  555. {
  556. ItemId: "6",
  557. Timestamp: time.Date(1999, 3, 15, 0, 0, 0, 0, time.UTC),
  558. Labels: []any{"b"},
  559. Comment: "comment 6",
  560. },
  561. {
  562. ItemId: "8",
  563. Timestamp: time.Date(2000, 3, 15, 0, 0, 0, 0, time.UTC),
  564. Labels: []any{"b"},
  565. Comment: "comment 8",
  566. },
  567. }
  568. err := suite.Database.BatchInsertItems(ctx, items)
  569. suite.NoError(err)
  570. timeLimit := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
  571. _, ret, err := suite.Database.GetItems(ctx, "", 100, &timeLimit)
  572. suite.NoError(err)
  573. suite.Equal([]Item{items[2], items[3], items[4]}, ret)
  574. // insert feedback
  575. feedbacks := []Feedback{
  576. {FeedbackKey{"type1", "2", "3"}, time.Date(1996, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  577. {FeedbackKey{"type2", "2", "3"}, time.Date(1997, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  578. {FeedbackKey{"type3", "2", "3"}, time.Date(1998, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  579. {FeedbackKey{"type1", "2", "4"}, time.Date(1999, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  580. {FeedbackKey{"type1", "1", "3"}, time.Date(2000, 3, 15, 0, 0, 0, 0, time.UTC), "comment"},
  581. }
  582. err = suite.Database.BatchInsertFeedback(ctx, feedbacks, true, true, true)
  583. suite.NoError(err)
  584. _, retFeedback, err := suite.Database.GetFeedback(ctx, "", 100, &timeLimit, lo.ToPtr(time.Now()))
  585. suite.NoError(err)
  586. suite.Equal([]Feedback{feedbacks[4], feedbacks[3], feedbacks[2]}, retFeedback)
  587. typeFilter := "type1"
  588. _, retFeedback, err = suite.Database.GetFeedback(ctx, "", 100, &timeLimit, lo.ToPtr(time.Now()), typeFilter)
  589. suite.NoError(err)
  590. suite.Equal([]Feedback{feedbacks[4], feedbacks[3]}, retFeedback)
  591. }
  592. func (suite *baseTestSuite) TestTimezone() {
  593. ctx := context.Background()
  594. loc, err := time.LoadLocation("Asia/Tokyo")
  595. suite.NoError(err)
  596. // insert feedbacks
  597. err = suite.Database.BatchInsertFeedback(ctx, []Feedback{
  598. {FeedbackKey: FeedbackKey{"read", "1", "1"}, Timestamp: time.Now().Add(-time.Second).In(loc)},
  599. {FeedbackKey: FeedbackKey{"read", "1", "2"}, Timestamp: time.Now().Add(-time.Second).In(loc)},
  600. {FeedbackKey: FeedbackKey{"read", "2", "2"}, Timestamp: time.Now().Add(-time.Second).In(loc)},
  601. {FeedbackKey: FeedbackKey{"like", "1", "1"}, Timestamp: time.Now().Add(time.Hour).In(loc)},
  602. {FeedbackKey: FeedbackKey{"like", "1", "2"}, Timestamp: time.Now().Add(time.Hour).In(loc)},
  603. {FeedbackKey: FeedbackKey{"like", "2", "2"}, Timestamp: time.Now().Add(time.Hour).In(loc)},
  604. }, true, true, true)
  605. suite.NoError(err)
  606. // get feedback stream
  607. feedback := suite.getFeedback(ctx, 10, nil, lo.ToPtr(time.Now()))
  608. suite.Equal(3, len(feedback))
  609. // get feedback
  610. _, feedback, err = suite.Database.GetFeedback(ctx, "", 10, nil, lo.ToPtr(time.Now()))
  611. suite.NoError(err)
  612. suite.Equal(3, len(feedback))
  613. // get user feedback
  614. feedback, err = suite.Database.GetUserFeedback(ctx, "1", lo.ToPtr(time.Now()))
  615. suite.NoError(err)
  616. suite.Equal(2, len(feedback))
  617. // get item feedback
  618. feedback, err = suite.Database.GetItemFeedback(ctx, "2") // no future feedback by default
  619. suite.NoError(err)
  620. suite.Equal(2, len(feedback))
  621. // get user item feedback
  622. feedback, err = suite.Database.GetUserItemFeedback(ctx, "1", "1") // return future feedback by default
  623. suite.NoError(err)
  624. suite.Equal(2, len(feedback))
  625. // insert items
  626. now := time.Now().In(loc)
  627. err = suite.Database.BatchInsertItems(ctx, []Item{{ItemId: "100", Timestamp: now}, {ItemId: "200"}})
  628. suite.NoError(err)
  629. err = suite.Database.ModifyItem(ctx, "200", ItemPatch{Timestamp: &now})
  630. suite.NoError(err)
  631. switch database := suite.Database.(type) {
  632. case *SQLDatabase:
  633. switch suite.Database.(*SQLDatabase).driver {
  634. case Postgres:
  635. item, err := suite.Database.GetItem(ctx, "100")
  636. suite.NoError(err)
  637. suite.Equal(now.Round(time.Microsecond).In(time.UTC), item.Timestamp)
  638. item, err = suite.Database.GetItem(ctx, "200")
  639. suite.NoError(err)
  640. suite.Equal(now.Round(time.Microsecond).In(time.UTC), item.Timestamp)
  641. case SQLite:
  642. item, err := suite.Database.GetItem(ctx, "100")
  643. suite.NoError(err)
  644. suite.Equal(now.In(time.UTC), item.Timestamp.In(time.UTC))
  645. item, err = suite.Database.GetItem(ctx, "200")
  646. suite.NoError(err)
  647. suite.Equal(now.In(time.UTC), item.Timestamp.In(time.UTC))
  648. default:
  649. suite.T().Skipf("unknown sql database: %v", database.driver)
  650. }
  651. case *MongoDB:
  652. item, err := suite.Database.GetItem(ctx, "100")
  653. suite.NoError(err)
  654. suite.Equal(now.Truncate(time.Millisecond).In(time.UTC), item.Timestamp)
  655. item, err = suite.Database.GetItem(ctx, "200")
  656. suite.NoError(err)
  657. suite.Equal(now.Truncate(time.Millisecond).In(time.UTC), item.Timestamp)
  658. default:
  659. suite.T().Skipf("unknown database: %v", reflect.TypeOf(suite.Database))
  660. }
  661. }
  662. func (suite *baseTestSuite) TestPurge() {
  663. ctx := context.Background()
  664. // insert data
  665. err := suite.Database.BatchInsertFeedback(ctx, lo.Map(lo.Range(100), func(t int, i int) Feedback {
  666. return Feedback{FeedbackKey: FeedbackKey{
  667. FeedbackType: "click",
  668. UserId: strconv.Itoa(t),
  669. ItemId: strconv.Itoa(t),
  670. }}
  671. }), true, true, true)
  672. suite.NoError(err)
  673. _, users, err := suite.Database.GetUsers(ctx, "", 100)
  674. suite.NoError(err)
  675. suite.Equal(100, len(users))
  676. _, items, err := suite.Database.GetItems(ctx, "", 100, nil)
  677. suite.NoError(err)
  678. suite.Equal(100, len(items))
  679. _, feedbacks, err := suite.Database.GetFeedback(ctx, "", 100, nil, lo.ToPtr(time.Now()))
  680. suite.NoError(err)
  681. suite.Equal(100, len(feedbacks))
  682. // purge data
  683. err = suite.Database.Purge()
  684. suite.NoError(err)
  685. _, users, err = suite.Database.GetUsers(ctx, "", 100)
  686. suite.NoError(err)
  687. suite.Empty(users)
  688. _, items, err = suite.Database.GetItems(ctx, "", 100, nil)
  689. suite.NoError(err)
  690. suite.Empty(items)
  691. _, feedbacks, err = suite.Database.GetFeedback(ctx, "", 100, nil, lo.ToPtr(time.Now()))
  692. suite.NoError(err)
  693. suite.Empty(feedbacks)
  694. // purge empty database
  695. err = suite.Database.Purge()
  696. suite.NoError(err)
  697. }
  698. func TestSortFeedbacks(t *testing.T) {
  699. feedback := []Feedback{
  700. {FeedbackKey: FeedbackKey{"star", "1", "1"}, Timestamp: time.Date(2000, 10, 1, 0, 0, 0, 0, time.UTC)},
  701. {FeedbackKey: FeedbackKey{"like", "1", "1"}, Timestamp: time.Date(2001, 10, 1, 0, 0, 0, 0, time.UTC)},
  702. {FeedbackKey: FeedbackKey{"read", "1", "1"}, Timestamp: time.Date(2002, 10, 1, 0, 0, 0, 0, time.UTC)},
  703. }
  704. SortFeedbacks(feedback)
  705. assert.Equal(t, []Feedback{
  706. {FeedbackKey: FeedbackKey{"read", "1", "1"}, Timestamp: time.Date(2002, 10, 1, 0, 0, 0, 0, time.UTC)},
  707. {FeedbackKey: FeedbackKey{"like", "1", "1"}, Timestamp: time.Date(2001, 10, 1, 0, 0, 0, 0, time.UTC)},
  708. {FeedbackKey: FeedbackKey{"star", "1", "1"}, Timestamp: time.Date(2000, 10, 1, 0, 0, 0, 0, time.UTC)},
  709. }, feedback)
  710. }
  711. func TestValidateLabels(t *testing.T) {
  712. assert.NoError(t, ValidateLabels(nil))
  713. assert.NoError(t, ValidateLabels(json.Number("1")))
  714. assert.NoError(t, ValidateLabels("label"))
  715. assert.NoError(t, ValidateLabels([]any{json.Number("1"), json.Number("2"), json.Number("3")}))
  716. assert.NoError(t, ValidateLabels([]any{"1", "2", "3"}))
  717. assert.NoError(t, ValidateLabels(map[string]any{"city": json.Number("1"), "tags": []any{json.Number("1"), json.Number("2"), json.Number("3")}}))
  718. assert.NoError(t, ValidateLabels(map[string]any{"city": "wenzhou", "tags": []any{"1", "2", "3"}}))
  719. assert.NoError(t, ValidateLabels(map[string]any{"address": map[string]any{"province": json.Number("1"), "city": json.Number("2")}}))
  720. assert.NoError(t, ValidateLabels(map[string]any{"address": map[string]any{"province": "zhejiang", "city": "wenzhou"}}))
  721. assert.Error(t, ValidateLabels(map[string]any{"price": 100, "tags": []any{json.Number("1"), "2", "3"}}))
  722. assert.Error(t, ValidateLabels(map[string]any{"city": "wenzhou", "tags": []any{"1", json.Number("2"), "3"}}))
  723. assert.Error(t, ValidateLabels(map[string]any{"city": "wenzhou", "tags": []any{"1", "2", json.Number("3")}}))
  724. }