Files
pubcli/internal/display/formatter_test.go
teernisse 53d65c2148 Add cascading fallback for deal titles instead of hardcoded "Unknown"
Replace the simple nil-title → "Unknown" logic in printDeal with a
fallbackDealTitle() function that tries multiple fields in priority
order before giving up:

  1. Title (cleaned) — the happy path, same as before
  2. Brand + Department — e.g. "Publix deal (Meat)"
  3. Brand alone — e.g. "Publix deal"
  4. Department alone — e.g. "Meat deal"
  5. Description truncated to 48 chars — last-resort meaningful text
  6. Item ID — e.g. "Deal 12345"
  7. "Untitled deal" — only when every field is empty

This makes the output more useful for the ~5-10% of weekly ad items
that ship with a nil Title from the Publix API, which previously all
showed as "Unknown" and were indistinguishable from each other.

Tests:
- TestPrintDeals_FallbackTitleFromBrandAndDepartment
- TestPrintDeals_FallbackTitleFromID

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:11:50 -05:00

188 lines
5.0 KiB
Go

package display_test
import (
"bytes"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tayloree/publix-deals/internal/api"
"github.com/tayloree/publix-deals/internal/display"
)
func ptr(s string) *string { return &s }
func sampleDeals() []api.SavingItem {
return []api.SavingItem{
{
ID: "1",
Title: ptr("Chicken Breasts"),
Savings: ptr("$3.99 lb"),
Description: ptr("USDA Grade A, 97% Fat Free"),
Department: ptr("Meat"),
Brand: ptr("Publix"),
Categories: []string{"meat"},
AdditionalDealInfo: ptr("SAVE UP TO $1.00 LB"),
StartFormatted: "2/18",
EndFormatted: "2/24",
},
{
ID: "2",
Title: ptr("Nutella &amp; More"),
Savings: ptr("Buy 1 Get 1 FREE"),
Department: ptr("Grocery"),
Categories: []string{"bogo", "grocery"},
StartFormatted: "2/18",
EndFormatted: "2/24",
},
}
}
func TestPrintDeals_ContainsExpectedContent(t *testing.T) {
var buf bytes.Buffer
display.PrintDeals(&buf, sampleDeals())
output := buf.String()
assert.Contains(t, output, "Publix Weekly Deals")
assert.Contains(t, output, "2 items")
assert.Contains(t, output, "Chicken Breasts")
assert.Contains(t, output, "$3.99 lb")
assert.Contains(t, output, "SAVE UP TO $1.00 LB")
assert.Contains(t, output, "BOGO") // Nutella is bogo
// HTML entities should be unescaped
assert.Contains(t, output, "Nutella & More")
assert.NotContains(t, output, "&amp;")
}
func TestPrintDeals_FallbackTitleFromBrandAndDepartment(t *testing.T) {
items := []api.SavingItem{
{
ID: "fallback-1",
Title: nil,
Brand: ptr("Publix"),
Department: ptr("Meat"),
Categories: []string{"meat"},
},
}
var buf bytes.Buffer
display.PrintDeals(&buf, items)
output := buf.String()
assert.Contains(t, output, "Publix deal (Meat)")
assert.NotContains(t, output, "Unknown")
}
func TestPrintDeals_FallbackTitleFromID(t *testing.T) {
items := []api.SavingItem{
{
ID: "fallback-2",
Title: nil,
},
}
var buf bytes.Buffer
display.PrintDeals(&buf, items)
output := buf.String()
assert.Contains(t, output, "Deal fallback-2")
assert.NotContains(t, output, "Unknown")
}
func TestPrintDealsJSON(t *testing.T) {
var buf bytes.Buffer
err := display.PrintDealsJSON(&buf, sampleDeals())
require.NoError(t, err)
assert.NotContains(t, buf.String(), "\n ")
var deals []display.DealJSON
err = json.Unmarshal(buf.Bytes(), &deals)
require.NoError(t, err)
assert.Len(t, deals, 2)
assert.Equal(t, "Chicken Breasts", deals[0].Title)
assert.Equal(t, "$3.99 lb", deals[0].Savings)
assert.Equal(t, "Meat", deals[0].Department)
assert.False(t, deals[0].IsBogo)
// HTML entities should be clean in JSON too
assert.Equal(t, "Nutella & More", deals[1].Title)
assert.True(t, deals[1].IsBogo)
}
func TestPrintDealsJSON_NilFields(t *testing.T) {
items := []api.SavingItem{{ID: "nil-test"}}
var buf bytes.Buffer
err := display.PrintDealsJSON(&buf, items)
require.NoError(t, err)
var deals []display.DealJSON
err = json.Unmarshal(buf.Bytes(), &deals)
require.NoError(t, err)
assert.Len(t, deals, 1)
assert.Equal(t, "", deals[0].Title)
assert.NotNil(t, deals[0].Categories)
}
func TestPrintStores(t *testing.T) {
stores := []api.Store{
{Key: "01425", Name: "Peachers Mill", Addr: "1490 Tiny Town Rd", City: "Clarksville", State: "TN", Zip: "37042", Distance: "5"},
}
var buf bytes.Buffer
display.PrintStores(&buf, stores, "37042")
output := buf.String()
assert.Contains(t, output, "37042")
assert.Contains(t, output, "#1425")
assert.Contains(t, output, "Peachers Mill")
assert.Contains(t, output, "5 miles")
}
func TestPrintStoresJSON(t *testing.T) {
stores := []api.Store{
{Key: "01425", Name: "Peachers Mill", Addr: "1490 Tiny Town Rd", City: "Clarksville", State: "TN", Zip: "37042", Distance: "5"},
}
var buf bytes.Buffer
err := display.PrintStoresJSON(&buf, stores)
require.NoError(t, err)
assert.NotContains(t, buf.String(), "\n ")
var out []display.StoreJSON
err = json.Unmarshal(buf.Bytes(), &out)
require.NoError(t, err)
assert.Len(t, out, 1)
assert.Equal(t, "1425", out[0].Number)
assert.Equal(t, "Peachers Mill", out[0].Name)
assert.Contains(t, out[0].Address, "Clarksville")
}
func TestPrintCategories(t *testing.T) {
cats := map[string]int{"bogo": 10, "meat": 5, "produce": 3}
var buf bytes.Buffer
display.PrintCategories(&buf, cats, "1425")
output := buf.String()
assert.Contains(t, output, "1425")
assert.Contains(t, output, "bogo")
assert.Contains(t, output, "10 deals")
assert.Contains(t, output, "meat")
assert.Contains(t, output, "produce")
}
func TestPrintCategoriesJSON(t *testing.T) {
cats := map[string]int{"bogo": 10, "meat": 5}
var buf bytes.Buffer
err := display.PrintCategoriesJSON(&buf, cats)
require.NoError(t, err)
assert.NotContains(t, buf.String(), "\n ")
var out map[string]int
err = json.Unmarshal(buf.Bytes(), &out)
require.NoError(t, err)
assert.Equal(t, 10, out["bogo"])
assert.Equal(t, 5, out["meat"])
}