Files
mal/internal/episodes/service/titles_test.go

146 lines
4.4 KiB
Go

package service
import (
"context"
"database/sql"
"encoding/json"
"testing"
"mal/integrations/jikan"
"mal/internal/db"
"mal/internal/domain"
_ "github.com/mattn/go-sqlite3"
)
type titleProviderStub struct {
loads int
resolves int
}
func (p *titleProviderStub) Name() string { return "TVmaze" }
func (p *titleProviderStub) ResolveEpisodeProviderID(context.Context, int, []string) (string, error) {
p.resolves++
return "80712", nil
}
func (p *titleProviderStub) GetEpisodeTitlesByProviderID(context.Context, string, domain.Anime, int) (map[int]string, error) {
p.loads++
return map[int]string{1: "TVmaze title one", 2: "TVmaze title two"}, nil
}
func TestMergeMissingTitlesPreservesAllAnimeTitles(t *testing.T) {
episodes := []domain.CanonicalEpisode{
{Number: 1, Title: "AllAnime title"},
{Number: 2, Title: "Episode 2"},
{Number: 3, Title: "Episode 3"},
}
changed := mergeMissingTitles(episodes, map[int]string{
1: "TVmaze title one",
2: "TVmaze title two",
})
if !changed {
t.Fatal("expected missing title to be enriched")
}
if episodes[0].Title != "AllAnime title" {
t.Fatalf("AllAnime title was overwritten: %q", episodes[0].Title)
}
if episodes[1].Title != "TVmaze title two" {
t.Fatalf("episode 2 title = %q", episodes[1].Title)
}
if episodes[2].Title != "Episode 3" {
t.Fatalf("episode 3 title = %q", episodes[2].Title)
}
}
func TestEnrichEpisodeTitlesCachesTVmazeTitles(t *testing.T) {
ctx := context.Background()
sqlDB := newTitleTestDB(ctx, t)
provider := &titleProviderStub{}
svc := &EpisodeService{queries: db.New(sqlDB), titles: provider, clock: realClock{}}
anime := domain.Anime{Anime: jikan.Anime{
MalID: 59846,
Title: "Saigo ni Hitotsu dake Onegai shitemo Yoroshii deshou ka",
TitleEnglish: "May I Ask for One Final Thing?",
}}
for range 2 {
assertEnrichedTitleList(t, svc, anime)
}
if provider.resolves != 1 || provider.loads != 1 {
t.Fatalf("provider calls = resolves:%d loads:%d, want 1 each", provider.resolves, provider.loads)
}
assertCachedTitles(ctx, t, sqlDB)
}
func newTitleTestDB(ctx context.Context, t *testing.T) *sql.DB {
t.Helper()
sqlDB, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := sqlDB.Close(); err != nil {
t.Errorf("close sqlite: %v", err)
}
})
sqlDB.SetMaxOpenConns(1)
for _, statement := range []string{
`CREATE TABLE episode_availability_cache (
anime_id INTEGER PRIMARY KEY, data TEXT NOT NULL, next_refresh_at DATETIME,
retry_until_at DATETIME, last_attempt_at DATETIME, last_success_at DATETIME,
failure_count INTEGER NOT NULL DEFAULT 0, last_error TEXT NOT NULL DEFAULT '',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE episode_provider_mapping (
anime_id INTEGER NOT NULL, provider TEXT NOT NULL, provider_show_id TEXT NOT NULL,
failed_until DATETIME, last_error TEXT NOT NULL DEFAULT '',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (anime_id, provider)
)`,
`INSERT INTO episode_availability_cache (
anime_id, data, next_refresh_at, failure_count, last_error
) VALUES (
59846,
'{"anime_id":59846,"source":"AllAnime","availability_version":4,"episodes":[{"number":1,"title":"AllAnime title","has_sub":true},{"number":2,"title":"Episode 2","has_sub":true}]}',
'2999-01-01T00:00:00Z', 2, 'preserve me'
)`,
} {
if _, err := sqlDB.ExecContext(ctx, statement); err != nil {
t.Fatal(err)
}
}
return sqlDB
}
func assertEnrichedTitleList(t *testing.T, svc *EpisodeService, anime domain.Anime) {
t.Helper()
list, err := svc.EnrichEpisodeTitles(context.Background(), anime)
if err != nil {
t.Fatal(err)
}
if list.Episodes[0].Title != "AllAnime title" || list.Episodes[1].Title != "TVmaze title two" {
t.Fatalf("episodes = %#v", list.Episodes)
}
}
func assertCachedTitles(ctx context.Context, t *testing.T, sqlDB *sql.DB) {
t.Helper()
var raw, lastError string
var failureCount int
if err := sqlDB.QueryRowContext(ctx, `SELECT data, failure_count, last_error FROM episode_availability_cache WHERE anime_id = 59846`).Scan(&raw, &failureCount, &lastError); err != nil {
t.Fatal(err)
}
var cached domain.CanonicalEpisodeList
if err := json.Unmarshal([]byte(raw), &cached); err != nil {
t.Fatal(err)
}
if cached.Episodes[1].Title != "TVmaze title two" || failureCount != 2 || lastError != "preserve me" {
t.Fatalf("cached = %#v failure_count=%d last_error=%q", cached.Episodes, failureCount, lastError)
}
}