ci / module boundaries / boundaries (push) Successful in 1m11s
ci / api / api (push) Successful in 1m54s
ci / mobile / mobile (Metro-бандл Android) (push) Failing after 6m50s
ci / ts / ts (packages + web) (push) Failing after 6m19s
ci / db / db (push) Failing after 11s
181 lines
7.6 KiB
Go
181 lines
7.6 KiB
Go
package uploads
|
|
|
|
import (
|
|
"context"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
"pipu/api/internal/auth"
|
|
"pipu/api/internal/library"
|
|
"pipu/api/internal/musicbrainz"
|
|
"testing"
|
|
)
|
|
|
|
func TestReleaseOperationsRequireReviewPermission(t *testing.T) {
|
|
s := &Service{}
|
|
for _, ctx := range []context.Context{context.Background(), auth.WithPrincipal(context.Background(), auth.Principal{UserID: uuid.New(), Role: auth.RoleAdmin})} {
|
|
_, err := s.GetReleaseReport(ctx, uuid.New())
|
|
require.Error(t, err)
|
|
_, err = s.SearchReleases(ctx, uuid.New(), musicbrainz.Query{})
|
|
require.Error(t, err)
|
|
_, err = s.ReleaseCover(ctx, uuid.New(), uuid.NewString())
|
|
require.Error(t, err)
|
|
_, err = s.ApplyRelease(ctx, uuid.New(), uuid.NewString(), true, true)
|
|
require.Error(t, err)
|
|
}
|
|
}
|
|
|
|
func releaseFile(title string, number, disc, duration int) File {
|
|
return File{ID: uuid.New(), Status: StatusPendingReview, Meta: &Meta{Artist: "Artist", AlbumArtist: "Artist", Album: "Album", Title: title, Track: &number, Disc: &disc}, DurationMs: &duration}
|
|
}
|
|
func TestReleaseCompletenessUsesTracksAndEditions(t *testing.T) {
|
|
r := musicbrainz.Release{ID: uuid.NewString(), Artist: "Artist", Type: "Single", Tracks: []musicbrainz.Track{
|
|
{Title: "One", Artist: "Artist", Disc: 1, Position: 1, DurationMs: 180000},
|
|
{Title: "Two", Artist: "Artist", Disc: 2, Position: 1, DurationMs: 200000},
|
|
}}
|
|
files := []File{releaseFile("One", 1, 1, 180100), releaseFile("Two", 1, 2, 199000)}
|
|
c := matchRelease(files, r)
|
|
require.True(t, c.Complete)
|
|
require.Equal(t, 2, c.Matched)
|
|
require.Equal(t, "high", c.Confidence)
|
|
// Two encodings of the same song cover one catalogue slot, never two.
|
|
c = matchRelease([]File{files[0], releaseFile("One", 1, 1, 180000)}, r)
|
|
require.False(t, c.Complete)
|
|
require.Equal(t, 1, c.Matched)
|
|
require.Len(t, c.Missing, 1)
|
|
// A deluxe edition with another track must remain incomplete.
|
|
deluxe := r
|
|
deluxe.Tracks = append(append([]musicbrainz.Track{}, r.Tracks...), musicbrainz.Track{Title: "Bonus", Artist: "Artist", Disc: 2, Position: 2, DurationMs: 220000})
|
|
require.False(t, matchRelease(files, deluxe).Complete)
|
|
// A one-track upload does not turn the two-track single into a full single.
|
|
require.False(t, matchRelease(files[:1], r).Complete)
|
|
}
|
|
func TestReleaseMatchingDoesNotClaimAmbiguousOrWeakMatchesAreComplete(t *testing.T) {
|
|
r := musicbrainz.Release{Artist: "Artist", Tracks: []musicbrainz.Track{{Title: "Intro", Artist: "Artist", Disc: 1, Position: 1, DurationMs: 12000}, {Title: "Intro", Artist: "Artist", Disc: 2, Position: 1, DurationMs: 12000}}}
|
|
c := matchRelease([]File{releaseFile("Intro", 1, 1, 12000)}, r)
|
|
require.False(t, c.Complete)
|
|
require.Len(t, c.Unmatched, 1)
|
|
r.Tracks = r.Tracks[:1]
|
|
f := releaseFile("Untitled", 1, 1, 12000)
|
|
c = matchRelease([]File{f}, r)
|
|
require.False(t, c.Complete)
|
|
require.Empty(t, c.Matches, "number and duration alone do not identify a differently titled recording")
|
|
f.Meta.Title = "Intro"
|
|
f.Meta.Artist = "Someone Else"
|
|
f.Meta.AlbumArtist = "Someone Else"
|
|
require.False(t, matchRelease([]File{f}, r).Complete)
|
|
f = releaseFile("Intro", 1, 1, 12000)
|
|
f.DurationMs = nil
|
|
require.False(t, matchRelease([]File{f}, r).Complete)
|
|
}
|
|
func TestReleaseQueryPrioritizesIDsAndRejectsMixedAlbums(t *testing.T) {
|
|
a, b := releaseFile("One", 1, 1, 180000), releaseFile("Two", 2, 1, 180000)
|
|
b.Meta.Album = "Another Album"
|
|
_, ok := batchReleaseQuery([]File{a, b})
|
|
require.False(t, ok)
|
|
id := uuid.NewString()
|
|
a.Tags = map[string]string{"musicbrainz_albumid": id}
|
|
a.Meta.Album = library.UnknownAlbum
|
|
q, ok := batchReleaseQuery([]File{a, b})
|
|
require.True(t, ok)
|
|
require.Equal(t, id, q.ReleaseID)
|
|
b.Tags = map[string]string{"musicbrainz_albumid": uuid.NewString()}
|
|
_, ok = batchReleaseQuery([]File{a, b})
|
|
require.False(t, ok)
|
|
}
|
|
func TestReleaseFingerprintInvalidatesEditsButNotApproval(t *testing.T) {
|
|
f := releaseFile("One", 1, 1, 180000)
|
|
files := []File{f}
|
|
hash := releaseHash(files)
|
|
files[0].Status = StatusApproved
|
|
require.Equal(t, hash, releaseHash(files))
|
|
files[0].Meta.Title = "Changed"
|
|
require.NotEqual(t, hash, releaseHash(files))
|
|
files[0].Status = StatusWithdrawn
|
|
require.NotEqual(t, hash, releaseHash(files))
|
|
}
|
|
|
|
func TestReleaseMatchesOldBloodCreditsWithoutClaimingVerifiedCompleteness(t *testing.T) {
|
|
tracks := []struct {
|
|
title, uploaded, guest string
|
|
seconds int
|
|
}{
|
|
{"Введение", "Введение", "", 34},
|
|
{"Ни много ни мало", "Ни много ни мало (Prod. Blacksurfer)", "", 143},
|
|
{"MOONWORK", "MOONWORK (feat. Basic Boy) (Prod. SP4K)", "Basic Boy", 214},
|
|
{"Angry Toy$", "Angry toy$ (Prod. Ray Qwa)", "", 127},
|
|
{"Возраст", "Возраст (feat. Artur Kreem) (Prod. Babyboi)", "Artur Kreem", 138},
|
|
{"OLD BLOOD", "OLD BLOOD (Prod. Chivaz)", "", 138},
|
|
{"Умы", "Умы (feat. DopeVvs) (Prod. MRKBVNGXR)", "DopeVvs", 125},
|
|
{"NO HOOK NO HOES", "NO HOOK NO HOES (feat. Yung Hurn) (Prod. Pretty Scream)", "Yung Hurn", 133},
|
|
{"ART HOES", "ART HOES (Prod. DavidRema)", "", 183},
|
|
{"Friendly Fire", "Friendly Fire (Prod. Avepack)", "", 122},
|
|
{"X2", "X2 (Prod. Gredy)", "", 176},
|
|
{"4 шага", "4 шага (Prod. DavidRema)", "", 135},
|
|
}
|
|
r := musicbrainz.Release{Artist: "Boulevard Depo", Title: "OLD BLOOD"}
|
|
files := []File{}
|
|
for i, track := range tracks {
|
|
artist, uploadedArtist := "Boulevard Depo", "BOULEVARD DEPO"
|
|
if track.guest != "" {
|
|
artist += " feat. " + track.guest
|
|
uploadedArtist += ", " + track.guest
|
|
}
|
|
r.Tracks = append(r.Tracks, musicbrainz.Track{Title: track.title, Artist: artist, Disc: 1, Position: i + 1, DurationMs: track.seconds * 1000})
|
|
f := releaseFile(track.uploaded, i+1, 1, track.seconds*1000+500)
|
|
f.Meta.Artist, f.Meta.AlbumArtist, f.Meta.Album = uploadedArtist, "BOULEVARD DEPO", "OLD BLOOD"
|
|
files = append([]File{f}, files...)
|
|
}
|
|
c := matchRelease(files, r)
|
|
require.Equal(t, 12, c.Matched)
|
|
require.Empty(t, c.Missing)
|
|
require.Empty(t, c.Unmatched)
|
|
require.False(t, c.Complete, "credit-normalized matches still require review")
|
|
require.Equal(t, "medium", c.Confidence)
|
|
for _, match := range c.Matches {
|
|
require.Equal(t, files[len(files)-1-match.TrackIndex].ID, match.FileID)
|
|
}
|
|
}
|
|
|
|
func TestReleaseCreditMatchingRequiresTitleArtistsPositionAndDuration(t *testing.T) {
|
|
for _, change := range []string{"none", "different guest", "different title", "remix", "live", "instrumental", "disc", "position", "duration", "unknown duration"} {
|
|
t.Run(change, func(t *testing.T) {
|
|
f := releaseFile("Song (feat. Guest) (Prod. Producer)", 3, 1, 180500)
|
|
f.Meta.Artist = "ARTIST; Guest"
|
|
r := musicbrainz.Release{Artist: "Artist", Tracks: []musicbrainz.Track{{Title: "Song", Artist: "Artist feat. Guest", Disc: 1, Position: 3, DurationMs: 180000}}}
|
|
switch change {
|
|
case "different guest":
|
|
f.Meta.Artist = "Artist, Someone Else"
|
|
case "different title":
|
|
f.Meta.Title = "Another song (Prod. Producer)"
|
|
f.Meta.Artist = r.Tracks[0].Artist
|
|
case "remix":
|
|
f.Meta.Title = "Song (Remix) (feat. Guest)"
|
|
f.Meta.Artist = r.Tracks[0].Artist
|
|
case "live":
|
|
f.Meta.Title = "Song [Live] (feat. Guest)"
|
|
f.Meta.Artist = r.Tracks[0].Artist
|
|
case "instrumental":
|
|
f.Meta.Title = "Song (Instrumental) (feat. Guest)"
|
|
f.Meta.Artist = r.Tracks[0].Artist
|
|
case "disc":
|
|
*f.Meta.Disc = 2
|
|
case "position":
|
|
*f.Meta.Track = 4
|
|
case "duration":
|
|
*f.DurationMs = 190000
|
|
case "unknown duration":
|
|
f.DurationMs = nil
|
|
}
|
|
c := matchRelease([]File{f}, r)
|
|
if change == "none" {
|
|
require.Equal(t, 1, c.Matched)
|
|
require.Equal(t, "medium", c.Matches[0].Confidence)
|
|
} else {
|
|
require.Zero(t, c.Matched)
|
|
require.Equal(t, []uuid.UUID{f.ID}, c.Unmatched)
|
|
}
|
|
require.False(t, c.Complete)
|
|
})
|
|
}
|
|
}
|