Skip to content

[Detail Bug] EtaResponse null filtering breaks index alignment of ETA results #46

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_6fd7a410-54ea-40e5-afa3-37379312d914

Summary

  • Context: The EtaResponse class represents the response from the Apple Maps ETA API, which provides travel time estimates for a list of destinations.
  • Bug: The EtaResponse constructor filters out null elements from the etas list, which breaks the index-based alignment between the requested destinations and the returned estimates.
  • Actual vs. expected: Currently, if the API returns null for a destination (e.g. if it's unreachable), it is removed from the list, causing subsequent estimates to shift; expected behavior is to preserve the list size so each index corresponds to the original request's destination index.
  • Impact: Users receive incorrect ETA information because estimates are misaligned with their corresponding destinations when any destination in the request fails to produce an estimate.

Code with bug

    private static <T> List<T> normalizeList(List<T> rawList) {
        if (rawList == null) {
            return List.of();
        }
        return rawList.stream()
            .filter(Objects::nonNull) // <-- BUG 🔴 Filtering nulls breaks index-based alignment
            .toList();
    }

Failing test

package com.williamcallahan.applemaps.domain.model;

import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;

class EtaReproductionTest {
    @Test
    void etaResponseShouldPreserveAlignment() {
        // Suppose we have 3 destinations
        EtaEstimate estimate1 = new EtaEstimate(Optional.empty(), Optional.of(100L), Optional.empty(), Optional.empty(), Optional.empty());
        EtaEstimate estimate3 = new EtaEstimate(Optional.empty(), Optional.of(300L), Optional.empty(), Optional.empty(), Optional.empty());
        
        List<EtaEstimate> etasFromApi = new ArrayList<>();
        etasFromApi.add(estimate1);
        etasFromApi.add(null); // Destination 2 failed or is unreachable
        etasFromApi.add(estimate3);
        
        EtaResponse response = new EtaResponse(etasFromApi);
        
        // This SHOULD be 3 to maintain alignment with the requested destinations.
        // If it's 2, then estimate3 (for destination 3) is now at index 1 (destination 2).
        assertEquals(3, response.etas().size(), "Alignment broken: list size should be 3");
    }
}

Test output:

EtaReproductionTest > etaResponseShouldPreserveAlignment() FAILED
    org.opentest4j.AssertionFailedError: Alignment broken: list size should be 3 ==> expected: <3> but was: <2>

Recommended fix

    public EtaResponse {
        etas = normalizeEtas(etas); // <-- FIX 🟢
    }

    private static List<EtaEstimate> normalizeEtas(List<EtaEstimate> rawList) {
        if (rawList == null) {
            return List.of();
        }
        return rawList.stream()
            .map(eta -> eta == null ? new EtaEstimate(null, null, null, null, null) : eta) // <-- FIX 🟢
            .toList();
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions