package database_test import ( "context" "database/sql" "errors" "fmt" "testing" "git.cryptic.systems/volker.raschek/flucky/pkg/testutils/database" "git.cryptic.systems/volker.raschek/flucky/pkg/testutils/dockerutils" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/require" _ "github.com/lib/pq" ) func requireCountNetworks(ctx context.Context, dockerClient *dockerutils.Client, dbOptions *database.PostgresOptions, equal int, t *testing.T) { require := require.New(t) containers, err := dockerClient.ContainerListByLabels(ctx, true, dbOptions.ContainerLabels) require.NoError(err) require.Equal(equal, len(containers[0].NetworkSettings.Networks)) } func requireContainerHasNetworksAndAliasses(ctx context.Context, dockerClient *dockerutils.Client, dbOptions *database.PostgresOptions, t *testing.T) { require := require.New(t) containers, err := dockerClient.ContainerListByNames(ctx, true, dbOptions.ContainerName) require.NoError(err) require.Contains(containers[0].Names, fmt.Sprintf("/%v", dbOptions.ContainerName)) for networkName, _ := range dbOptions.ContainerNetworks { networks, err := dockerClient.NetworkListByNames(ctx, networkName) require.NoError(err) require.Equal(networkName, networks[0].Name) require.Equal(dbOptions.ContainerName, networks[0].Containers[containers[0].ID].Name) } } func requireTestCleanup(ctx context.Context, dockerClient *dockerutils.Client, cleanupLabels map[string]string, t *testing.T) { require := require.New(t) containers, err := dockerClient.ContainerListByLabels(ctx, true, cleanupLabels) require.NoError(err) require.NotNil(containers) require.Len(containers, 0) networks, err := dockerClient.NetworkListByLabels(ctx, cleanupLabels) require.NoError(err) require.NotNil(networks) require.Len(networks, 0) } func TestPostgresDatabase(t *testing.T) { require := require.New(t) dbOptions, cleanup := database.NewPostgresDatabase(t) t.Cleanup(func() { cleanup() }) require.NotEqual(0, dbOptions.HostPort) dockerClient, err := dockerutils.New() require.NoError(err) dbContainers, err := dockerClient.ContainerListByLabels(context.Background(), true, dbOptions.ContainerLabels) require.NoError(err) require.NotNil(dbContainers) require.Len(dbContainers, 1) dbo, err := sql.Open(dbOptions.Driver, dbOptions.DSN) require.NoError(err) require.NoError(dbo.Ping()) query := "SELECT 1" row := dbo.QueryRow(query) var i int err = row.Scan(&i) require.NoError(err) require.Equal(1, i) } func TestPostgresNetwork(t *testing.T) { var ( ctx = context.Background() require = require.New(t) cleanupLabels = map[string]string{ uuid.NewV4().String(): uuid.NewV4().String(), } ) dockerClient, err := dockerutils.New() require.NoError(err) t.Cleanup(func() { dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels) dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels) dockerClient.Close() }) // TestDefaultNetwork dbOptionsPointer, cleanup := database.NewPostgresDatabase(t) t.Cleanup(cleanup) require.NotNil(dbOptionsPointer.ContainerNetworks) require.Len(dbOptionsPointer.ContainerNetworks, 0) requireContainerHasNetworksAndAliasses(ctx, dockerClient, dbOptionsPointer, t) // TestRandomNetwork dbOptions := database.NewPostgresOptions() dbOptions.HostPort = "0" dbOptions.ContainerName = "Volker" dbOptions.ContainerLabels = cleanupLabels dbOptions.ContainerNetworks[uuid.NewV4().String()] = []string{ uuid.NewV4().String(), uuid.NewV4().String(), } _, cleanup = database.StartPostgres(t, dbOptions) t.Cleanup(cleanup) requireCountNetworks(ctx, dockerClient, dbOptions, 1, t) requireContainerHasNetworksAndAliasses(ctx, dockerClient, dbOptions, t) // TestRandomMultipleNetworks containerNetworkNameA := uuid.NewV4().String() containerNetworkNameB := uuid.NewV4().String() containerNetworks := map[string][]string{ containerNetworkNameA: { uuid.NewV4().String(), uuid.NewV4().String(), }, containerNetworkNameB: { uuid.NewV4().String(), uuid.NewV4().String(), }, } containerNetworkLabels := map[string]map[string]string{ containerNetworkNameA: cleanupLabels, containerNetworkNameB: cleanupLabels, } dbOptions = database.NewPostgresOptions() dbOptions.HostPort = "0" dbOptions.ContainerName = "Raschek" dbOptions.ContainerNetworks = containerNetworks dbOptions.ContainerNetworkLabels = containerNetworkLabels dbOptions.ContainerLabels = cleanupLabels _, cleanup = database.StartPostgres(t, dbOptions) t.Cleanup(cleanup) requireCountNetworks(ctx, dockerClient, dbOptions, 2, t) requireContainerHasNetworksAndAliasses(ctx, dockerClient, dbOptions, t) // TestCleanup // Test if a network with an additional endpoint will not be removed containerNetwork := uuid.NewV4().String() containerNetworkA := map[string][]string{ containerNetwork: { uuid.NewV4().String(), uuid.NewV4().String(), }, } containerNetworkB := map[string][]string{ containerNetwork: { uuid.NewV4().String(), uuid.NewV4().String(), }, } containerNetworkLabels = map[string]map[string]string{ containerNetwork: cleanupLabels, } dbOptions = database.NewPostgresOptions() dbOptions.HostPort = "0" dbOptions.ContainerName = "is" dbOptions.ContainerLabels = cleanupLabels dbOptions.ContainerNetworks = containerNetworkA dbOptions.ContainerNetworkLabels = containerNetworkLabels _, cleanup = database.StartPostgres(t, dbOptions) dbOptions = database.NewPostgresOptions() dbOptions.HostPort = "0" dbOptions.ContainerName = "the_best" dbOptions.ContainerLabels = cleanupLabels dbOptions.ContainerNetworks = containerNetworkB dbOptions.ContainerNetworkLabels = containerNetworkLabels _, cleanupSecond := database.StartPostgres(t, dbOptions) cleanup() networkResources, err := dockerClient.NetworkListByNames(ctx, containerNetwork) require.NoError(err) require.Len(networkResources[0].Containers, 1) cleanupSecond() requireTestCleanup(ctx, dockerClient, cleanupLabels, t) } func TestPostgresOptionsValidate(t *testing.T) { require := require.New(t) // ContainerImage is empty dbOptions := database.PostgresOptions{} err := dbOptions.Validate() require.Error(err) require.True(errors.Is(err, database.ErrInvalidAttr)) require.EqualError(err, "Invalid DatabaseOption attribute: Attribute ContainerImage is empty") // ContainerPort is empty dbOptions = database.PostgresOptions{ ContainerImage: "docker.io/library/postgres:13-alpine", } err = dbOptions.Validate() require.Error(err) require.True(errors.Is(err, database.ErrInvalidAttr)) require.EqualError(err, "Invalid DatabaseOption attribute: Attribute ContainerPort is empty") // Driver is empty dbOptions = database.PostgresOptions{ ContainerImage: "docker.io/library/postgres:13-alpine", ContainerPort: "5432", } err = dbOptions.Validate() require.Error(err) require.True(errors.Is(err, database.ErrInvalidAttr)) require.EqualError(err, "Invalid DatabaseOption attribute: Attribute Driver is empty") // HostPort is empty dbOptions = database.PostgresOptions{ ContainerImage: "docker.io/library/postgres:13-alpine", ContainerPort: "5432", Driver: "sdfsdfsdf", } err = dbOptions.Validate() require.Error(err) require.True(errors.Is(err, database.ErrInvalidAttr)) require.EqualError(err, "Invalid DatabaseOption attribute: Attribute HostPort is empty") // ContainerEnv: POSTGRES_PASSWORD dbOptions = database.PostgresOptions{ ContainerImage: "docker.io/library/postgres:13-alpine", ContainerPort: "5432", Driver: "sdfsdfsdf", HostPort: "asdasd", } err = dbOptions.Validate() require.Error(err) require.True(errors.Is(err, database.ErrInvalidAttr)) require.EqualError(err, "Invalid DatabaseOption attribute: Required env POSTGRES_PASSWORD not defined") // ContainerEnv: POSTGRES_USER dbOptions = database.PostgresOptions{ ContainerEnv: map[string]string{ "POSTGRES_PASSWORD": "HelloWorld", }, ContainerImage: "docker.io/library/postgres:13-alpine", ContainerPort: "5432", Driver: "sdfsdfsdf", HostPort: "asdasd", } err = dbOptions.Validate() require.Error(err) require.True(errors.Is(err, database.ErrInvalidAttr)) require.EqualError(err, "Invalid DatabaseOption attribute: Required env POSTGRES_USER not defined") // ContainerEnv: POSTGRES_USER dbOptions = database.PostgresOptions{ ContainerEnv: map[string]string{ "POSTGRES_PASSWORD": "postgres", "POSTGRES_USER": "postgres", }, ContainerImage: "docker.io/library/postgres:13-alpine", ContainerPort: "5432", Driver: "sdfsdfsdf", HostPort: "asdasd", } err = dbOptions.Validate() require.Error(err) require.True(errors.Is(err, database.ErrInvalidAttr)) require.EqualError(err, "Invalid DatabaseOption attribute: Required env POSTGRES_DB not defined") // Driver not supported dbOptions = database.PostgresOptions{ ContainerEnv: map[string]string{ "POSTGRES_PASSWORD": "postgres", "POSTGRES_USER": "postgres", "POSTGRES_DB": "postgres", }, ContainerImage: "docker.io/library/postgres:13-alpine", ContainerPort: "5432", Driver: "sdfsdfsdf", HostPort: "asdasd", } err = dbOptions.Validate() require.Error(err) require.True(errors.Is(err, database.ErrInvalidAttr)) require.EqualError(err, "Invalid DatabaseOption attribute: Driver sdfsdfsdf not supported") // HostPort: Failed to parse dbOptions = database.PostgresOptions{ ContainerEnv: map[string]string{ "POSTGRES_PASSWORD": "postgres", "POSTGRES_USER": "postgres", "POSTGRES_DB": "postgres", }, ContainerImage: "docker.io/library/postgres:13-alpine", ContainerPort: "5432", Driver: "postgres", HostPort: "asdasd", } err = dbOptions.Validate() require.Error(err) require.EqualError(err, "Failed to parse hostport asdasd: strconv.ParseInt: parsing \"asdasd\": invalid syntax") // HostPort: Well-Known port dbOptions = database.PostgresOptions{ ContainerEnv: map[string]string{ "POSTGRES_PASSWORD": "postgres", "POSTGRES_USER": "postgres", "POSTGRES_DB": "postgres", }, ContainerImage: "docker.io/library/postgres:13-alpine", ContainerPort: "5432", Driver: "postgres", HostPort: "443", } err = dbOptions.Validate() require.Error(err) require.EqualError(err, "Invalid DatabaseOption attribute: ContainerPort 443: Protect well-known ports between 1-1023") }