112 lines
4.2 KiB
Go
112 lines
4.2 KiB
Go
package collect
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
|
)
|
|
|
|
func TestRunFetchesBundle(t *testing.T) {
|
|
var currentRequests atomic.Int32
|
|
server := collectionTestServer(t, nil, func(r *http.Request) {
|
|
if r.URL.Path == "/conditions/current" {
|
|
currentRequests.Add(1)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
cfg := config.Defaults()
|
|
cfg.WeatherAPI.BaseURL = server.URL + "/"
|
|
|
|
result, err := Run(context.Background(), Request{Config: cfg})
|
|
if err != nil {
|
|
t.Fatalf("Run() error = %v", err)
|
|
}
|
|
if result == nil || result.Bundle == nil {
|
|
t.Fatal("Run() result bundle = nil, want fetched bundle")
|
|
}
|
|
if result.Bundle.Hourly == nil {
|
|
t.Fatal("Hourly = nil, want fetched hourly forecast")
|
|
}
|
|
if result.Bundle.WeatherStory == nil || result.Bundle.WeatherStory.Title != "Several Chances for Rain Through Monday" {
|
|
t.Fatalf("WeatherStory = %#v, want fetched weather story", result.Bundle.WeatherStory)
|
|
}
|
|
if got := currentRequests.Load(); got != 1 {
|
|
t.Fatalf("conditions/current requests = %d, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestRunWrapsAdapterConstructionError(t *testing.T) {
|
|
cfg := config.Defaults()
|
|
cfg.WeatherAPI.BaseURL = ""
|
|
cfg.Secrets.Directory = "super-secret-directory"
|
|
|
|
_, err := Run(context.Background(), Request{Config: cfg})
|
|
if err == nil {
|
|
t.Fatal("Run() error = nil, want adapter construction error")
|
|
}
|
|
if !strings.Contains(err.Error(), "prepare weather collection") {
|
|
t.Fatalf("error = %q, want collection setup context", err.Error())
|
|
}
|
|
if strings.Contains(err.Error(), cfg.Secrets.Directory) {
|
|
t.Fatalf("error = %q, want no secret path leakage", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestRunWrapsFetchError(t *testing.T) {
|
|
server := collectionTestServer(t, map[string]int{"/observations": http.StatusBadRequest}, nil)
|
|
defer server.Close()
|
|
|
|
cfg := config.Defaults()
|
|
cfg.WeatherAPI.BaseURL = server.URL + "/"
|
|
|
|
_, err := Run(context.Background(), Request{Config: cfg})
|
|
if err == nil {
|
|
t.Fatal("Run() error = nil, want fetch error")
|
|
}
|
|
if !strings.Contains(err.Error(), "collect weather bundle") {
|
|
t.Fatalf("error = %q, want collection fetch context", err.Error())
|
|
}
|
|
if !strings.Contains(err.Error(), "/observations") {
|
|
t.Fatalf("error = %q, want source endpoint context", err.Error())
|
|
}
|
|
}
|
|
|
|
func collectionTestServer(t *testing.T, statusByPath map[string]int, onRequest func(*http.Request)) *httptest.Server {
|
|
t.Helper()
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if onRequest != nil {
|
|
onRequest(r)
|
|
}
|
|
if status := statusByPath[r.URL.Path]; status != 0 {
|
|
http.Error(w, "upstream failure", status)
|
|
return
|
|
}
|
|
switch r.URL.Path {
|
|
case "/observations":
|
|
_, _ = w.Write([]byte(`{"data":{"timestamp":"2026-05-29T14:00:00Z","conditionCode":3}}`))
|
|
case "/conditions/current":
|
|
_, _ = w.Write([]byte(`{"data":{"conditionText":"Clear"}}`))
|
|
case "/forecast/hourly":
|
|
_, _ = w.Write([]byte(`{"data":{"issuedAt":"2026-05-29T10:30:00-05:00","product":"hourly","periods":[{"startTime":"2026-05-29T13:00:00-05:00","endTime":"2026-05-29T14:00:00-05:00"}]}}`))
|
|
case "/forecast/narrative":
|
|
_, _ = w.Write([]byte(`{"data":{"issuedAt":"2026-05-29T10:30:00-05:00","product":"narrative","periods":[]}}`))
|
|
case "/alerts/active":
|
|
_, _ = w.Write([]byte(`{"data":{"alerts":[]}}`))
|
|
case "/discussion":
|
|
_, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T09:25:00-05:00","keyMessages":[],"shortTerm":{"qualifier":"(Short Term)","text":"Short-term AFD narrative for saved bundle."},"longTerm":{"qualifier":"(Long Term)","text":"Long-term AFD narrative for saved bundle."}}}`))
|
|
case "/weatherstories/latest":
|
|
_, _ = w.Write([]byte(`{"data":{"officeId":"LSX","startTime":"2026-05-30T08:46:00Z","endTime":"2026-05-31T11:00:00Z","updatedAt":"2026-05-30T09:00:34Z","title":"Several Chances for Rain Through Monday","description":"Scattered showers and thunderstorms remain possible.","altText":"Forecast weather story graphic.","priority":false,"order":1,"downloadUrl":"https://api.weather.gov/offices/LSX/weatherstories/download/test"}}`))
|
|
case "/outlooks/convective":
|
|
_, _ = w.Write([]byte(`{"data":{"asOf":"2026-05-29T16:00:00Z","outlooks":[],"discussions":[]}}`))
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
}
|