92 lines
3.1 KiB
Go
92 lines
3.1 KiB
Go
package recognize_test
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.vakhrushev.me/av/jellybit/internal/llm"
|
|
"git.vakhrushev.me/av/jellybit/internal/recognize"
|
|
)
|
|
|
|
func derefInt(p *int) string {
|
|
if p == nil {
|
|
return "nil"
|
|
}
|
|
return strconv.Itoa(*p)
|
|
}
|
|
|
|
// TestIntegration_RecognizeSeries гоняет полный конвейер против реального
|
|
// LLM на настоящих (русских) именах файлов раздачи. По умолчанию
|
|
// пропускается; включается так же, как llm-интеграция:
|
|
//
|
|
// JELLYBIT_LLM_BASE_URL=https://bothub.chat/api/v2/openai/v1 \
|
|
// JELLYBIT_LLM_API_KEY=... JELLYBIT_LLM_MODEL=deepseek-v4-flash \
|
|
// go test ./internal/recognize/ -run Integration -v
|
|
func TestIntegration_RecognizeSeries(t *testing.T) {
|
|
base := os.Getenv("JELLYBIT_LLM_BASE_URL")
|
|
key := os.Getenv("JELLYBIT_LLM_API_KEY")
|
|
model := os.Getenv("JELLYBIT_LLM_MODEL")
|
|
if base == "" || model == "" {
|
|
t.Skip("set JELLYBIT_LLM_BASE_URL and JELLYBIT_LLM_MODEL to run")
|
|
}
|
|
|
|
provider, err := llm.New(llm.Config{
|
|
Type: "openai-compat", BaseURL: base, APIKey: key, Model: model,
|
|
Timeout: 90 * time.Second,
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatalf("llm.New: %v", err)
|
|
}
|
|
|
|
log := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
r := recognize.New(provider, nil, recognize.Config{MaxRetries: 2}, log)
|
|
|
|
const dir = "Аватар Легенда об Аанге.Книга 2.Земля(Avatar The Last Airbender The book 2.Earth)/"
|
|
in := recognize.Input{
|
|
Name: "Аватар Легенда об Аанге.Книга 2.Земля",
|
|
Context: "Аватар: Легенда об Аанге / Книга 2: Земля [2006, США, DVDRip-AVC]",
|
|
Files: []recognize.File{
|
|
{Path: dir + "1.Состояние Аватара (The Avatar State).mkv", Size: 215_000_000},
|
|
{Path: dir + "6.Слепой бандит (Blind bandit).mkv", Size: 215_910_977},
|
|
{Path: dir + "8.Погоня (The Chase).mkv", Size: 216_587_695},
|
|
{Path: dir + "12.Змеиный перевал (The Serpent's Pass).mkv", Size: 216_330_940},
|
|
{Path: dir + "20.Перекрестки судьбы (The Crossroads of Destiny).mkv", Size: 215_934_285},
|
|
},
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
|
defer cancel()
|
|
|
|
res, err := r.Recognize(ctx, in)
|
|
if err != nil {
|
|
t.Fatalf("Recognize: %v", err)
|
|
}
|
|
t.Logf("type=%s title=%q year=%d files=%d attempts=%d\nreasons=%v\nnotes=%s",
|
|
res.Plan.Type, res.Plan.Title, res.Plan.Year, len(res.Plan.Files),
|
|
res.Attempts, res.Decision.Reasons, res.Plan.Notes)
|
|
for _, f := range res.Plan.Files {
|
|
t.Logf(" %s -> role=%s season=%s episode=%s", f.Src, f.Role, derefInt(f.Season), derefInt(f.Episode))
|
|
}
|
|
|
|
if res.Plan.Type != recognize.MediaSeries {
|
|
t.Errorf("type = %q, want series", res.Plan.Type)
|
|
}
|
|
if res.Decision.Auto {
|
|
t.Error("Ф2 must not auto-resolve")
|
|
}
|
|
episodes := 0
|
|
for _, f := range res.Plan.Files {
|
|
if f.Role == recognize.RoleEpisode {
|
|
episodes++
|
|
}
|
|
}
|
|
if episodes != len(in.Files) {
|
|
t.Errorf("recognized %d episodes, want %d", episodes, len(in.Files))
|
|
}
|
|
}
|