Create exported extractortest package with MockBrowser, MockDocument, and MockNode that support selector-based responses for testing site extractors without a real browser. Add extraction tests for DuckDuckGo (result parsing, empty results, no links, full search flow) and Powerball (drawing parsing, next drawing parsing with billion/million, error cases, full GetCurrent flow). Closes #21 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
// Package extractortest provides mock implementations of the extractor
|
|
// interfaces for use in unit tests. The mocks support selector-based
|
|
// responses so site extractors can exercise their parsing logic without
|
|
// a real browser.
|
|
package extractortest
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/go-extractor"
|
|
)
|
|
|
|
// MockNode implements extractor.Node with configurable selector responses.
|
|
type MockNode struct {
|
|
TextValue string
|
|
TextErr error
|
|
ContentValue string
|
|
Attrs map[string]string
|
|
// Children maps CSS selectors to the nodes that should be returned.
|
|
Children map[string]extractor.Nodes
|
|
}
|
|
|
|
var _ extractor.Node = &MockNode{}
|
|
|
|
func (m *MockNode) Content() (string, error) { return m.ContentValue, nil }
|
|
func (m *MockNode) Text() (string, error) { return m.TextValue, m.TextErr }
|
|
|
|
func (m *MockNode) Attr(name string) (string, error) {
|
|
if m.Attrs == nil {
|
|
return "", nil
|
|
}
|
|
v, ok := m.Attrs[name]
|
|
if !ok {
|
|
return "", nil
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
func (m *MockNode) Screenshot() ([]byte, error) { return nil, nil }
|
|
func (m *MockNode) Type(_ string) error { return nil }
|
|
func (m *MockNode) Click() error { return nil }
|
|
func (m *MockNode) SetHidden(_ bool) error { return nil }
|
|
func (m *MockNode) SetAttribute(_, _ string) error { return nil }
|
|
|
|
func (m *MockNode) Select(selector string) extractor.Nodes {
|
|
if m.Children == nil {
|
|
return nil
|
|
}
|
|
return m.Children[selector]
|
|
}
|
|
|
|
func (m *MockNode) SelectFirst(selector string) extractor.Node {
|
|
return m.Select(selector).First()
|
|
}
|
|
|
|
func (m *MockNode) ForEach(selector string, fn func(extractor.Node) error) error {
|
|
nodes := m.Select(selector)
|
|
for _, n := range nodes {
|
|
if err := fn(n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MockDocument implements extractor.Document with configurable selector responses.
|
|
type MockDocument struct {
|
|
MockNode
|
|
URLValue string
|
|
}
|
|
|
|
var _ extractor.Document = &MockDocument{}
|
|
|
|
func (m *MockDocument) URL() string { return m.URLValue }
|
|
func (m *MockDocument) Refresh() error { return nil }
|
|
func (m *MockDocument) Close() error { return nil }
|
|
func (m *MockDocument) WaitForNetworkIdle(_ *time.Duration) error { return nil }
|
|
|
|
// Content on MockDocument returns its own ContentValue (overrides embedded MockNode).
|
|
func (m *MockDocument) Content() (string, error) { return m.ContentValue, nil }
|
|
|
|
// MockBrowser implements extractor.Browser. Configure Documents to map URLs
|
|
// to MockDocuments that will be returned from Open.
|
|
type MockBrowser struct {
|
|
Documents map[string]*MockDocument
|
|
}
|
|
|
|
var _ extractor.Browser = &MockBrowser{}
|
|
|
|
func (m *MockBrowser) Close() error { return nil }
|
|
|
|
func (m *MockBrowser) Open(_ context.Context, url string, _ extractor.OpenPageOptions) (extractor.Document, error) {
|
|
if m.Documents == nil {
|
|
return nil, fmt.Errorf("no documents configured")
|
|
}
|
|
doc, ok := m.Documents[url]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no mock document for URL %q", url)
|
|
}
|
|
return doc, nil
|
|
}
|