Files
weatherapi/internal/app/service_test.go
Eric Rakestraw ecea856e8e
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
Implemented weather stories support
2026-05-30 07:06:29 -05:00

173 lines
5.1 KiB
Go

// service_test.go validates application service delegation behavior.
// Layer: internal/app tests for read use-case orchestration.
package app
import (
"context"
"errors"
"testing"
"gitea.maximumdirect.net/ejr/weatherfeeder/model"
)
type fakeRepository struct {
observation *model.WeatherObservation
forecast *model.WeatherForecastRun
narrative *model.WeatherForecastRun
discussion *model.WeatherForecastDiscussion
storyRun *model.WeatherStoryRun
story *model.WeatherStory
alerts *model.WeatherAlertRun
conditions *CurrentConditions
err error
currentConditionsWindow int
}
func (r *fakeRepository) LatestObservation(context.Context) (*model.WeatherObservation, error) {
return r.observation, r.err
}
func (r *fakeRepository) LatestHourlyForecast(context.Context) (*model.WeatherForecastRun, error) {
return r.forecast, r.err
}
func (r *fakeRepository) LatestNarrativeForecast(context.Context) (*model.WeatherForecastRun, error) {
return r.narrative, r.err
}
func (r *fakeRepository) LatestForecastDiscussion(context.Context) (*model.WeatherForecastDiscussion, error) {
return r.discussion, r.err
}
func (r *fakeRepository) LatestWeatherStoryRun(context.Context) (*model.WeatherStoryRun, error) {
return r.storyRun, r.err
}
func (r *fakeRepository) LatestWeatherStory(context.Context) (*model.WeatherStory, error) {
return r.story, r.err
}
func (r *fakeRepository) LatestAlertRun(context.Context) (*model.WeatherAlertRun, error) {
return r.alerts, r.err
}
func (r *fakeRepository) CurrentConditions(_ context.Context, observationWindowMinutes int) (*CurrentConditions, error) {
r.currentConditionsWindow = observationWindowMinutes
return r.conditions, r.err
}
func TestServiceDelegatesObservation(t *testing.T) {
repo := &fakeRepository{observation: &model.WeatherObservation{StationID: "KSTL"}}
svc := NewService(repo)
obs, err := svc.LatestObservation(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obs == nil || obs.StationID != "KSTL" {
t.Fatalf("unexpected observation: %+v", obs)
}
}
func TestServiceDelegatesForecast(t *testing.T) {
repo := &fakeRepository{forecast: &model.WeatherForecastRun{LocationID: "stl"}}
svc := NewService(repo)
run, err := svc.LatestHourlyForecast(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if run == nil || run.LocationID != "stl" {
t.Fatalf("unexpected forecast: %+v", run)
}
}
func TestServiceDelegatesNarrativeForecast(t *testing.T) {
repo := &fakeRepository{narrative: &model.WeatherForecastRun{LocationID: "stl-narrative"}}
svc := NewService(repo)
run, err := svc.LatestNarrativeForecast(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if run == nil || run.LocationID != "stl-narrative" {
t.Fatalf("unexpected forecast: %+v", run)
}
}
func TestServiceDelegatesAlerts(t *testing.T) {
repo := &fakeRepository{alerts: &model.WeatherAlertRun{LocationID: "stl"}}
svc := NewService(repo)
run, err := svc.LatestAlertRun(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if run == nil || run.LocationID != "stl" {
t.Fatalf("unexpected alert run: %+v", run)
}
}
func TestServiceDelegatesForecastDiscussion(t *testing.T) {
repo := &fakeRepository{discussion: &model.WeatherForecastDiscussion{OfficeID: "LSX"}}
svc := NewService(repo)
run, err := svc.LatestForecastDiscussion(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if run == nil || run.OfficeID != "LSX" {
t.Fatalf("unexpected forecast discussion: %+v", run)
}
}
func TestServiceDelegatesWeatherStoryRun(t *testing.T) {
repo := &fakeRepository{storyRun: &model.WeatherStoryRun{OfficeID: "LSX"}}
svc := NewService(repo)
run, err := svc.LatestWeatherStoryRun(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if run == nil || run.OfficeID != "LSX" {
t.Fatalf("unexpected weather story run: %+v", run)
}
}
func TestServiceDelegatesWeatherStory(t *testing.T) {
repo := &fakeRepository{story: &model.WeatherStory{OfficeID: "LSX", Title: "Rain chances"}}
svc := NewService(repo)
story, err := svc.LatestWeatherStory(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if story == nil || story.Title != "Rain chances" {
t.Fatalf("unexpected weather story: %+v", story)
}
}
func TestServiceUsesDefaultCurrentConditionsWindow(t *testing.T) {
repo := &fakeRepository{conditions: &CurrentConditions{ConditionCode: model.WMOUnknown}}
svc := NewService(repo)
_, err := svc.CurrentConditions(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if repo.currentConditionsWindow != ObservationWindowMinutesDefault {
t.Fatalf("expected observation window %d, got %d", ObservationWindowMinutesDefault, repo.currentConditionsWindow)
}
}
func TestServicePropagatesErrors(t *testing.T) {
want := errors.New("boom")
repo := &fakeRepository{err: want}
svc := NewService(repo)
if _, err := svc.LatestObservation(context.Background()); !errors.Is(err, want) {
t.Fatalf("expected error %v, got %v", want, err)
}
}