Skip to content

Commit 46d58ba

Browse files
authored
Merge pull request #25 from rusq/tests
tests, deps bump, Makefile fix
2 parents 5ac1439 + 7e5c30c commit 46d58ba

10 files changed

Lines changed: 89 additions & 14 deletions

File tree

.github/workflows/go.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ jobs:
1414
build:
1515
runs-on: ubuntu-latest
1616
steps:
17-
- uses: actions/checkout@v4
17+
- uses: actions/checkout@v6
1818

1919
- name: Set up Go
20-
uses: actions/setup-go@v5
20+
uses: actions/setup-go@v6
2121
with:
2222
go-version: 1.25
2323

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ aklrubbish
22
*~
33
.vscode
44
.idea
5+
.env
56

67
# binary files
78
aklapi

AGENTS.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# AGENTS.md
2+
3+
## Project Snapshot
4+
- Module: `github.com/rusq/aklapi`
5+
- Language: Go
6+
- Purpose: unofficial Auckland Council API wrapper/service for address lookup and rubbish/recycling collection dates.
7+
8+
## Repository Map
9+
- `cmd/`: executable entrypoints.
10+
- `addr.go`: address lookup logic.
11+
- `rubbish.go`: rubbish/recycling API logic and response shaping.
12+
- `caches.go`: cache helpers.
13+
- `time.go`: date/time helpers.
14+
- `*_test.go`: unit tests.
15+
- `test_assets/`: fixtures used by tests.
16+
17+
## Development Commands
18+
- Run tests: `go test ./...`
19+
- Run focused tests: `go test ./... -run <Name>`
20+
- Tidy dependencies: `go mod tidy`
21+
- Build all packages: `go build ./...`
22+
23+
## Working Conventions
24+
- Prefer small, targeted changes over broad refactors.
25+
- Keep API behavior backward compatible unless explicitly requested.
26+
- Add or update tests for behavioral changes.
27+
- Keep exported identifiers and package-level docs concise.
28+
29+
## Validation Checklist
30+
Before finishing a code change, run:
31+
1. `go test ./...`
32+
2. `go build ./...`
33+
34+
If a change only affects docs or comments, note that tests/build were not required.

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ SHELL=/bin/sh
22

33
IMAGE=aklapi
44

5-
SRC=main.go $(wildcard aklapi/*.go)
5+
SRC=$(wildcard aklapi/*.go)
6+
PKG=./cmd/aklapi
67

78
server: $(SRC)
8-
go build -o $@
9+
go build -o $@ $(PKG)
10+
11+
test:
12+
go test ./... -race
13+
.PHONY: test
914

1015
docker:
1116
docker build -t $(IMAGE) .

addr.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
var (
1414
// defined as a variable so it can be overridden in tests.
1515
addrURI = `https://www.aucklandcouncil.govt.nz/nextapi/property`
16+
// defined as a variable so tests can replace it.
17+
addrHTTPClient = &http.Client{Timeout: 15 * time.Second}
1618
)
1719

1820
// AddrRequest is the address request.
@@ -61,8 +63,7 @@ func MatchingPropertyAddresses(ctx context.Context, addrReq *AddrRequest) (*Addr
6163
req.URL.RawQuery = q.Encode()
6264

6365
start := time.Now()
64-
client := &http.Client{}
65-
resp, err := client.Do(req)
66+
resp, err := addrHTTPClient.Do(req)
6667
if err != nil {
6768
return nil, err
6869
}

cmd/aklapi/handlers.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import (
1212

1313
const dttmLayout = "2006-01-02"
1414

15+
var (
16+
addressLookup = aklapi.AddressLookup
17+
collectionDayDetail = aklapi.CollectionDayDetail
18+
)
19+
1520
type rrResponse struct {
1621
Rubbish string `json:"rubbish,omitempty"`
1722
Recycle string `json:"recycle,omitempty"`
@@ -24,6 +29,7 @@ func respond(w http.ResponseWriter, data any, code int) {
2429
b, err := json.Marshal(data)
2530
if err != nil {
2631
http.Error(w, err.Error(), http.StatusInternalServerError)
32+
return
2733
}
2834
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
2935
w.WriteHeader(code)
@@ -35,7 +41,7 @@ func rubbish(r *http.Request) (*aklapi.CollectionDayDetailResult, error) {
3541
if addr == "" {
3642
return nil, errors.New(http.StatusText(http.StatusBadRequest))
3743
}
38-
return aklapi.CollectionDayDetail(r.Context(), addr)
44+
return collectionDayDetail(r.Context(), addr)
3945
}
4046

4147
func addrHandler(w http.ResponseWriter, r *http.Request) {
@@ -44,10 +50,10 @@ func addrHandler(w http.ResponseWriter, r *http.Request) {
4450
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
4551
return
4652
}
47-
resp, err := aklapi.AddressLookup(r.Context(), addr)
53+
resp, err := addressLookup(r.Context(), addr)
4854
if err != nil {
4955
slog.Error("address lookup failed", "error", err)
50-
http.NotFound(w, r)
56+
http.Error(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
5157
return
5258
}
5359
respond(w, resp, http.StatusOK)

go.mod

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module github.com/rusq/aklapi
22

3-
go 1.24.0
4-
5-
toolchain go1.24.2
3+
go 1.25.0
64

75
require (
86
github.com/PuerkitoBio/goquery v1.11.0
@@ -15,6 +13,6 @@ require (
1513
github.com/andybalholm/cascadia v1.3.3 // indirect
1614
github.com/davecgh/go-spew v1.1.1 // indirect
1715
github.com/pmezard/go-difflib v1.0.0 // indirect
18-
golang.org/x/net v0.48.0 // indirect
16+
golang.org/x/net v0.51.0 // indirect
1917
gopkg.in/yaml.v3 v3.0.1 // indirect
2018
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
4646
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
4747
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
4848
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
49+
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
50+
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
4951
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
5052
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
5153
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

rubbish.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const (
2323
var (
2424
// defined as a variable so it can be overridden in tests.
2525
collectionDayURI = `https://new.aucklandcouncil.govt.nz/en/rubbish-recycling/rubbish-recycling-collections/rubbish-recycling-collection-days/%s.html`
26+
// defined as a variable so tests can replace it.
27+
collectionHTTPClient = &http.Client{Timeout: 15 * time.Second}
2628
)
2729

2830
var errSkip = errors.New("skip this date")
@@ -118,11 +120,14 @@ func fetchandparse(ctx context.Context, addressID string) (*CollectionDayDetailR
118120
if err != nil {
119121
return nil, err
120122
}
121-
resp, err := http.DefaultClient.Do(req)
123+
resp, err := collectionHTTPClient.Do(req)
122124
if err != nil {
123125
return nil, err
124126
}
125127
defer resp.Body.Close()
128+
if resp.StatusCode != http.StatusOK {
129+
return nil, fmt.Errorf("collection API returned status code: %d", resp.StatusCode)
130+
}
126131
return parse(resp.Body)
127132
}
128133

rubbish_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,29 @@ func TestCollectionDayDetail(t *testing.T) {
163163
}
164164
}
165165

166+
func TestFetchAndParse_StatusCodeError(t *testing.T) {
167+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
168+
w.WriteHeader(http.StatusServiceUnavailable)
169+
w.Write([]byte("temporary error"))
170+
}))
171+
defer srv.Close()
172+
173+
oldURI := collectionDayURI
174+
oldClient := collectionHTTPClient
175+
defer func() {
176+
collectionDayURI = oldURI
177+
collectionHTTPClient = oldClient
178+
}()
179+
180+
collectionDayURI = srv.URL + "/rubbish/%s"
181+
collectionHTTPClient = srv.Client()
182+
183+
_, err := fetchandparse(t.Context(), "42")
184+
if err == nil {
185+
t.Fatal("expected error, got nil")
186+
}
187+
}
188+
166189
func TestCollectionDayDetailResult_NextRubbish(t *testing.T) {
167190
type fields struct {
168191
Collections []RubbishCollection

0 commit comments

Comments
 (0)