Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/aklapi/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ func rubbish(r *http.Request) (*aklapi.CollectionDayDetailResult, error) {
func addrHandler(w http.ResponseWriter, r *http.Request) {
addr := r.FormValue("addr")
if addr == "" {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
respond(w, rrResponse{Error: http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
return
}
resp, err := addressLookup(r.Context(), addr)
if err != nil {
slog.Error("address lookup failed", "error", err)
http.Error(w, err.Error(), http.StatusBadGateway)
respond(w, rrResponse{Error: err.Error()}, http.StatusBadGateway)
return
}
respond(w, resp, http.StatusOK)
Expand Down
33 changes: 33 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build integration

package aklapi

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestLiveSite_CollectionDayDetail hits the live Auckland Council website and
// verifies that the HTML scraper still returns usable data. Run with:
//
// go test -tags integration -run TestLiveSite
//
// Useful as a periodic smoke test to catch council website redesigns early.
func TestLiveSite_CollectionDayDetail(t *testing.T) {
NoCache = true
t.Cleanup(func() { NoCache = false })

// 500 Queen Street, Auckland Central — a stable, well-known address.
const knownAddressID = "12342478585"

res, err := fetchandparse(context.Background(), knownAddressID)
require.NoError(t, err, "fetchandparse failed — council page structure may have changed")
assert.NotEmpty(t, res.Collections, "expected at least one collection entry")
for _, c := range res.Collections {
assert.NotEmpty(t, c.Day, "collection entry has empty Day")
assert.False(t, c.Date.IsZero(), "collection entry has zero Date")
}
}
31 changes: 23 additions & 8 deletions rubbish.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ var NoCache = false

const (
dateLayout = "Monday, 2 January"

// CSS selectors and text constants for the Auckland Council rubbish page.
// Update these if the council website redesigns its HTML structure.
selScheduleCard = ".acpl-schedule-card"
selCardTitle = "h4.card-title"
selCollectionItem = "p.mb-0.lead span.acpl-icon-with-attribute.left"
selIcon = "i.acpl-icon"
cardTitleHousehold = "Household collection"
iconRubbish = "rubbish"
iconRecycle = "recycle"
iconFoodWaste = "food-waste"
)

var (
Expand Down Expand Up @@ -162,15 +173,19 @@ func (p *refuseParser) parse(r io.Reader) ([]RubbishCollection, error) {
return nil, err
}

doc.Find(scheduledCardSelector).Each(func(i int, card *goquery.Selection) {
doc.Find(selScheduleCard).Each(func(i int, card *goquery.Selection) {
// Only parse Household collection card for now, not Commercial
cardTitle := strings.TrimSpace(card.Find("h4.card-title").Text())
if !strings.Contains(cardTitle, "Household collection") {
cardTitle := strings.TrimSpace(card.Find(selCardTitle).Text())
if !strings.Contains(cardTitle, cardTitleHousehold) {
return
}
p.parseScheduleCard(card)
})

if len(p.detail) == 0 {
return nil, errors.New("no collection data found: page structure may have changed")
}

for i := range p.detail {
if err := (&p.detail[i]).parseDate(); err != nil {
if err == errSkip {
Expand All @@ -189,19 +204,19 @@ func (p *refuseParser) parse(r io.Reader) ([]RubbishCollection, error) {
// parseScheduleCard parses a schedule card and extracts collection information.
func (p *refuseParser) parseScheduleCard(card *goquery.Selection) {
// Each collection line is a <p class="mb-0 lead"> containing a span.acpl-icon-with-attribute.left
card.Find("p.mb-0.lead span.acpl-icon-with-attribute.left").Each(func(i int, span *goquery.Selection) {
icon := span.Find("i.acpl-icon")
card.Find(selCollectionItem).Each(func(i int, span *goquery.Selection) {
icon := span.Find(selIcon)
date := strings.TrimSpace(span.Find("b").First().Text())
if date == "" { // skip empty (e.g. food scraps absent)
return
}
var rc RubbishCollection
rc.Day = date
if icon.HasClass("rubbish") {
if icon.HasClass(iconRubbish) {
rc.Rubbish = true
} else if icon.HasClass("recycle") {
} else if icon.HasClass(iconRecycle) {
rc.Recycle = true
} else if icon.HasClass("food-waste") {
} else if icon.HasClass(iconFoodWaste) {
rc.FoodScraps = true
} else {
// Unknown icon type; ignore
Expand Down
44 changes: 44 additions & 0 deletions rubbish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,50 @@ func TestCollectionDayDetailResult_NextRecycle(t *testing.T) {
}
}

func TestCollectionDayDetailResult_NextFoodScraps(t *testing.T) {
type fields struct {
Collections []RubbishCollection
Address *Address
}
tests := []struct {
name string
fields fields
want time.Time
}{
{"found",
fields{
Collections: []RubbishCollection{
{Day: "Someday",
Date: time.Date(2000, 2, 1, 0, 0, 0, 0, defaultLoc),
Rubbish: true,
Recycle: false,
FoodScraps: false,
},
{Day: "This is the day",
Date: time.Date(1991, 9, 16, 0, 0, 0, 0, defaultLoc),
Rubbish: false,
Recycle: false,
FoodScraps: true,
},
},
},
time.Date(1991, 9, 16, 0, 0, 0, 0, defaultLoc),
},
{"not found", fields{Collections: []RubbishCollection{}}, time.Time{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := &CollectionDayDetailResult{
Collections: tt.fields.Collections,
Address: tt.fields.Address,
}
if got := res.NextFoodScraps(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("CollectionDayDetailResult.NextFoodScraps() = %v, want %v", got, tt.want)
}
})
}
}

func TestRubbishCollection_parseDate(t *testing.T) {
type fields struct {
Day string
Expand Down
2 changes: 1 addition & 1 deletion time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Test_adjustYear(t *testing.T) {
args{mustTime(time.Parse(dttmFmt, "0000-04-07 00:00:00"))},
mustTime(time.Parse(dttmFmt, fmt.Sprintf("%d-04-07 00:00:00", time.Now().Year()))),
},
{"differnt days",
{"different days",
func() time.Time {
t, _ := time.Parse(dttmFmt, fmt.Sprintf("%d-09-15 20:00:00", time.Now().Year()))
return t
Expand Down