Добавил реализацию
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
package naming
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"git.vakhrushev.me/av/jellybit/internal/llm"
|
||||
)
|
||||
|
||||
func testLogger() *slog.Logger {
|
||||
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
|
||||
// fakeProvider отдаёт заранее заданные ответы по очереди; считает вызовы.
|
||||
type fakeProvider struct {
|
||||
responses []string
|
||||
err error
|
||||
calls int
|
||||
}
|
||||
|
||||
func (f *fakeProvider) Complete(_ context.Context, _ llm.Request) (llm.Response, error) {
|
||||
f.calls++
|
||||
if f.err != nil {
|
||||
return llm.Response{}, f.err
|
||||
}
|
||||
idx := f.calls - 1
|
||||
if idx >= len(f.responses) {
|
||||
idx = len(f.responses) - 1
|
||||
}
|
||||
return llm.Response{Content: f.responses[idx]}, nil
|
||||
}
|
||||
|
||||
const duneContext = "Дюна: Часть вторая / Dune: Part Two [2024, фантастика, WEB-DL 2160p]\nDub + MVO"
|
||||
|
||||
func TestDeriveNameViaLLM(t *testing.T) {
|
||||
fp := &fakeProvider{responses: []string{
|
||||
`{"type":"movie","title":"Дюна: Часть вторая","year":2024,"director":"Дени Вильнёв","is_russian":false}`,
|
||||
}}
|
||||
got := New(fp, 3, testLogger()).DeriveName(context.Background(), duneContext, "")
|
||||
want := "Дюна: Часть вторая (Дени Вильнёв, 2024)"
|
||||
if got != want {
|
||||
t.Errorf("DeriveName() = %q, want %q", got, want)
|
||||
}
|
||||
if fp.calls != 1 {
|
||||
t.Errorf("вызовов LLM = %d, want 1", fp.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveNameRussianTitle(t *testing.T) {
|
||||
fp := &fakeProvider{responses: []string{
|
||||
`{"type":"movie","title":"Брат","original_title":"Brat","year":1997,"is_russian":true}`,
|
||||
}}
|
||||
got := New(fp, 3, testLogger()).DeriveName(context.Background(), "Брат / Brat [1997]", "")
|
||||
if got != "Брат (1997)" {
|
||||
t.Errorf("DeriveName() = %q, want %q", got, "Брат (1997)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveNameRetriesThenSucceeds(t *testing.T) {
|
||||
fp := &fakeProvider{responses: []string{
|
||||
"не json вовсе",
|
||||
`{"type":"movie","title":"Дюна: Часть вторая","year":2024}`,
|
||||
}}
|
||||
got := New(fp, 3, testLogger()).DeriveName(context.Background(), duneContext, "")
|
||||
if got != "Дюна: Часть вторая (2024)" {
|
||||
t.Errorf("DeriveName() = %q", got)
|
||||
}
|
||||
if fp.calls != 2 {
|
||||
t.Errorf("вызовов LLM = %d, want 2", fp.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveNameExhaustsThenFallback(t *testing.T) {
|
||||
fp := &fakeProvider{responses: []string{"мусор"}}
|
||||
got := New(fp, 3, testLogger()).DeriveName(context.Background(), duneContext, "")
|
||||
// Бюджет исчерпан → алгоритмический фолбек: первая строка без тех. спецификаций.
|
||||
if got != "Дюна: Часть вторая / Dune: Part Two" {
|
||||
t.Errorf("DeriveName() = %q (ожидался фолбек)", got)
|
||||
}
|
||||
if fp.calls != 3 {
|
||||
t.Errorf("вызовов LLM = %d, want 3 (исчерпание бюджета)", fp.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveNameProviderErrorFallsBack(t *testing.T) {
|
||||
fp := &fakeProvider{err: errors.New("connection refused")}
|
||||
got := New(fp, 3, testLogger()).DeriveName(context.Background(), duneContext, "")
|
||||
if got != "Дюна: Часть вторая / Dune: Part Two" {
|
||||
t.Errorf("DeriveName() = %q (ожидался фолбек при ошибке)", got)
|
||||
}
|
||||
if fp.calls != 1 {
|
||||
t.Errorf("вызовов LLM = %d, want 1 (на транспортной ошибке не повторяем)", fp.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveNameNilProviderUsesFallback(t *testing.T) {
|
||||
got := New(nil, 3, testLogger()).DeriveName(context.Background(), duneContext, "")
|
||||
if got != "Дюна: Часть вторая / Dune: Part Two" {
|
||||
t.Errorf("DeriveName() = %q (ожидался фолбек без LLM)", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user