Add weather.go with GetWeather() for extracting structured weather data (location, temp, conditions, forecast) and stock.go with GetStockQuote() and GetStockChart() for stock data extraction and chart screenshots. Both include mock-based tests. CSS selectors may need tuning against the live site since DuckDuckGo's React-rendered widgets use dynamic class names. Closes #25, #26 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
99 lines
2.4 KiB
Go
99 lines
2.4 KiB
Go
package duckduckgo
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/go-extractor"
|
|
"gitea.stevedudenhoeffer.com/steve/go-extractor/extractortest"
|
|
)
|
|
|
|
func makeStockDoc() *extractortest.MockDocument {
|
|
return &extractortest.MockDocument{
|
|
URLValue: "https://duckduckgo.com/?q=aapl+stock",
|
|
MockNode: extractortest.MockNode{
|
|
Children: map[string]extractor.Nodes{
|
|
"div.module--stocks .module__title__link": {
|
|
&extractortest.MockNode{TextValue: "AAPL"},
|
|
},
|
|
"div.module--stocks .module__subtitle": {
|
|
&extractortest.MockNode{TextValue: "Apple Inc."},
|
|
},
|
|
"div.module--stocks .module__price": {
|
|
&extractortest.MockNode{TextValue: "$189.84"},
|
|
},
|
|
"div.module--stocks .module__price-change": {
|
|
&extractortest.MockNode{TextValue: "+2.45"},
|
|
},
|
|
"div.module--stocks .module__price-change-pct": {
|
|
&extractortest.MockNode{TextValue: "(1.31%)"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestExtractStock(t *testing.T) {
|
|
doc := makeStockDoc()
|
|
|
|
data, err := extractStock(doc)
|
|
if err != nil {
|
|
t.Fatalf("extractStock() error: %v", err)
|
|
}
|
|
|
|
if data.Symbol != "AAPL" {
|
|
t.Errorf("Symbol = %q, want %q", data.Symbol, "AAPL")
|
|
}
|
|
if data.Name != "Apple Inc." {
|
|
t.Errorf("Name = %q, want %q", data.Name, "Apple Inc.")
|
|
}
|
|
if data.Price != 189.84 {
|
|
t.Errorf("Price = %v, want 189.84", data.Price)
|
|
}
|
|
if data.Change != 2.45 {
|
|
t.Errorf("Change = %v, want 2.45", data.Change)
|
|
}
|
|
if data.ChangePct != 1.31 {
|
|
t.Errorf("ChangePct = %v, want 1.31", data.ChangePct)
|
|
}
|
|
}
|
|
|
|
func TestGetStockQuote_MockBrowser(t *testing.T) {
|
|
doc := makeStockDoc()
|
|
|
|
browser := &extractortest.MockBrowser{
|
|
Documents: map[string]*extractortest.MockDocument{
|
|
"https://duckduckgo.com/?kp=-2&q=aapl+stock": doc,
|
|
},
|
|
}
|
|
|
|
data, err := DefaultConfig.GetStockQuote(context.Background(), browser, "aapl")
|
|
if err != nil {
|
|
t.Fatalf("GetStockQuote() error: %v", err)
|
|
}
|
|
|
|
if data.Symbol != "AAPL" {
|
|
t.Errorf("Symbol = %q, want %q", data.Symbol, "AAPL")
|
|
}
|
|
if data.Price != 189.84 {
|
|
t.Errorf("Price = %v, want 189.84", data.Price)
|
|
}
|
|
}
|
|
|
|
func TestExtractStock_Empty(t *testing.T) {
|
|
doc := &extractortest.MockDocument{
|
|
MockNode: extractortest.MockNode{
|
|
Children: map[string]extractor.Nodes{},
|
|
},
|
|
}
|
|
|
|
data, err := extractStock(doc)
|
|
if err != nil {
|
|
t.Fatalf("extractStock() error: %v", err)
|
|
}
|
|
|
|
if data.Symbol != "" || data.Price != 0 {
|
|
t.Error("expected zero values for empty doc")
|
|
}
|
|
}
|