diff_test.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. package activity
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "testing"
  6. "time"
  7. "github.com/qor5/admin/media/media_library"
  8. )
  9. type (
  10. Post struct {
  11. ID uint `gorm:"primarykey"`
  12. CreatedAt time.Time
  13. UpdatedAt time.Time
  14. PublishedDate time.Time
  15. Image media_library.MediaBox
  16. Title string
  17. Content string
  18. Author Author
  19. Comments []Comment
  20. Tags map[string]Tag
  21. }
  22. Tag struct {
  23. Name string
  24. }
  25. Author struct {
  26. Name string
  27. Age int
  28. }
  29. Comment struct {
  30. Text string
  31. }
  32. )
  33. func TestDiff(t *testing.T) {
  34. testCases := []struct {
  35. description string
  36. modelBuilder *ModelBuilder
  37. old Post
  38. now Post
  39. want []Diff
  40. }{
  41. {
  42. description: "Simple basic update",
  43. modelBuilder: &ModelBuilder{},
  44. old: Post{Title: "test", Content: ""},
  45. now: Post{Title: "test1", Content: "124"},
  46. want: []Diff{
  47. {
  48. Field: "Title",
  49. Old: "test",
  50. Now: "test1",
  51. },
  52. {
  53. Field: "Content",
  54. Old: "",
  55. Now: "124",
  56. }},
  57. },
  58. {
  59. description: "Default type handles",
  60. modelBuilder: &ModelBuilder{},
  61. old: Post{Image: media_library.MediaBox{ID: json.Number("1"), Url: "https://s3.com/1.jpg", Description: "test"}},
  62. now: Post{Image: media_library.MediaBox{ID: json.Number("2"), Url: "https://s3.com/2.jpg", Description: "test2"}},
  63. want: []Diff{
  64. {
  65. Field: "Image.Url",
  66. Old: "https://s3.com/1.jpg",
  67. Now: "https://s3.com/2.jpg",
  68. },
  69. {
  70. Field: "Image.Description",
  71. Old: "test",
  72. Now: "test2",
  73. },
  74. },
  75. },
  76. {
  77. description: "Default ingored fields",
  78. modelBuilder: &ModelBuilder{},
  79. old: Post{ID: 1, CreatedAt: time.Unix(1257894000, 0)},
  80. now: Post{ID: 2, CreatedAt: time.Unix(1457894000, 0)},
  81. want: nil,
  82. },
  83. {
  84. description: "Using model ingored fields",
  85. modelBuilder: (&ModelBuilder{}).AddIgnoredFields("Name"),
  86. old: Post{Author: Author{Name: "test", Age: 10}},
  87. now: Post{Author: Author{Name: "test1", Age: 12}},
  88. want: []Diff{
  89. {
  90. Field: "Author.Age",
  91. Old: "10",
  92. Now: "12",
  93. },
  94. },
  95. },
  96. {
  97. description: "Using model type handles",
  98. modelBuilder: (&ModelBuilder{}).AddTypeHanders(Author{}, func(old, now interface{}, prefixField string) (diffs []Diff) {
  99. oldAuthor := old.(Author)
  100. nowAuthor := now.(Author)
  101. if oldAuthor.Name != nowAuthor.Name {
  102. diffs = append(diffs, Diff{Field: fmt.Sprintf("%s.Name", prefixField), Old: oldAuthor.Name, Now: nowAuthor.Name})
  103. }
  104. return diffs
  105. }),
  106. old: Post{Author: Author{Name: "test", Age: 10}},
  107. now: Post{Author: Author{Name: "test1", Age: 12}},
  108. want: []Diff{
  109. {
  110. Field: "Author.Name",
  111. Old: "test",
  112. Now: "test1",
  113. },
  114. },
  115. },
  116. {
  117. description: "Test slice data",
  118. modelBuilder: &ModelBuilder{},
  119. old: Post{Comments: []Comment{{Text: "1"}, {Text: "2"}}},
  120. now: Post{Comments: []Comment{{Text: "1.1"}, {Text: "2.2"}}},
  121. want: []Diff{
  122. {
  123. Field: "Comments.0.Text",
  124. Old: "1",
  125. Now: "1.1",
  126. },
  127. {
  128. Field: "Comments.1.Text",
  129. Old: "2",
  130. Now: "2.2",
  131. },
  132. },
  133. },
  134. {
  135. description: "Test deleting slice data",
  136. modelBuilder: &ModelBuilder{},
  137. old: Post{Comments: []Comment{{Text: "1"}, {Text: "2"}}},
  138. now: Post{Comments: []Comment{{Text: "1.1"}}},
  139. want: []Diff{
  140. {
  141. Field: "Comments.0.Text",
  142. Old: "1",
  143. Now: "1.1",
  144. },
  145. {
  146. Field: "Comments.1",
  147. Old: "{Text:2}",
  148. Now: "",
  149. },
  150. },
  151. },
  152. {
  153. description: "Test adding slice data",
  154. modelBuilder: &ModelBuilder{},
  155. old: Post{Comments: []Comment{{Text: "1"}}},
  156. now: Post{Comments: []Comment{{Text: "1.1"}, {Text: "2"}}},
  157. want: []Diff{
  158. {
  159. Field: "Comments.0.Text",
  160. Old: "1",
  161. Now: "1.1",
  162. },
  163. {
  164. Field: "Comments.1",
  165. Old: "",
  166. Now: "{Text:2}",
  167. },
  168. },
  169. },
  170. {
  171. description: "Test creating slice data",
  172. modelBuilder: &ModelBuilder{},
  173. old: Post{},
  174. now: Post{Comments: []Comment{{Text: "1.1"}, {Text: "2"}}},
  175. want: []Diff{
  176. {
  177. Field: "Comments",
  178. Old: "",
  179. Now: "[{Text:1.1} {Text:2}]",
  180. },
  181. },
  182. },
  183. {
  184. description: "Test remove all slice data",
  185. modelBuilder: &ModelBuilder{},
  186. old: Post{Comments: []Comment{{Text: "1.1"}, {Text: "2"}}},
  187. now: Post{},
  188. want: []Diff{
  189. {
  190. Field: "Comments",
  191. Old: "[{Text:1.1} {Text:2}]",
  192. Now: "",
  193. },
  194. },
  195. },
  196. {
  197. description: "Test map data",
  198. modelBuilder: &ModelBuilder{},
  199. old: Post{Tags: map[string]Tag{"tag1": {Name: "tst1"}}},
  200. now: Post{Tags: map[string]Tag{"tag1": {Name: "tst12"}}},
  201. want: []Diff{
  202. {
  203. Field: "Tags.tag1.Name",
  204. Old: "tst1",
  205. Now: "tst12",
  206. },
  207. },
  208. },
  209. {
  210. description: "Test adding map data",
  211. modelBuilder: &ModelBuilder{},
  212. old: Post{Tags: map[string]Tag{"tag1": {Name: "tst1"}}},
  213. now: Post{Tags: map[string]Tag{"tag1": {Name: "tst12"}, "tag2": {Name: "tst121"}}},
  214. want: []Diff{
  215. {
  216. Field: "Tags.tag1.Name",
  217. Old: "tst1",
  218. Now: "tst12",
  219. },
  220. {
  221. Field: "Tags.tag2",
  222. Old: "",
  223. Now: "{Name:tst121}",
  224. },
  225. },
  226. },
  227. {
  228. description: "Test deleting map data",
  229. modelBuilder: &ModelBuilder{},
  230. old: Post{Tags: map[string]Tag{"tag1": {Name: "tst1"}, "tag2": {Name: "tst1"}}},
  231. now: Post{Tags: map[string]Tag{"tag1": {Name: "tst1"}}},
  232. want: []Diff{
  233. {
  234. Field: "Tags.tag2",
  235. Old: "{Name:tst1}",
  236. Now: "",
  237. },
  238. },
  239. },
  240. {
  241. description: "Test creating map data",
  242. modelBuilder: &ModelBuilder{},
  243. old: Post{},
  244. now: Post{Tags: map[string]Tag{"tag1": {Name: "tst1"}}},
  245. want: []Diff{
  246. {
  247. Field: "Tags",
  248. Old: "",
  249. Now: "map[tag1:{Name:tst1}]",
  250. },
  251. },
  252. },
  253. {
  254. description: "Test remove all map data",
  255. modelBuilder: &ModelBuilder{},
  256. old: Post{Tags: map[string]Tag{"tag1": {Name: "tst1"}}},
  257. now: Post{Tags: nil},
  258. want: []Diff{
  259. {
  260. Field: "Tags",
  261. Old: "map[tag1:{Name:tst1}]",
  262. Now: "",
  263. },
  264. },
  265. },
  266. }
  267. for _, test := range testCases {
  268. t.Run(test.description, func(t *testing.T) {
  269. diffs, err := NewDiffBuilder(test.modelBuilder).Diff(test.old, test.now)
  270. if err != nil {
  271. t.Fatalf("want: %v, but got error: %v", test.want, err)
  272. }
  273. w, _ := json.Marshal(test.want)
  274. d, _ := json.Marshal(diffs)
  275. if string(w) != string(d) {
  276. t.Fatalf("want: %v, but got: %v", string(w), string(d))
  277. }
  278. })
  279. }
  280. }
  281. func TestDiffTypesError(t *testing.T) {
  282. _, err := NewDiffBuilder(&ModelBuilder{}).Diff(Post{Title: "123"}, Author{Name: "ccc"})
  283. if err.Error() != "old and now type mismatch: activity.Post != activity.Author" {
  284. t.Fatalf("difference type error")
  285. }
  286. _, err = NewDiffBuilder(&ModelBuilder{}).Diff(Post{Title: "123"}, struct{}{})
  287. if err.Error() != "old and now type mismatch: activity.Post != struct {}" {
  288. t.Fatalf("difference type error")
  289. }
  290. }
  291. func BenchmarkSimpleDiff(b *testing.B) {
  292. builder := NewDiffBuilder(&ModelBuilder{})
  293. for i := 0; i < b.N; i++ {
  294. builder.Diff(Author{Name: "test1", Age: 10}, Author{Name: "test12", Age: 18})
  295. }
  296. }
  297. func BenchmarkComplexDiff(b *testing.B) {
  298. old := Post{
  299. ID: 1,
  300. CreatedAt: time.Now(),
  301. PublishedDate: time.Now(),
  302. Image: media_library.MediaBox{ID: json.Number("1"), Url: "https://s3.com/1.jpg", Description: "test"},
  303. Title: "title",
  304. Content: "content111",
  305. Author: Author{Name: "author1", Age: 10},
  306. Comments: []Comment{},
  307. Tags: map[string]Tag{},
  308. }
  309. for i := 0; i < 50; i++ {
  310. old.Comments = append(old.Comments, Comment{Text: fmt.Sprintf("text - %d", i)})
  311. old.Tags[fmt.Sprintf("tag - %d", i)] = Tag{Name: fmt.Sprintf("title - %d", i)}
  312. }
  313. now := Post{
  314. ID: 1,
  315. CreatedAt: time.Now().Add(1 * time.Hour),
  316. PublishedDate: time.Now().Add(3 * time.Hour),
  317. Image: media_library.MediaBox{ID: json.Number("2"), Url: "https://s3.com/2.jpg", Description: "test2"},
  318. Title: "title1",
  319. Content: "content111",
  320. Author: Author{
  321. Name: "author2",
  322. Age: 19,
  323. },
  324. Comments: []Comment{},
  325. Tags: map[string]Tag{},
  326. }
  327. for i := 0; i < 80; i++ {
  328. now.Comments = append(now.Comments, Comment{Text: fmt.Sprintf("text ---%d", i)})
  329. old.Tags[fmt.Sprintf("tag - %d", i)] = Tag{Name: fmt.Sprintf("title - %d", i)}
  330. }
  331. builder := NewDiffBuilder(&ModelBuilder{})
  332. b.ResetTimer()
  333. for i := 0; i < b.N; i++ {
  334. builder.Diff(old, now)
  335. }
  336. }
  337. // goos: darwin
  338. // goarch: amd64
  339. // pkg: github.com/qor5/admin/activity
  340. // cpu: Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
  341. // BenchmarkSimpleDiff-4 669444 1869 ns/op
  342. // BenchmarkComplexDiff-4 1381 729444 ns/op