github.com/etecs-ru/gnomock@v0.13.2/preset/mongo/preset.go (about) 1 // Package mongo includes mongo implementation of Gnomock Preset interface. 2 // This Preset can be passed to gnomock.StartPreset function to create a 3 // configured mongo container to use in tests 4 package mongo 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "path" 14 "strings" 15 16 "github.com/etecs-ru/gnomock" 17 "github.com/etecs-ru/gnomock/internal/registry" 18 "go.mongodb.org/mongo-driver/bson" 19 "go.mongodb.org/mongo-driver/bson/bsonrw" 20 mongodb "go.mongodb.org/mongo-driver/mongo" 21 mongooptions "go.mongodb.org/mongo-driver/mongo/options" 22 ) 23 24 const defaultVersion = "4.4" 25 26 func init() { 27 registry.Register("mongo", func() gnomock.Preset { return &P{} }) 28 } 29 30 // Preset creates a new Gmomock MongoDB preset. This preset includes a MongoDB 31 // specific healthcheck function, default MongoDB image and port, and allows to 32 // optionally set up initial state. 33 // 34 // By default, this preset uses MongoDB 4.4. 35 func Preset(opts ...Option) gnomock.Preset { 36 p := &P{} 37 38 for _, opt := range opts { 39 opt(p) 40 } 41 42 return p 43 } 44 45 // P is a Gnomock Preset implementation of MongoDB 46 type P struct { 47 DataPath string `json:"data_path"` 48 User string `json:"user"` 49 Password string `json:"password"` 50 Version string `json:"version"` 51 } 52 53 // Image returns an image that should be pulled to create this container 54 func (p *P) Image() string { 55 return fmt.Sprintf("docker.io/library/mongo:%s", p.Version) 56 } 57 58 // Ports returns ports that should be used to access this container 59 func (p *P) Ports() gnomock.NamedPorts { 60 return gnomock.DefaultTCP(27017) 61 } 62 63 // Options returns a list of options to configure this container 64 func (p *P) Options() []gnomock.Option { 65 p.setDefaults() 66 67 opts := []gnomock.Option{ 68 gnomock.WithHealthCheck(healthcheck), 69 } 70 71 if p.DataPath != "" { 72 opts = append(opts, gnomock.WithInit(p.initf)) 73 } 74 75 if p.User != "" && p.Password != "" { 76 opts = append( 77 opts, 78 gnomock.WithEnv("MONGO_INITDB_ROOT_USERNAME="+p.User), 79 gnomock.WithEnv("MONGO_INITDB_ROOT_PASSWORD="+p.Password), 80 ) 81 } 82 83 return opts 84 } 85 86 func (p *P) setDefaults() { 87 if p.Version == "" { 88 p.Version = defaultVersion 89 } 90 } 91 92 func (p *P) initf(ctx context.Context, c *gnomock.Container) error { 93 addr := c.Address(gnomock.DefaultPort) 94 uri := "mongodb://" + addr 95 96 if p.useCustomUser() { 97 uri = fmt.Sprintf("mongodb://%s:%s@%s", p.User, p.Password, addr) 98 } 99 100 clientOptions := mongooptions.Client().ApplyURI(uri) 101 102 client, err := mongodb.NewClient(clientOptions) 103 if err != nil { 104 return fmt.Errorf("can't create mongo client: %w", err) 105 } 106 107 err = client.Connect(context.Background()) 108 if err != nil { 109 return fmt.Errorf("can't connect: %w", err) 110 } 111 112 topLevelDirs, err := ioutil.ReadDir(p.DataPath) 113 if err != nil { 114 return fmt.Errorf("can't read test data path: %w", err) 115 } 116 117 for _, topLevelDir := range topLevelDirs { 118 if !topLevelDir.IsDir() { 119 continue 120 } 121 122 err = p.setupDB(client, topLevelDir.Name()) 123 if err != nil { 124 return err 125 } 126 } 127 128 return nil 129 } 130 131 func (p *P) useCustomUser() bool { 132 return p.User != "" && p.Password != "" 133 } 134 135 func (p *P) setupDB(client *mongodb.Client, dirName string) error { 136 dataFiles, err := ioutil.ReadDir(path.Join(p.DataPath, dirName)) 137 if err != nil { 138 return fmt.Errorf("can't read test data sub path '%s', %w", dirName, err) 139 } 140 141 for _, dataFile := range dataFiles { 142 if dataFile.IsDir() { 143 continue 144 } 145 146 fName := dataFile.Name() 147 148 err = p.setupCollection(client, dirName, fName) 149 if err != nil { 150 return fmt.Errorf("can't setup collection from file '%s': %w", fName, err) 151 } 152 } 153 154 return nil 155 } 156 157 func (p *P) setupCollection(client *mongodb.Client, dirName, dataFileName string) error { 158 collectionName := strings.TrimSuffix(dataFileName, path.Ext(dataFileName)) 159 160 file, err := os.Open(path.Join(p.DataPath, dirName, dataFileName)) //nolint:gosec 161 if err != nil { 162 return fmt.Errorf("can't open file '%s': %w", dataFileName, err) 163 } 164 165 vr, err := bsonrw.NewExtJSONValueReader(file, false) 166 if err != nil { 167 return fmt.Errorf("can't read file '%s': %w", dataFileName, err) 168 } 169 170 dec, err := bson.NewDecoder(vr) 171 if err != nil { 172 return fmt.Errorf("can't create BSON decoder for '%s': %w", dataFileName, err) 173 } 174 175 ctx := context.Background() 176 177 for { 178 var val interface{} 179 180 err = dec.Decode(&val) 181 if errors.Is(err, io.EOF) { 182 return nil 183 } 184 185 if err != nil { 186 return fmt.Errorf("can't decode file '%s': %w", dataFileName, err) 187 } 188 189 _, err = client.Database(dirName).Collection(collectionName).InsertOne(ctx, val) 190 if err != nil { 191 return fmt.Errorf("can't insert value from '%s' (%v): %w", dataFileName, val, err) 192 } 193 } 194 } 195 196 func healthcheck(ctx context.Context, c *gnomock.Container) error { 197 addr := c.Address(gnomock.DefaultPort) 198 clientOptions := mongooptions.Client().ApplyURI("mongodb://" + addr) 199 200 client, err := mongodb.NewClient(clientOptions) 201 if err != nil { 202 return fmt.Errorf("can't create mongo client: %w", err) 203 } 204 205 err = client.Connect(context.Background()) 206 if err != nil { 207 return fmt.Errorf("can't connect: %w", err) 208 } 209 210 return client.Ping(context.Background(), nil) 211 }