diff --git a/geo_app/schema.py b/geo_app/schema.py index 5ed64ad..b8d08e3 100644 --- a/geo_app/schema.py +++ b/geo_app/schema.py @@ -1677,7 +1677,8 @@ class Query(graphene.ObjectType): logger.info("Found nearest hub %s to coordinates (%.3f, %.3f)", hub_uuid, lat, lon) # Use existing offer_to_hub logic - return self.resolve_offer_to_hub(info, offer_uuid, hub_uuid) + # Note: in graphene, self is None (root value), so we call as class method + return Query.resolve_offer_to_hub(Query, info, offer_uuid, hub_uuid) except Exception as e: logger.error("Error finding route to coordinates: %s", e) return None diff --git a/tests/__pycache__/test_graphql_endpoints.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_graphql_endpoints.cpython-313-pytest-9.0.2.pyc index c1df1bc..05afb75 100644 Binary files a/tests/__pycache__/test_graphql_endpoints.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_graphql_endpoints.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/test_graphql_endpoints.py b/tests/test_graphql_endpoints.py index f4a991f..4c12ecd 100644 --- a/tests/test_graphql_endpoints.py +++ b/tests/test_graphql_endpoints.py @@ -454,6 +454,176 @@ class TestNearestEndpoints: suppliers = data['data']['nearestSuppliers'] print(f"✓ nearestSuppliers with product: {len(suppliers)} suppliers for product {product_uuid[:8]}") + def test_nearest_offers_with_hub_uuid(self): + """Test nearestOffers with hubUuid - should return offers with calculated routes. + + This tests the fix for the bug where resolve_offer_to_hub was called incorrectly + (self was None in graphene resolvers). + """ + # First, get a hub UUID from the database + hubs_query = """ + query { + nearestHubs(lat: 0, lon: 0, radius: 20000, limit: 5) { + uuid + name + latitude + longitude + } + } + """ + hubs_response = requests.post(GEO_URL, json={'query': hubs_query}) + hubs_data = hubs_response.json() + + if not hubs_data.get('data', {}).get('nearestHubs'): + pytest.skip("No hubs found in database") + + hub = hubs_data['data']['nearestHubs'][0] + hub_uuid = hub['uuid'] + hub_lat = hub['latitude'] + hub_lon = hub['longitude'] + + # Now test nearestOffers with this hub UUID + query = """ + query NearestOffers($lat: Float!, $lon: Float!, $radius: Float, $hubUuid: String, $limit: Int) { + nearestOffers(lat: $lat, lon: $lon, radius: $radius, hubUuid: $hubUuid, limit: $limit) { + uuid + productUuid + productName + supplierUuid + supplierName + latitude + longitude + pricePerUnit + currency + distanceKm + routes { + totalDistanceKm + totalTimeSeconds + stages { + fromUuid + fromName + fromLat + fromLon + toUuid + toName + toLat + toLon + distanceKm + travelTimeSeconds + transportType + } + } + } + } + """ + # Search around the hub location with large radius + variables = { + 'lat': float(hub_lat) if hub_lat else 0.0, + 'lon': float(hub_lon) if hub_lon else 0.0, + 'radius': 5000, # 5000km radius to find offers + 'hubUuid': hub_uuid, + 'limit': 10 + } + + response = requests.post(GEO_URL, json={'query': query, 'variables': variables}) + assert response.status_code == 200, f"Status: {response.status_code}, Body: {response.text}" + + data = response.json() + assert 'errors' not in data, f"GraphQL errors: {data.get('errors')}" + + offers = data['data']['nearestOffers'] + assert isinstance(offers, list), "nearestOffers should return a list" + + # The key assertion: with hubUuid, we should get offers with routes calculated + # (This was the bug - resolve_offer_to_hub was failing silently) + print(f"✓ nearestOffers with hubUuid: {len(offers)} offers for hub '{hub['name']}'") + + if len(offers) > 0: + # Check first offer structure + offer = offers[0] + assert 'uuid' in offer + assert 'productUuid' in offer + assert 'routes' in offer, "Offer should have routes field when hubUuid is provided" + + # If routes exist, verify structure + if offer['routes'] and len(offer['routes']) > 0: + route = offer['routes'][0] + assert 'totalDistanceKm' in route + assert 'totalTimeSeconds' in route + assert 'stages' in route + + if route['stages'] and len(route['stages']) > 0: + stage = route['stages'][0] + assert 'fromUuid' in stage + assert 'toUuid' in stage + assert 'transportType' in stage + assert 'distanceKm' in stage + print(f" Route has {len(route['stages'])} stages, total {route['totalDistanceKm']:.1f}km") + + def test_nearest_offers_with_hub_and_product(self): + """Test nearestOffers with both hubUuid and productUuid filters.""" + # Get a product and hub + products_query = "query { products { uuid name } }" + prod_response = requests.post(GEO_URL, json={'query': products_query}) + products = prod_response.json().get('data', {}).get('products', []) + + hubs_query = """ + query { + nearestHubs(lat: 0, lon: 0, radius: 20000, limit: 1) { + uuid + name + latitude + longitude + } + } + """ + hubs_response = requests.post(GEO_URL, json={'query': hubs_query}) + hubs = hubs_response.json().get('data', {}).get('nearestHubs', []) + + if not products or not hubs: + pytest.skip("No products or hubs in database") + + product = products[0] + hub = hubs[0] + + query = """ + query NearestOffers($lat: Float!, $lon: Float!, $radius: Float, $productUuid: String, $hubUuid: String) { + nearestOffers(lat: $lat, lon: $lon, radius: $radius, productUuid: $productUuid, hubUuid: $hubUuid) { + uuid + productUuid + productName + routes { + totalDistanceKm + stages { + transportType + } + } + } + } + """ + variables = { + 'lat': float(hub['latitude']) if hub['latitude'] else 0.0, + 'lon': float(hub['longitude']) if hub['longitude'] else 0.0, + 'radius': 10000, + 'productUuid': product['uuid'], + 'hubUuid': hub['uuid'] + } + + response = requests.post(GEO_URL, json={'query': query, 'variables': variables}) + assert response.status_code == 200 + + data = response.json() + assert 'errors' not in data, f"GraphQL errors: {data.get('errors')}" + + offers = data['data']['nearestOffers'] + + # All offers should be for the requested product + for offer in offers: + assert offer['productUuid'] == product['uuid'], \ + f"Offer has wrong productUuid: {offer['productUuid']} != {product['uuid']}" + + print(f"✓ nearestOffers with hub+product: {len(offers)} offers for '{product['name']}' via hub '{hub['name']}'") + class TestRoutingEndpoints: """Test routing and pathfinding endpoints."""