Files
Jay Brown 72de690894 Merged in feature/chatbot1 (pull request #223)
Chatbot functionality

* baseline working

* missing test file

* more tests
2026-05-07 20:56:18 +00:00

149 lines
4.2 KiB
Go

package test
import (
"bufio"
"context"
"fmt"
"log/slog"
"strconv"
"testing"
"queryorchestration/internal/serviceconfig"
"queryorchestration/internal/serviceconfig/objectstore"
"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 ContainerConfigProvider interface {
serviceconfig.ConfigProvider
objectstore.ConfigProvider
}
type containerConfig struct {
Name string
Cfg ContainerConfigProvider
DownstreamQueues []RunnerName
Env map[string]string
WaitForMsg string
ExposedPorts []nat.Port
}
const (
imageTag = "queryorchestration:latest"
dockerfilePath = "build/Dockerfile"
)
func createContainer(t testing.TB, ctx context.Context, network string, cfg *containerConfig) (testcontainers.Container, func()) {
bucket := cfg.Cfg.GetBucket()
if bucket == "" {
bucket = "invalid-example"
}
env := map[string]string{
"BUCKET": bucket,
"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_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",
// Rate limiting config - very high limits for testing to avoid interference
"RATE_LIMIT_GLOBAL_RATE": "10000", // 10k req/s global (vs 500 in prod)
"RATE_LIMIT_GLOBAL_BURST": "20000", // 20k burst global (vs 1000 in prod)
"RATE_LIMIT_DEFAULT_RATE": "1000", // 1k req/s per IP (vs 5 in prod)
"RATE_LIMIT_DEFAULT_BURST": "2000", // 2k burst per IP (vs 10 in prod)
// Chatbot config — required+notEmpty per plan §8. Test placeholder
// values satisfy the loader and ChatbotConfig.Validate so the
// queryAPI container starts; M3 introduces the FastAPI client and
// will exercise these values against a real or test FastAPI.
"CHATBOT_SERVICE_URL": "http://chatbot.test:8000",
"CHATBOT_API_KEY": "test-api-key",
"CHATBOT_REQUEST_TIMEOUT_SECONDS": "60",
"CHATBOT_MAX_TURN_CHARS": "2000",
"CHATBOT_RECENT_TURNS": "5",
}
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)
}
req := testcontainers.ContainerRequest{
Image: imageTag,
Env: env,
//WaitingFor: wait.ForLog(cfg.WaitForMsg),
WaitingFor: wait.ForHealthCheck(),
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)
}
}