feat: use postgres
This commit is contained in:
@@ -97,17 +97,15 @@ func newAuthsessionMailHarness(t *testing.T, opts authsessionMailHarnessOptions)
|
||||
opts.mailSMTPMode = "stub"
|
||||
}
|
||||
|
||||
mailEnv := map[string]string{
|
||||
"MAIL_LOG_LEVEL": "info",
|
||||
"MAIL_INTERNAL_HTTP_ADDR": mailInternalAddr,
|
||||
"MAIL_REDIS_ADDR": redisRuntime.Addr,
|
||||
"MAIL_TEMPLATE_DIR": moduleTemplateDir(t),
|
||||
"MAIL_STREAM_BLOCK_TIMEOUT": "100ms",
|
||||
"MAIL_OPERATOR_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"MAIL_SHUTDOWN_TIMEOUT": "2s",
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
}
|
||||
mailEnv := harness.StartMailServicePersistence(t, redisRuntime.Addr).Env
|
||||
mailEnv["MAIL_LOG_LEVEL"] = "info"
|
||||
mailEnv["MAIL_INTERNAL_HTTP_ADDR"] = mailInternalAddr
|
||||
mailEnv["MAIL_TEMPLATE_DIR"] = moduleTemplateDir(t)
|
||||
mailEnv["MAIL_STREAM_BLOCK_TIMEOUT"] = "100ms"
|
||||
mailEnv["MAIL_OPERATOR_REQUEST_TIMEOUT"] = time.Second.String()
|
||||
mailEnv["MAIL_SHUTDOWN_TIMEOUT"] = "2s"
|
||||
mailEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
mailEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
|
||||
var smtpCapture *harness.SMTPCapture
|
||||
switch opts.mailSMTPMode {
|
||||
@@ -135,7 +133,9 @@ func newAuthsessionMailHarness(t *testing.T, opts authsessionMailHarnessOptions)
|
||||
"AUTHSESSION_LOG_LEVEL": "info",
|
||||
"AUTHSESSION_PUBLIC_HTTP_ADDR": authsessionPublicAddr,
|
||||
"AUTHSESSION_INTERNAL_HTTP_ADDR": authsessionInternalAddr,
|
||||
"AUTHSESSION_REDIS_ADDR": redisRuntime.Addr,
|
||||
"AUTHSESSION_REDIS_MASTER_ADDR": redisRuntime.Addr,
|
||||
|
||||
"AUTHSESSION_REDIS_PASSWORD": "integration",
|
||||
"AUTHSESSION_USER_SERVICE_MODE": "rest",
|
||||
"AUTHSESSION_USER_SERVICE_BASE_URL": userStub.BaseURL(),
|
||||
"AUTHSESSION_MAIL_SERVICE_MODE": "rest",
|
||||
|
||||
@@ -43,13 +43,11 @@ func newAuthsessionUserHarness(t *testing.T) *authsessionUserHarness {
|
||||
userServiceBinary := harness.BuildBinary(t, "userservice", "./user/cmd/userservice")
|
||||
authsessionBinary := harness.BuildBinary(t, "authsession", "./authsession/cmd/authsession")
|
||||
|
||||
userServiceEnv := map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisServer.Addr(),
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
}
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisServer.Addr()).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
waitForUserServiceReady(t, userServiceProcess, "http://"+userServiceAddr)
|
||||
|
||||
@@ -57,7 +55,9 @@ func newAuthsessionUserHarness(t *testing.T) *authsessionUserHarness {
|
||||
"AUTHSESSION_LOG_LEVEL": "info",
|
||||
"AUTHSESSION_PUBLIC_HTTP_ADDR": authsessionPublicAddr,
|
||||
"AUTHSESSION_INTERNAL_HTTP_ADDR": authsessionInternalAddr,
|
||||
"AUTHSESSION_REDIS_ADDR": redisServer.Addr(),
|
||||
"AUTHSESSION_REDIS_MASTER_ADDR": redisServer.Addr(),
|
||||
|
||||
"AUTHSESSION_REDIS_PASSWORD": "integration",
|
||||
"AUTHSESSION_USER_SERVICE_MODE": "rest",
|
||||
"AUTHSESSION_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"AUTHSESSION_MAIL_SERVICE_MODE": "rest",
|
||||
|
||||
@@ -98,7 +98,9 @@ func newGatewayAuthSessionHarness(t *testing.T, opts gatewayAuthSessionOptions)
|
||||
"AUTHSESSION_PUBLIC_HTTP_REQUEST_TIMEOUT": opts.authsessionPublicHTTPTimeout.String(),
|
||||
"AUTHSESSION_INTERNAL_HTTP_ADDR": authsessionInternalAddr,
|
||||
"AUTHSESSION_INTERNAL_HTTP_REQUEST_TIMEOUT": defaultAuthsessionInternalHTTPTimeout.String(),
|
||||
"AUTHSESSION_REDIS_ADDR": redisServer.Addr(),
|
||||
"AUTHSESSION_REDIS_MASTER_ADDR": redisServer.Addr(),
|
||||
|
||||
"AUTHSESSION_REDIS_PASSWORD": "integration",
|
||||
"AUTHSESSION_USER_SERVICE_MODE": "rest",
|
||||
"AUTHSESSION_USER_SERVICE_BASE_URL": userStub.BaseURL(),
|
||||
"AUTHSESSION_USER_SERVICE_REQUEST_TIMEOUT": defaultAuthsessionDependencyTimeout.String(),
|
||||
@@ -118,7 +120,9 @@ func newGatewayAuthSessionHarness(t *testing.T, opts gatewayAuthSessionOptions)
|
||||
"GATEWAY_LOG_LEVEL": "info",
|
||||
"GATEWAY_PUBLIC_HTTP_ADDR": gatewayPublicAddr,
|
||||
"GATEWAY_AUTHENTICATED_GRPC_ADDR": gatewayGRPCAddr,
|
||||
"GATEWAY_SESSION_CACHE_REDIS_ADDR": redisServer.Addr(),
|
||||
"GATEWAY_REDIS_MASTER_ADDR": redisServer.Addr(),
|
||||
|
||||
"GATEWAY_REDIS_PASSWORD": "integration",
|
||||
"GATEWAY_SESSION_CACHE_REDIS_KEY_PREFIX": "gateway:session:",
|
||||
"GATEWAY_SESSION_EVENTS_REDIS_STREAM": "gateway:session_events",
|
||||
"GATEWAY_CLIENT_EVENTS_REDIS_STREAM": "gateway:client_events",
|
||||
|
||||
@@ -126,18 +126,17 @@ func newGatewayAuthsessionMailHarness(t *testing.T) *gatewayAuthsessionMailHarne
|
||||
authsessionBinary := harness.BuildBinary(t, "authsession", "./authsession/cmd/authsession")
|
||||
gatewayBinary := harness.BuildBinary(t, "gateway", "./gateway/cmd/gateway")
|
||||
|
||||
mailProcess := harness.StartProcess(t, "mail", mailBinary, map[string]string{
|
||||
"MAIL_LOG_LEVEL": "info",
|
||||
"MAIL_INTERNAL_HTTP_ADDR": mailInternalAddr,
|
||||
"MAIL_REDIS_ADDR": redisRuntime.Addr,
|
||||
"MAIL_TEMPLATE_DIR": moduleTemplateDir(t),
|
||||
"MAIL_SMTP_MODE": "stub",
|
||||
"MAIL_STREAM_BLOCK_TIMEOUT": "100ms",
|
||||
"MAIL_OPERATOR_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"MAIL_SHUTDOWN_TIMEOUT": "2s",
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
mailEnv := harness.StartMailServicePersistence(t, redisRuntime.Addr).Env
|
||||
mailEnv["MAIL_LOG_LEVEL"] = "info"
|
||||
mailEnv["MAIL_INTERNAL_HTTP_ADDR"] = mailInternalAddr
|
||||
mailEnv["MAIL_TEMPLATE_DIR"] = moduleTemplateDir(t)
|
||||
mailEnv["MAIL_SMTP_MODE"] = "stub"
|
||||
mailEnv["MAIL_STREAM_BLOCK_TIMEOUT"] = "100ms"
|
||||
mailEnv["MAIL_OPERATOR_REQUEST_TIMEOUT"] = time.Second.String()
|
||||
mailEnv["MAIL_SHUTDOWN_TIMEOUT"] = "2s"
|
||||
mailEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
mailEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
mailProcess := harness.StartProcess(t, "mail", mailBinary, mailEnv)
|
||||
waitForMailReady(t, mailProcess, "http://"+mailInternalAddr)
|
||||
|
||||
authsessionProcess := harness.StartProcess(t, "authsession", authsessionBinary, map[string]string{
|
||||
@@ -146,7 +145,9 @@ func newGatewayAuthsessionMailHarness(t *testing.T) *gatewayAuthsessionMailHarne
|
||||
"AUTHSESSION_PUBLIC_HTTP_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"AUTHSESSION_INTERNAL_HTTP_ADDR": authsessionInternalAddr,
|
||||
"AUTHSESSION_INTERNAL_HTTP_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"AUTHSESSION_REDIS_ADDR": redisRuntime.Addr,
|
||||
"AUTHSESSION_REDIS_MASTER_ADDR": redisRuntime.Addr,
|
||||
|
||||
"AUTHSESSION_REDIS_PASSWORD": "integration",
|
||||
"AUTHSESSION_USER_SERVICE_MODE": "rest",
|
||||
"AUTHSESSION_USER_SERVICE_BASE_URL": userStub.BaseURL(),
|
||||
"AUTHSESSION_USER_SERVICE_REQUEST_TIMEOUT": time.Second.String(),
|
||||
@@ -164,7 +165,9 @@ func newGatewayAuthsessionMailHarness(t *testing.T) *gatewayAuthsessionMailHarne
|
||||
"GATEWAY_LOG_LEVEL": "info",
|
||||
"GATEWAY_PUBLIC_HTTP_ADDR": gatewayPublicAddr,
|
||||
"GATEWAY_AUTHENTICATED_GRPC_ADDR": gatewayGRPCAddr,
|
||||
"GATEWAY_SESSION_CACHE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"GATEWAY_REDIS_MASTER_ADDR": redisRuntime.Addr,
|
||||
|
||||
"GATEWAY_REDIS_PASSWORD": "integration",
|
||||
"GATEWAY_SESSION_CACHE_REDIS_KEY_PREFIX": "gateway:session:",
|
||||
"GATEWAY_SESSION_EVENTS_REDIS_STREAM": "gateway:session_events",
|
||||
"GATEWAY_CLIENT_EVENTS_REDIS_STREAM": "gateway:client_events",
|
||||
|
||||
@@ -71,13 +71,11 @@ func newGatewayAuthsessionUserHarness(t *testing.T) *gatewayAuthsessionUserHarne
|
||||
authsessionBinary := harness.BuildBinary(t, "authsession", "./authsession/cmd/authsession")
|
||||
gatewayBinary := harness.BuildBinary(t, "gateway", "./gateway/cmd/gateway")
|
||||
|
||||
userServiceEnv := map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisServer.Addr(),
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
}
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisServer.Addr()).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
harness.WaitForHTTPStatus(t, userServiceProcess, "http://"+userServiceAddr+"/api/v1/internal/users/user-missing/exists", http.StatusOK)
|
||||
|
||||
@@ -87,7 +85,9 @@ func newGatewayAuthsessionUserHarness(t *testing.T) *gatewayAuthsessionUserHarne
|
||||
"AUTHSESSION_PUBLIC_HTTP_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"AUTHSESSION_INTERNAL_HTTP_ADDR": authsessionInternalAddr,
|
||||
"AUTHSESSION_INTERNAL_HTTP_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"AUTHSESSION_REDIS_ADDR": redisServer.Addr(),
|
||||
"AUTHSESSION_REDIS_MASTER_ADDR": redisServer.Addr(),
|
||||
|
||||
"AUTHSESSION_REDIS_PASSWORD": "integration",
|
||||
"AUTHSESSION_USER_SERVICE_MODE": "rest",
|
||||
"AUTHSESSION_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"AUTHSESSION_USER_SERVICE_REQUEST_TIMEOUT": time.Second.String(),
|
||||
@@ -109,7 +109,9 @@ func newGatewayAuthsessionUserHarness(t *testing.T) *gatewayAuthsessionUserHarne
|
||||
"GATEWAY_AUTH_SERVICE_BASE_URL": "http://" + authsessionPublicAddr,
|
||||
"GATEWAY_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"GATEWAY_PUBLIC_AUTH_UPSTREAM_TIMEOUT": (500 * time.Millisecond).String(),
|
||||
"GATEWAY_SESSION_CACHE_REDIS_ADDR": redisServer.Addr(),
|
||||
"GATEWAY_REDIS_MASTER_ADDR": redisServer.Addr(),
|
||||
|
||||
"GATEWAY_REDIS_PASSWORD": "integration",
|
||||
"GATEWAY_SESSION_CACHE_REDIS_KEY_PREFIX": "gateway:session:",
|
||||
"GATEWAY_SESSION_EVENTS_REDIS_STREAM": "gateway:session_events",
|
||||
"GATEWAY_CLIENT_EVENTS_REDIS_STREAM": "gateway:client_events",
|
||||
|
||||
@@ -186,27 +186,25 @@ func newGatewayAuthsessionUserMailHarness(t *testing.T) *gatewayAuthsessionUserM
|
||||
authsessionBinary := harness.BuildBinary(t, "authsession", "./authsession/cmd/authsession")
|
||||
gatewayBinary := harness.BuildBinary(t, "gateway", "./gateway/cmd/gateway")
|
||||
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisRuntime.Addr).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
waitForUserServiceReady(t, userServiceProcess, "http://"+userServiceAddr)
|
||||
|
||||
mailProcess := harness.StartProcess(t, "mail", mailBinary, map[string]string{
|
||||
"MAIL_LOG_LEVEL": "info",
|
||||
"MAIL_INTERNAL_HTTP_ADDR": mailInternalAddr,
|
||||
"MAIL_REDIS_ADDR": redisRuntime.Addr,
|
||||
"MAIL_TEMPLATE_DIR": moduleTemplateDir(t),
|
||||
"MAIL_SMTP_MODE": "stub",
|
||||
"MAIL_STREAM_BLOCK_TIMEOUT": "100ms",
|
||||
"MAIL_OPERATOR_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"MAIL_SHUTDOWN_TIMEOUT": "2s",
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
mailEnv := harness.StartMailServicePersistence(t, redisRuntime.Addr).Env
|
||||
mailEnv["MAIL_LOG_LEVEL"] = "info"
|
||||
mailEnv["MAIL_INTERNAL_HTTP_ADDR"] = mailInternalAddr
|
||||
mailEnv["MAIL_TEMPLATE_DIR"] = moduleTemplateDir(t)
|
||||
mailEnv["MAIL_SMTP_MODE"] = "stub"
|
||||
mailEnv["MAIL_STREAM_BLOCK_TIMEOUT"] = "100ms"
|
||||
mailEnv["MAIL_OPERATOR_REQUEST_TIMEOUT"] = time.Second.String()
|
||||
mailEnv["MAIL_SHUTDOWN_TIMEOUT"] = "2s"
|
||||
mailEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
mailEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
mailProcess := harness.StartProcess(t, "mail", mailBinary, mailEnv)
|
||||
waitForMailReady(t, mailProcess, "http://"+mailInternalAddr)
|
||||
|
||||
authsessionProcess := harness.StartProcess(t, "authsession", authsessionBinary, map[string]string{
|
||||
@@ -215,7 +213,9 @@ func newGatewayAuthsessionUserMailHarness(t *testing.T) *gatewayAuthsessionUserM
|
||||
"AUTHSESSION_PUBLIC_HTTP_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"AUTHSESSION_INTERNAL_HTTP_ADDR": authsessionInternalAddr,
|
||||
"AUTHSESSION_INTERNAL_HTTP_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"AUTHSESSION_REDIS_ADDR": redisRuntime.Addr,
|
||||
"AUTHSESSION_REDIS_MASTER_ADDR": redisRuntime.Addr,
|
||||
|
||||
"AUTHSESSION_REDIS_PASSWORD": "integration",
|
||||
"AUTHSESSION_USER_SERVICE_MODE": "rest",
|
||||
"AUTHSESSION_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"AUTHSESSION_USER_SERVICE_REQUEST_TIMEOUT": time.Second.String(),
|
||||
@@ -233,7 +233,9 @@ func newGatewayAuthsessionUserMailHarness(t *testing.T) *gatewayAuthsessionUserM
|
||||
"GATEWAY_LOG_LEVEL": "info",
|
||||
"GATEWAY_PUBLIC_HTTP_ADDR": gatewayPublicAddr,
|
||||
"GATEWAY_AUTHENTICATED_GRPC_ADDR": gatewayGRPCAddr,
|
||||
"GATEWAY_SESSION_CACHE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"GATEWAY_REDIS_MASTER_ADDR": redisRuntime.Addr,
|
||||
|
||||
"GATEWAY_REDIS_PASSWORD": "integration",
|
||||
"GATEWAY_SESSION_CACHE_REDIS_KEY_PREFIX": "gateway:session:",
|
||||
"GATEWAY_SESSION_EVENTS_REDIS_STREAM": "gateway:session_events",
|
||||
"GATEWAY_CLIENT_EVENTS_REDIS_STREAM": "gateway:client_events",
|
||||
|
||||
@@ -63,13 +63,11 @@ func newGatewayUserHarness(t *testing.T) *gatewayUserHarness {
|
||||
userServiceBinary := harness.BuildBinary(t, "userservice", "./user/cmd/userservice")
|
||||
gatewayBinary := harness.BuildBinary(t, "gateway", "./gateway/cmd/gateway")
|
||||
|
||||
userServiceEnv := map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisServer.Addr(),
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
}
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisServer.Addr()).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
harness.WaitForHTTPStatus(t, userServiceProcess, "http://"+userServiceAddr+"/api/v1/internal/users/user-missing/exists", http.StatusOK)
|
||||
|
||||
@@ -78,7 +76,9 @@ func newGatewayUserHarness(t *testing.T) *gatewayUserHarness {
|
||||
"GATEWAY_PUBLIC_HTTP_ADDR": gatewayPublicAddr,
|
||||
"GATEWAY_AUTHENTICATED_GRPC_ADDR": gatewayGRPCAddr,
|
||||
"GATEWAY_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"GATEWAY_SESSION_CACHE_REDIS_ADDR": redisServer.Addr(),
|
||||
"GATEWAY_REDIS_MASTER_ADDR": redisServer.Addr(),
|
||||
|
||||
"GATEWAY_REDIS_PASSWORD": "integration",
|
||||
"GATEWAY_SESSION_CACHE_REDIS_KEY_PREFIX": "gateway:session:",
|
||||
"GATEWAY_SESSION_EVENTS_REDIS_STREAM": "gateway:session_events",
|
||||
"GATEWAY_CLIENT_EVENTS_REDIS_STREAM": "gateway:client_events",
|
||||
|
||||
+22
-8
@@ -1,12 +1,15 @@
|
||||
module galaxy/integration
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
galaxy/postgres v0.0.0
|
||||
github.com/alicebob/miniredis/v2 v2.37.0
|
||||
github.com/jackc/pgx/v5 v5.9.2
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/testcontainers/testcontainers-go v0.42.0
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.42.0
|
||||
google.golang.org/grpc v1.80.0
|
||||
)
|
||||
@@ -15,6 +18,7 @@ require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/XSAM/otelsql v0.42.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
@@ -25,7 +29,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.7.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -33,15 +37,19 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mdelapenya/tlscert v0.2.0 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.2.0 // indirect
|
||||
github.com/moby/moby/api v1.54.1 // indirect
|
||||
github.com/moby/moby/client v0.4.0 // indirect
|
||||
github.com/moby/moby/api v1.54.2 // indirect
|
||||
github.com/moby/moby/client v0.4.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
@@ -51,6 +59,8 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/pressly/goose/v3 v3.27.1 // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
@@ -63,11 +73,15 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace galaxy/postgres => ../pkg/postgres
|
||||
|
||||
+63
-16
@@ -6,6 +6,8 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/XSAM/otelsql v0.42.0 h1:Li0xF4eJUxG2e0x3D4rvRlys1f27yJKvjTh7ljkUP5o=
|
||||
github.com/XSAM/otelsql v0.42.0/go.mod h1:4mOrEv+cS1KmKzrvTktvJnstr5GtKSAK+QHvFR9OcpI=
|
||||
github.com/alicebob/miniredis/v2 v2.37.0 h1:RheObYW32G1aiJIj81XVt78ZHJpHonHLHW7OLIshq68=
|
||||
github.com/alicebob/miniredis/v2 v2.37.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
@@ -28,16 +30,19 @@ github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GK
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -56,6 +61,14 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
@@ -64,20 +77,26 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
|
||||
github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
|
||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
||||
github.com/moby/moby/api v1.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4=
|
||||
github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw=
|
||||
github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g=
|
||||
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
|
||||
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
|
||||
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
|
||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
@@ -88,28 +107,42 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pressly/goose/v3 v3.27.1 h1:6uEvcprBybDmW4hcz3gYujhARhye+GoWKhEWyzD5sh4=
|
||||
github.com/pressly/goose/v3 v3.27.1/go.mod h1:maruOxsPnIG2yHHyo8UqKWXYKFcH7Q76csUV7+7KYoM=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
|
||||
github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY=
|
||||
github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 h1:GCbb1ndrF7OTDiIvxXyItaDab4qkzTFJ48LKFdM7EIo=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0/go.mod h1:IRPBaI8jXdrNfD0e4Zm7Fbcgaz5shKxOQv4axiL09xs=
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.42.0 h1:id/6LH8ZeDrtAUVSuNvZUAJ1kVpb82y1pr9yweAWsRg=
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.42.0/go.mod h1:uF0jI8FITagQpBNOgweGBmPf6rP4K0SeL1XFPbsZSSY=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
@@ -125,6 +158,7 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
@@ -137,24 +171,28 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
@@ -162,9 +200,18 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
modernc.org/libc v1.72.1 h1:db1xwJ6u1kE3KHTFTTbe2GCrczHPKzlURP0aDC4NGD0=
|
||||
modernc.org/libc v1.72.1/go.mod h1:HRMiC/PhPGLIPM7GzAFCbI+oSgE3dhZ8FWftmRrHVlY=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/sqlite v1.49.1 h1:dYGHTKcX1sJ+EQDnUzvz4TJ5GbuvhNJa8Fg6ElGx73U=
|
||||
modernc.org/sqlite v1.49.1/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package harness
|
||||
|
||||
// AuthsessionRedisEnv returns the env-var map that wires the authsession
|
||||
// binary to a Redis master at masterAddr using the master/replica/password
|
||||
// shape required by `pkg/redisconn`. The integration suites pass a fixed
|
||||
// placeholder password because the test Redis container runs without
|
||||
// `requirepass`.
|
||||
func AuthsessionRedisEnv(masterAddr string) map[string]string {
|
||||
return map[string]string{
|
||||
"AUTHSESSION_REDIS_MASTER_ADDR": masterAddr,
|
||||
"AUTHSESSION_REDIS_PASSWORD": "integration",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package harness
|
||||
|
||||
// GatewayRedisEnv returns the env-var map that wires the gateway binary to a
|
||||
// Redis master at masterAddr using the master/replica/password shape required
|
||||
// by `pkg/redisconn`. The integration suites pass a fixed placeholder
|
||||
// password because the test Redis container runs without `requirepass`.
|
||||
func GatewayRedisEnv(masterAddr string) map[string]string {
|
||||
return map[string]string{
|
||||
"GATEWAY_REDIS_MASTER_ADDR": masterAddr,
|
||||
"GATEWAY_REDIS_PASSWORD": "integration",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package harness
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// LobbyServicePersistence captures the per-test persistence dependencies of
|
||||
// the Game Lobby Service binary: a PostgreSQL container hosting the `lobby`
|
||||
// schema owned by the `lobbyservice` role, plus the Redis credentials that
|
||||
// point the service at the caller-supplied master address.
|
||||
type LobbyServicePersistence struct {
|
||||
// Postgres exposes the started container so tests that need direct SQL
|
||||
// access to the lobby schema (verifying side effects, seeding fixtures)
|
||||
// can read or write through it.
|
||||
Postgres *PostgresRuntime
|
||||
|
||||
// Env carries the environment entries that must be passed to the
|
||||
// lobby-service process. It is safe to merge into the caller's existing
|
||||
// env map, or to use as-is and append further LOBBY_* knobs in place.
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
// StartLobbyServicePersistence brings up one isolated PostgreSQL container,
|
||||
// provisions the `lobby` schema with the `lobbyservice` role, and returns
|
||||
// the environment entries that wire the lobby-service binary at that
|
||||
// container plus the supplied Redis master address.
|
||||
//
|
||||
// The returned password (`integration`) matches the architectural rule that
|
||||
// Redis traffic is password-protected; miniredis accepts arbitrary password
|
||||
// values when its own RequireAuth is not engaged, so the same value works
|
||||
// against both miniredis and the real `tcredis` runtime.
|
||||
//
|
||||
// Cleanup of the container is handled by StartPostgresContainer through
|
||||
// `t.Cleanup`; callers do not need to defer anything.
|
||||
func StartLobbyServicePersistence(t testing.TB, redisMasterAddr string) LobbyServicePersistence {
|
||||
t.Helper()
|
||||
|
||||
rt := StartPostgresContainer(t)
|
||||
if err := rt.EnsureRoleAndSchema(context.Background(), "lobby", "lobbyservice", "lobbyservice"); err != nil {
|
||||
t.Fatalf("ensure lobby schema/role: %v", err)
|
||||
}
|
||||
|
||||
env := WithPostgres(rt, "LOBBY", "lobby", "lobbyservice")
|
||||
env["LOBBY_REDIS_MASTER_ADDR"] = redisMasterAddr
|
||||
env["LOBBY_REDIS_PASSWORD"] = "integration"
|
||||
return LobbyServicePersistence{
|
||||
Postgres: rt,
|
||||
Env: env,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package harness
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// MailServicePersistence captures the per-test persistence dependencies of
|
||||
// the Mail Service binary: a PostgreSQL container hosting the `mail` schema
|
||||
// owned by the `mailservice` role, and the Redis credentials that point the
|
||||
// service at the caller-supplied master address.
|
||||
type MailServicePersistence struct {
|
||||
// Postgres exposes the started container so tests that need direct SQL
|
||||
// access to the mail schema (verifying side effects, seeding fixtures)
|
||||
// can read or write through it.
|
||||
Postgres *PostgresRuntime
|
||||
|
||||
// Env carries the environment entries that must be passed to the
|
||||
// mail-service process. It is safe to merge into the caller's existing env
|
||||
// map, or to use as-is and append further MAIL_* knobs in place.
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
// StartMailServicePersistence brings up one isolated PostgreSQL container,
|
||||
// provisions the `mail` schema with the `mailservice` role, and returns the
|
||||
// environment entries that wire the mail-service binary at that container plus
|
||||
// the supplied Redis master address.
|
||||
//
|
||||
// The returned password (`integration`) matches the architectural rule that
|
||||
// Redis traffic is password-protected; miniredis accepts arbitrary password
|
||||
// values when its own RequireAuth is not engaged, so the same value works
|
||||
// against both miniredis and the real `tcredis` runtime.
|
||||
//
|
||||
// Cleanup of the container is handled by the underlying StartPostgresContainer
|
||||
// through `t.Cleanup`; callers do not need to defer anything.
|
||||
func StartMailServicePersistence(t testing.TB, redisMasterAddr string) MailServicePersistence {
|
||||
t.Helper()
|
||||
|
||||
rt := StartPostgresContainer(t)
|
||||
if err := rt.EnsureRoleAndSchema(context.Background(), "mail", "mailservice", "mailservice"); err != nil {
|
||||
t.Fatalf("ensure mail schema/role: %v", err)
|
||||
}
|
||||
|
||||
env := WithPostgres(rt, "MAIL", "mail", "mailservice")
|
||||
env["MAIL_REDIS_MASTER_ADDR"] = redisMasterAddr
|
||||
env["MAIL_REDIS_PASSWORD"] = "integration"
|
||||
return MailServicePersistence{
|
||||
Postgres: rt,
|
||||
Env: env,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package harness
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// NotificationServicePersistence captures the per-test persistence
|
||||
// dependencies of the Notification Service binary: a PostgreSQL container
|
||||
// hosting the `notification` schema owned by the `notificationservice` role,
|
||||
// and the Redis credentials that point the service at the caller-supplied
|
||||
// master address.
|
||||
type NotificationServicePersistence struct {
|
||||
// Postgres exposes the started container so tests that need direct SQL
|
||||
// access to the notification schema (verifying side effects, seeding
|
||||
// fixtures) can read or write through it.
|
||||
Postgres *PostgresRuntime
|
||||
|
||||
// Env carries the environment entries that must be passed to the
|
||||
// notification-service process. It is safe to merge into the caller's
|
||||
// existing env map, or to use as-is and append further NOTIFICATION_*
|
||||
// knobs in place.
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
// StartNotificationServicePersistence brings up one isolated PostgreSQL
|
||||
// container, provisions the `notification` schema with the
|
||||
// `notificationservice` role, and returns the environment entries that wire
|
||||
// the notification-service binary at that container plus the supplied Redis
|
||||
// master address.
|
||||
//
|
||||
// The returned password (`integration`) matches the architectural rule that
|
||||
// Redis traffic is password-protected; miniredis accepts arbitrary password
|
||||
// values when its own RequireAuth is not engaged, so the same value works
|
||||
// against both miniredis and the real `tcredis` runtime.
|
||||
//
|
||||
// Cleanup of the container is handled by the underlying
|
||||
// StartPostgresContainer through `t.Cleanup`; callers do not need to defer
|
||||
// anything.
|
||||
func StartNotificationServicePersistence(t testing.TB, redisMasterAddr string) NotificationServicePersistence {
|
||||
t.Helper()
|
||||
|
||||
rt := StartPostgresContainer(t)
|
||||
if err := rt.EnsureRoleAndSchema(context.Background(), "notification", "notificationservice", "notificationservice"); err != nil {
|
||||
t.Fatalf("ensure notification schema/role: %v", err)
|
||||
}
|
||||
|
||||
env := WithPostgres(rt, "NOTIFICATION", "notification", "notificationservice")
|
||||
env["NOTIFICATION_REDIS_MASTER_ADDR"] = redisMasterAddr
|
||||
env["NOTIFICATION_REDIS_PASSWORD"] = "integration"
|
||||
return NotificationServicePersistence{
|
||||
Postgres: rt,
|
||||
Env: env,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package harness
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"galaxy/postgres"
|
||||
|
||||
testcontainers "github.com/testcontainers/testcontainers-go"
|
||||
tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPostgresContainerImage = "postgres:16-alpine"
|
||||
defaultPostgresDatabase = "galaxy_integration"
|
||||
defaultPostgresSuperuser = "galaxy_integration"
|
||||
defaultPostgresSuperPassword = "galaxy_integration"
|
||||
|
||||
postgresAdminConnectTimeout = 5 * time.Second
|
||||
postgresStartupTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
// PostgresRuntime stores one started real PostgreSQL container together with
|
||||
// the parsed connection coordinates and the per-test role credentials issued
|
||||
// by EnsureRoleAndSchema.
|
||||
//
|
||||
// The struct is safe to call from concurrent tests because credential lookups
|
||||
// guard the internal map with a mutex; each test should still keep its own
|
||||
// PostgresRuntime to preserve container-level isolation.
|
||||
type PostgresRuntime struct {
|
||||
Container *tcpostgres.PostgresContainer
|
||||
|
||||
baseDSN string
|
||||
host string
|
||||
port string
|
||||
database string
|
||||
|
||||
mu sync.Mutex
|
||||
creds map[string]string
|
||||
}
|
||||
|
||||
// StartPostgresContainer starts one isolated PostgreSQL container and registers
|
||||
// automatic cleanup for the suite. The container exposes a superuser created
|
||||
// from the package-level constants; per-service roles are issued lazily by
|
||||
// EnsureRoleAndSchema.
|
||||
func StartPostgresContainer(t testing.TB) *PostgresRuntime {
|
||||
t.Helper()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
container, err := tcpostgres.Run(ctx,
|
||||
defaultPostgresContainerImage,
|
||||
tcpostgres.WithDatabase(defaultPostgresDatabase),
|
||||
tcpostgres.WithUsername(defaultPostgresSuperuser),
|
||||
tcpostgres.WithPassword(defaultPostgresSuperPassword),
|
||||
// The default Postgres image emits the "ready to accept connections"
|
||||
// log line twice during startup: once during temporary bootstrap, once
|
||||
// after the real listener opens on the mapped port. Waiting for the
|
||||
// second occurrence avoids racing the temporary instance.
|
||||
testcontainers.WithWaitStrategy(
|
||||
wait.ForLog("database system is ready to accept connections").
|
||||
WithOccurrence(2).
|
||||
WithStartupTimeout(postgresStartupTimeout),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("start postgres container: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := testcontainers.TerminateContainer(container); err != nil {
|
||||
t.Errorf("terminate postgres container: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
baseDSN, err := container.ConnectionString(ctx, "sslmode=disable")
|
||||
if err != nil {
|
||||
t.Fatalf("resolve postgres connection string: %v", err)
|
||||
}
|
||||
|
||||
host, port, err := splitHostPort(baseDSN)
|
||||
if err != nil {
|
||||
t.Fatalf("parse postgres connection string: %v", err)
|
||||
}
|
||||
|
||||
return &PostgresRuntime{
|
||||
Container: container,
|
||||
baseDSN: baseDSN,
|
||||
host: host,
|
||||
port: port,
|
||||
database: defaultPostgresDatabase,
|
||||
creds: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
// BaseDSN returns the superuser DSN exposed by the container, suitable for
|
||||
// administrative tasks such as creating roles or schemas. Callers should
|
||||
// prefer DSNForSchema for service-scoped access.
|
||||
func (rt *PostgresRuntime) BaseDSN() string {
|
||||
return rt.baseDSN
|
||||
}
|
||||
|
||||
// DSNForSchema returns a DSN that connects as role and pins search_path to
|
||||
// schema. EnsureRoleAndSchema must have populated credentials for role first;
|
||||
// otherwise the call panics, signalling a test setup bug.
|
||||
func (rt *PostgresRuntime) DSNForSchema(schema, role string) string {
|
||||
rt.mu.Lock()
|
||||
password, ok := rt.creds[role]
|
||||
rt.mu.Unlock()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(
|
||||
"harness: DSNForSchema called for role %q with no credentials; call EnsureRoleAndSchema first",
|
||||
role,
|
||||
))
|
||||
}
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("search_path", schema)
|
||||
values.Set("sslmode", "disable")
|
||||
|
||||
dsn := url.URL{
|
||||
Scheme: "postgres",
|
||||
User: url.UserPassword(role, password),
|
||||
Host: net.JoinHostPort(rt.host, rt.port),
|
||||
Path: "/" + rt.database,
|
||||
RawQuery: values.Encode(),
|
||||
}
|
||||
return dsn.String()
|
||||
}
|
||||
|
||||
// EnsureRoleAndSchema creates role with the given password (idempotent) and a
|
||||
// schema owned by that role (idempotent), then grants USAGE so the role can
|
||||
// resolve table references inside it. The credentials are cached for later
|
||||
// DSNForSchema lookups.
|
||||
//
|
||||
// The operation runs through a temporary administrative connection opened
|
||||
// from BaseDSN; the connection is closed before the call returns.
|
||||
func (rt *PostgresRuntime) EnsureRoleAndSchema(ctx context.Context, schema, role, password string) error {
|
||||
if strings.TrimSpace(schema) == "" {
|
||||
return fmt.Errorf("ensure role and schema: schema must not be empty")
|
||||
}
|
||||
if strings.TrimSpace(role) == "" {
|
||||
return fmt.Errorf("ensure role and schema: role must not be empty")
|
||||
}
|
||||
|
||||
cfg := postgres.DefaultConfig()
|
||||
cfg.PrimaryDSN = rt.baseDSN
|
||||
cfg.OperationTimeout = postgresAdminConnectTimeout
|
||||
|
||||
db, err := postgres.OpenPrimary(ctx, cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ensure role and schema: open admin connection: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
createRole := fmt.Sprintf(`DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = %s) THEN
|
||||
CREATE ROLE %s LOGIN PASSWORD %s;
|
||||
END IF;
|
||||
END $$;`,
|
||||
quoteSQLLiteral(role),
|
||||
quoteSQLIdentifier(role),
|
||||
quoteSQLLiteral(password),
|
||||
)
|
||||
if _, err := db.ExecContext(ctx, createRole); err != nil {
|
||||
return fmt.Errorf("ensure role and schema: create role %q: %w", role, err)
|
||||
}
|
||||
|
||||
createSchema := fmt.Sprintf(`CREATE SCHEMA IF NOT EXISTS %s AUTHORIZATION %s;`,
|
||||
quoteSQLIdentifier(schema),
|
||||
quoteSQLIdentifier(role),
|
||||
)
|
||||
if _, err := db.ExecContext(ctx, createSchema); err != nil {
|
||||
return fmt.Errorf("ensure role and schema: create schema %q: %w", schema, err)
|
||||
}
|
||||
|
||||
grantUsage := fmt.Sprintf(`GRANT USAGE ON SCHEMA %s TO %s;`,
|
||||
quoteSQLIdentifier(schema),
|
||||
quoteSQLIdentifier(role),
|
||||
)
|
||||
if _, err := db.ExecContext(ctx, grantUsage); err != nil {
|
||||
return fmt.Errorf("ensure role and schema: grant usage on %q to %q: %w", schema, role, err)
|
||||
}
|
||||
|
||||
rt.mu.Lock()
|
||||
rt.creds[role] = password
|
||||
rt.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithPostgres returns env entries pointing the service identified by
|
||||
// envPrefix at schema/role inside rt. EnsureRoleAndSchema must have populated
|
||||
// credentials for role first.
|
||||
//
|
||||
// The returned map carries only `<envPrefix>_POSTGRES_PRIMARY_DSN`; the other
|
||||
// per-service Postgres knobs (operation timeout, pool sizes) keep the
|
||||
// defaults provided by `pkg/postgres.DefaultConfig`.
|
||||
func WithPostgres(rt *PostgresRuntime, envPrefix, schema, role string) map[string]string {
|
||||
return map[string]string{
|
||||
envPrefix + "_POSTGRES_PRIMARY_DSN": rt.DSNForSchema(schema, role),
|
||||
}
|
||||
}
|
||||
|
||||
// quoteSQLIdentifier wraps name in double quotes and escapes any embedded
|
||||
// double quote, producing a SQL identifier that survives reserved words such
|
||||
// as `user`.
|
||||
func quoteSQLIdentifier(name string) string {
|
||||
return `"` + strings.ReplaceAll(name, `"`, `""`) + `"`
|
||||
}
|
||||
|
||||
// quoteSQLLiteral wraps value in single quotes and escapes any embedded single
|
||||
// quote, producing a SQL literal usable in DDL statements where parameter
|
||||
// binding is not available.
|
||||
func quoteSQLLiteral(value string) string {
|
||||
return "'" + strings.ReplaceAll(value, "'", "''") + "'"
|
||||
}
|
||||
|
||||
// splitHostPort extracts host and port from a postgres:// DSN.
|
||||
func splitHostPort(dsn string) (string, string, error) {
|
||||
parsed, err := url.Parse(dsn)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("parse dsn: %w", err)
|
||||
}
|
||||
host := parsed.Hostname()
|
||||
port := parsed.Port()
|
||||
if host == "" || port == "" {
|
||||
return "", "", fmt.Errorf("dsn %q missing host or port", dsn)
|
||||
}
|
||||
return host, port, nil
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package harness
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"galaxy/postgres"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPostgresContainerRoundTrip(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
rt := StartPostgresContainer(t)
|
||||
|
||||
require.NoError(t, rt.EnsureRoleAndSchema(ctx, "smoke_schema", "smoke_role", "smoke_pass"))
|
||||
|
||||
cfg := postgres.DefaultConfig()
|
||||
cfg.PrimaryDSN = rt.DSNForSchema("smoke_schema", "smoke_role")
|
||||
cfg.OperationTimeout = 5 * time.Second
|
||||
|
||||
db, err := postgres.OpenPrimary(ctx, cfg)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, db.Close())
|
||||
})
|
||||
|
||||
require.NoError(t, postgres.Ping(ctx, db, cfg.OperationTimeout))
|
||||
|
||||
_, err = db.ExecContext(ctx, `CREATE TABLE notes (id serial PRIMARY KEY, body text NOT NULL)`)
|
||||
require.NoError(t, err)
|
||||
|
||||
var insertedID int64
|
||||
require.NoError(t, db.QueryRowContext(ctx,
|
||||
`INSERT INTO notes (body) VALUES ($1) RETURNING id`, "hello").Scan(&insertedID))
|
||||
require.Greater(t, insertedID, int64(0))
|
||||
|
||||
var body string
|
||||
require.NoError(t, db.QueryRowContext(ctx,
|
||||
`SELECT body FROM notes WHERE id = $1`, insertedID).Scan(&body))
|
||||
require.Equal(t, "hello", body)
|
||||
|
||||
// search_path is honoured: the unqualified table created above resolved
|
||||
// inside smoke_schema.
|
||||
var schemaName string
|
||||
require.NoError(t, db.QueryRowContext(ctx,
|
||||
`SELECT table_schema FROM information_schema.tables WHERE table_name = 'notes'`,
|
||||
).Scan(&schemaName))
|
||||
require.Equal(t, "smoke_schema", schemaName)
|
||||
}
|
||||
|
||||
func TestEnsureRoleAndSchemaIsIdempotent(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
rt := StartPostgresContainer(t)
|
||||
|
||||
require.NoError(t, rt.EnsureRoleAndSchema(ctx, "schema_x", "role_x", "pass_x"))
|
||||
require.NoError(t, rt.EnsureRoleAndSchema(ctx, "schema_x", "role_x", "pass_x"))
|
||||
}
|
||||
|
||||
func TestEnsureRoleAndSchemaSupportsReservedWordIdentifiers(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
rt := StartPostgresContainer(t)
|
||||
|
||||
// `user` is a SQL reserved word; identifier quoting must keep this working.
|
||||
require.NoError(t, rt.EnsureRoleAndSchema(ctx, "user", "userservice", "secret"))
|
||||
|
||||
cfg := postgres.DefaultConfig()
|
||||
cfg.PrimaryDSN = rt.DSNForSchema("user", "userservice")
|
||||
cfg.OperationTimeout = 5 * time.Second
|
||||
|
||||
db, err := postgres.OpenPrimary(ctx, cfg)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, db.Close())
|
||||
})
|
||||
|
||||
require.NoError(t, postgres.Ping(ctx, db, cfg.OperationTimeout))
|
||||
}
|
||||
|
||||
func TestWithPostgresBuildsPrimaryDSNEnv(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rt := newRuntimeForTest("127.0.0.1", "55432", "galaxy_integration", "userservice", "s3cr3t!")
|
||||
|
||||
env := WithPostgres(rt, "USERSERVICE", "user", "userservice")
|
||||
|
||||
require.Len(t, env, 1)
|
||||
|
||||
dsn, ok := env["USERSERVICE_POSTGRES_PRIMARY_DSN"]
|
||||
require.True(t, ok, "missing USERSERVICE_POSTGRES_PRIMARY_DSN entry")
|
||||
|
||||
parsed, err := url.Parse(dsn)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "postgres", parsed.Scheme)
|
||||
require.Equal(t, "127.0.0.1:55432", parsed.Host)
|
||||
require.Equal(t, "/galaxy_integration", parsed.Path)
|
||||
require.Equal(t, "userservice", parsed.User.Username())
|
||||
|
||||
password, hasPassword := parsed.User.Password()
|
||||
require.True(t, hasPassword)
|
||||
require.Equal(t, "s3cr3t!", password)
|
||||
|
||||
query := parsed.Query()
|
||||
require.Equal(t, "user", query.Get("search_path"))
|
||||
require.Equal(t, "disable", query.Get("sslmode"))
|
||||
}
|
||||
|
||||
func TestDSNForSchemaPanicsWithoutCredentials(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rt := newRuntimeForTest("127.0.0.1", "55432", "galaxy_integration", "userservice", "secret")
|
||||
|
||||
require.PanicsWithValue(t,
|
||||
`harness: DSNForSchema called for role "unknown" with no credentials; call EnsureRoleAndSchema first`,
|
||||
func() {
|
||||
_ = rt.DSNForSchema("user", "unknown")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// newRuntimeForTest builds a PostgresRuntime without spinning a container.
|
||||
// It exists only to exercise the pure DSN/env-builder paths.
|
||||
func newRuntimeForTest(host, port, database, role, password string) *PostgresRuntime {
|
||||
return &PostgresRuntime{
|
||||
host: host,
|
||||
port: port,
|
||||
database: database,
|
||||
creds: map[string]string{role: password},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package harness
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// UserServicePersistence captures the per-test persistence dependencies of
|
||||
// the User Service binary: a PostgreSQL container hosting the `user` schema
|
||||
// owned by the `userservice` role, and the Redis credentials that point the
|
||||
// service at the caller-supplied master address.
|
||||
type UserServicePersistence struct {
|
||||
// Postgres exposes the started container so tests that need direct SQL
|
||||
// access to the user schema (verifying side effects, seeding fixtures)
|
||||
// can read or write through it.
|
||||
Postgres *PostgresRuntime
|
||||
|
||||
// Env carries the environment entries that must be passed to the
|
||||
// userservice process. It is safe to merge into the caller's existing env
|
||||
// map, or to use as-is and append further USERSERVICE_* knobs in place.
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
// StartUserServicePersistence brings up one isolated PostgreSQL container,
|
||||
// provisions the `user` schema with the `userservice` role, and returns the
|
||||
// environment entries that wire the userservice binary at that container plus
|
||||
// the supplied Redis master address.
|
||||
//
|
||||
// The returned password (`integration`) matches the architectural rule that
|
||||
// Redis traffic is password-protected; miniredis accepts arbitrary password
|
||||
// values when its own RequireAuth is not engaged, so the same value works
|
||||
// against both miniredis and the real `tcredis` runtime.
|
||||
//
|
||||
// Cleanup of the container is handled by the underlying StartPostgresContainer
|
||||
// through `t.Cleanup`; callers do not need to defer anything.
|
||||
func StartUserServicePersistence(t testing.TB, redisMasterAddr string) UserServicePersistence {
|
||||
t.Helper()
|
||||
|
||||
rt := StartPostgresContainer(t)
|
||||
if err := rt.EnsureRoleAndSchema(context.Background(), "user", "userservice", "userservice"); err != nil {
|
||||
t.Fatalf("ensure user schema/role: %v", err)
|
||||
}
|
||||
|
||||
env := WithPostgres(rt, "USERSERVICE", "user", "userservice")
|
||||
env["USERSERVICE_REDIS_MASTER_ADDR"] = redisMasterAddr
|
||||
env["USERSERVICE_REDIS_PASSWORD"] = "integration"
|
||||
return UserServicePersistence{
|
||||
Postgres: rt,
|
||||
Env: env,
|
||||
}
|
||||
}
|
||||
@@ -218,13 +218,12 @@ func newLobbyNotificationHarness(t *testing.T, gmHandler http.HandlerFunc) *lobb
|
||||
userServiceBinary := harness.BuildBinary(t, "userservice", "./user/cmd/userservice")
|
||||
lobbyBinary := harness.BuildBinary(t, "lobby", "./lobby/cmd/lobby")
|
||||
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisRuntime.Addr).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
waitForUserServiceReady(t, userServiceProcess, "http://"+userServiceAddr)
|
||||
|
||||
// Use unique stream prefixes per test so concurrent runs do not bleed.
|
||||
@@ -234,23 +233,22 @@ func newLobbyNotificationHarness(t *testing.T, gmHandler http.HandlerFunc) *lobb
|
||||
jobResultsStream := runtimeJobResultsStream + ":" + suffix
|
||||
gmEventsStream := gmLobbyEventsStream + ":" + suffix
|
||||
|
||||
lobbyProcess := harness.StartProcess(t, "lobby", lobbyBinary, map[string]string{
|
||||
"LOBBY_LOG_LEVEL": "info",
|
||||
"LOBBY_PUBLIC_HTTP_ADDR": lobbyPublicAddr,
|
||||
"LOBBY_INTERNAL_HTTP_ADDR": lobbyInternalAddr,
|
||||
"LOBBY_REDIS_ADDR": redisRuntime.Addr,
|
||||
"LOBBY_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"LOBBY_GM_BASE_URL": gmStub.URL,
|
||||
"LOBBY_NOTIFICATION_INTENTS_STREAM": intentsStream,
|
||||
"LOBBY_USER_LIFECYCLE_STREAM": lifecycleStream,
|
||||
"LOBBY_RUNTIME_JOB_RESULTS_STREAM": jobResultsStream,
|
||||
"LOBBY_GM_EVENTS_STREAM": gmEventsStream,
|
||||
"LOBBY_RUNTIME_JOB_RESULTS_READ_BLOCK_TIMEOUT": "200ms",
|
||||
"LOBBY_USER_LIFECYCLE_READ_BLOCK_TIMEOUT": "200ms",
|
||||
"LOBBY_GM_EVENTS_READ_BLOCK_TIMEOUT": "200ms",
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
lobbyEnv := harness.StartLobbyServicePersistence(t, redisRuntime.Addr).Env
|
||||
lobbyEnv["LOBBY_LOG_LEVEL"] = "info"
|
||||
lobbyEnv["LOBBY_PUBLIC_HTTP_ADDR"] = lobbyPublicAddr
|
||||
lobbyEnv["LOBBY_INTERNAL_HTTP_ADDR"] = lobbyInternalAddr
|
||||
lobbyEnv["LOBBY_USER_SERVICE_BASE_URL"] = "http://" + userServiceAddr
|
||||
lobbyEnv["LOBBY_GM_BASE_URL"] = gmStub.URL
|
||||
lobbyEnv["LOBBY_NOTIFICATION_INTENTS_STREAM"] = intentsStream
|
||||
lobbyEnv["LOBBY_USER_LIFECYCLE_STREAM"] = lifecycleStream
|
||||
lobbyEnv["LOBBY_RUNTIME_JOB_RESULTS_STREAM"] = jobResultsStream
|
||||
lobbyEnv["LOBBY_GM_EVENTS_STREAM"] = gmEventsStream
|
||||
lobbyEnv["LOBBY_RUNTIME_JOB_RESULTS_READ_BLOCK_TIMEOUT"] = "200ms"
|
||||
lobbyEnv["LOBBY_USER_LIFECYCLE_READ_BLOCK_TIMEOUT"] = "200ms"
|
||||
lobbyEnv["LOBBY_GM_EVENTS_READ_BLOCK_TIMEOUT"] = "200ms"
|
||||
lobbyEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
lobbyEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
lobbyProcess := harness.StartProcess(t, "lobby", lobbyBinary, lobbyEnv)
|
||||
harness.WaitForHTTPStatus(t, lobbyProcess, "http://"+lobbyInternalAddr+"/readyz", http.StatusOK)
|
||||
|
||||
return &lobbyNotificationHarness{
|
||||
|
||||
@@ -106,25 +106,23 @@ func newLobbyUserHarness(t *testing.T) *lobbyUserHarness {
|
||||
userServiceBinary := harness.BuildBinary(t, "userservice", "./user/cmd/userservice")
|
||||
lobbyBinary := harness.BuildBinary(t, "lobby", "./lobby/cmd/lobby")
|
||||
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisRuntime.Addr).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
waitForUserServiceReady(t, userServiceProcess, "http://"+userServiceAddr)
|
||||
|
||||
lobbyProcess := harness.StartProcess(t, "lobby", lobbyBinary, map[string]string{
|
||||
"LOBBY_LOG_LEVEL": "info",
|
||||
"LOBBY_PUBLIC_HTTP_ADDR": lobbyPublicAddr,
|
||||
"LOBBY_INTERNAL_HTTP_ADDR": lobbyInternalAddr,
|
||||
"LOBBY_REDIS_ADDR": redisRuntime.Addr,
|
||||
"LOBBY_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"LOBBY_GM_BASE_URL": gmStub.URL,
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
lobbyEnv := harness.StartLobbyServicePersistence(t, redisRuntime.Addr).Env
|
||||
lobbyEnv["LOBBY_LOG_LEVEL"] = "info"
|
||||
lobbyEnv["LOBBY_PUBLIC_HTTP_ADDR"] = lobbyPublicAddr
|
||||
lobbyEnv["LOBBY_INTERNAL_HTTP_ADDR"] = lobbyInternalAddr
|
||||
lobbyEnv["LOBBY_USER_SERVICE_BASE_URL"] = "http://" + userServiceAddr
|
||||
lobbyEnv["LOBBY_GM_BASE_URL"] = gmStub.URL
|
||||
lobbyEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
lobbyEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
lobbyProcess := harness.StartProcess(t, "lobby", lobbyBinary, lobbyEnv)
|
||||
harness.WaitForHTTPStatus(t, lobbyProcess, "http://"+lobbyInternalAddr+"/readyz", http.StatusOK)
|
||||
|
||||
return &lobbyUserHarness{
|
||||
|
||||
@@ -167,35 +167,35 @@ func newNotificationGatewayHarness(t *testing.T) *notificationGatewayHarness {
|
||||
notificationBinary := harness.BuildBinary(t, "notification", "./notification/cmd/notification")
|
||||
gatewayBinary := harness.BuildBinary(t, "gateway", "./gateway/cmd/gateway")
|
||||
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisRuntime.Addr).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
waitForUserServiceReady(t, userServiceProcess, "http://"+userServiceAddr)
|
||||
|
||||
notificationProcess := harness.StartProcess(t, "notification", notificationBinary, map[string]string{
|
||||
"NOTIFICATION_LOG_LEVEL": "info",
|
||||
"NOTIFICATION_INTERNAL_HTTP_ADDR": notificationInternalAddr,
|
||||
"NOTIFICATION_REDIS_ADDR": redisRuntime.Addr,
|
||||
"NOTIFICATION_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"NOTIFICATION_USER_SERVICE_TIMEOUT": time.Second.String(),
|
||||
"NOTIFICATION_INTENTS_READ_BLOCK_TIMEOUT": "100ms",
|
||||
"NOTIFICATION_ROUTE_BACKOFF_MIN": "100ms",
|
||||
"NOTIFICATION_ROUTE_BACKOFF_MAX": "100ms",
|
||||
"NOTIFICATION_GATEWAY_CLIENT_EVENTS_STREAM": notificationGatewayClientEventsStream,
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
notificationEnv := harness.StartNotificationServicePersistence(t, redisRuntime.Addr).Env
|
||||
notificationEnv["NOTIFICATION_LOG_LEVEL"] = "info"
|
||||
notificationEnv["NOTIFICATION_INTERNAL_HTTP_ADDR"] = notificationInternalAddr
|
||||
notificationEnv["NOTIFICATION_USER_SERVICE_BASE_URL"] = "http://" + userServiceAddr
|
||||
notificationEnv["NOTIFICATION_USER_SERVICE_TIMEOUT"] = time.Second.String()
|
||||
notificationEnv["NOTIFICATION_INTENTS_READ_BLOCK_TIMEOUT"] = "100ms"
|
||||
notificationEnv["NOTIFICATION_ROUTE_BACKOFF_MIN"] = "100ms"
|
||||
notificationEnv["NOTIFICATION_ROUTE_BACKOFF_MAX"] = "100ms"
|
||||
notificationEnv["NOTIFICATION_GATEWAY_CLIENT_EVENTS_STREAM"] = notificationGatewayClientEventsStream
|
||||
notificationEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
notificationEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
notificationProcess := harness.StartProcess(t, "notification", notificationBinary, notificationEnv)
|
||||
harness.WaitForHTTPStatus(t, notificationProcess, "http://"+notificationInternalAddr+"/readyz", http.StatusOK)
|
||||
|
||||
gatewayProcess := harness.StartProcess(t, "gateway", gatewayBinary, map[string]string{
|
||||
"GATEWAY_LOG_LEVEL": "info",
|
||||
"GATEWAY_PUBLIC_HTTP_ADDR": gatewayPublicAddr,
|
||||
"GATEWAY_AUTHENTICATED_GRPC_ADDR": gatewayGRPCAddr,
|
||||
"GATEWAY_SESSION_CACHE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"GATEWAY_REDIS_MASTER_ADDR": redisRuntime.Addr,
|
||||
|
||||
"GATEWAY_REDIS_PASSWORD": "integration",
|
||||
"GATEWAY_SESSION_CACHE_REDIS_KEY_PREFIX": "gateway:session:",
|
||||
"GATEWAY_SESSION_EVENTS_REDIS_STREAM": "gateway:session_events",
|
||||
"GATEWAY_CLIENT_EVENTS_REDIS_STREAM": notificationGatewayClientEventsStream,
|
||||
|
||||
@@ -332,45 +332,42 @@ func newNotificationMailHarness(t *testing.T) *notificationMailHarness {
|
||||
mailBinary := harness.BuildBinary(t, "mail", "./mail/cmd/mail")
|
||||
notificationBinary := harness.BuildBinary(t, "notification", "./notification/cmd/notification")
|
||||
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisRuntime.Addr).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
waitForUserServiceReady(t, userServiceProcess, "http://"+userServiceAddr)
|
||||
|
||||
mailProcess := harness.StartProcess(t, "mail", mailBinary, map[string]string{
|
||||
"MAIL_LOG_LEVEL": "info",
|
||||
"MAIL_INTERNAL_HTTP_ADDR": mailInternalAddr,
|
||||
"MAIL_REDIS_ADDR": redisRuntime.Addr,
|
||||
"MAIL_TEMPLATE_DIR": mailTemplateDir(t),
|
||||
"MAIL_SMTP_MODE": "stub",
|
||||
"MAIL_STREAM_BLOCK_TIMEOUT": "100ms",
|
||||
"MAIL_OPERATOR_REQUEST_TIMEOUT": time.Second.String(),
|
||||
"MAIL_SHUTDOWN_TIMEOUT": "2s",
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
mailEnv := harness.StartMailServicePersistence(t, redisRuntime.Addr).Env
|
||||
mailEnv["MAIL_LOG_LEVEL"] = "info"
|
||||
mailEnv["MAIL_INTERNAL_HTTP_ADDR"] = mailInternalAddr
|
||||
mailEnv["MAIL_TEMPLATE_DIR"] = mailTemplateDir(t)
|
||||
mailEnv["MAIL_SMTP_MODE"] = "stub"
|
||||
mailEnv["MAIL_STREAM_BLOCK_TIMEOUT"] = "100ms"
|
||||
mailEnv["MAIL_OPERATOR_REQUEST_TIMEOUT"] = time.Second.String()
|
||||
mailEnv["MAIL_SHUTDOWN_TIMEOUT"] = "2s"
|
||||
mailEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
mailEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
mailProcess := harness.StartProcess(t, "mail", mailBinary, mailEnv)
|
||||
waitForMailReady(t, mailProcess, "http://"+mailInternalAddr)
|
||||
|
||||
notificationProcess := harness.StartProcess(t, "notification", notificationBinary, map[string]string{
|
||||
"NOTIFICATION_LOG_LEVEL": "info",
|
||||
"NOTIFICATION_INTERNAL_HTTP_ADDR": notificationInternalAddr,
|
||||
"NOTIFICATION_REDIS_ADDR": redisRuntime.Addr,
|
||||
"NOTIFICATION_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"NOTIFICATION_USER_SERVICE_TIMEOUT": time.Second.String(),
|
||||
"NOTIFICATION_INTENTS_READ_BLOCK_TIMEOUT": "100ms",
|
||||
"NOTIFICATION_ROUTE_BACKOFF_MIN": "100ms",
|
||||
"NOTIFICATION_ROUTE_BACKOFF_MAX": "100ms",
|
||||
"NOTIFICATION_ADMIN_EMAILS_GEO_REVIEW_RECOMMENDED": "geo-admin@example.com",
|
||||
"NOTIFICATION_ADMIN_EMAILS_GAME_GENERATION_FAILED": "game-admin@example.com",
|
||||
"NOTIFICATION_ADMIN_EMAILS_LOBBY_RUNTIME_PAUSED_AFTER_START": "lobby-ops@example.com",
|
||||
"NOTIFICATION_ADMIN_EMAILS_LOBBY_APPLICATION_SUBMITTED": "lobby-admin@example.com",
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
notificationEnv := harness.StartNotificationServicePersistence(t, redisRuntime.Addr).Env
|
||||
notificationEnv["NOTIFICATION_LOG_LEVEL"] = "info"
|
||||
notificationEnv["NOTIFICATION_INTERNAL_HTTP_ADDR"] = notificationInternalAddr
|
||||
notificationEnv["NOTIFICATION_USER_SERVICE_BASE_URL"] = "http://" + userServiceAddr
|
||||
notificationEnv["NOTIFICATION_USER_SERVICE_TIMEOUT"] = time.Second.String()
|
||||
notificationEnv["NOTIFICATION_INTENTS_READ_BLOCK_TIMEOUT"] = "100ms"
|
||||
notificationEnv["NOTIFICATION_ROUTE_BACKOFF_MIN"] = "100ms"
|
||||
notificationEnv["NOTIFICATION_ROUTE_BACKOFF_MAX"] = "100ms"
|
||||
notificationEnv["NOTIFICATION_ADMIN_EMAILS_GEO_REVIEW_RECOMMENDED"] = "geo-admin@example.com"
|
||||
notificationEnv["NOTIFICATION_ADMIN_EMAILS_GAME_GENERATION_FAILED"] = "game-admin@example.com"
|
||||
notificationEnv["NOTIFICATION_ADMIN_EMAILS_LOBBY_RUNTIME_PAUSED_AFTER_START"] = "lobby-ops@example.com"
|
||||
notificationEnv["NOTIFICATION_ADMIN_EMAILS_LOBBY_APPLICATION_SUBMITTED"] = "lobby-admin@example.com"
|
||||
notificationEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
notificationEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
notificationProcess := harness.StartProcess(t, "notification", notificationBinary, notificationEnv)
|
||||
harness.WaitForHTTPStatus(t, notificationProcess, "http://"+notificationInternalAddr+"/readyz", http.StatusOK)
|
||||
|
||||
return ¬ificationMailHarness{
|
||||
|
||||
@@ -3,6 +3,7 @@ package notificationuser_test
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
|
||||
"galaxy/integration/internal/harness"
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -66,17 +68,13 @@ func TestNotificationUserTemporaryUnavailabilityDoesNotAdvanceOffset(t *testing.
|
||||
return ok && offset.LastProcessedEntryID == messageID
|
||||
}, time.Second, 50*time.Millisecond)
|
||||
|
||||
exists, err := h.redis.Exists(context.Background(), notificationMalformedIntentKey(messageID)).Result()
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, exists)
|
||||
|
||||
exists, err = h.redis.Exists(context.Background(), notificationRouteKey(messageID, "email:user:"+recipient.UserID)).Result()
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, exists)
|
||||
require.False(t, h.malformedIntentExists(t, messageID))
|
||||
require.False(t, h.routeExists(t, messageID, "email:user:"+recipient.UserID))
|
||||
}
|
||||
|
||||
type notificationUserHarness struct {
|
||||
redis *redis.Client
|
||||
pg *sql.DB
|
||||
|
||||
userServiceURL string
|
||||
|
||||
@@ -141,31 +139,34 @@ func newNotificationUserHarness(t *testing.T) *notificationUserHarness {
|
||||
userServiceBinary := harness.BuildBinary(t, "userservice", "./user/cmd/userservice")
|
||||
notificationBinary := harness.BuildBinary(t, "notification", "./notification/cmd/notification")
|
||||
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, map[string]string{
|
||||
"USERSERVICE_LOG_LEVEL": "info",
|
||||
"USERSERVICE_INTERNAL_HTTP_ADDR": userServiceAddr,
|
||||
"USERSERVICE_REDIS_ADDR": redisRuntime.Addr,
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
userServiceEnv := harness.StartUserServicePersistence(t, redisRuntime.Addr).Env
|
||||
userServiceEnv["USERSERVICE_LOG_LEVEL"] = "info"
|
||||
userServiceEnv["USERSERVICE_INTERNAL_HTTP_ADDR"] = userServiceAddr
|
||||
userServiceEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
userServiceEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
userServiceProcess := harness.StartProcess(t, "userservice", userServiceBinary, userServiceEnv)
|
||||
waitForUserServiceReady(t, userServiceProcess, "http://"+userServiceAddr)
|
||||
|
||||
notificationProcess := harness.StartProcess(t, "notification", notificationBinary, map[string]string{
|
||||
"NOTIFICATION_LOG_LEVEL": "info",
|
||||
"NOTIFICATION_INTERNAL_HTTP_ADDR": notificationInternalAddr,
|
||||
"NOTIFICATION_REDIS_ADDR": redisRuntime.Addr,
|
||||
"NOTIFICATION_USER_SERVICE_BASE_URL": "http://" + userServiceAddr,
|
||||
"NOTIFICATION_USER_SERVICE_TIMEOUT": "250ms",
|
||||
"NOTIFICATION_INTENTS_READ_BLOCK_TIMEOUT": "100ms",
|
||||
"NOTIFICATION_ROUTE_BACKOFF_MIN": "100ms",
|
||||
"NOTIFICATION_ROUTE_BACKOFF_MAX": "100ms",
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
})
|
||||
notificationPersistence := harness.StartNotificationServicePersistence(t, redisRuntime.Addr)
|
||||
notificationEnv := notificationPersistence.Env
|
||||
notificationPG, err := sql.Open("pgx", notificationPersistence.Postgres.DSNForSchema("notification", "notificationservice"))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = notificationPG.Close() })
|
||||
notificationEnv["NOTIFICATION_LOG_LEVEL"] = "info"
|
||||
notificationEnv["NOTIFICATION_INTERNAL_HTTP_ADDR"] = notificationInternalAddr
|
||||
notificationEnv["NOTIFICATION_USER_SERVICE_BASE_URL"] = "http://" + userServiceAddr
|
||||
notificationEnv["NOTIFICATION_USER_SERVICE_TIMEOUT"] = "250ms"
|
||||
notificationEnv["NOTIFICATION_INTENTS_READ_BLOCK_TIMEOUT"] = "100ms"
|
||||
notificationEnv["NOTIFICATION_ROUTE_BACKOFF_MIN"] = "100ms"
|
||||
notificationEnv["NOTIFICATION_ROUTE_BACKOFF_MAX"] = "100ms"
|
||||
notificationEnv["OTEL_TRACES_EXPORTER"] = "none"
|
||||
notificationEnv["OTEL_METRICS_EXPORTER"] = "none"
|
||||
notificationProcess := harness.StartProcess(t, "notification", notificationBinary, notificationEnv)
|
||||
harness.WaitForHTTPStatus(t, notificationProcess, "http://"+notificationInternalAddr+"/readyz", http.StatusOK)
|
||||
|
||||
return ¬ificationUserHarness{
|
||||
redis: redisClient,
|
||||
pg: notificationPG,
|
||||
userServiceURL: "http://" + userServiceAddr,
|
||||
notificationProcess: notificationProcess,
|
||||
userServiceProcess: userServiceProcess,
|
||||
@@ -213,14 +214,27 @@ func (h *notificationUserHarness) publishUserIntent(t *testing.T, recipientUserI
|
||||
func (h *notificationUserHarness) waitForRoute(t *testing.T, notificationID string, routeID string) notificationRouteRecord {
|
||||
t.Helper()
|
||||
|
||||
key := notificationRouteKey(notificationID, routeID)
|
||||
var route notificationRouteRecord
|
||||
require.Eventually(t, func() bool {
|
||||
payload, err := h.redis.Get(context.Background(), key).Bytes()
|
||||
if err != nil {
|
||||
return false
|
||||
row := h.pg.QueryRowContext(context.Background(),
|
||||
`SELECT notification_id, route_id, channel, recipient_ref, status, resolved_email, resolved_locale
|
||||
FROM routes WHERE notification_id = $1 AND route_id = $2`,
|
||||
notificationID, routeID,
|
||||
)
|
||||
if err := row.Scan(
|
||||
&route.NotificationID,
|
||||
&route.RouteID,
|
||||
&route.Channel,
|
||||
&route.RecipientRef,
|
||||
&route.Status,
|
||||
&route.ResolvedEmail,
|
||||
&route.ResolvedLocale,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false
|
||||
}
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, decodeJSONPayload(payload, &route))
|
||||
return true
|
||||
}, 10*time.Second, 50*time.Millisecond)
|
||||
|
||||
@@ -230,14 +244,30 @@ func (h *notificationUserHarness) waitForRoute(t *testing.T, notificationID stri
|
||||
func (h *notificationUserHarness) waitForMalformedIntent(t *testing.T, streamEntryID string) malformedIntentRecord {
|
||||
t.Helper()
|
||||
|
||||
key := notificationMalformedIntentKey(streamEntryID)
|
||||
var record malformedIntentRecord
|
||||
require.Eventually(t, func() bool {
|
||||
payload, err := h.redis.Get(context.Background(), key).Bytes()
|
||||
if err != nil {
|
||||
return false
|
||||
row := h.pg.QueryRowContext(context.Background(),
|
||||
`SELECT stream_entry_id, notification_type, producer, idempotency_key,
|
||||
failure_code, failure_message, recorded_at
|
||||
FROM malformed_intents WHERE stream_entry_id = $1`,
|
||||
streamEntryID,
|
||||
)
|
||||
var recordedAt time.Time
|
||||
if err := row.Scan(
|
||||
&record.StreamEntryID,
|
||||
&record.NotificationType,
|
||||
&record.Producer,
|
||||
&record.IdempotencyKey,
|
||||
&record.FailureCode,
|
||||
&record.FailureMessage,
|
||||
&recordedAt,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false
|
||||
}
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, decodeStrictJSONPayload(payload, &record))
|
||||
record.RecordedAtMS = recordedAt.UTC().UnixMilli()
|
||||
return true
|
||||
}, 10*time.Second, 50*time.Millisecond)
|
||||
|
||||
@@ -374,12 +404,26 @@ func decodeJSONPayload(payload []byte, target any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func notificationRouteKey(notificationID string, routeID string) string {
|
||||
return "notification:routes:" + encodeKeyComponent(notificationID) + ":" + encodeKeyComponent(routeID)
|
||||
func (h *notificationUserHarness) routeExists(t *testing.T, notificationID string, routeID string) bool {
|
||||
t.Helper()
|
||||
var exists bool
|
||||
err := h.pg.QueryRowContext(context.Background(),
|
||||
`SELECT EXISTS(SELECT 1 FROM routes WHERE notification_id = $1 AND route_id = $2)`,
|
||||
notificationID, routeID,
|
||||
).Scan(&exists)
|
||||
require.NoError(t, err)
|
||||
return exists
|
||||
}
|
||||
|
||||
func notificationMalformedIntentKey(streamEntryID string) string {
|
||||
return "notification:malformed_intents:" + encodeKeyComponent(streamEntryID)
|
||||
func (h *notificationUserHarness) malformedIntentExists(t *testing.T, streamEntryID string) bool {
|
||||
t.Helper()
|
||||
var exists bool
|
||||
err := h.pg.QueryRowContext(context.Background(),
|
||||
`SELECT EXISTS(SELECT 1 FROM malformed_intents WHERE stream_entry_id = $1)`,
|
||||
streamEntryID,
|
||||
).Scan(&exists)
|
||||
require.NoError(t, err)
|
||||
return exists
|
||||
}
|
||||
|
||||
func notificationStreamOffsetKey() string {
|
||||
|
||||
Reference in New Issue
Block a user