feat: use postgres
This commit is contained in:
@@ -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