161 lines
3.9 KiB
Go
161 lines
3.9 KiB
Go
package watchlist
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"mal/internal/db"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
type fakeQuerier struct {
|
|
db.Querier
|
|
upsertAnimeCalled bool
|
|
upsertEntryCalled bool
|
|
upsertEntryParams db.UpsertWatchListEntryParams
|
|
getAnimeFunc func(ctx context.Context, id int64) (db.Anime, error)
|
|
}
|
|
|
|
func (f *fakeQuerier) GetAnime(ctx context.Context, id int64) (db.Anime, error) {
|
|
if f.getAnimeFunc != nil {
|
|
return f.getAnimeFunc(ctx, id)
|
|
}
|
|
return db.Anime{}, nil
|
|
}
|
|
|
|
func (f *fakeQuerier) UpsertAnime(ctx context.Context, arg db.UpsertAnimeParams) (db.Anime, error) {
|
|
f.upsertAnimeCalled = true
|
|
return db.Anime{}, nil
|
|
}
|
|
|
|
func (f *fakeQuerier) UpsertWatchListEntry(ctx context.Context, arg db.UpsertWatchListEntryParams) (db.WatchListEntry, error) {
|
|
f.upsertEntryCalled = true
|
|
f.upsertEntryParams = arg
|
|
return db.WatchListEntry{}, nil
|
|
}
|
|
|
|
func (f *fakeQuerier) GetUserWatchList(ctx context.Context, userID string) ([]db.GetUserWatchListRow, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func TestAddEntry_RejectsInvalidAnimeID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
q := &fakeQuerier{}
|
|
svc := NewService(q, nil, nil)
|
|
|
|
err := svc.AddToWatchlist(context.Background(), "user-1", 0, "watching")
|
|
|
|
if err != ErrInvalidAnimeID {
|
|
t.Fatalf("expected ErrInvalidAnimeID, got %v", err)
|
|
}
|
|
|
|
if q.upsertAnimeCalled || q.upsertEntryCalled {
|
|
t.Fatal("expected no database writes for invalid anime id")
|
|
}
|
|
}
|
|
|
|
func TestAddEntry_RejectsInvalidStatus(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
q := &fakeQuerier{}
|
|
svc := NewService(q, nil, nil)
|
|
|
|
err := svc.AddToWatchlist(context.Background(), "user-1", 1, "invalid")
|
|
|
|
if err != ErrInvalidStatus {
|
|
t.Fatalf("expected ErrInvalidStatus, got %v", err)
|
|
}
|
|
|
|
if q.upsertAnimeCalled || q.upsertEntryCalled {
|
|
t.Fatal("expected no database writes for invalid status")
|
|
}
|
|
}
|
|
|
|
func TestImportWatchlist(t *testing.T) {
|
|
dbFile := "test_watchlist.db"
|
|
defer os.Remove(dbFile)
|
|
|
|
sqlDB, err := sql.Open("sqlite3", dbFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sqlDB.Close()
|
|
|
|
// Minimal schema for testing
|
|
_, err = sqlDB.Exec(`
|
|
CREATE TABLE anime (
|
|
id INTEGER PRIMARY KEY,
|
|
title_original TEXT NOT NULL,
|
|
image_url TEXT NOT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
title_english TEXT,
|
|
title_japanese TEXT,
|
|
airing BOOLEAN,
|
|
status TEXT,
|
|
relations_synced_at DATETIME,
|
|
duration_seconds REAL
|
|
);
|
|
CREATE TABLE watch_list_entry (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL,
|
|
anime_id INTEGER NOT NULL REFERENCES anime(id),
|
|
status TEXT NOT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
current_episode INTEGER DEFAULT 0,
|
|
last_episode_at DATETIME,
|
|
current_time_seconds REAL NOT NULL DEFAULT 0,
|
|
UNIQUE(user_id, anime_id)
|
|
);
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
queries := db.New(sqlDB)
|
|
svc := NewService(queries, sqlDB, nil)
|
|
|
|
// Pre-insert anime so ensureAnimeExists succeeds
|
|
_, err = sqlDB.Exec(`INSERT INTO anime (id, title_original, image_url) VALUES (1, 'Test 1', '');`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = sqlDB.Exec(`INSERT INTO anime (id, title_original, image_url) VALUES (2, 'Test 2', '');`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
csvData := `anime_id,status,current_episode,current_time_seconds
|
|
1,watching,5,120.5
|
|
2,invalid,10,0
|
|
`
|
|
err = svc.ImportWatchlist(context.Background(), "user-1", strings.NewReader(csvData))
|
|
if err != nil {
|
|
t.Fatalf("ImportWatchlist failed: %v", err)
|
|
}
|
|
|
|
// Verify entries
|
|
var count int
|
|
err = sqlDB.QueryRow("SELECT COUNT(*) FROM watch_list_entry WHERE user_id = 'user-1'").Scan(&count)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != 2 {
|
|
t.Errorf("expected 2 entries, got %d", count)
|
|
}
|
|
|
|
var status string
|
|
err = sqlDB.QueryRow("SELECT status FROM watch_list_entry WHERE anime_id = 2").Scan(&status)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status != "plan_to_watch" {
|
|
t.Errorf("expected status to be defaulted to plan_to_watch, got %s", status)
|
|
}
|
|
}
|