package setup import ( "context" "log" "strconv" redisadapter "git.gocasts.ir/ebhomengo/niki/adapter/redis" "git.gocasts.ir/ebhomengo/niki/repository/mysql" // nolint _ "github.com/go-sql-driver/mysql" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" ) // TestContainer holds the state of docker test. type TestContainer struct { dockerPool *dockertest.Pool // the connection pool to Docker. mariaResource *dockertest.Resource // MariaDB Docker container resource. redisResource *dockertest.Resource // Redis Docker container resource. mariaDBConn *mysql.DB // Connection to the MariaDB database. redisDBConn *redisadapter.Adapter // Connection to the Redis database. containerExpiryInSeconds uint } func NewTestContainer() *TestContainer { return &TestContainer{ // nolint containerExpiryInSeconds: 120, } } func (t *TestContainer) GetMariaDBConnection() *mysql.DB { if t.mariaDBConn == nil { log.Fatal("did you forgot to start the test container?") } return t.mariaDBConn } func (t *TestContainer) GetRedisDBConnection() *redisadapter.Adapter { if t.redisDBConn == nil { log.Fatal("did you forgot to start the test container?") } return t.redisDBConn } func (t *TestContainer) GetMariaDBConfig() mysql.Config { if t.mariaResource == nil { log.Fatal("did you forgot to start the test container?") } port, err := strconv.Atoi(t.mariaResource.GetPort("3306/tcp")) if err != nil { log.Fatalf("couldn't convert port to integer: %s", err) } return mysql.Config{ Username: "root", Password: "secret", Port: port, Host: "localhost", DBName: "mysql", } } func (t *TestContainer) GetRedisConfig() redisadapter.Config { if t.mariaResource == nil { log.Fatal("did you forgot to start the test container?") } port, err := strconv.Atoi(t.redisResource.GetPort("6379/tcp")) if err != nil { log.Fatalf("couldn't convert port to integer: %s", err) } return redisadapter.Config{ Host: "localhost", Port: port, Password: "", DB: 0, } } // Start initializes and starts the MariaDB and Redis Docker container for testing. func (t *TestContainer) Start() { // uses a sensible default on windows (tcp/http) and linux/osx (socket) var err error t.dockerPool, err = dockertest.NewPool("") if err != nil { log.Fatalf("Could not construct pool: %s", err) } // uses pool to try to connect to Docker err = t.dockerPool.Client.Ping() if err != nil { log.Fatalf("Could not connect to Docker: %s", err) } // pulls mariaDB image, creates a container based on it and runs it t.mariaResource, err = t.dockerPool.RunWithOptions( &dockertest.RunOptions{ Repository: "mariadb", Tag: "11.1", Env: []string{"MARIADB_ROOT_PASSWORD=secret"}, }, func(config *docker.HostConfig) { config.AutoRemove = true // Ensure the container is removed after the test. config.RestartPolicy = docker.RestartPolicy{ Name: "no", // Do not automatically restart the container. } }, ) if err != nil { log.Fatalf("Could not start maria resource: %s", err) } // Set container to expire after two minutes to avoid dangling resources in case of test interruption. if err = t.mariaResource.Expire(t.containerExpiryInSeconds); err != nil { log.Fatalf("Couldn't set MariaDB container expiration: %s", err) } t.redisResource, err = t.dockerPool.RunWithOptions( &dockertest.RunOptions{ Repository: "redis", Tag: "6.2", Env: []string{"ALLOW_EMPTY_PASSWORD=yes"}, }, func(config *docker.HostConfig) { config.AutoRemove = true // Ensure the container is removed after the test. config.RestartPolicy = docker.RestartPolicy{ Name: "no", // Do not automatically restart the container. } }, ) if err != nil { log.Fatalf("Could not start redis resource: %s", err) } // Set container to expire after two minutes to avoid dangling resources in case of test interruption. if err = t.redisResource.Expire(t.containerExpiryInSeconds); err != nil { log.Fatalf("Couldn't set RedisDB container expiration: %s", err) } // exponential backoff-retry, because the application in the container might not be ready to accept connections yet if err = t.dockerPool.Retry(func() error { var err error t.mariaDBConn = mysql.New(t.GetMariaDBConfig()) err = t.mariaDBConn.Conn().Ping() if err != nil { return err } t.redisDBConn = redisadapter.New(t.GetRedisConfig()) err = t.redisDBConn.Client().Ping(context.Background()).Err() if err != nil { return err } return nil }); err != nil { log.Fatalf("Could not connect to database: %s", err) } } // Stop cleans up the Docker containers. func (t *TestContainer) Stop() { err := t.mariaDBConn.CloseStatements() if err != nil { log.Fatalf("Could not close statements on maria db connection: %s", err) } err = t.mariaDBConn.Conn().Close() if err != nil { log.Fatalf("Could not close maria db connection: %s", err) } if err = t.dockerPool.Purge(t.mariaResource); err != nil { log.Fatalf("Could not purge maria resource: %s", err) } if err = t.dockerPool.Purge(t.redisResource); err != nil { log.Fatalf("Could not purge redis resource: %s", err) } }