Files
libnovel/scraper/internal/storage/hybrid_unit_test.go
Admin fb6b364382 refactor: audit, split server.go, add unit tests, and fix latent bugs
- Remove dead code: browser cdp/content_scrape strategies, writer package,
  printUsage, downloadAndStoreCoverCLI in main.go
- Fix bugs: defer-in-loop in pocketbase deleteWhere, listAll() pagination
  hard cap removed, splitChapterTitle off-by-one in date extraction
- Split server.go (~1700 lines) into focused handler files:
  handlers_audio, handlers_browse, handlers_progress, handlers_ranking,
  handlers_scrape
- Export htmlutil.AttrVal/TextContent/ResolveURL; add storage/coverutil.go
  to consolidate duplicate helpers
- Flatten deeply nested conditionals: voices() early-return guards,
  ScrapeCatalogue next-link double attr scan, chapterNumberFromKey dead
  strings.Cut line, splitChapterTitle double-nested unit/suffix loop
- Add unit tests: htmlutil (9 funcs), novelfire ScrapeMetadata (3 cases),
  orchestrator Run (5 cases), storage chapterNumberFromKey/splitChapterTitle
  (22 cases); all pass with go build/vet/test clean
2026-03-04 22:14:23 +05:00

78 lines
2.7 KiB
Go

package storage
import (
"testing"
)
// ── chapterNumberFromKey ──────────────────────────────────────────────────────
func TestChapterNumberFromKey(t *testing.T) {
cases := []struct {
key string
want int
}{
// Standard four-segment key.
{"my-novel/vol-0/1-50/chapter-1.md", 1},
{"my-novel/vol-0/1-50/chapter-42.md", 42},
{"my-novel/vol-0/51-100/chapter-99.md", 99},
// Large chapter numbers.
{"some-novel/vol-1/1001-1050/chapter-1024.md", 1024},
// Nested deeper paths should still work (last segment used).
{"a/b/c/d/chapter-7.md", 7},
// Malformed / unexpected inputs — should return 0 without panicking.
{"chapter-notanumber.md", 0},
{"", 0},
// No .md extension — TrimSuffix is a no-op; TrimPrefix still strips
// "chapter-", so the number is parsed successfully.
{"no-md-extension/chapter-5", 5},
{"my-novel/vol-0/1-50/chapter-0.md", 0}, // 0 is invalid (chapters are 1-based)
{"my-novel/vol-0/1-50/chapter--1.md", 0},
}
for _, tc := range cases {
got := chapterNumberFromKey(tc.key)
if got != tc.want {
t.Errorf("chapterNumberFromKey(%q) = %d, want %d", tc.key, got, tc.want)
}
}
}
// ── splitChapterTitle ─────────────────────────────────────────────────────────
func TestSplitChapterTitle(t *testing.T) {
cases := []struct {
raw string
wantTitle string
wantDate string
}{
// No date — title is returned as-is.
{"The Great Battle", "The Great Battle", ""},
// Leading numeric index is stripped.
{"42 The Great Battle", "The Great Battle", ""},
// Relative date with plural unit.
{"The Storm Arrives 3 days ago", "The Storm Arrives", "3 days ago"},
// Singular unit.
{"A New Hope 1 week ago", "A New Hope", "1 week ago"},
// Minutes and seconds.
{"Flash Fight 5 minutes ago", "Flash Fight", "5 minutes ago"},
{"Quick Strike 30 seconds ago", "Quick Strike", "30 seconds ago"},
// Months and years.
{"Old Chapter 2 months ago", "Old Chapter", "2 months ago"},
{"Ancient Story 1 year ago", "Ancient Story", "1 year ago"},
// Leading index AND trailing date.
{"5 The Final Chapter 2 hours ago", "The Final Chapter", "2 hours ago"},
// Extra whitespace.
{" The Calm ", "The Calm", ""},
// Empty string.
{"", "", ""},
}
for _, tc := range cases {
title, date := splitChapterTitle(tc.raw)
if title != tc.wantTitle || date != tc.wantDate {
t.Errorf("splitChapterTitle(%q) = (%q, %q), want (%q, %q)",
tc.raw, title, date, tc.wantTitle, tc.wantDate)
}
}
}