Add MonitorAlbum component tests: 21 cases covering all flow diagrams (bufconn + testcontainers + hand-rolled mocks)
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
|
||||
pb "homelab.lan/music-agregator/gen/music_agregator/v1"
|
||||
"homelab.lan/music-agregator/internal"
|
||||
"homelab.lan/music-agregator/internal/database"
|
||||
"homelab.lan/music-agregator/internal/metadata"
|
||||
)
|
||||
|
||||
const bufSize = 1024 * 1024
|
||||
|
||||
type testMocks struct {
|
||||
metadata *mockMetadataClient
|
||||
torrent *mockTorrentClient
|
||||
indexer *mockSearcher
|
||||
magnet *mockResolver
|
||||
}
|
||||
|
||||
type testSuite struct {
|
||||
db *database.DB
|
||||
grpcConn *grpc.ClientConn
|
||||
client pb.MusicAgregatorServiceClient
|
||||
pool *pgxpool.Pool
|
||||
mocks *testMocks
|
||||
}
|
||||
|
||||
func setupSuite(t *testing.T) *testSuite {
|
||||
ctx := context.Background()
|
||||
|
||||
schemaPath := getSchemaPath(t)
|
||||
schemaSQL, err := os.ReadFile(schemaPath)
|
||||
require.NoError(t, err, "failed to read schema file")
|
||||
|
||||
pgContainer, err := postgres.Run(ctx,
|
||||
"postgres:16-alpine",
|
||||
postgres.WithDatabase("music_agregator_test"),
|
||||
postgres.WithUsername("test"),
|
||||
postgres.WithPassword("test"),
|
||||
postgres.WithInitScripts(),
|
||||
testcontainers.WithWaitStrategy(
|
||||
wait.ForLog("database system is ready to accept connections").
|
||||
WithOccurrence(2).
|
||||
WithStartupTimeout(30*time.Second),
|
||||
),
|
||||
)
|
||||
require.NoError(t, err, "failed to start postgres container")
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := pgContainer.Terminate(ctx); err != nil {
|
||||
t.Logf("failed to terminate postgres container: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
|
||||
require.NoError(t, err, "failed to get connection string")
|
||||
|
||||
pool, err := pgxpool.New(ctx, connStr)
|
||||
require.NoError(t, err, "failed to create pgxpool")
|
||||
|
||||
t.Cleanup(func() {
|
||||
pool.Close()
|
||||
})
|
||||
|
||||
_, err = pool.Exec(ctx, string(schemaSQL))
|
||||
require.NoError(t, err, "failed to apply schema")
|
||||
|
||||
db := &database.DB{Pool: pool}
|
||||
|
||||
mocks := &testMocks{
|
||||
metadata: &mockMetadataClient{},
|
||||
torrent: &mockTorrentClient{},
|
||||
indexer: &mockSearcher{},
|
||||
magnet: &mockResolver{},
|
||||
}
|
||||
|
||||
metadataSvc := metadata.NewMetadataService(mocks.metadata, db)
|
||||
|
||||
service := internal.NewMusicAgregatorServiceWithDeps(
|
||||
metadataSvc,
|
||||
mocks.indexer,
|
||||
mocks.torrent,
|
||||
mocks.magnet,
|
||||
nil,
|
||||
db,
|
||||
)
|
||||
|
||||
server := internal.NewMusicAgregatorServerWithService(service)
|
||||
|
||||
lis := bufconn.Listen(bufSize)
|
||||
grpcServer := grpc.NewServer()
|
||||
server.Register(grpcServer)
|
||||
|
||||
go func() {
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
t.Logf("grpc server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
t.Cleanup(func() {
|
||||
grpcServer.GracefulStop()
|
||||
})
|
||||
|
||||
conn, err := grpc.NewClient(
|
||||
"passthrough://bufnet",
|
||||
grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
|
||||
return lis.DialContext(ctx)
|
||||
}),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
require.NoError(t, err, "failed to create grpc client connection")
|
||||
|
||||
t.Cleanup(func() {
|
||||
conn.Close()
|
||||
})
|
||||
|
||||
client := pb.NewMusicAgregatorServiceClient(conn)
|
||||
|
||||
return &testSuite{
|
||||
db: db,
|
||||
grpcConn: conn,
|
||||
client: client,
|
||||
pool: pool,
|
||||
mocks: mocks,
|
||||
}
|
||||
}
|
||||
|
||||
func getSchemaPath(t *testing.T) string {
|
||||
_, currentFile, _, ok := runtime.Caller(0)
|
||||
require.True(t, ok, "failed to get current file path")
|
||||
|
||||
testDir := filepath.Dir(currentFile)
|
||||
schemaPath := filepath.Join(testDir, "..", "..", "..", "containers", "database", "music-agregator", "002_schema.sql")
|
||||
|
||||
if _, err := os.Stat(schemaPath); os.IsNotExist(err) {
|
||||
schemaPath = filepath.Join(testDir, "..", "..", "containers", "database", "music-agregator", "002_schema.sql")
|
||||
}
|
||||
|
||||
return schemaPath
|
||||
}
|
||||
|
||||
func cleanTables(t *testing.T, pool *pgxpool.Pool) {
|
||||
ctx := context.Background()
|
||||
|
||||
tables := []string{
|
||||
"download_files",
|
||||
"downloads",
|
||||
"torrents",
|
||||
"tracks",
|
||||
"albums",
|
||||
"artists",
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
_, err := pool.Exec(ctx, "TRUNCATE TABLE "+table+" CASCADE")
|
||||
require.NoError(t, err, "failed to truncate table %s", table)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user