docs(03-02): complete inventory endpoints plan summary
This commit is contained in:
parent
743611f488
commit
a892ae1c38
1 changed files with 121 additions and 0 deletions
121
.planning/phases/03-dashboard-intake-ui/03-02-SUMMARY.md
Normal file
121
.planning/phases/03-dashboard-intake-ui/03-02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
---
|
||||
phase: 03-dashboard-intake-ui
|
||||
plan: "02"
|
||||
subsystem: api, netbox
|
||||
tags: [go, inventory, rest-api, interface, tdd, chi]
|
||||
dependency_graph:
|
||||
requires:
|
||||
- internal/netbox.Client (ListDevices + GetDevice)
|
||||
provides:
|
||||
- internal/api/handlers.InventoryHandler
|
||||
- internal/api/handlers.NewInventoryHandler
|
||||
- internal/api/handlers.InventoryNetBoxClient (interface)
|
||||
- internal/api/handlers.InventoryItemResponse
|
||||
- GET /api/inventory route
|
||||
- GET /api/inventory/{id} route
|
||||
affects:
|
||||
- internal/api/router.go (NewRouter signature extended)
|
||||
- cmd/hwlab/main.go (InventoryHandler constructed and wired)
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- interface-for-testability (InventoryNetBoxClient mirrors IntakeNetBoxClient pattern)
|
||||
- nil-safe-slice (nil PhotoURLs coerced to [] before JSON encoding)
|
||||
- tdd-red-green (7 tests written failing, then handler implemented to pass)
|
||||
key_files:
|
||||
created:
|
||||
- internal/api/handlers/inventory.go
|
||||
- internal/api/handlers/inventory_test.go
|
||||
modified:
|
||||
- internal/api/router.go
|
||||
- cmd/hwlab/main.go
|
||||
decisions:
|
||||
- id: INV-01
|
||||
summary: "InventoryNetBoxClient interface wraps only ListDevices+GetDevice — keeps the handler narrow and fully testable without a live NetBox connection"
|
||||
- id: INV-02
|
||||
summary: "nil PhotoURLs from netbox.CustomFields coerced to []string{} in deviceToResponse to guarantee JSON encodes as [] not null — prevents frontend null-handling bugs"
|
||||
- id: INV-03
|
||||
summary: "Hard-coded limit=200 in ListDevices call (T-03-06) — caller cannot request unbounded result sets; pagination deferred to a future plan"
|
||||
metrics:
|
||||
duration: "~10 minutes"
|
||||
completed: "2026-04-10T00:00:00Z"
|
||||
tasks_completed: 2
|
||||
files_created: 2
|
||||
files_modified: 2
|
||||
---
|
||||
|
||||
# Phase 3 Plan 02: GET /api/inventory + GET /api/inventory/:id Summary
|
||||
|
||||
**One-liner:** Inventory list and detail REST endpoints behind a narrow InventoryNetBoxClient interface, with 7 TDD unit tests covering all HTTP status paths and routes wired into the chi router.
|
||||
|
||||
## What Was Built
|
||||
|
||||
### `internal/api/handlers/inventory.go`
|
||||
|
||||
- `InventoryNetBoxClient` interface with `ListDevices` and `GetDevice` — `*netbox.Client` satisfies it without any changes
|
||||
- `InventoryHandler` struct and `NewInventoryHandler(nb InventoryNetBoxClient) *InventoryHandler` constructor
|
||||
- `InventoryItemResponse` JSON struct: `id`, `name`, `asset_tag`, `hw_id`, `catalog_status`, `product_url`, `firmware_version`, `test_date`, `test_data`, `ai_notes`, `photo_urls`
|
||||
- `deviceToResponse` helper: maps `netbox.Device` → `InventoryItemResponse`, coerces nil `PhotoURLs` to `[]string{}`
|
||||
- `ListInventory`: `GET /api/inventory` — calls `ListDevices(ctx, 200)`, returns 200 JSON array or 502 on error
|
||||
- `GetInventoryItem`: `GET /api/inventory/{id}` — validates ID with `strconv.Atoi` (400 on failure), calls `GetDevice`, returns 200/404/502
|
||||
|
||||
### `internal/api/handlers/inventory_test.go`
|
||||
|
||||
Seven TDD unit tests using `mockInventoryNetBoxClient`:
|
||||
|
||||
| Test | Scenario | Expected |
|
||||
|------|----------|----------|
|
||||
| TestListInventoryEmpty | mock returns [] | 200, empty JSON array |
|
||||
| TestListInventoryItems | mock returns 2 devices | 200, 2-item array with hw_id/name/catalog_status/asset_tag/photo_urls |
|
||||
| TestListInventoryNetBoxError | mock returns error | 502, {"error": "..."} |
|
||||
| TestGetInventoryItemFound | mock returns device id=42 | 200, device JSON with correct fields |
|
||||
| TestGetInventoryItemNotFound | mock returns "404 not found" error | 404, {"error": "item not found"} |
|
||||
| TestGetInventoryItemInvalidID | path param = "abc" | 400, {"error": "id must be an integer"} |
|
||||
| TestGetInventoryItemNetBoxError | mock returns server error | 502, {"error": "..."} |
|
||||
|
||||
### `internal/api/router.go`
|
||||
|
||||
- `NewRouter` signature extended: `NewRouter(staticFiles fs.FS, intakeHandler http.Handler, inventoryHandler *handlers.InventoryHandler) http.Handler`
|
||||
- Two new routes in `/api` group: `GET /inventory` and `GET /inventory/{id}`
|
||||
|
||||
### `cmd/hwlab/main.go`
|
||||
|
||||
- `inventoryHandler := handlers.NewInventoryHandler(nbClient)` constructed after NetBox client
|
||||
- Passed as third argument to `api.NewRouter`
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None — both endpoints call real `netbox.Client` methods (`ListDevices`, `GetDevice`). No mock or placeholder data flows to responses in production.
|
||||
|
||||
## Threat Surface Coverage
|
||||
|
||||
All four threats from the plan's threat register are addressed:
|
||||
|
||||
| Threat | Mitigation | Where |
|
||||
|--------|------------|-------|
|
||||
| T-03-04: URL param tampering | `strconv.Atoi` rejects non-integer IDs → 400 | inventory.go:GetInventoryItem |
|
||||
| T-03-05: Error message info disclosure | Errors describe NetBox connectivity only; no credentials or paths exposed | inventory.go error paths |
|
||||
| T-03-06: DoS via unbounded list | Hard-coded `limit=200` in ListDevices call | inventory.go:ListInventory |
|
||||
| T-03-07: Unauthenticated endpoints | Accepted for Phase 3 homelab single-operator; Phase 4+ can add auth middleware | router.go |
|
||||
|
||||
## Self-Check
|
||||
|
||||
Files created:
|
||||
- internal/api/handlers/inventory.go: FOUND
|
||||
- internal/api/handlers/inventory_test.go: FOUND
|
||||
|
||||
Commits:
|
||||
- b0b6153: feat(03-02): InventoryHandler with list+detail endpoints and 7 unit tests
|
||||
- 743611f: feat(03-02): wire GET /api/inventory and GET /api/inventory/{id} routes
|
||||
|
||||
`go build ./...`: PASS
|
||||
`go test ./internal/api/handlers/... -run "TestListInventory|TestGetInventory" -v`: 7/7 PASS
|
||||
`go test ./...`: all packages PASS
|
||||
`grep "inventory" internal/api/router.go`: both GET routes PRESENT
|
||||
`grep "NewInventoryHandler" cmd/hwlab/main.go`: PRESENT
|
||||
|
||||
## Self-Check: PASSED
|
||||
Loading…
Add table
Reference in a new issue