Skip to content

Fix block endpoint 404 response and schema validation - Issue #95#175

Merged
aaronbrethorst merged 7 commits intoOneBusAway:mainfrom
ARCoder181105:blockTesting
Jan 27, 2026
Merged

Fix block endpoint 404 response and schema validation - Issue #95#175
aaronbrethorst merged 7 commits intoOneBusAway:mainfrom
ARCoder181105:blockTesting

Conversation

@ARCoder181105
Copy link
Copy Markdown
Contributor

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

  1. Fixed 404 Response Handling (internal/restapi/block_handler. go)

    • Added proper JSON error response when block is not found
    • Returns 404 Not Found with structured JSON instead of server error
    • Response includes: code, currentTime, text, and version fields
  2. Fixed Schema Validation (internal/models/stop_times.go)

    • Removed omitempty from dropOffType and pickupType fields
    • Ensures these fields are always present in the response (required by schema)

Comprehensive Test Coverage

Added 271 lines of new tests in internal/restapi/block_handler_test.go:

  • TestBlockHandlerNonExistentBlock - Validates 404 error handling for missing blocks
  • TestBlockHandlerInvalidBlockID - Tests various invalid block ID formats
  • TestBlockHandlerResponseSchema - Complete schema validation for all response fields
  • TestBlockHandlerDifferentBlockIDs - Tests multiple valid block IDs
  • TestBlockHandlerMissingApiKey - Validates authentication requirements
  • BenchmarkBlockHandler - Performance benchmarking
  • TestBlockHandlerResponseTime - Ensures responses are under 1 second
  • TestBlockHandlerDataCompleteness - Verifies all required fields are present
  • TestBlockHandlerJSONSerialization - Tests JSON marshaling/unmarshaling

Issue #95 Checklist Progress

  • Verify the endpoint is reachable and responding with correct status codes
  • Validate response schema (fields, types, required properties)
  • Confirm the block configuration is correct for the given block ID
  • Test with different block IDs (valid, invalid, non-existent)
  • Test edge cases (e.g., blocks with no data)
  • Check response times and ensure performance is acceptable (<1s)
  • Ensure proper error handling (400, 404, 500 cases)
  • Write/strengthen unit tests with comprehensive coverage
  • Create PR with fixes and updated tests

Testing

All new tests pass locally. The test suite now includes:

  • Schema validation tests
  • Error handling tests (400, 404, 401)
  • Performance benchmarks
  • Data completeness verification
  • JSON serialization tests

Files Changed

  • internal/restapi/block_handler.go - Fixed 404 handling
  • internal/models/stop_times.go - Fixed schema validation
  • internal/restapi/block_handler_test.go - Added comprehensive tests

Related 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.

@ARCoder181105
Copy link
Copy Markdown
Contributor Author

@amrhossamdev

Pls revies the changes. It passes all the test SS below

Thank you

image

Copy link
Copy Markdown
Member

@aaronbrethorst aaronbrethorst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Inconsistent error handling: The custom code only logs on encode failure, while sendNotFound calls serverErrorResponse to send a proper 500 response
  2. Duplicate code: This duplicates the structure in models.ResponseModel
  3. Inconsistent message: Uses "block not found" vs standard "resource not found"
  4. 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:

  • TestBlockHandlerResponseSchema
  • TestBlockHandlerDifferentBlockIDs
  • TestBlockHandlerResponseTime
  • TestBlockHandlerDataCompleteness
  • TestBlockHandlerJSONSerialization

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 omitempty from dropOffType and pickupType ensures GTFS compliance where 0 is a valid value
  • Comprehensive schema validation - TestBlockHandlerResponseSchema thoroughly validates the nested response structure
  • JSON round-trip test - TestBlockHandlerJSONSerialization verifies response can be marshaled/unmarshaled
  • Good test organization - Table-driven tests with descriptive names
  • Authentication coverage - TestBlockHandlerMissingApiKey properly 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.

@ARCoder181105
Copy link
Copy Markdown
Contributor Author

image

@aaronbrethorst

I implemented the changes requested, all the test are passing now

Copy link
Copy Markdown
Member

@aaronbrethorst aaronbrethorst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks great! thanks for implementing the requested feedback.

@aaronbrethorst aaronbrethorst merged commit fd242f7 into OneBusAway:main Jan 27, 2026
4 checks passed
@ARCoder181105 ARCoder181105 deleted the blockTesting branch January 28, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Verification & Testing Checklist for block Endpoint

2 participants