137 lines
4.0 KiB
Go
137 lines
4.0 KiB
Go
package recommend
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func explainItem(t *testing.T, payload string) Item {
|
||
|
|
t.Helper()
|
||
|
|
items := Decode([]json.RawMessage{json.RawMessage(payload)})
|
||
|
|
if len(items) != 1 {
|
||
|
|
t.Fatalf("payload did not decode to one item: %s", payload)
|
||
|
|
}
|
||
|
|
return items[0]
|
||
|
|
}
|
||
|
|
|
||
|
|
func explainProfile(t *testing.T, history ...string) Profile {
|
||
|
|
t.Helper()
|
||
|
|
items := make([]Item, 0, len(history))
|
||
|
|
for _, payload := range history {
|
||
|
|
items = append(items, explainItem(t, payload))
|
||
|
|
}
|
||
|
|
return BuildProfile(items, nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
const thrillerHistory = `{
|
||
|
|
"Id":"h1","Name":"Sicario","Type":"Movie","Genres":["Thriller","Crime"],
|
||
|
|
"Studios":[{"Name":"Lionsgate"}],
|
||
|
|
"People":[{"Name":"Denis Villeneuve","Type":"Director"},{"Name":"Emily Blunt","Type":"Actor"}]
|
||
|
|
}`
|
||
|
|
|
||
|
|
func TestWhyNamesTheGenreTheViewerActuallyWatches(t *testing.T) {
|
||
|
|
profile := explainProfile(t, thrillerHistory)
|
||
|
|
candidate := explainItem(t, `{
|
||
|
|
"Id":"c1","Name":"Prisoners","Type":"Movie","Genres":["Thriller"],
|
||
|
|
"People":[{"Name":"Denis Villeneuve","Type":"Director"}]
|
||
|
|
}`)
|
||
|
|
|
||
|
|
reasons := Why(profile, candidate, ReasonLimit)
|
||
|
|
|
||
|
|
if len(reasons) == 0 || reasons[0] != "Because you watch Thriller" {
|
||
|
|
t.Fatalf("expected the genre reason first, got %v", reasons)
|
||
|
|
}
|
||
|
|
if !strings.Contains(strings.Join(reasons, "|"), "Denis Villeneuve") {
|
||
|
|
t.Fatalf("expected the shared director to be named, got %v", reasons)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWhyNeverClaimsAnAffinityThatIsNotThere(t *testing.T) {
|
||
|
|
profile := explainProfile(t, thrillerHistory)
|
||
|
|
candidate := explainItem(t, `{
|
||
|
|
"Id":"c2","Name":"Paddington","Type":"Movie","Genres":["Family"],
|
||
|
|
"CommunityRating":8.2,
|
||
|
|
"People":[{"Name":"Paul King","Type":"Director"}]
|
||
|
|
}`)
|
||
|
|
|
||
|
|
reasons := Why(profile, candidate, ReasonLimit)
|
||
|
|
|
||
|
|
for _, reason := range reasons {
|
||
|
|
if strings.HasPrefix(reason, "Because you watch") ||
|
||
|
|
strings.HasPrefix(reason, "You've watched") {
|
||
|
|
t.Fatalf("invented a taste the profile does not hold: %v", reasons)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if len(reasons) == 0 || reasons[0] != "Well rated (8.2)" {
|
||
|
|
t.Fatalf("expected the catalogue fact to carry the strip, got %v", reasons)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWhyAlwaysSaysSomething(t *testing.T) {
|
||
|
|
candidate := explainItem(t, `{"Id":"c3","Name":"Unknown","Type":"Movie","Genres":["Drama"]}`)
|
||
|
|
|
||
|
|
reasons := Why(Profile{}, candidate, ReasonLimit)
|
||
|
|
|
||
|
|
if len(reasons) != 1 || reasons[0] != "Drama from your library" {
|
||
|
|
t.Fatalf("an empty profile should still explain the title, got %v", reasons)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWhyIsCappedAndOrdered(t *testing.T) {
|
||
|
|
// A second, Thriller-only title so Thriller genuinely outweighs Crime — with one
|
||
|
|
// history item they tie and the alphabetical tie-break would decide it.
|
||
|
|
profile := explainProfile(t, thrillerHistory, `{
|
||
|
|
"Id":"h2","Name":"Nightcrawler","Type":"Movie","Genres":["Thriller"],
|
||
|
|
"Studios":[{"Name":"Lionsgate"}],
|
||
|
|
"People":[{"Name":"Emily Blunt","Type":"Actor"}]
|
||
|
|
}`)
|
||
|
|
candidate := explainItem(t, `{
|
||
|
|
"Id":"c4","Name":"Wind River","Type":"Movie","Genres":["Thriller","Crime"],
|
||
|
|
"Studios":[{"Name":"Lionsgate"}],"CommunityRating":9.1,"ProductionYear":2017,
|
||
|
|
"People":[{"Name":"Emily Blunt","Type":"Actor"}]
|
||
|
|
}`)
|
||
|
|
|
||
|
|
reasons := Why(profile, candidate, ReasonLimit)
|
||
|
|
|
||
|
|
if len(reasons) != ReasonLimit {
|
||
|
|
t.Fatalf("expected exactly %d reasons, got %v", ReasonLimit, reasons)
|
||
|
|
}
|
||
|
|
want := []string{
|
||
|
|
"Because you watch Thriller",
|
||
|
|
"You've watched Emily Blunt before",
|
||
|
|
"More from Lionsgate",
|
||
|
|
}
|
||
|
|
for i, reason := range want {
|
||
|
|
if reasons[i] != reason {
|
||
|
|
t.Fatalf("reason %d: want %q, got %q (%v)", i, reason, reasons[i], reasons)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWhyIsStableAcrossCalls(t *testing.T) {
|
||
|
|
profile := explainProfile(t, thrillerHistory)
|
||
|
|
candidate := explainItem(t, `{
|
||
|
|
"Id":"c5","Name":"Hell or High Water","Type":"Movie","Genres":["Crime","Thriller"]
|
||
|
|
}`)
|
||
|
|
|
||
|
|
first := Why(profile, candidate, ReasonLimit)
|
||
|
|
for i := 0; i < 20; i++ {
|
||
|
|
if got := Why(profile, candidate, ReasonLimit); !equalStrings(first, got) {
|
||
|
|
t.Fatalf("reasons changed between calls: %v then %v", first, got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func equalStrings(a, b []string) bool {
|
||
|
|
if len(a) != len(b) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
for i := range a {
|
||
|
|
if a[i] != b[i] {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|