Files
Alexander a1f6701bac feat: initial implementation of metadata aggregator
- gRPC service with MusicBrainz provider
- PostgreSQL schema with migrations
- Service layer with database-first caching
- Repository pattern for data access
- YAML configuration support
- Research documentation for 17 music metadata projects
2026-04-28 16:28:53 +02:00

416 lines
9.1 KiB
Go

package e2e
import (
"context"
"net"
"testing"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/metadata-agregator/internal/server"
metadatav1 "github.com/metadata-agregator/pkg/gen/metadata/v1"
)
const (
radioheadMBID = "a74b1b7f-71a5-4011-9441-d0b5e4122711"
okComputerMBID = "b1392450-e666-3926-a536-22c65f834433"
paranoidAndroid = "9f9cf187-d6f9-437f-9d98-d59cdbd52757"
)
type testServer struct {
addr string
server *grpc.Server
}
func startTestServer(t *testing.T) *testServer {
t.Helper()
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
metadatav1.RegisterMetadataServiceServer(grpcServer, server.NewMetadataServer())
go func() {
if err := grpcServer.Serve(lis); err != nil {
t.Logf("server stopped: %v", err)
}
}()
return &testServer{
addr: lis.Addr().String(),
server: grpcServer,
}
}
func (s *testServer) stop() {
s.server.GracefulStop()
}
func newClient(t *testing.T, addr string) metadatav1.MetadataServiceClient {
t.Helper()
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
t.Fatalf("failed to connect: %v", err)
}
t.Cleanup(func() { conn.Close() })
return metadatav1.NewMetadataServiceClient(conn)
}
func TestSearchArtists(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
srv := startTestServer(t)
defer srv.stop()
client := newClient(t, srv.addr)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
resp, err := client.SearchArtists(ctx, &metadatav1.SearchArtistsRequest{
Query: "Radiohead",
Limit: 5,
})
if err != nil {
t.Fatalf("SearchArtists failed: %v", err)
}
if len(resp.Artists) == 0 {
t.Fatal("expected at least one artist")
}
found := false
for _, a := range resp.Artists {
if a.Id == radioheadMBID {
found = true
if a.Name != "Radiohead" {
t.Errorf("expected name 'Radiohead', got %q", a.Name)
}
if a.Country != "GB" {
t.Errorf("expected country 'GB', got %q", a.Country)
}
break
}
}
if !found {
t.Error("Radiohead not found in search results")
}
}
func TestGetArtist(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
srv := startTestServer(t)
defer srv.stop()
client := newClient(t, srv.addr)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
artist, err := client.GetArtist(ctx, &metadatav1.GetArtistRequest{
Identifier: &metadatav1.GetArtistRequest_Id{Id: radioheadMBID},
})
if err != nil {
t.Fatalf("GetArtist failed: %v", err)
}
if artist.Id != radioheadMBID {
t.Errorf("expected ID %q, got %q", radioheadMBID, artist.Id)
}
if artist.Name != "Radiohead" {
t.Errorf("expected name 'Radiohead', got %q", artist.Name)
}
if artist.ArtistType != "Group" {
t.Errorf("expected type 'Group', got %q", artist.ArtistType)
}
if len(artist.Genres) == 0 {
t.Error("expected genres to be populated")
}
if len(artist.ExternalIds) == 0 {
t.Error("expected external IDs to be populated")
}
hasMusicBrainz := false
for _, ext := range artist.ExternalIds {
if ext.Source == "musicbrainz" {
hasMusicBrainz = true
break
}
}
if !hasMusicBrainz {
t.Error("expected musicbrainz external ID")
}
}
func TestGetArtistAlbums(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
srv := startTestServer(t)
defer srv.stop()
client := newClient(t, srv.addr)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
resp, err := client.GetArtistAlbums(ctx, &metadatav1.GetArtistAlbumsRequest{
ArtistId: radioheadMBID,
Limit: 10,
})
if err != nil {
t.Fatalf("GetArtistAlbums failed: %v", err)
}
if len(resp.Albums) == 0 {
t.Fatal("expected at least one album")
}
if resp.Total == 0 {
t.Error("expected total to be greater than 0")
}
foundOKComputer := false
for _, album := range resp.Albums {
if album.Id == okComputerMBID {
foundOKComputer = true
if album.Title != "OK Computer" {
t.Errorf("expected title 'OK Computer', got %q", album.Title)
}
break
}
}
if !foundOKComputer {
t.Log("OK Computer not in first 10 results (pagination)")
}
for _, album := range resp.Albums {
if album.AlbumType == "" {
t.Errorf("album %q missing type", album.Title)
}
if len(album.Artists) == 0 {
t.Errorf("album %q missing artists", album.Title)
}
}
}
func TestGetAlbum(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
srv := startTestServer(t)
defer srv.stop()
client := newClient(t, srv.addr)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
album, err := client.GetAlbum(ctx, &metadatav1.GetAlbumRequest{
Identifier: &metadatav1.GetAlbumRequest_Id{Id: okComputerMBID},
})
if err != nil {
t.Fatalf("GetAlbum failed: %v", err)
}
if album.Id != okComputerMBID {
t.Errorf("expected ID %q, got %q", okComputerMBID, album.Id)
}
if album.Title != "OK Computer" {
t.Errorf("expected title 'OK Computer', got %q", album.Title)
}
if album.AlbumType != "Album" {
t.Errorf("expected type 'Album', got %q", album.AlbumType)
}
if album.ReleaseDate == "" {
t.Error("expected release date to be populated")
}
if len(album.Artists) == 0 {
t.Error("expected artists to be populated")
}
}
func TestGetAlbumTracks(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
srv := startTestServer(t)
defer srv.stop()
client := newClient(t, srv.addr)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
resp, err := client.GetAlbumTracks(ctx, &metadatav1.GetAlbumTracksRequest{
AlbumId: okComputerMBID,
})
if err != nil {
t.Fatalf("GetAlbumTracks failed: %v", err)
}
if len(resp.Tracks) == 0 {
t.Fatal("expected tracks")
}
if len(resp.Tracks) != 12 {
t.Errorf("expected 12 tracks for OK Computer, got %d", len(resp.Tracks))
}
expectedTracks := []string{
"Airbag",
"Paranoid Android",
"Subterranean Homesick Alien",
"Exit Music (for a Film)",
"Let Down",
"Karma Police",
"Fitter Happier",
"Electioneering",
"Climbing Up the Walls",
"No Surprises",
"Lucky",
"The Tourist",
}
for i, track := range resp.Tracks {
if track.TrackNumber != int32(i+1) {
t.Errorf("track %d: expected track number %d, got %d", i, i+1, track.TrackNumber)
}
if i < len(expectedTracks) && track.Title != expectedTracks[i] {
t.Errorf("track %d: expected title %q, got %q", i+1, expectedTracks[i], track.Title)
}
if track.DurationMs == 0 {
t.Errorf("track %q: missing duration", track.Title)
}
if track.Isrc == "" {
t.Errorf("track %q: missing ISRC", track.Title)
}
}
}
func TestGetTrack(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
srv := startTestServer(t)
defer srv.stop()
client := newClient(t, srv.addr)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
track, err := client.GetTrack(ctx, &metadatav1.GetTrackRequest{
Identifier: &metadatav1.GetTrackRequest_Id{Id: paranoidAndroid},
})
if err != nil {
t.Fatalf("GetTrack failed: %v", err)
}
if track.Id != paranoidAndroid {
t.Errorf("expected ID %q, got %q", paranoidAndroid, track.Id)
}
if track.Title != "Paranoid Android" {
t.Errorf("expected title 'Paranoid Android', got %q", track.Title)
}
if track.DurationMs == 0 {
t.Error("expected duration to be populated")
}
if track.Isrc == "" {
t.Error("expected ISRC to be populated")
}
if len(track.Artists) == 0 {
t.Error("expected artists to be populated")
}
}
func TestGetTrackByISRC(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
srv := startTestServer(t)
defer srv.stop()
client := newClient(t, srv.addr)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
track, err := client.GetTrack(ctx, &metadatav1.GetTrackRequest{
Identifier: &metadatav1.GetTrackRequest_Isrc{Isrc: "GBAYE9701376"},
})
if err != nil {
t.Fatalf("GetTrack by ISRC failed: %v", err)
}
if track.Title != "Paranoid Android" {
t.Errorf("expected title 'Paranoid Android', got %q", track.Title)
}
}
func TestProviderSelection(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
srv := startTestServer(t)
defer srv.stop()
client := newClient(t, srv.addr)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
resp, err := client.SearchArtists(ctx, &metadatav1.SearchArtistsRequest{
Query: "Radiohead",
Limit: 1,
Provider: metadatav1.Provider_PROVIDER_MUSICBRAINZ,
})
if err != nil {
t.Fatalf("SearchArtists with provider failed: %v", err)
}
if len(resp.Artists) == 0 {
t.Fatal("expected at least one artist")
}
hasMB := false
for _, ext := range resp.Artists[0].ExternalIds {
if ext.Source == "musicbrainz" {
hasMB = true
break
}
}
if !hasMB {
t.Error("expected musicbrainz source when provider=MUSICBRAINZ")
}
}