123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- package exchange_test
- import (
- "bytes"
- "database/sql/driver"
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "strings"
- "testing"
- "time"
- "github.com/qor5/x/exchange"
- "github.com/stretchr/testify/assert"
- "gorm.io/gorm"
- )
- type Phone struct {
- gorm.Model
- Code string `gorm:"uniqueIndex;not null"`
- Name string
- ReleaseDate *time.Time
- // promoted fields
- SizeInfo
- // embedded field
- Screen ScreenInfo `gorm:"embedded;embeddedPrefix:screen_"`
- // struct text field
- Features Features `gorm:"type:text"`
- // associations
- // has one
- Intro Intro `gorm:"foreignKey:PhoneCode;references:Code"`
- // has one
- ExtraIntro *ExtraIntro `gorm:"foreignKey:PhoneCode;references:Code"`
- // has many
- Cameras []*Camera `gorm:"foreignKey:PhoneCode;references:Code"`
- // many to many
- SellingSites []*ShoppingSite `gorm:"many2many:phone_selling_shopping_site;"`
- }
- type SizeInfo struct {
- Width string
- Height string
- Depth string
- }
- type ScreenInfo struct {
- Size string
- Type string
- }
- type Features []string
- func (p Features) Value() (driver.Value, error) {
- if len(p) == 0 {
- return json.Marshal(nil)
- }
- return json.Marshal(p)
- }
- func (p *Features) Scan(data interface{}) error {
- var byteData []byte
- switch values := data.(type) {
- case []byte:
- byteData = values
- case string:
- byteData = []byte(values)
- default:
- return errors.New("unsupported type of data")
- }
- return json.Unmarshal(byteData, p)
- }
- type Intro struct {
- gorm.Model
- PhoneCode string
- Content string
- }
- type ExtraIntro struct {
- gorm.Model
- PhoneCode string
- Content string
- }
- type Camera struct {
- gorm.Model
- PhoneCode string
- Type string
- Pixel string
- }
- type ShoppingSite struct {
- ID uint `gorm:"primarykey"`
- Name string
- }
- var phoneAssociations = []string{"Intro", "ExtraIntro", "Cameras", "SellingSites"}
- var phoneMetas = []*exchange.Meta{
- exchange.NewMeta("Code").PrimaryKey(true),
- exchange.NewMeta("Name"),
- exchange.NewMeta("ReleaseDate").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
- r := record.(*Phone)
- if value == "" {
- r.ReleaseDate = nil
- return nil
- }
- t, err := time.ParseInLocation("2006-01-02", value, time.Local)
- if err != nil {
- return err
- }
- r.ReleaseDate = &t
- return nil
- }).Valuer(func(record interface{}) (string, error) {
- r := record.(*Phone)
- if r.ReleaseDate == nil {
- return "", nil
- }
- return r.ReleaseDate.Local().Format("2006-01-02"), nil
- }),
- exchange.NewMeta("Width"),
- exchange.NewMeta("Height"),
- exchange.NewMeta("Depth"),
- exchange.NewMeta("ScreenSize").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
- r := record.(*Phone)
- r.Screen.Size = value
- return nil
- }).Valuer(func(record interface{}) (string, error) {
- r := record.(*Phone)
- return r.Screen.Size, nil
- }),
- exchange.NewMeta("ScreenType").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
- r := record.(*Phone)
- r.Screen.Type = value
- return nil
- }).Valuer(func(record interface{}) (string, error) {
- r := record.(*Phone)
- return r.Screen.Type, nil
- }),
- exchange.NewMeta("5G").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
- has5G := strings.ToLower(value) == "true"
- r := record.(*Phone)
- if has5G {
- setted := false
- for _, f := range r.Features {
- if f == "5G" {
- setted = true
- break
- }
- }
- if !setted {
- r.Features = append(r.Features, "5G")
- }
- } else {
- var newFeatures []string
- for _, f := range r.Features {
- if f != "5G" {
- newFeatures = append(newFeatures, f)
- }
- }
- r.Features = newFeatures
- }
- return nil
- }).Valuer(func(record interface{}) (string, error) {
- r := record.(*Phone)
- for _, f := range r.Features {
- if f == "5G" {
- return "TRUE", nil
- }
- }
- return "FALSE", nil
- }),
- exchange.NewMeta("WirelessCharge").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
- hasWirelessCharge := strings.ToLower(value) == "true"
- r := record.(*Phone)
- if hasWirelessCharge {
- setted := false
- for _, f := range r.Features {
- if f == "WirelessCharge" {
- setted = true
- break
- }
- }
- if !setted {
- r.Features = append(r.Features, "WirelessCharge")
- }
- } else {
- var newFeatures []string
- for _, f := range r.Features {
- if f != "WirelessCharge" {
- newFeatures = append(newFeatures, f)
- }
- }
- r.Features = newFeatures
- }
- return nil
- }).Valuer(func(record interface{}) (string, error) {
- r := record.(*Phone)
- for _, f := range r.Features {
- if f == "WirelessCharge" {
- return "TRUE", nil
- }
- }
- return "FALSE", nil
- }),
- exchange.NewMeta("Intro").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
- r := record.(*Phone)
- r.Intro.Content = value
- return nil
- }).Valuer(func(record interface{}) (string, error) {
- r := record.(*Phone)
- return r.Intro.Content, nil
- }),
- exchange.NewMeta("ExtraIntro").Setter(func(record interface{}, value string, metaValues exchange.MetaValues) error {
- r := record.(*Phone)
- if value == "" {
- r.ExtraIntro = nil
- return nil
- }
- if r.ExtraIntro == nil {
- r.ExtraIntro = &ExtraIntro{}
- }
- r.ExtraIntro.Content = value
- return nil
- }).Valuer(func(record interface{}) (string, error) {
- r := record.(*Phone)
- if r.ExtraIntro == nil {
- return "", nil
- }
- return r.ExtraIntro.Content, nil
- }),
- exchange.NewMeta("FrontCamera").
- Setter(phoneCameraSetter("FrontCamera", "front")).
- Valuer(phoneCameraValuer("front")),
- exchange.NewMeta("BackCamera").
- Setter(phoneCameraSetter("BackCamera", "back")).
- Valuer(phoneCameraValuer("back")),
- exchange.NewMeta("SellingOnJD").
- Setter(sellingSiteSetter("SellingOnJD", 1)).
- Valuer(sellingSiteValuer(1)),
- exchange.NewMeta("SellingOnTaoBao").
- Setter(sellingSiteSetter("SellingOnTaoBao", 2)).
- Valuer(sellingSiteValuer(2)),
- }
- func phoneCameraSetter(field string, cameraType string) exchange.MetaSetter {
- return func(record interface{}, value string, metaValues exchange.MetaValues) error {
- r := record.(*Phone)
- if value != "" {
- for _, m := range r.Cameras {
- if m.Type == cameraType {
- m.Pixel = value
- return nil
- }
- }
- r.Cameras = append(r.Cameras, &Camera{
- Type: cameraType,
- Pixel: value,
- })
- } else {
- var newCameras []*Camera
- for i, _ := range r.Cameras {
- m := r.Cameras[i]
- if m.Type != cameraType {
- newCameras = append(newCameras, m)
- }
- }
- r.Cameras = newCameras
- }
- return nil
- }
- }
- func phoneCameraValuer(cameraType string) exchange.MetaValuer {
- return func(record interface{}) (string, error) {
- r := record.(*Phone)
- for _, m := range r.Cameras {
- if m.Type == cameraType {
- return m.Pixel, nil
- }
- }
- return "", nil
- }
- }
- func sellingSiteSetter(field string, id uint) exchange.MetaSetter {
- return func(record interface{}, value string, metaValues exchange.MetaValues) error {
- r := record.(*Phone)
- if strings.ToLower(value) == "true" {
- setted := false
- for _, m := range r.SellingSites {
- if m.ID == id {
- setted = true
- break
- }
- }
- if !setted {
- r.SellingSites = append(r.SellingSites, &ShoppingSite{
- ID: id,
- })
- }
- } else {
- var newSellingSites []*ShoppingSite
- for _, m := range r.SellingSites {
- if m.ID != id {
- newSellingSites = append(newSellingSites, m)
- }
- }
- r.SellingSites = newSellingSites
- }
- return nil
- }
- }
- func sellingSiteValuer(id uint) exchange.MetaValuer {
- return func(record interface{}) (string, error) {
- r := record.(*Phone)
- for _, m := range r.SellingSites {
- if m.ID == id {
- return "TRUE", nil
- }
- }
- return "FALSE", nil
- }
- }
- func initPhoneAssociations() {
- // init manyToMany records
- if err := db.Create([]*ShoppingSite{
- {ID: 1, Name: "JD"}, {ID: 2, Name: "TaoBao"}, {ID: 3, Name: "PDD"},
- }).Error; err != nil {
- panic(err)
- }
- }
- func TestExample(t *testing.T) {
- initTables()
- initPhoneAssociations()
- importer := exchange.NewImporter(&Phone{}).
- Metas(phoneMetas...).
- Associations(phoneAssociations...)
- exporter := exchange.NewExporter(&Phone{}).
- Metas(phoneMetas...).
- Associations(phoneAssociations...)
- csvContent := `Code,Name,ReleaseDate,Width,Height,Depth,ScreenSize,ScreenType,5G,WirelessCharge,Intro,ExtraIntro,FrontCamera,BackCamera,SellingOnJD,SellingOnTaoBao
- 100,Orange13,2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
- 101,Orange14,2021-01-02,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
- 102,Orange15,2021-01-02,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
- 103,Orange16,2021-01-02,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
- 200,DaMi11,2021-02-02,100,200,10,6.1,LCD,TRUE,FALSE,dddd,edddd,2000px,5000px,FALSE,TRUE
- `
- r, err := exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(csvContent)))
- assert.NoError(t, err)
- err = importer.Exec(db, r)
- assert.NoError(t, err)
- buf := bytes.Buffer{}
- w, err := exchange.NewCSVWriter(&buf)
- assert.NoError(t, err)
- err = exporter.Exec(db, w)
- assert.NoError(t, err)
- assert.Equal(t, csvContent, buf.String())
- csvContent = `Code,Name,ReleaseDate,Width,Height,Depth,ScreenSize,ScreenType,5G,WirelessCharge,Intro,ExtraIntro,FrontCamera,BackCamera,SellingOnJD,SellingOnTaoBao
- 100,Orange13+,2021-02-01,88,188,8,6.3,LED,TRUE,FALSE,,,,,FALSE,FALSE
- 101,Orange14,2021-01-02,82,180,8,6.5,IPS,FALSE,TRUE,,,,,FALSE,FALSE
- 102,Orange15,2021-01-03,83,180,8,6.5,IPS,FALSE,TRUE,yyds3,eyyds3,4000px,7000px,FALSE,TRUE
- 103,Orange16,2021-01-04,84,180,8,6.5,IPS,FALSE,TRUE,yyds4,eyyds4,5000px,8000px,FALSE,TRUE
- 200,DaMi11,2021-02-02,100,200,10,6.1,LCD,TRUE,FALSE,dddd,edddd,2000px,5000px,FALSE,TRUE
- 300,Pear100,,,,,,,FALSE,FALSE,,,,,FALSE,FALSE
- `
- r, err = exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(csvContent)))
- assert.NoError(t, err)
- err = importer.Exec(db, r)
- assert.NoError(t, err)
- buf = bytes.Buffer{}
- w, err = exchange.NewCSVWriter(&buf)
- assert.NoError(t, err)
- err = exporter.Exec(db, w)
- assert.NoError(t, err)
- assert.Equal(t, csvContent, buf.String())
- }
- func TestBatch(t *testing.T) {
- initTables()
- initPhoneAssociations()
- importer := exchange.NewImporter(&Phone{}).
- Metas(phoneMetas...).
- Associations(phoneAssociations...)
- exporter := exchange.NewExporter(&Phone{}).
- Metas(phoneMetas...).
- Associations(phoneAssociations...)
- csvContentB := bytes.Buffer{}
- csvContentB.WriteString("Code,Name,ReleaseDate,Width,Height,Depth,ScreenSize,ScreenType,5G,WirelessCharge,Intro,ExtraIntro,FrontCamera,BackCamera,SellingOnJD,SellingOnTaoBao\n")
- for i := 0; i < 600; i++ {
- // 100,Orange13,2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
- code := fmt.Sprintf("%d", i+100)
- csvContentB.WriteString(code)
- csvContentB.WriteString(fmt.Sprintf(",Orange"))
- csvContentB.WriteString(code)
- csvContentB.WriteString(",2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE\n")
- }
- csvContent := csvContentB.String()
- // batch create
- maxParamsPerSQLOpt := exchange.MaxParamsPerSQL(100)
- r, err := exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(csvContent)))
- assert.NoError(t, err)
- err = importer.Exec(db, r, maxParamsPerSQLOpt)
- assert.NoError(t, err)
- buf := bytes.Buffer{}
- w, err := exchange.NewCSVWriter(&buf)
- assert.NoError(t, err)
- err = exporter.Exec(db, w, maxParamsPerSQLOpt)
- assert.NoError(t, err)
- assert.Equal(t, csvContent, buf.String())
- // batch find
- r, err = exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(csvContent)))
- assert.NoError(t, err)
- err = importer.Exec(db, r, maxParamsPerSQLOpt)
- assert.NoError(t, err)
- buf = bytes.Buffer{}
- w, err = exchange.NewCSVWriter(&buf)
- assert.NoError(t, err)
- err = exporter.Exec(db, w, maxParamsPerSQLOpt)
- assert.NoError(t, err)
- assert.Equal(t, csvContent, buf.String())
- // batch update
- csvContentB = bytes.Buffer{}
- csvContentB.WriteString("Code,Name,ReleaseDate,Width,Height,Depth,ScreenSize,ScreenType,5G,WirelessCharge,Intro,ExtraIntro,FrontCamera,BackCamera,SellingOnJD,SellingOnTaoBao\n")
- for i := 0; i < 300; i++ {
- // 100,Orange13,2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
- code := fmt.Sprintf("%d", i+100)
- csvContentB.WriteString(code)
- csvContentB.WriteString(fmt.Sprintf(",Orange"))
- csvContentB.WriteString(code)
- csvContentB.WriteString(",2021-02-01,90,190,9,7.5,IPS+,FALSE,FALSE,,,,,FALSE,FALSE\n")
- }
- for i := 300; i < 600; i++ {
- // 100,Orange13,2021-01-01,80,180,8,6.5,IPS,FALSE,TRUE,yyds,eyyds,3000px,6000px,TRUE,FALSE
- code := fmt.Sprintf("%d", i+100)
- csvContentB.WriteString(code)
- csvContentB.WriteString(fmt.Sprintf(",Orangee"))
- csvContentB.WriteString(code)
- csvContentB.WriteString(",2021-02-01,90,190,9,7.5,IPS+,TRUE,FALSE,yyds2,eyyds2,4000px,7000px,FALSE,TRUE\n")
- }
- newCsvContent := csvContentB.String()
- r, err = exchange.NewCSVReader(ioutil.NopCloser(strings.NewReader(newCsvContent)))
- assert.NoError(t, err)
- err = importer.Exec(db, r, maxParamsPerSQLOpt)
- assert.NoError(t, err)
- buf = bytes.Buffer{}
- w, err = exchange.NewCSVWriter(&buf)
- assert.NoError(t, err)
- err = exporter.Exec(db, w, maxParamsPerSQLOpt)
- assert.NoError(t, err)
- assert.Equal(t, newCsvContent, buf.String())
- }
|