1
1
Files
audio-catalyst/cmd/main_test.go
Dmitriy Fofanov 72a66f1664 Добавить тесты репозитория файловой системы и реализовать функциональность журналирования файлов.
- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы.
- Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов.
- Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов.
- Добавить тесты для нормализации URL в репозиторий RuTracker.
- Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер.
- Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
2025-09-29 20:40:05 +03:00

639 lines
25 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build ignore
// Legacy tests for monolithic cmd package are ignored after refactor to Clean Architecture.
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// TestCleanSearchTitle тестирует функцию очистки названий
func TestCleanSearchTitle(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "Удаление круглых скобок",
input: "Название книги (автор)",
expected: "Название книги",
},
{
name: "Удаление квадратных скобок",
input: "Название книги [жанр]",
expected: "Название книги",
},
{
name: "Удаление множественных пробелов",
input: "Название книги",
expected: "Название книги",
},
{
name: "Комплексная очистка",
input: "Название книги (автор) [жанр] ",
expected: "Название книги",
},
{
name: "Пустая строка",
input: "",
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := cleanSearchTitle(tt.input)
if result != tt.expected {
t.Errorf("cleanSearchTitle(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
// TestFindMP3Files тестирует поиск MP3 файлов
func TestFindMP3Files(t *testing.T) {
// Создаем временную директорию для теста
tmpDir := t.TempDir()
// Создаем тестовые файлы
testFiles := []string{
"test1.mp3",
"test2.MP3",
"test3.txt",
"test4.mp3",
"notmp3.wav",
}
for _, file := range testFiles {
filePath := filepath.Join(tmpDir, file)
f, err := os.Create(filePath)
if err != nil {
t.Fatalf("Не удалось создать тестовый файл %s: %v", file, err)
}
f.Close()
}
// Тестируем функцию
mp3Files, err := findMP3Files(tmpDir)
if err != nil {
t.Fatalf("findMP3Files вернула ошибку: %v", err)
}
// Проверяем результат
expectedCount := 3 // test1.mp3, test2.MP3, test4.mp3
if len(mp3Files) != expectedCount {
t.Errorf("Ожидалось %d MP3 файлов, найдено %d", expectedCount, len(mp3Files))
}
// Проверяем, что найдены правильные файлы
foundFiles := make(map[string]bool)
for _, file := range mp3Files {
foundFiles[filepath.Base(file)] = true
}
expectedFiles := []string{"test1.mp3", "test2.MP3", "test4.mp3"}
for _, expected := range expectedFiles {
if !foundFiles[expected] {
t.Errorf("Файл %s не найден в результатах", expected)
}
}
}
// TestCreateChapters тестирует создание глав из MP3 файлов
func TestCreateChapters(t *testing.T) {
// Создаем временную директорию с тестовыми MP3 файлами
tempDir := t.TempDir()
// Создаем тестовые MP3 файлы
testFiles := []string{
"01 - Введение.mp3",
"02 - Первая глава.mp3",
"03 - Вторая глава.mp3",
"10 - Заключение.mp3",
}
var mp3FilePaths []string
for _, filename := range testFiles {
filePath := filepath.Join(tempDir, filename)
// Создаем пустой файл
file, err := os.Create(filePath)
if err != nil {
t.Fatalf("Не удалось создать тестовый файл %s: %v", filename, err)
}
file.Close()
mp3FilePaths = append(mp3FilePaths, filePath)
}
// Тестируем создание глав
chapters := createChapters(mp3FilePaths)
// Проверяем количество созданных глав
if len(chapters) != len(testFiles) {
t.Errorf("Ожидалось %d глав, получено %d", len(testFiles), len(chapters))
}
// Проверяем первую главу
if chapters[0].ID != 0 {
t.Errorf("Ожидался ID 0 для первой главы, получен %d", chapters[0].ID)
}
if chapters[0].Title != "01 - Введение" {
t.Errorf("Ожидалось название '01 - Введение', получено '%s'", chapters[0].Title)
}
// Проверяем последнюю главу
lastChapter := chapters[len(chapters)-1]
if lastChapter.ID != len(chapters)-1 {
t.Errorf("Ожидался ID %d для последней главы, получен %d", len(chapters)-1, lastChapter.ID)
}
// Проверяем, что главы отсортированы правильно
expectedTitles := []string{"01 - Введение", "02 - Первая глава", "03 - Вторая глава", "10 - Заключение"}
for i, expectedTitle := range expectedTitles {
if chapters[i].Title != expectedTitle {
t.Errorf("Глава %d: ожидалось название '%s', получено '%s'", i, expectedTitle, chapters[i].Title)
}
}
}
// TestFindCoverFile тестирует поиск файлов обложек
func TestFindCoverFile(t *testing.T) {
// Создаем временную директорию для теста
tmpDir := t.TempDir()
// Создаем тестовые файлы
testFiles := []string{
"cover.jpg",
"other.txt",
"music.mp3",
}
for _, file := range testFiles {
filePath := filepath.Join(tmpDir, file)
f, err := os.Create(filePath)
if err != nil {
t.Fatalf("Не удалось создать тестовый файл %s: %v", file, err)
}
f.Close()
}
// Тестируем функцию
coverFile := findCoverFile(tmpDir)
expectedPath := filepath.Join(tmpDir, "cover.jpg")
if coverFile != expectedPath {
t.Errorf("Ожидался путь к обложке %q, получен %q", expectedPath, coverFile)
}
}
// TestFindCoverFileNotFound тестирует случай, когда обложка не найдена
func TestFindCoverFileNotFound(t *testing.T) {
// Создаем временную директорию без файлов обложек
tmpDir := t.TempDir()
// Создаем файлы, которые не являются обложками
testFiles := []string{
"music.mp3",
"readme.txt",
}
for _, file := range testFiles {
filePath := filepath.Join(tmpDir, file)
f, err := os.Create(filePath)
if err != nil {
t.Fatalf("Не удалось создать тестовый файл %s: %v", file, err)
}
f.Close()
}
// Тестируем функцию
coverFile := findCoverFile(tmpDir)
if coverFile != "" {
t.Errorf("Ожидалась пустая строка, получен %q", coverFile)
}
}
// BenchmarkCleanSearchTitle тестирует производительность очистки названий
func BenchmarkCleanSearchTitle(b *testing.B) {
testTitle := "Очень длинное название книги с (автором) [жанром] и множественными пробелами"
b.ResetTimer()
for i := 0; i < b.N; i++ {
cleanSearchTitle(testTitle)
}
}
// TestCreateMetadata тестирует создание метаданных
func TestCreateMetadata(t *testing.T) {
// Подготавливаем тестовые данные
book := AudioBookInfo{
Title: "Тестовая книга",
Path: "/test/path",
MP3Files: []string{"chapter1.mp3", "chapter2.mp3"},
Description: "Описание тестовой книги",
}
rutrackerData := &RuTrackerResult{
Title: "RuTracker название",
Authors: []string{"Автор 1", "Автор 2"},
Narrators: []string{"Рассказчик 1"},
Genres: []string{"Фантастика", "Приключения"},
Description: "RuTracker описание",
}
// Тестируем функцию
metadata := createMetadata(book, rutrackerData)
// Проверяем результат
if metadata.Title != rutrackerData.Title {
t.Errorf("Ожидалось название %q, получено %q", rutrackerData.Title, metadata.Title)
}
if len(metadata.Authors) != len(rutrackerData.Authors) {
t.Errorf("Ожидалось %d авторов, получено %d", len(rutrackerData.Authors), len(metadata.Authors))
}
if len(metadata.Chapters) != len(book.MP3Files) {
t.Errorf("Ожидалось %d глав, получено %d", len(book.MP3Files), len(metadata.Chapters))
}
if metadata.Language != "ru" {
t.Errorf("Ожидался язык 'ru', получен %q", metadata.Language)
}
}
// TestParseSearchResults тестирует функцию парсинга результатов поиска RuTracker
func TestParseSearchResults(t *testing.T) {
// Пример HTML контента с результатами поиска (упрощенная версия)
htmlContent := `
<table>
<tr class="tCenter">
<td><a href="dl.php?t=123456">Download</a></td>
<td><a href="viewtopic.php?t=123456">Тестовое название торрента</a></td>
<td class="tor-size">1.2 GB</td>
<td class="seedmed">15</td>
<td class="leechmed">3</td>
</tr>
<tr class="tCenter">
<td><a href="dl.php?t=789012">Download</a></td>
<td><a href="viewtopic.php?t=789012">Другой торрент</a></td>
<td class="tor-size">2.5 GB</td>
<td class="seedmed">8</td>
<td class="leechmed">1</td>
</tr>
</table>
`
torrents, err := parseSearchResults(htmlContent)
if err != nil {
t.Fatalf("Ошибка парсинга: %v", err)
}
// Проверяем количество найденных торрентов
expectedCount := 2
if len(torrents) != expectedCount {
t.Errorf("Ожидалось %d торрентов, найдено %d", expectedCount, len(torrents))
}
// Проверяем, что найдены торренты с правильными ID
foundIDs := make(map[string]bool)
for _, torrent := range torrents {
foundIDs[torrent.ID] = true
// Проверяем, что URL формируются правильно
expectedTopicURL := "https://rutracker.org/forum/viewtopic.php?t=" + torrent.ID
if torrent.TopicURL != expectedTopicURL {
t.Errorf("Неправильный URL темы для ID %s: ожидался %q, получен %q",
torrent.ID, expectedTopicURL, torrent.TopicURL)
}
expectedDownloadURL := "https://rutracker.org/forum/dl.php?t=" + torrent.ID
if torrent.DownloadURL != expectedDownloadURL {
t.Errorf("Неправильный URL скачивания для ID %s: ожидался %q, получен %q",
torrent.ID, expectedDownloadURL, torrent.DownloadURL)
}
}
// Проверяем наличие ожидаемых ID
expectedIDs := []string{"123456", "789012"}
for _, expectedID := range expectedIDs {
if !foundIDs[expectedID] {
t.Errorf("Не найден торрент с ID %s", expectedID)
}
}
}
// TestParseTopicMetadata тестирует функцию парсинга метаданных со страницы темы
func TestParseTopicMetadata(t *testing.T) {
// Реальный пример HTML контента страницы темы RuTracker с post-b структурой
htmlContent := `
<html>
<div class="post_body">
<span class="post-b">Год выпуска</span>: 2025<br>
<span class="post-b">Фамилия автора</span>: Первухин<br>
<span class="post-b">Имя автора</span>: Андрей<br>
<span class="post-b">Исполнитель</span>: Парфенов Константин<br>
<span class="post-b">Жанр</span>: Боевое фэнтези<br>
<span class="post-b">Издательство</span>: ЛитРес<br>
<span class="post-b">Описание</span>: Простой парень попал в магический мир и стал наследником рода.<br>
</div>
</html>
`
torrent := TorrentInfo{
ID: "6643935",
Title: "Первухин Андрей - Наследник. Книга 03",
}
metadata, err := parseTopicMetadata(htmlContent, torrent)
if err != nil {
t.Fatalf("Ошибка парсинга метаданных: %v", err)
}
// Проверяем извлеченные данные
if metadata.Title != torrent.Title {
t.Errorf("Неправильное название: ожидалось %q, получено %q", torrent.Title, metadata.Title)
}
// Проверяем автора (должен быть объединен из фамилии и имени)
expectedAuthor := "Первухин Андрей"
if len(metadata.Authors) != 1 || metadata.Authors[0] != expectedAuthor {
t.Errorf("Неправильные авторы: ожидался ['%s'], получено %v", expectedAuthor, metadata.Authors)
}
// Проверяем исполнителя
expectedNarrator := "Парфенов Константин"
if len(metadata.Narrators) != 1 || metadata.Narrators[0] != expectedNarrator {
t.Errorf("Неправильные чтецы: ожидался ['%s'], получено %v", expectedNarrator, metadata.Narrators)
}
// Проверяем год
expectedYear := 2025
if metadata.PublishedYear == nil || *metadata.PublishedYear != expectedYear {
t.Errorf("Неправильный год: ожидался %d, получено %v", expectedYear, metadata.PublishedYear)
}
// Проверяем издательство
expectedPublisher := "ЛитРес"
if metadata.Publisher == nil || *metadata.Publisher != expectedPublisher {
t.Errorf("Неправильное издательство: ожидалось '%s', получено %v", expectedPublisher, metadata.Publisher)
}
// Проверяем жанры
expectedGenres := []string{"Боевое фэнтези"}
if len(metadata.Genres) != 1 || metadata.Genres[0] != expectedGenres[0] {
t.Errorf("Неправильные жанры: ожидались %v, получено %v", expectedGenres, metadata.Genres)
}
// Проверяем описание
expectedDesc := "Простой парень попал в магический мир и стал наследником рода."
if metadata.Description != expectedDesc {
t.Errorf("Неправильное описание: ожидалось '%s', получено '%s'", expectedDesc, metadata.Description)
}
if metadata.Language != "ru" {
t.Errorf("Неправильный язык: ожидался 'ru', получен %q", metadata.Language)
}
}
// TestParseTopicMetadataRealHTML тестирует парсинг с реальным HTML от RuTracker
func TestParseTopicMetadataRealHTML(t *testing.T) {
// Реальный фрагмент HTML со страницы RuTracker
htmlContent := `<div class="post_body" id="p-87376820">
<span style="font-size: 24px; line-height: normal;">Наследник. Книга 03</span> <span class="post-br"><br></span>
<var class="postImg postImgAligned img-right" title="https://i124.fastpic.org/big/2025/0209/7a/d953d7a9b39477e7da76e222993b1f7a.jpg">&#10;</var>
<span class="post-b">Год выпуска</span>: 2025<br>
<span class="post-b">Фамилия автора</span>: Первухин<br>
<span class="post-b">Имя автора</span>: Андрей<br>
<span class="post-b">Исполнитель</span>: Парфенов Константин<br>
<span class="post-b">Жанр</span>: Боевое фэнтези<br>
<span class="post-b">Издательство</span>: ЛитРес: чтец , Автор<br>
<span class="post-b">Аудиокодек</span>: MP3<br>
<span class="post-b">Битрейт</span>: 96 kbps<br>
<span class="post-b">Вид битрейта</span>: постоянный битрейт (CBR)<br>
<span class="post-b">Частота дискретизации</span>: 32 kHz<br>
<span class="post-b">Количество каналов (моно-стерео)</span>: Стерео<br>
<span class="post-b">Время звучания</span>: 07:25:47<br>
<span class="post-b">Описание</span>: Простой парень, без каких-то особых талантов попал в магический мир. Вроде бы живи и радуйся, тем более, в новом мире ты не простой человек, а целый наследник рода, правда, не самого богатого и влиятельного. Только вот беда в том, что боги не обошли его своим вниманием.<span class="post-br"><br></span>
<a href="tracker.php?f=2387&amp;nm=%CF%E5%F0%E2%F3%F5%E8%ED+%ED%E0%F1%EB%E5%E4%ED%E8%EA" class="postLink">Цикл «Наследник»</a><br>
01. Наследник. Книга 01<br>
02. Наследник. Книга 02<br>
03. Наследник. Книга 03<br>
03. Наследник. Книга 04
</div>`
torrent := TorrentInfo{
ID: "6643935",
Title: "Первухин Андрей - Наследник. Книга 03 [Парфенов Константин, 2025, 96 kbps, MP3]",
}
metadata, err := parseTopicMetadata(htmlContent, torrent)
if err != nil {
t.Fatalf("Ошибка парсинга реального HTML: %v", err)
}
// Проверяем автора
expectedAuthor := "Первухин Андрей"
if len(metadata.Authors) != 1 || metadata.Authors[0] != expectedAuthor {
t.Errorf("Неправильные авторы: ожидался ['%s'], получено %v", expectedAuthor, metadata.Authors)
}
// Проверяем исполнителя
expectedNarrator := "Парфенов Константин"
if len(metadata.Narrators) != 1 || metadata.Narrators[0] != expectedNarrator {
t.Errorf("Неправильные чтецы: ожидался ['%s'], получено %v", expectedNarrator, metadata.Narrators)
}
// Проверяем жанр
expectedGenre := "Боевое фэнтези"
if len(metadata.Genres) != 1 || metadata.Genres[0] != expectedGenre {
t.Errorf("Неправильные жанры: ожидался ['%s'], получено %v", expectedGenre, metadata.Genres)
}
// Проверяем год
expectedYear := 2025
if metadata.PublishedYear == nil || *metadata.PublishedYear != expectedYear {
t.Errorf("Неправильный год: ожидался %d, получено %v", expectedYear, metadata.PublishedYear)
}
// Проверяем издательство (должно очистить ": чтец , Автор")
expectedPublisher := "ЛитРес"
if metadata.Publisher == nil || *metadata.Publisher != expectedPublisher {
t.Errorf("Неправильное издательство: ожидалось '%s', получено %v", expectedPublisher, metadata.Publisher)
}
// Проверяем описание
if metadata.Description == "" {
t.Error("Описание не найдено")
} else if !strings.Contains(metadata.Description, "Простой парень") {
t.Errorf("Описание не содержит ожидаемый текст: %s", metadata.Description)
}
// Проверяем информацию о серии (должна найтись в ссылке)
if len(metadata.Series) == 0 {
t.Error("Серия не найдена")
} else if !strings.Contains(metadata.Series[0], "Наследник") {
t.Errorf("Неправильная серия: %v", metadata.Series)
}
t.Logf("Успешно извлечены метаданные: автор=%v, чтец=%v, жанр=%v, год=%v",
metadata.Authors, metadata.Narrators, metadata.Genres, *metadata.PublishedYear)
}
// TestExtractCoverURL тестирует извлечение URL обложки из HTML
func TestExtractCoverURL(t *testing.T) {
// HTML с var тегом как в реальном RuTracker
htmlWithVar := `<div class="post_body">
<var class="postImg postImgAligned img-right" title="https://i124.fastpic.org/big/2025/0209/7a/d953d7a9b39477e7da76e222993b1f7a.jpg">&#10;</var>
Некоторый текст...
</div>`
coverURL := extractCoverURL(htmlWithVar)
expectedURL := "https://i124.fastpic.org/big/2025/0209/7a/d953d7a9b39477e7da76e222993b1f7a.jpg"
if coverURL != expectedURL {
t.Errorf("Неправильный URL обложки: ожидался '%s', получен '%s'", expectedURL, coverURL)
}
// HTML с обычным img тегом
htmlWithImg := `<div class="post_body">
<img src="https://example.com/cover.png" alt="Cover">
Текст...
</div>`
coverURL2 := extractCoverURL(htmlWithImg)
expectedURL2 := "https://example.com/cover.png"
if coverURL2 != expectedURL2 {
t.Errorf("Неправильный URL обложки из img: ожидался '%s', получен '%s'", expectedURL2, coverURL2)
}
// HTML без изображений
htmlEmpty := `<div class="post_body">
Только текст без изображений
</div>`
coverURL3 := extractCoverURL(htmlEmpty)
if coverURL3 != "" {
t.Errorf("Ожидался пустой URL, получен '%s'", coverURL3)
}
// HTML с неправильным URL (не изображение)
htmlWrongURL := `<div class="post_body">
<var class="postImg" title="https://example.com/not-image.txt">&#10;</var>
</div>`
coverURL4 := extractCoverURL(htmlWrongURL)
if coverURL4 != "" {
t.Errorf("Ожидалась пустая строка для не-изображения, получен '%s'", coverURL4)
}
}
// BenchmarkParseSearchResults бенчмарк для функции парсинга результатов поиска
func BenchmarkParseSearchResults(b *testing.B) {
htmlContent := `
<table>
<tr class="tCenter">
<td><a href="dl.php?t=123456">Download</a></td>
<td><a href="viewtopic.php?t=123456">Тестовое название торрента</a></td>
<td class="tor-size">1.2 GB</td>
<td class="seedmed">15</td>
<td class="leechmed">3</td>
</tr>
</table>
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := parseSearchResults(htmlContent)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkParseTopicMetadata бенчмарк для функции парсинга метаданных страницы темы
func BenchmarkParseTopicMetadata(b *testing.B) {
htmlContent := `
<html>
<div class="post_body">
<span class="post-b">Год выпуска</span>: 2025<br>
<span class="post-b">Фамилия автора</span>: Первухин<br>
<span class="post-b">Имя автора</span>: Андрей<br>
<span class="post-b">Исполнитель</span>: Парфенов Константин<br>
<span class="post-b">Жанр</span>: Боевое фэнтези<br>
<span class="post-b">Описание</span>: Описание книги.
</div>
</html>
`
torrent := TorrentInfo{
ID: "6643935",
Title: "Первухин Андрей - Наследник. Книга 03",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := parseTopicMetadata(htmlContent, torrent)
if err != nil {
b.Fatal(err)
}
}
}
// TestDownloadCoverValidation тестирует валидацию функции downloadCover
func TestDownloadCoverValidation(t *testing.T) {
// Создаем временную директорию для тестов
tempDir, err := os.MkdirTemp("", "cover_test")
if err != nil {
t.Fatalf("Не удалось создать временную директорию: %v", err)
}
defer os.RemoveAll(tempDir)
// Тест с пустым URL
err = downloadCover("", tempDir)
if err == nil {
t.Error("Ожидалась ошибка для пустого URL")
}
// Тест с неправильным URL
err = downloadCover("not-a-url", tempDir)
if err == nil {
t.Error("Ожидалась ошибка для неправильного URL")
}
// Примечание: полные HTTP-тесты требуют мокирования или реального сервера
// Для production тестирования можно использовать httptest.Server
}
// TestExtractChapterTitle тестирует извлечение названий глав из имен файлов
func TestExtractChapterTitle(t *testing.T) {
testCases := []struct {
input string
expected string
}{
{"01 - Введение", "01 - Введение"},
{"001_Первая_глава", "001 - Первая_глава"},
{"Chapter 05 - Название", "05 - Название"},
{"Глава 3 - Важная информация", "3 - Важная информация"},
{"Часть 10", "10"},
{"простоеазвание", "простоеазвание"},
{"15", "15"},
{"chapter_1_test", "1 - test"},
{"глава_2_описание", "2 - описание"},
{"", ""},
}
for _, tc := range testCases {
result := extractChapterTitle(tc.input)
if result != tc.expected {
t.Errorf("extractChapterTitle(%q) = %q, ожидалось %q", tc.input, result, tc.expected)
}
}
}