Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
16c3073
test(search-service): share NATS / ES / Valkey containers across inte…
claude May 20, 2026
4252c54
test(search-service): fail tests when shared Valkey FLUSHDB cleanup e…
claude May 20, 2026
6b23294
test(search-service): split CCS integration tests into their own file
claude May 20, 2026
24e9280
test(search-service): split integration tests by search endpoint
claude May 20, 2026
17ef382
test(search-service): add router cleanup to CCS fixture; trim comments
claude May 20, 2026
1e5d970
test(search-service): pre-warm shared containers in parallel; halve E…
claude May 20, 2026
a71664b
test(search-service): explicit shared-container cleanup; disable Ryuk…
claude May 20, 2026
436dc39
test(testutil,search-service): add TerminateMongo for explicit cleanup
claude May 20, 2026
a5a5c5a
test(testutil): add Elasticsearch, NATS, Valkey + Terminate{All,Xxx}
claude May 20, 2026
08ba0b4
test(testutil): disable Ryuk repo-wide via init(); drop CI env var
claude May 20, 2026
d356c6a
test(repo): migrate every integration package to testutil shared cont…
claude May 20, 2026
66cecfe
test(loadgen): migrate members_integration_test.go to testutil.NATS
claude May 20, 2026
94827bc
test(testutil): re-disable Ryuk via init(); audit per-container cleanup
claude May 20, 2026
cda13a2
test(testutil,search-service): simplify pass — RunTests helper, const…
claude May 20, 2026
713d5eb
test(search-sync-worker): fail-fast on prewarm errors
claude May 20, 2026
1cdad60
test: switch integration test files to internal package per CLAUDE.md
claude May 20, 2026
ff2312a
test(roomkeystore): add missing TestMain
claude May 20, 2026
42552f7
docs(CLAUDE.md): document the testcontainer best practice this PR est…
claude May 20, 2026
ff03e8d
Merge origin/main into testcontainer-share branch
claude May 21, 2026
7a6cc36
Post-merge fixes for Valkey cluster-mode migration
claude May 21, 2026
80514d1
test(testutil): add SharedValkeyCluster + FlushValkey for shared cluster
claude May 21, 2026
c6424e3
chore(history-service): remove local docker-compose folder
claude May 21, 2026
7b76258
test: trim comments per simplify pass
claude May 21, 2026
e757c42
test(testutil): extract PrewarmFailFast, RunTestsWithPrewarm, Elastic…
claude May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions broadcast-worker/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package main

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
11 changes: 11 additions & 0 deletions history-service/internal/cassrepo/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package cassrepo

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
11 changes: 11 additions & 0 deletions history-service/internal/mongorepo/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package mongorepo

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
11 changes: 11 additions & 0 deletions history-service/internal/service/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package service_test
Comment thread
Joey0538 marked this conversation as resolved.
Outdated

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
15 changes: 3 additions & 12 deletions inbox-worker/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ import (
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
natsmod "github.com/testcontainers/testcontainers-go/modules/nats"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo"

"github.com/hmchangw/chat/pkg/model"
"github.com/hmchangw/chat/pkg/stream"
"github.com/hmchangw/chat/pkg/subject"
"github.com/hmchangw/chat/pkg/testutil"
"github.com/hmchangw/chat/pkg/testutil/testimages"
)

func setupMongo(t *testing.T) *mongo.Database {
Expand Down Expand Up @@ -577,20 +575,13 @@ func TestHandleMemberAdded_DM_PersistsRemoteCounterpartSub(t *testing.T) {
assert.False(t, bobSub.IsSubscribed, "DM does not set IsSubscribed=true")
}

// setupNATS starts a NATS container with JetStream enabled and returns a
// JetStream client tied to the test's lifetime.
// setupNATS connects to the process-shared NATS (JetStream enabled in
// testutil) and returns a JetStream client tied to the test's lifetime.
func setupNATS(t *testing.T) (context.Context, jetstream.JetStream) {
t.Helper()
ctx := context.Background()

c, err := natsmod.Run(ctx, testimages.NATS)
require.NoError(t, err)
t.Cleanup(func() { _ = c.Terminate(ctx) })

url, err := c.ConnectionString(ctx)
require.NoError(t, err)

nc, err := nats.Connect(url)
nc, err := nats.Connect(testutil.NATS(t))
require.NoError(t, err)
t.Cleanup(func() { nc.Close() })

Expand Down
11 changes: 11 additions & 0 deletions inbox-worker/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package main

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
11 changes: 11 additions & 0 deletions message-worker/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package main

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
11 changes: 11 additions & 0 deletions notification-worker/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package main

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
11 changes: 11 additions & 0 deletions pkg/minioutil/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package minioutil

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
11 changes: 11 additions & 0 deletions pkg/mongoutil/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package mongoutil

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
24 changes: 4 additions & 20 deletions pkg/natsrouter/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,21 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
tcnats "github.com/testcontainers/testcontainers-go/modules/nats"

"github.com/Marz32onE/instrumentation-go/otel-nats/otelnats"

"github.com/hmchangw/chat/pkg/natsrouter"
"github.com/hmchangw/chat/pkg/testutil/testimages"
"github.com/hmchangw/chat/pkg/testutil"
)

// setupNATS starts a real NATS container and returns a connected otelnats
// client. Required to surface timing races that in-process NATS cannot
// setupNATS returns an otelnats client connected to the process-shared
// NATS. Required to surface timing races that in-process NATS cannot
// reproduce (real TCP, real server dispatch goroutines, real latency).
func setupNATS(t *testing.T) *otelnats.Conn {
t.Helper()
ctx := context.Background()

container, err := tcnats.Run(ctx, testimages.NATS)
require.NoError(t, err, "start NATS container")
t.Cleanup(func() {
// Best-effort container teardown; failures here don't affect outcome.
if err := container.Terminate(ctx); err != nil {
t.Logf("terminate nats container: %v", err)
}
})

url, err := container.ConnectionString(ctx)
require.NoError(t, err, "nats connection string")

nc, err := otelnats.Connect(url)
nc, err := otelnats.Connect(testutil.NATS(t))
require.NoError(t, err, "connect to NATS")
t.Cleanup(nc.Close)

return nc
}

Expand Down
11 changes: 11 additions & 0 deletions pkg/natsrouter/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build integration

package natsrouter_test
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
14 changes: 14 additions & 0 deletions pkg/roomcrypto/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build integration

package roomcrypto

// Import testutil for the Ryuk-disable init() side effect even though
// this package starts its containers per-test (t.Cleanup handles teardown).

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
14 changes: 14 additions & 0 deletions pkg/roomkeysender/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build integration

package roomkeysender_test
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

// Import testutil for the Ryuk-disable init() side effect even though
// this package starts its containers per-test (t.Cleanup handles teardown).

import (
"testing"

"github.com/hmchangw/chat/pkg/testutil"
)

func TestMain(m *testing.M) { testutil.RunTests(m) }
32 changes: 5 additions & 27 deletions pkg/roomkeystore/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,25 @@ import (
"bytes"
"context"
"errors"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"

"github.com/hmchangw/chat/pkg/testutil/testimages"
"github.com/hmchangw/chat/pkg/testutil"
)

// setupValkey starts a valkey/valkey:8 container and returns a connected valkeyStore.
// The container is terminated via t.Cleanup.
// setupValkey returns a RoomKeyStore connected to the process-shared
// Valkey, with FLUSHDB on cleanup so sibling tests start clean.
func setupValkey(t *testing.T, gracePeriod time.Duration) RoomKeyStore {
t.Helper()
ctx := context.Background()

container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: testimages.Valkey,
ExposedPorts: []string{"6379/tcp"},
WaitingFor: wait.ForLog("Ready to accept connections"),
},
Started: true,
})
require.NoError(t, err, "start valkey container")
t.Cleanup(func() {
_ = container.Terminate(ctx) // best-effort; ignore cleanup errors
})

host, err := container.Host(ctx)
require.NoError(t, err)
port, err := container.MappedPort(ctx, "6379")
require.NoError(t, err)

store, err := NewValkeyStore(Config{
Addr: fmt.Sprintf("%s:%s", host, port.Port()),
Addr: testutil.Valkey(t),
GracePeriod: gracePeriod,
})
require.NoError(t, err, "create valkeyStore")
t.Cleanup(func() { testutil.FlushValkey(t) })
return store
}

Expand Down
34 changes: 7 additions & 27 deletions pkg/roomsubcache/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,27 @@ package roomsubcache_test

import (
"context"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"

"github.com/hmchangw/chat/pkg/roomsubcache"
"github.com/hmchangw/chat/pkg/testutil/testimages"
"github.com/hmchangw/chat/pkg/testutil"
"github.com/hmchangw/chat/pkg/valkeyutil"
)

// setupValkey starts a valkey/valkey:8 container and returns a connected
// valkeyutil.Client. The container is terminated via t.Cleanup.
// setupValkey returns a client connected to the process-shared Valkey,
// with FLUSHDB on cleanup so sibling tests start clean.
func setupValkey(t *testing.T) valkeyutil.Client {
t.Helper()
ctx := context.Background()

container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: testimages.Valkey,
ExposedPorts: []string{"6379/tcp"},
WaitingFor: wait.ForLog("Ready to accept connections"),
},
Started: true,
})
require.NoError(t, err, "start valkey container")
client, err := valkeyutil.Connect(context.Background(), testutil.Valkey(t), "")
require.NoError(t, err, "connect valkey")
t.Cleanup(func() {
_ = container.Terminate(ctx) // best-effort; ignore cleanup errors
testutil.FlushValkey(t)
_ = client.Close()
})

host, err := container.Host(ctx)
require.NoError(t, err)
port, err := container.MappedPort(ctx, "6379")
require.NoError(t, err)

client, err := valkeyutil.Connect(ctx, fmt.Sprintf("%s:%s", host, port.Port()), "")
require.NoError(t, err, "connect valkey")
t.Cleanup(func() { _ = client.Close() }) // best-effort; ignore cleanup errors
return client
}

Expand Down
32 changes: 28 additions & 4 deletions pkg/testutil/cassandra.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"fmt"
"hash/fnv"
"os"
"sync"
"testing"
"time"
Expand All @@ -18,10 +19,11 @@ import (
const cassandraImage = "cassandra:5"

var (
cassOnce sync.Once
cassHost string
cassSession *gocql.Session
cassInitErr error
cassOnce sync.Once
cassContainer testcontainers.Container
cassHost string
cassSession *gocql.Session
cassInitErr error
)

func ensureCassandraSession() (string, *gocql.Session, error) {
Expand Down Expand Up @@ -70,10 +72,32 @@ func ensureCassandraSession() (string, *gocql.Session, error) {
}
cassHost = addr
cassSession = s
cassContainer = container
})
return cassHost, cassSession, cassInitErr
}

// TerminateCassandra closes the shared session and stops the shared
// container. Best-effort, idempotent.
func TerminateCassandra() {
if cassSession != nil {
cassSession.Close()
cassSession = nil
}
if cassContainer != nil {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := cassContainer.Terminate(ctx); err != nil {
fmt.Fprintf(os.Stderr, "terminate shared cassandra: %v\n", err)
}
cassContainer = nil
}
}

// EnsureCassandra starts the shared Cassandra container if not already
// started. No-t variant intended for TestMain pre-warming.
func EnsureCassandra() error { _, _, err := ensureCassandraSession(); return err }

// CassandraKeyspace creates an isolated keyspace for the test (SimpleStrategy, RF=1).
// Returns the keyspace name, an admin session for DDL, and the container host.
func CassandraKeyspace(t *testing.T, prefix string) (keyspace string, admin *gocql.Session, hostAddr string) {
Expand Down
Loading
Loading