example_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. package exchange_test
  2. import (
  3. "bytes"
  4. "database/sql/driver"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io/ioutil"
  9. "strings"
  10. "testing"
  11. "time"
  12. "github.com/qor5/x/exchange"
  13. "github.com/stretchr/testify/assert"
  14. "gorm.io/gorm"
  15. )
  16. type Phone struct {
  17. gorm.Model
  18. Code string `gorm:"uniqueIndex;not null"`
  19. Name string
  20. ReleaseDate *time.Time
  21. // promoted fields
  22. SizeInfo
  23. // embedded field
  24. Screen ScreenInfo `gorm:"embedded;embeddedPrefix:screen_"`
  25. // struct text field
  26. Features Features `gorm:"type:text"`
  27. // associations
  28. // has one
  29. Intro Intro `gorm:"foreignKey:PhoneCode;references:Code"`
  30. // has one
  31. ExtraIntro *ExtraIntro `gorm:"foreignKey:PhoneCode;references:Code"`
  32. // has many
  33. Cameras []*Camera `gorm:"foreignKey:PhoneCode;references:Code"`
  34. // many to many
  35. SellingSites []*ShoppingSite `gorm:"many2many:phone_selling_shopping_site;"`
  36. }
  37. type SizeInfo struct {
  38. Width string
  39. Height string
  40. Depth string
  41. }
  42. type ScreenInfo struct {
  43. Size string
  44. Type string
  45. }
  46. type Features []string
  47. func (p Features) Value() (driver.Value, error) {
  48. if len(p) == 0 {
  49. return json.Marshal(nil)
  50. }
  51. return json.Marshal(p)
  52. }
  53. func (p *Features) Scan(data interface{}) error {
  54. var byteData []byte
  55. switch values := data.(type) {
  56. case []byte:
  57. byteData = values
  58. case string:
  59. byteData = []byte(values)
  60. default:
  61. return errors.New("unsupported type of data")
  62. }
  63. return json.Unmarshal(byteData, p)
  64. }
  65. type Intro struct {
  66. gorm.Model
  67. PhoneCode string
  68. Content string
  69. }
  70. type ExtraIntro struct {
  71. gorm.Model
  72. PhoneCode string
  73. Content string
  74. }
  75. type Camera struct {
  76. gorm.Model
  77. PhoneCode string
  78. Type string
  79. Pixel string
  80. }
  81. type ShoppingSite struct {
  82. ID uint `gorm:"primarykey"`
  83. Name string
  84. }
  85. var phoneAssociations = []string{"Intro", "ExtraIntro", "Cameras", "SellingSites"}
  86. var phoneMetas = []*exchange.Meta{
  87. exchange.NewMeta("Code").PrimaryKey(true),
  88. exchange.NewMeta("Name"),
  89. exchange.NewMeta("ReleaseDate").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
  90. r := record.(*Phone)
  91. if value == "" {
  92. r.ReleaseDate = nil
  93. return nil
  94. }
  95. t, err := time.ParseInLocation("2006-01-02", value, time.Local)
  96. if err != nil {
  97. return err
  98. }
  99. r.ReleaseDate = &t
  100. return nil
  101. }).Valuer(func(record interface{}) (string, error) {
  102. r := record.(*Phone)
  103. if r.ReleaseDate == nil {
  104. return "", nil
  105. }
  106. return r.ReleaseDate.Local().Format("2006-01-02"), nil
  107. }),
  108. exchange.NewMeta("Width"),
  109. exchange.NewMeta("Height"),
  110. exchange.NewMeta("Depth"),
  111. exchange.NewMeta("ScreenSize").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
  112. r := record.(*Phone)
  113. r.Screen.Size = value
  114. return nil
  115. }).Valuer(func(record interface{}) (string, error) {
  116. r := record.(*Phone)
  117. return r.Screen.Size, nil
  118. }),
  119. exchange.NewMeta("ScreenType").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
  120. r := record.(*Phone)
  121. r.Screen.Type = value
  122. return nil
  123. }).Valuer(func(record interface{}) (string, error) {
  124. r := record.(*Phone)
  125. return r.Screen.Type, nil
  126. }),
  127. exchange.NewMeta("5G").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
  128. has5G := strings.ToLower(value) == "true"
  129. r := record.(*Phone)
  130. if has5G {
  131. setted := false
  132. for _, f := range r.Features {
  133. if f == "5G" {
  134. setted = true
  135. break
  136. }
  137. }
  138. if !setted {
  139. r.Features = append(r.Features, "5G")
  140. }
  141. } else {
  142. var newFeatures []string
  143. for _, f := range r.Features {
  144. if f != "5G" {
  145. newFeatures = append(newFeatures, f)
  146. }
  147. }
  148. r.Features = newFeatures
  149. }
  150. return nil
  151. }).Valuer(func(record interface{}) (string, error) {
  152. r := record.(*Phone)
  153. for _, f := range r.Features {
  154. if f == "5G" {
  155. return "TRUE", nil
  156. }
  157. }
  158. return "FALSE", nil
  159. }),
  160. exchange.NewMeta("WirelessCharge").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
  161. hasWirelessCharge := strings.ToLower(value) == "true"
  162. r := record.(*Phone)
  163. if hasWirelessCharge {
  164. setted := false
  165. for _, f := range r.Features {
  166. if f == "WirelessCharge" {
  167. setted = true
  168. break
  169. }
  170. }
  171. if !setted {
  172. r.Features = append(r.Features, "WirelessCharge")
  173. }
  174. } else {
  175. var newFeatures []string
  176. for _, f := range r.Features {
  177. if f != "WirelessCharge" {
  178. newFeatures = append(newFeatures, f)
  179. }
  180. }
  181. r.Features = newFeatures
  182. }
  183. return nil
  184. }).Valuer(func(record interface{}) (string, error) {
  185. r := record.(*Phone)
  186. for _, f := range r.Features {
  187. if f == "WirelessCharge" {
  188. return "TRUE", nil
  189. }
  190. }
  191. return "FALSE", nil
  192. }),
  193. exchange.NewMeta("Intro").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
  194. r := record.(*Phone)
  195. r.Intro.Content = value
  196. return nil
  197. }).Valuer(func(record interface{}) (string, error) {
  198. r := record.(*Phone)
  199. return r.Intro.Content, nil
  200. }),
  201. exchange.NewMeta("ExtraIntro").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
  202. r := record.(*Phone)
  203. if value == "" {
  204. r.ExtraIntro = nil
  205. return nil
  206. }
  207. if r.ExtraIntro == nil {
  208. r.ExtraIntro = &ExtraIntro{}
  209. }
  210. r.ExtraIntro.Content = value
  211. return nil
  212. }).Valuer(func(record interface{}) (string, error) {
  213. r := record.(*Phone)
  214. if r.ExtraIntro == nil {
  215. return "", nil
  216. }
  217. return r.ExtraIntro.Content, nil
  218. }),
  219. exchange.NewMeta("FrontCamera").
  220. Setter(phoneCameraSetter("FrontCamera", "front")).
  221. Valuer(phoneCameraValuer("front")),
  222. exchange.NewMeta("BackCamera").
  223. Setter(phoneCameraSetter("BackCamera", "back")).
  224. Valuer(phoneCameraValuer("back")),
  225. exchange.NewMeta("SellingOnJD").
  226. Setter(sellingSiteSetter("SellingOnJD", 1)).
  227. Valuer(sellingSiteValuer(1)),
  228. exchange.NewMeta("SellingOnTaoBao").
  229. Setter(sellingSiteSetter("SellingOnTaoBao", 2)).
  230. Valuer(sellingSiteValuer(2)),
  231. }
  232. func phoneCameraSetter(field string, cameraType string) exchange.MetaSetter {
  233. return func(record interface{}, value string, metaValues exchange.MetaValues) error {
  234. r := record.(*Phone)
  235. if value != "" {
  236. for _, m := range r.Cameras {
  237. if m.Type == cameraType {
  238. m.Pixel = value
  239. return nil
  240. }
  241. }
  242. r.Cameras = append(r.Cameras, &Camera{
  243. Type: cameraType,
  244. Pixel: value,
  245. })
  246. } else {
  247. var newCameras []*Camera
  248. for i, _ := range r.Cameras {
  249. m := r.Cameras[i]
  250. if m.Type != cameraType {
  251. newCameras = append(newCameras, m)
  252. }
  253. }
  254. r.Cameras = newCameras
  255. }
  256. return nil
  257. }
  258. }
  259. func phoneCameraValuer(cameraType string) exchange.MetaValuer {
  260. return func(record interface{}) (string, error) {
  261. r := record.(*Phone)
  262. for _, m := range r.Cameras {
  263. if m.Type == cameraType {
  264. return m.Pixel, nil
  265. }
  266. }
  267. return "", nil
  268. }
  269. }
  270. func sellingSiteSetter(field string, id uint) exchange.MetaSetter {
  271. return func(record interface{}, value string, metaValues exchange.MetaValues) error {
  272. r := record.(*Phone)
  273. if strings.ToLower(value) == "true" {
  274. setted := false
  275. for _, m := range r.SellingSites {
  276. if m.ID == id {
  277. setted = true
  278. break
  279. }
  280. }
  281. if !setted {
  282. r.SellingSites = append(r.SellingSites, &ShoppingSite{
  283. ID: id,
  284. })
  285. }
  286. } else {
  287. var newSellingSites []*ShoppingSite
  288. for _, m := range r.SellingSites {
  289. if m.ID != id {
  290. newSellingSites = append(newSellingSites, m)
  291. }
  292. }
  293. r.SellingSites = newSellingSites
  294. }
  295. return nil
  296. }
  297. }
  298. func sellingSiteValuer(id uint) exchange.MetaValuer {
  299. return func(record interface{}) (string, error) {
  300. r := record.(*Phone)
  301. for _, m := range r.SellingSites {
  302. if m.ID == id {
  303. return "TRUE", nil
  304. }
  305. }
  306. return "FALSE", nil
  307. }
  308. }
  309. func initPhoneAssociations() {
  310. // init manyToMany records
  311. if err := db.Create([]*ShoppingSite{
  312. {ID: 1, Name: "JD"}, {ID: 2, Name: "TaoBao"}, {ID: 3, Name: "PDD"},
  313. }).Error; err != nil {
  314. panic(err)
  315. }
  316. }
  317. func TestExample(t *testing.T) {
  318. initTables()
  319. initPhoneAssociations()
  320. importer := exchange.NewImporter(&Phone{}).
  321. Metas(phoneMetas...).
  322. Associations(phoneAssociations...)
  323. exporter := exchange.NewExporter(&Phone{}).
  324. Metas(phoneMetas...).
  325. Associations(phoneAssociations...)
  326. csvContent := `Code,Name,ReleaseDate,Width,Height,Depth,ScreenSize,ScreenType,5G,WirelessCharge,Intro,ExtraIntro,FrontCamera,BackCamera,SellingOnJD,SellingOnTaoBao
  327. 100,Orange13,2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
  328. 101,Orange14,2021-01-02,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
  329. 102,Orange15,2021-01-02,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
  330. 103,Orange16,2021-01-02,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
  331. 200,DaMi11,2021-02-02,100,200,10,6.1,LCD,TRUE,FALSE,dddd,edddd,2000px,5000px,FALSE,TRUE
  332. `
  333. r, err := exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(csvContent)))
  334. assert.NoError(t, err)
  335. err = importer.Exec(db, r)
  336. assert.NoError(t, err)
  337. buf := bytes.Buffer{}
  338. w, err := exchange.NewCSVWriter(&buf)
  339. assert.NoError(t, err)
  340. err = exporter.Exec(db, w)
  341. assert.NoError(t, err)
  342. assert.Equal(t, csvContent, buf.String())
  343. csvContent = `Code,Name,ReleaseDate,Width,Height,Depth,ScreenSize,ScreenType,5G,WirelessCharge,Intro,ExtraIntro,FrontCamera,BackCamera,SellingOnJD,SellingOnTaoBao
  344. 100,Orange13+,2021-02-01,88,188,8,6.3,LED,TRUE,FALSE,,,,,FALSE,FALSE
  345. 101,Orange14,2021-01-02,82,180,8,6.5,IPS,FALSE,TRUE,,,,,FALSE,FALSE
  346. 102,Orange15,2021-01-03,83,180,8,6.5,IPS,FALSE,TRUE,yyds3,eyyds3,4000px,7000px,FALSE,TRUE
  347. 103,Orange16,2021-01-04,84,180,8,6.5,IPS,FALSE,TRUE,yyds4,eyyds4,5000px,8000px,FALSE,TRUE
  348. 200,DaMi11,2021-02-02,100,200,10,6.1,LCD,TRUE,FALSE,dddd,edddd,2000px,5000px,FALSE,TRUE
  349. 300,Pear100,,,,,,,FALSE,FALSE,,,,,FALSE,FALSE
  350. `
  351. r, err = exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(csvContent)))
  352. assert.NoError(t, err)
  353. err = importer.Exec(db, r)
  354. assert.NoError(t, err)
  355. buf = bytes.Buffer{}
  356. w, err = exchange.NewCSVWriter(&buf)
  357. assert.NoError(t, err)
  358. err = exporter.Exec(db, w)
  359. assert.NoError(t, err)
  360. assert.Equal(t, csvContent, buf.String())
  361. }
  362. func TestBatch(t *testing.T) {
  363. initTables()
  364. initPhoneAssociations()
  365. importer := exchange.NewImporter(&Phone{}).
  366. Metas(phoneMetas...).
  367. Associations(phoneAssociations...)
  368. exporter := exchange.NewExporter(&Phone{}).
  369. Metas(phoneMetas...).
  370. Associations(phoneAssociations...)
  371. csvContentB := bytes.Buffer{}
  372. csvContentB.WriteString("Code,Name,ReleaseDate,Width,Height,Depth,ScreenSize,ScreenType,5G,WirelessCharge,Intro,ExtraIntro,FrontCamera,BackCamera,SellingOnJD,SellingOnTaoBao\n")
  373. for i := 0; i < 600; i++ {
  374. // 100,Orange13,2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
  375. code := fmt.Sprintf("%d", i+100)
  376. csvContentB.WriteString(code)
  377. csvContentB.WriteString(fmt.Sprintf(",Orange"))
  378. csvContentB.WriteString(code)
  379. csvContentB.WriteString(",2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE\n")
  380. }
  381. csvContent := csvContentB.String()
  382. // batch create
  383. maxParamsPerSQLOpt := exchange.MaxParamsPerSQL(100)
  384. r, err := exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(csvContent)))
  385. assert.NoError(t, err)
  386. err = importer.Exec(db, r, maxParamsPerSQLOpt)
  387. assert.NoError(t, err)
  388. buf := bytes.Buffer{}
  389. w, err := exchange.NewCSVWriter(&buf)
  390. assert.NoError(t, err)
  391. err = exporter.Exec(db, w, maxParamsPerSQLOpt)
  392. assert.NoError(t, err)
  393. assert.Equal(t, csvContent, buf.String())
  394. // batch find
  395. r, err = exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(csvContent)))
  396. assert.NoError(t, err)
  397. err = importer.Exec(db, r, maxParamsPerSQLOpt)
  398. assert.NoError(t, err)
  399. buf = bytes.Buffer{}
  400. w, err = exchange.NewCSVWriter(&buf)
  401. assert.NoError(t, err)
  402. err = exporter.Exec(db, w, maxParamsPerSQLOpt)
  403. assert.NoError(t, err)
  404. assert.Equal(t, csvContent, buf.String())
  405. // batch update
  406. csvContentB = bytes.Buffer{}
  407. csvContentB.WriteString("Code,Name,ReleaseDate,Width,Height,Depth,ScreenSize,ScreenType,5G,WirelessCharge,Intro,ExtraIntro,FrontCamera,BackCamera,SellingOnJD,SellingOnTaoBao\n")
  408. for i := 0; i < 300; i++ {
  409. // 100,Orange13,2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
  410. code := fmt.Sprintf("%d", i+100)
  411. csvContentB.WriteString(code)
  412. csvContentB.WriteString(fmt.Sprintf(",Orange"))
  413. csvContentB.WriteString(code)
  414. csvContentB.WriteString(",2021-02-01,90,190,9,7.5,IPS+,FALSE,FALSE,,,,,FALSE,FALSE\n")
  415. }
  416. for i := 300; i < 600; i++ {
  417. // 100,Orange13,2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
  418. code := fmt.Sprintf("%d", i+100)
  419. csvContentB.WriteString(code)
  420. csvContentB.WriteString(fmt.Sprintf(",Orangee"))
  421. csvContentB.WriteString(code)
  422. csvContentB.WriteString(",2021-02-01,90,190,9,7.5,IPS+,TRUE,FALSE,yyds2,eyyds2,4000px,7000px,FALSE,TRUE\n")
  423. }
  424. newCsvContent := csvContentB.String()
  425. r, err = exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(newCsvContent)))
  426. assert.NoError(t, err)
  427. err = importer.Exec(db, r, maxParamsPerSQLOpt)
  428. assert.NoError(t, err)
  429. buf = bytes.Buffer{}
  430. w, err = exchange.NewCSVWriter(&buf)
  431. assert.NoError(t, err)
  432. err = exporter.Exec(db, w, maxParamsPerSQLOpt)
  433. assert.NoError(t, err)
  434. assert.Equal(t, newCsvContent, buf.String())
  435. }