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
142 lines
3.9 KiB
Go
142 lines
3.9 KiB
Go
package browser_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/libnovel/backend/internal/browser"
|
|
)
|
|
|
|
func TestDirectClient_GetContent_Success(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("<html>hello</html>"))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := browser.NewDirectClient(browser.Config{MaxConcurrent: 2, Timeout: 5 * time.Second})
|
|
body, err := c.GetContent(context.Background(), srv.URL)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if body != "<html>hello</html>" {
|
|
t.Errorf("want <html>hello</html>, got %q", body)
|
|
}
|
|
}
|
|
|
|
func TestDirectClient_GetContent_4xxReturnsError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := browser.NewDirectClient(browser.Config{})
|
|
_, err := c.GetContent(context.Background(), srv.URL)
|
|
if err == nil {
|
|
t.Fatal("expected error for 404")
|
|
}
|
|
}
|
|
|
|
func TestDirectClient_SemaphoreBlocksConcurrency(t *testing.T) {
|
|
const maxConcurrent = 2
|
|
var inflight atomic.Int32
|
|
var peak atomic.Int32
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
n := inflight.Add(1)
|
|
if int(n) > int(peak.Load()) {
|
|
peak.Store(n)
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
inflight.Add(-1)
|
|
w.Write([]byte("ok"))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := browser.NewDirectClient(browser.Config{MaxConcurrent: maxConcurrent, Timeout: 5 * time.Second})
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 8; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
c.GetContent(context.Background(), srv.URL)
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
if int(peak.Load()) > maxConcurrent {
|
|
t.Errorf("concurrent requests exceeded limit: peak=%d, limit=%d", peak.Load(), maxConcurrent)
|
|
}
|
|
}
|
|
|
|
func TestDirectClient_ContextCancel(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(200 * time.Millisecond)
|
|
w.Write([]byte("ok"))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // cancel before making the request
|
|
|
|
c := browser.NewDirectClient(browser.Config{})
|
|
_, err := c.GetContent(ctx, srv.URL)
|
|
if err == nil {
|
|
t.Fatal("expected context cancellation error")
|
|
}
|
|
}
|
|
|
|
// ── StubClient ────────────────────────────────────────────────────────────────
|
|
|
|
func TestStubClient_ReturnsRegisteredPage(t *testing.T) {
|
|
stub := browser.NewStub()
|
|
stub.SetPage("http://example.com/page1", "<html>page1</html>")
|
|
|
|
body, err := stub.GetContent(context.Background(), "http://example.com/page1")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if body != "<html>page1</html>" {
|
|
t.Errorf("want page1 html, got %q", body)
|
|
}
|
|
}
|
|
|
|
func TestStubClient_ReturnsRegisteredError(t *testing.T) {
|
|
stub := browser.NewStub()
|
|
want := errors.New("network failure")
|
|
stub.SetError("http://example.com/bad", want)
|
|
|
|
_, err := stub.GetContent(context.Background(), "http://example.com/bad")
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
}
|
|
|
|
func TestStubClient_UnknownURLReturnsError(t *testing.T) {
|
|
stub := browser.NewStub()
|
|
_, err := stub.GetContent(context.Background(), "http://unknown.example.com/")
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown URL")
|
|
}
|
|
}
|
|
|
|
func TestStubClient_CallLog(t *testing.T) {
|
|
stub := browser.NewStub()
|
|
stub.SetPage("http://example.com/a", "a")
|
|
stub.SetPage("http://example.com/b", "b")
|
|
|
|
stub.GetContent(context.Background(), "http://example.com/a")
|
|
stub.GetContent(context.Background(), "http://example.com/b")
|
|
|
|
log := stub.CallLog()
|
|
if len(log) != 2 || log[0] != "http://example.com/a" || log[1] != "http://example.com/b" {
|
|
t.Errorf("unexpected call log: %v", log)
|
|
}
|
|
}
|