"""Tests for the Google Routes API wrapper (HTTP layer mocked).""" import datetime as dt import numpy as np import pandas as pd import pytest import tatami.traveltimes as tt from tatami.classes import Group from tatami.traveltimes import ( get_distance_matrix, get_participant_distance_matrix, reduce_distance_matrix, ) from conftest import make_participants, make_timedelta_matrix class FakeResponse: def __init__(self, status_code, json_data=None, text=""): self.status_code = status_code self._json = json_data self.text = text def json(self): return self._json def route_matrix_payload(durations_seconds): """Build a computeRouteMatrix-style flat list of elements from an n x n array.""" payload = [] n = len(durations_seconds) for i in range(n): for j in range(n): payload.append( { "originIndex": i, "destinationIndex": j, "duration": f"{int(durations_seconds[i][j])}s", "distanceMeters": int(durations_seconds[i][j]) * 4, } ) return payload @pytest.fixture def mock_post(monkeypatch): """Patch requests.post; returns a setter for the desired response.""" state = {} def fake_post(url, json=None, headers=None): return state["response"] monkeypatch.setattr(tt.requests, "post", fake_post) def set_response(response): state["response"] = response return set_response class TestGetDistanceMatrix: def test_builds_duration_matrix(self, mock_post): durations = np.array([[0, 120], [300, 0]]) mock_post(FakeResponse(200, route_matrix_payload(durations))) matrix = get_distance_matrix(["a", "b"]) assert matrix.shape == (2, 2) assert matrix.loc[0, 1] == pd.to_timedelta("120s") assert matrix.loc[1, 0] == pd.to_timedelta("300s") def test_distance_meters_value(self, mock_post): durations = np.array([[0, 120], [300, 0]]) mock_post(FakeResponse(200, route_matrix_payload(durations))) matrix = get_distance_matrix(["a", "b"], value="distanceMeters") assert matrix.loc[0, 1] == 480.0 def test_empty_addresses_raises(self): with pytest.raises(ValueError): get_distance_matrix([]) def test_non_200_raises(self, mock_post): mock_post(FakeResponse(500, text="boom")) with pytest.raises(Exception, match="boom"): get_distance_matrix(["a", "b"]) def test_invalid_value_raises(self, mock_post): durations = np.array([[0, 120], [300, 0]]) mock_post(FakeResponse(200, route_matrix_payload(durations))) with pytest.raises(ValueError): get_distance_matrix(["a", "b"], value="bogus") class TestGetParticipantDistanceMatrix: def test_labels_axes_with_uuids(self, mock_post): participants = make_participants(2) durations = np.array([[0, 120], [300, 0]]) mock_post(FakeResponse(200, route_matrix_payload(durations))) matrix = get_participant_distance_matrix(participants) uuids = [p.uuid for p in participants] assert list(matrix.index) == uuids assert list(matrix.columns) == uuids class TestReduceDistanceMatrix: def test_selects_main_members_and_adds_penalty(self): # group 0 main member has kitchen_size 7 -> 9 minute (540s) penalty. people = make_participants(2, kitchen_sizes=[7.0, 10.0]) seconds = np.array([[0, 100], [200, 0]]) matrix = make_timedelta_matrix(people, seconds) groups = [Group(members=[people[0]]), Group(members=[people[1]])] reduced = reduce_distance_matrix(matrix, groups) u0, u1 = people[0].uuid, people[1].uuid assert list(reduced.index) == [u0, u1] # Penalty added to every entry of group 0's row. assert reduced.loc[u0, u1] == dt.timedelta(seconds=100) + dt.timedelta( minutes=9 ) # Group 1 has no penalty (kitchen_size 10). assert reduced.loc[u1, u0] == dt.timedelta(seconds=200)