package pizzint import ( "context" "testing" "gitea.stevedudenhoeffer.com/steve/go-extractor/extractortest" ) const sampleAPIResponse = `{ "success": true, "overall_index": 42, "defcon_level": 3, "data": [ { "place_id": "abc123", "name": "DOMINO'S PIZZA", "address": "https://maps.google.com/test", "current_popularity": 15, "percentage_of_usual": null, "is_spike": false, "spike_magnitude": null, "data_source": "live", "data_freshness": "fresh", "is_closed_now": false }, { "place_id": "def456", "name": "EXTREME PIZZA", "address": "https://maps.google.com/test2", "current_popularity": 0, "percentage_of_usual": null, "is_spike": false, "spike_magnitude": null, "data_source": "live", "data_freshness": "stale", "is_closed_now": true }, { "place_id": "ghi789", "name": "PIZZATO PIZZA", "address": "https://maps.google.com/test3", "current_popularity": 85, "percentage_of_usual": 239, "is_spike": true, "spike_magnitude": "EXTREME", "data_source": "live", "data_freshness": "fresh", "is_closed_now": false } ], "events": [ { "name": "PIZZATO PIZZA", "minutes_ago": 5 } ] }` func TestExtractStatus(t *testing.T) { doc := &extractortest.MockDocument{ URLValue: dashboardAPIURL, MockNode: extractortest.MockNode{ TextValue: sampleAPIResponse, }, } status, err := extractStatus(doc) if err != nil { t.Fatalf("extractStatus returned error: %v", err) } if status.DoughconLevel != DoughconElevated { t.Errorf("DoughconLevel = %d, want %d", status.DoughconLevel, DoughconElevated) } if status.OverallIndex != 42 { t.Errorf("OverallIndex = %d, want 42", status.OverallIndex) } if status.DoughconLabel != "ELEVATED" { t.Errorf("DoughconLabel = %q, want %q", status.DoughconLabel, "ELEVATED") } if len(status.Restaurants) != 3 { t.Fatalf("len(Restaurants) = %d, want 3", len(status.Restaurants)) } // First restaurant: quiet r0 := status.Restaurants[0] if r0.Name != "DOMINO'S PIZZA" { t.Errorf("Restaurants[0].Name = %q, want %q", r0.Name, "DOMINO'S PIZZA") } if r0.Status() != "QUIET" { t.Errorf("Restaurants[0].Status() = %q, want %q", r0.Status(), "QUIET") } if r0.CurrentPopularity != 15 { t.Errorf("Restaurants[0].CurrentPopularity = %d, want 15", r0.CurrentPopularity) } // Second restaurant: closed r1 := status.Restaurants[1] if r1.Status() != "CLOSED" { t.Errorf("Restaurants[1].Status() = %q, want %q", r1.Status(), "CLOSED") } if !r1.IsClosed { t.Error("Restaurants[1].IsClosed = false, want true") } // Third restaurant: spike r2 := status.Restaurants[2] if r2.Status() != "239% SPIKE" { t.Errorf("Restaurants[2].Status() = %q, want %q", r2.Status(), "239% SPIKE") } if r2.SpikeMagnitude != "EXTREME" { t.Errorf("Restaurants[2].SpikeMagnitude = %q, want %q", r2.SpikeMagnitude, "EXTREME") } if r2.CurrentPopularity != 85 { t.Errorf("Restaurants[2].CurrentPopularity = %d, want 85", r2.CurrentPopularity) } // Events if len(status.Events) != 1 { t.Fatalf("len(Events) = %d, want 1", len(status.Events)) } if status.Events[0].Name != "PIZZATO PIZZA" { t.Errorf("Events[0].Name = %q, want %q", status.Events[0].Name, "PIZZATO PIZZA") } if status.Events[0].MinutesAgo != 5 { t.Errorf("Events[0].MinutesAgo = %d, want 5", status.Events[0].MinutesAgo) } } func TestExtractStatusFromHTML(t *testing.T) { // Simulate Chromium wrapping JSON in a
tag. doc.Text() returns // the InnerText which may include the JSON, but Content() returns the // raw HTML with the JSON inside. htmlWrapped := `` + sampleAPIResponse + `` doc := &extractortest.MockDocument{ URLValue: dashboardAPIURL, MockNode: extractortest.MockNode{ // Text() might fail or return garbage TextValue: "", // Content() returns the HTML ContentValue: htmlWrapped, }, } status, err := extractStatus(doc) if err != nil { t.Fatalf("extractStatus returned error: %v", err) } if status.DoughconLevel != DoughconElevated { t.Errorf("DoughconLevel = %d, want %d", status.DoughconLevel, DoughconElevated) } if len(status.Restaurants) != 3 { t.Errorf("len(Restaurants) = %d, want 3", len(status.Restaurants)) } } func TestExtractStatusFailure(t *testing.T) { doc := &extractortest.MockDocument{ URLValue: dashboardAPIURL, MockNode: extractortest.MockNode{ TextValue: `{"success": false}`, ContentValue: `{"success": false}`, }, } _, err := extractStatus(doc) if err == nil { t.Fatal("expected error for success=false response") } } func TestGetStatus(t *testing.T) { mock := &extractortest.MockBrowser{ Documents: map[string]*extractortest.MockDocument{ dashboardAPIURL: { URLValue: dashboardAPIURL, MockNode: extractortest.MockNode{ TextValue: sampleAPIResponse, }, }, }, } status, err := GetStatus(context.Background(), mock) if err != nil { t.Fatalf("GetStatus returned error: %v", err) } if status.OverallIndex != 42 { t.Errorf("OverallIndex = %d, want 42", status.OverallIndex) } } func TestFindJSON(t *testing.T) { tests := []struct { name string input string want string wantErr bool }{ { name: "plain JSON", input: `{"key": "value"}`, want: `{"key": "value"}`, }, { name: "JSON in HTML", input: `{"key": "value"}`, want: `{"key": "value"}`, }, { name: "nested braces", input: `{"a": {"b": "c"}}`, want: `{"a": {"b": "c"}}`, }, { name: "braces in strings", input: `{"a": "hello {world}"}`, want: `{"a": "hello {world}"}`, }, { name: "escaped quotes", input: `{"a": "he said \"hi\""}`, want: `{"a": "he said \"hi\""}`, }, { name: "no JSON", input: "just some text", wantErr: true, }, { name: "empty string", input: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := findJSON(tt.input) if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if got != tt.want { t.Errorf("findJSON() = %q, want %q", got, tt.want) } }) } } func TestDoughconLevelString(t *testing.T) { tests := []struct { level DoughconLevel want string }{ {DoughconQuiet, "DOUGHCON 5 - ALL QUIET"}, {DoughconWatch, "DOUGHCON 4 - DOUBLE TAKE"}, {DoughconElevated, "DOUGHCON 3 - ELEVATED"}, {DoughconHigh, "DOUGHCON 2 - HIGH ACTIVITY"}, {DoughconMaximum, "DOUGHCON 1 - MAXIMUM ALERT"}, {DoughconLevel(99), "DOUGHCON 99"}, } for _, tt := range tests { t.Run(tt.want, func(t *testing.T) { if got := tt.level.String(); got != tt.want { t.Errorf("String() = %q, want %q", got, tt.want) } }) } } func TestRestaurantStatus(t *testing.T) { pct := 150 tests := []struct { name string r Restaurant want string }{ {"quiet", Restaurant{Name: "Test"}, "QUIET"}, {"closed", Restaurant{IsClosed: true}, "CLOSED"}, {"spike with percent", Restaurant{IsSpike: true, PercentOfUsual: &pct}, "150% SPIKE"}, {"spike without percent", Restaurant{IsSpike: true}, "SPIKE"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.r.Status(); got != tt.want { t.Errorf("Status() = %q, want %q", got, tt.want) } }) } }