All checks were successful
Release / Scraper / Test (push) Successful in 10s
Release / UI / Build (push) Successful in 26s
Release / v2 / Build ui-v2 (push) Successful in 17s
Release / Scraper / Docker (push) Successful in 47s
Release / UI / Docker (push) Successful in 56s
CI / Scraper / Lint (pull_request) Successful in 7s
CI / Scraper / Test (pull_request) Successful in 8s
CI / UI / Build (pull_request) Successful in 16s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
Release / v2 / Docker / ui-v2 (push) Successful in 56s
Release / v2 / Test backend (push) Successful in 4m35s
iOS CI / Build (pull_request) Successful in 4m28s
Release / v2 / Docker / backend (push) Successful in 1m29s
Release / v2 / Docker / runner (push) Successful in 1m39s
iOS CI / Test (pull_request) Successful in 9m51s
- backend/: Go API server and runner binaries with PocketBase + MinIO storage - ui-v2/: SvelteKit frontend rewrite - docker-compose-new.yml: compose file for the v2 stack - .gitea/workflows/release-v2.yaml: CI/CD for backend, runner, and ui-v2 Docker Hub images - scripts/pb-init.sh: migrate from wget to curl, add superuser bootstrap for fresh installs - .env.example: document DOCKER_BUILDKIT=1 for Colima users
130 lines
3.2 KiB
Go
130 lines
3.2 KiB
Go
package novelfire
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
func TestSlugFromURL(t *testing.T) {
|
|
cases := []struct {
|
|
url string
|
|
want string
|
|
}{
|
|
{"https://novelfire.net/book/shadow-slave", "shadow-slave"},
|
|
{"https://novelfire.net/book/a-dragon-against-the-whole-world", "a-dragon-against-the-whole-world"},
|
|
{"https://novelfire.net/book/foo/chapter-1", "foo"},
|
|
{"https://novelfire.net/", ""},
|
|
{"not-a-url", "not-a-url"},
|
|
}
|
|
for _, c := range cases {
|
|
got := slugFromURL(c.url)
|
|
if got != c.want {
|
|
t.Errorf("slugFromURL(%q) = %q, want %q", c.url, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChapterNumberFromURL(t *testing.T) {
|
|
cases := []struct {
|
|
url string
|
|
want int
|
|
}{
|
|
{"https://novelfire.net/book/shadow-slave/chapter-42", 42},
|
|
{"https://novelfire.net/book/shadow-slave/chapter-1000", 1000},
|
|
{"https://novelfire.net/book/shadow-slave/chap-7", 7},
|
|
{"https://novelfire.net/book/shadow-slave/ch-3", 3},
|
|
{"https://novelfire.net/book/shadow-slave/42", 42},
|
|
{"https://novelfire.net/book/shadow-slave/no-number-here", 0},
|
|
{"not-a-url", 0},
|
|
}
|
|
for _, c := range cases {
|
|
got := chapterNumberFromURL(c.url)
|
|
if got != c.want {
|
|
t.Errorf("chapterNumberFromURL(%q) = %d, want %d", c.url, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseChapterCount(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want int
|
|
}{
|
|
{"123 Chapters", 123},
|
|
{"1,234 Chapters", 1234},
|
|
{"0", 0},
|
|
{"", 0},
|
|
{"500", 500},
|
|
}
|
|
for _, c := range cases {
|
|
got := parseChapterCount(c.in)
|
|
if got != c.want {
|
|
t.Errorf("parseChapterCount(%q) = %d, want %d", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRetryGet_ContextCancellation(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // cancel immediately
|
|
|
|
stub := newStubClient()
|
|
stub.setError("https://example.com/page", context.Canceled)
|
|
|
|
_, err := retryGet(ctx, nil, stub, "https://example.com/page", 3, 0)
|
|
if err == nil {
|
|
t.Fatal("expected error on cancelled context")
|
|
}
|
|
}
|
|
|
|
func TestRetryGet_EventualSuccess(t *testing.T) {
|
|
stub := newStubClient()
|
|
calls := 0
|
|
stub.setFn("https://example.com/page", func() (string, error) {
|
|
calls++
|
|
if calls < 3 {
|
|
return "", context.DeadlineExceeded
|
|
}
|
|
return "<html>ok</html>", nil
|
|
})
|
|
|
|
got, err := retryGet(context.Background(), nil, stub, "https://example.com/page", 5, 0)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got != "<html>ok</html>" {
|
|
t.Errorf("got %q, want html", got)
|
|
}
|
|
if calls != 3 {
|
|
t.Errorf("expected 3 calls, got %d", calls)
|
|
}
|
|
}
|
|
|
|
// ── minimal stub client for tests ─────────────────────────────────────────────
|
|
|
|
type stubClient struct {
|
|
errors map[string]error
|
|
fns map[string]func() (string, error)
|
|
}
|
|
|
|
func newStubClient() *stubClient {
|
|
return &stubClient{
|
|
errors: make(map[string]error),
|
|
fns: make(map[string]func() (string, error)),
|
|
}
|
|
}
|
|
|
|
func (s *stubClient) setError(u string, err error) { s.errors[u] = err }
|
|
|
|
func (s *stubClient) setFn(u string, fn func() (string, error)) { s.fns[u] = fn }
|
|
|
|
func (s *stubClient) GetContent(_ context.Context, pageURL string) (string, error) {
|
|
if fn, ok := s.fns[pageURL]; ok {
|
|
return fn()
|
|
}
|
|
if err, ok := s.errors[pageURL]; ok {
|
|
return "", err
|
|
}
|
|
return "", context.DeadlineExceeded
|
|
}
|