package test import ( "bufio" "context" "fmt" "log/slog" "os" "path/filepath" "strconv" "sync" "testing" "queryorchestration/internal/serviceconfig" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/docker/docker/pkg/archive" "github.com/docker/go-connections/nat" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) type Container struct { Container testcontainers.Container URI string } type containerConfig struct { Name string Cfg serviceconfig.ConfigProvider DownstreamQueues []RunnerName Env map[string]string WaitForMsg string ExposedPorts []nat.Port MockHTTP string } const ( imageTag = "queryorchestration:latest" dockerfilePath = "build/Dockerfile" ) var imageBuild sync.Once func buildImage(t testing.TB, ctx context.Context) { imageBuild.Do(func() { dockerContextPath, err := os.Getwd() require.NoError(t, err, "Failed to get current directory") for { candidatePath := filepath.Join(dockerContextPath, dockerfilePath) t.Logf("Checking for Dockerfile at: %s", dockerContextPath) _, err := os.Stat(candidatePath) if err == nil { break } dockerContextPath = filepath.Join(dockerContextPath, "..") } cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) require.NoError(t, err) excludePatterns := []string{ "LICENSE", "README.md", "**/*_test.go", "cmd/*_test/", ".bitbucket/", ".devbox/", ".vscode/", "deployments/", "mocks/", "out/", "serviceAPIs/", "test/", "docs/", "assets/", "pkg/", "internal/test/", "Taskfile.yml", "scripts/", ".gitattributes", ".gitignore", "devbox.*", "sqlc.yml", "vaccum.conf.yaml", ".*.yml", "**/*.cast", "**/*.http", } buildContext, err := archive.TarWithOptions(dockerContextPath, &archive.TarOptions{ ExcludePatterns: excludePatterns, }) require.NoError(t, err) defer buildContext.Close() out, err := cli.ImageBuild(ctx, buildContext, types.ImageBuildOptions{ Dockerfile: dockerfilePath, Tags: []string{imageTag}, }) require.NoError(t, err) defer func() { err := out.Body.Close() require.NoError(t, err) }() }) } func createContainer(t testing.TB, ctx context.Context, network string, cfg *containerConfig) (testcontainers.Container, func()) { env := map[string]string{ "PGUSER": cfg.Cfg.GetDBUser(), "PGPASSWORD": cfg.Cfg.GetDBSecret(), "PGHOST": dbAlias, "PGDATABASE": cfg.Cfg.GetDBName(), "PGPORT": strconv.Itoa(dbPort), "DB_NOSSL": strconv.FormatBool(cfg.Cfg.IsDBNoSSL()), "AWS_ACCESS_KEY_ID": cfg.Cfg.GetAWSKeyID(), "AWS_SECRET_ACCESS_KEY": cfg.Cfg.GetAWSSecretKey(), "AWS_REGION": cfg.Cfg.GetAWSRegion(), "AWS_DEFAULT_REGION": cfg.Cfg.GetAWSRegion(), "AWS_ENDPOINT_URL": cfg.Cfg.GetAWSEndpoint(), "AWS_ENDPOINT_URL_SQS": cfg.Cfg.GetAWSEndpoint(), "AWS_ENDPOINT_URL_S3": cfg.Cfg.GetAWSEndpoint(), "AWS_ENDPOINT_URL_TEXTRACT": cfg.MockHTTP, "AWS_S3_USE_PATH_STYLE": strconv.FormatBool(true), "DISABLE_AUTH": "true", "LOG_LEVEL": "DEBUG", "COGNITO_USER_POOL_ID": "coguserpoolid", "COGNITO_CLIENT_SECRET": "cogsecret", "COGNITO_DOMAIN": "cogdomain", "COGNITO_CLIENT_ID": "clientid", } if cfg.Env != nil { for k, v := range cfg.Env { env[k] = v } } for _, e := range cfg.DownstreamQueues { env[string(GetRunnerEnvFromName(e))] = GetQueueURL(t, cfg.Cfg, e) } buildImage(t, ctx) req := testcontainers.ContainerRequest{ Image: imageTag, Env: env, WaitingFor: wait.ForLog(cfg.WaitForMsg), Entrypoint: []string{fmt.Sprintf("./%s", cfg.Name)}, Networks: []string{network}, } if len(cfg.ExposedPorts) > 0 { ports := make([]string, len(cfg.ExposedPorts)) for index, port := range cfg.ExposedPorts { ports[index] = port.Port() } req.ExposedPorts = ports } container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) if err != nil { PrintContainerLogs(t, container) require.NoError(t, err) } return container, func() { err := container.Terminate(ctx) if err != nil { t.Error(err) } } } func PrintContainerLogs(t testing.TB, container testcontainers.Container) { if container == nil { return } logs, err := container.Logs(t.Context()) require.NoError(t, err) defer func() { err := logs.Close() require.NoError(t, err) }() scanner := bufio.NewScanner(logs) for scanner.Scan() { line := scanner.Text() slog.Info(line) } }