Fix block endpoint 404 response and schema validation - Issue #95#175
Fix block endpoint 404 response and schema validation - Issue #95#175aaronbrethorst merged 7 commits intoOneBusAway:mainfrom
Conversation
aaronbrethorst
left a comment
There was a problem hiding this comment.
Good work on addressing the block endpoint's error handling! The schema fix for dropOffType and pickupType is correct, and the comprehensive test coverage is appreciated. However, there's a significant issue with the 404 implementation that should be addressed before merging.
Critical
1. Custom 404 implementation should use existing sendNotFound helper
The PR introduces a hand-crafted inline struct for the 404 response instead of using the existing api.sendNotFound(w, r) method that's used consistently across 10+ other handlers.
File: internal/restapi/block_handler.go:31-48
// Current PR code - custom implementation
if len(block) == 0 {
response := struct {
Code int `json:"code"`
CurrentTime int64 `json:"currentTime"`
Text string `json:"text"`
Version int `json:"version"`
}{
Code: http.StatusNotFound,
CurrentTime: models.ResponseCurrentTime(),
Text: "block not found",
Version: 2,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
if err := json.NewEncoder(w).Encode(response); err != nil {
api.Logger.Error("failed to encode 404 response", "error", err)
}
return
}Problems:
- Inconsistent error handling: The custom code only logs on encode failure, while
sendNotFoundcallsserverErrorResponseto send a proper 500 response - Duplicate code: This duplicates the structure in
models.ResponseModel - Inconsistent message: Uses "block not found" vs standard "resource not found"
- Maintenance burden: Changes to error response format would need to be made in multiple places
The sendNotFound helper exists in responses.go and is used by stop_handler.go, trip_handler.go, shapes_handler.go, agency_handler.go, and many others.
Fix:
if len(block) == 0 {
api.sendNotFound(w, r)
return
}If the custom "block not found" message is required, add a parameterized helper to responses.go that all handlers can use.
High Priority
2. Benchmark test uses invalid &testing.T{} pattern
File: internal/restapi/block_handler_test.go:476-484
func BenchmarkBlockHandler(b *testing.B) {
api := createTestApi(&testing.T{}) // Wrong: creates empty testing.T
endpoint := "/api/where/block/25_1.json?key=TEST"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = serveApiAndRetrieveEndpoint(&testing.T{}, api, endpoint) // Wrong again
}
}&testing.T{} creates an empty, uninitialized struct. If createTestApi or serveApiAndRetrieveEndpoint call t.Fatal(), t.Error(), or require.*, the test will panic or behave incorrectly. Results from this benchmark are unreliable.
Fix: Either refactor createTestApi to accept testing.TB (interface satisfied by both *testing.T and *testing.B), or skip the benchmark until that refactoring is done.
3. TestBlockHandlerNonExistentBlock has weak assertions
The test that should verify the core 404 fix accepts too many outcomes.
File: internal/restapi/block_handler_test.go:212-223
func TestBlockHandlerNonExistentBlock(t *testing.T) {
_, resp, model := serveAndRetrieveEndpoint(t, "/api/where/block/25_nonexistent.json?key=TEST")
// Comment says "either 404 or 500 is acceptable" - but the PR's purpose is to return 404!
if resp.StatusCode == http.StatusOK {
assert.NotEqual(t, http.StatusOK, model.Code, "Expected error code in model for non-existent block")
} else {
assert.True(t, resp.StatusCode >= 400, "Expected error status code for non-existent block")
}
}Fix: Assert the specific expected behavior:
func TestBlockHandlerNonExistentBlock(t *testing.T) {
_, resp, model := serveAndRetrieveEndpoint(t, "/api/where/block/25_nonexistent.json?key=TEST")
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
assert.Equal(t, http.StatusNotFound, model.Code)
assert.Equal(t, "block not found", model.Text) // or "resource not found" if using sendNotFound
assert.Equal(t, 2, model.Version)
assert.Greater(t, model.CurrentTime, int64(0))
}4. Missing defer api.Shutdown() in new tests
Existing tests consistently call defer api.Shutdown() after createTestApi(t), but several new tests don't follow this pattern:
TestBlockHandlerResponseSchemaTestBlockHandlerDifferentBlockIDsTestBlockHandlerResponseTimeTestBlockHandlerDataCompletenessTestBlockHandlerJSONSerialization
Fix: Add defer api.Shutdown() after each createTestApi(t) call.
5. TestBlockHandlerInvalidBlockID doesn't verify response body
File: internal/restapi/block_handler_test.go:226-244
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, resp, _ := serveAndRetrieveEndpoint(t, tc.endpoint) // Response model discarded
assert.True(t, resp.StatusCode == http.StatusBadRequest || resp.StatusCode >= 400,
"Expected error status for invalid block ID: %s", tc.name)
})
}The response model is discarded. Looking at block_handler.go, invalid block IDs return http.Error(w, "null", http.StatusBadRequest) which is NOT valid JSON - inconsistent with the rest of the API where 404 returns proper JSON.
Test Coverage Gaps
6. No test for 400 response body format
The handler returns http.Error(w, "null", http.StatusBadRequest) for invalid block IDs, which is a raw string, not JSON. Given the PR fixes 404 to return proper JSON, the 400 path should arguably also return proper JSON for API consistency.
7. Redundant tests could be consolidated
TestBlockHandlerResponseSchema and TestBlockHandlerDataCompleteness validate nearly identical structure. Consider consolidating or making the distinction clearer.
Lower Priority
8. // NEW: and // Fix: comment prefixes are non-standard
Files: block_handler.go:31, block_handler_test.go (9 instances)
The codebase uses // TODO:, // NOTE:, and // FIXME: as comment prefixes, but // Fix: and // NEW: don't appear elsewhere in source code. After the PR is merged, tests are no longer "new."
Fix: Remove // NEW: prefixes from all tests. Either remove // Fix: comment (code is self-explanatory) or rewrite to explain why, not what.
9. Response time test may be flaky on CI
File: internal/restapi/block_handler_test.go:489-496
assert.Less(t, duration.Milliseconds(), int64(1000), "Response should be under 1 second")Time-based tests are inherently flaky on CI systems under load. Consider using a much higher threshold (e.g., 5s) for CI reliability.
What's Good
- Schema fix is correct - Removing
omitemptyfromdropOffTypeandpickupTypeensures GTFS compliance where 0 is a valid value - Comprehensive schema validation -
TestBlockHandlerResponseSchemathoroughly validates the nested response structure - JSON round-trip test -
TestBlockHandlerJSONSerializationverifies response can be marshaled/unmarshaled - Good test organization - Table-driven tests with descriptive names
- Authentication coverage -
TestBlockHandlerMissingApiKeyproperly tests the missing API key scenario
Summary
| Priority | Issue | Location |
|---|---|---|
| Critical | Custom 404 should use existing sendNotFound helper | block_handler.go:31-48 |
| High | Benchmark uses invalid &testing.T{} |
block_handler_test.go:476-484 |
| High | Non-existent block test has weak assertions | block_handler_test.go:212-223 |
| High | Missing defer api.Shutdown() |
Multiple new tests |
| High | Invalid block ID test ignores response body | block_handler_test.go:226-244 |
| Tests | No test for 400 response body format | block_handler.go |
| Tests | Redundant tests could be consolidated | block_handler_test.go |
| Lower | Non-standard comment prefixes | Multiple files |
| Lower | Response time test may be flaky | block_handler_test.go:489-496 |
The core idea is right - returning a proper 404 JSON response instead of an empty or error response. The main fix needed is to use the existing sendNotFound helper for consistency with the rest of the codebase.
Let me know if you have questions about any of this feedback.
aaronbrethorst
left a comment
There was a problem hiding this comment.
this looks great! thanks for implementing the requested feedback.


Fix block endpoint 404 response and schema validation errors
Summary
This PR addresses issue #95 by fixing the block endpoint's error handling and response schema validation, along with comprehensive test coverage.
Changes Made
Core Fixes
Fixed 404 Response Handling (
internal/restapi/block_handler. go)404 Not Foundwith structured JSON instead of server errorcode,currentTime,text, andversionfieldsFixed Schema Validation (
internal/models/stop_times.go)omitemptyfromdropOffTypeandpickupTypefieldsComprehensive Test Coverage
Added 271 lines of new tests in
internal/restapi/block_handler_test.go:TestBlockHandlerNonExistentBlock- Validates 404 error handling for missing blocksTestBlockHandlerInvalidBlockID- Tests various invalid block ID formatsTestBlockHandlerResponseSchema- Complete schema validation for all response fieldsTestBlockHandlerDifferentBlockIDs- Tests multiple valid block IDsTestBlockHandlerMissingApiKey- Validates authentication requirementsBenchmarkBlockHandler- Performance benchmarkingTestBlockHandlerResponseTime- Ensures responses are under 1 secondTestBlockHandlerDataCompleteness- Verifies all required fields are presentTestBlockHandlerJSONSerialization- Tests JSON marshaling/unmarshalingIssue #95 Checklist Progress
400,404,500cases)Testing
All new tests pass locally. The test suite now includes:
Files Changed
internal/restapi/block_handler.go- Fixed 404 handlinginternal/models/stop_times.go- Fixed schema validationinternal/restapi/block_handler_test.go- Added comprehensive testsRelated Issue
Closes #95
Notes
This PR addresses all testable items from the verification checklist. Production comparison would need to be done post-merge in a testing environment.