Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions backend/routers/medicines.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,49 @@
from pydantic import BaseModel
from typing import List, Optional
from database import supabase
from functools import lru_cache
import math
import random
import time
import urllib.request
import json

LAST_IP_LAT = 18.5204
LAST_IP_LON = 73.8567
IP_FETCHED = False
DEFAULT_LOCATION = (18.5204, 73.8567)
IP_LOCATION_URL = "https://ipwho.is/"


def _parse_location(data):
if data.get("success") is False:
raise ValueError("IP location lookup was not successful")
lat = float(data.get("latitude", data.get("lat")))
lon = float(data.get("longitude", data.get("lon")))
if not (
math.isfinite(lat)
and math.isfinite(lon)
and -90 <= lat <= 90
and -180 <= lon <= 180
):
raise ValueError("IP location response contains invalid coordinates")
return lat, lon


@lru_cache(maxsize=1)
def _fetch_ip_location():
req = urllib.request.Request(
IP_LOCATION_URL,
headers={'User-Agent': 'Mozilla/5.0'}
)
with urllib.request.urlopen(req, timeout=2) as response:
data = json.loads(response.read().decode())
return _parse_location(data)


def get_default_location():
global LAST_IP_LAT, LAST_IP_LON, IP_FETCHED
if IP_FETCHED:
return LAST_IP_LAT, LAST_IP_LON
try:
req = urllib.request.Request("http://ip-api.com/json/", headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, timeout=2) as response:
data = json.loads(response.read().decode())
LAST_IP_LAT = data["lat"]
LAST_IP_LON = data["lon"]
IP_FETCHED = True
return LAST_IP_LAT, LAST_IP_LON
return _fetch_ip_location()
except Exception:
return 18.5204, 73.8567
return DEFAULT_LOCATION


def fetch_real_pharmacies(lat, lon):
import urllib.parse
Expand Down
112 changes: 112 additions & 0 deletions backend/tests/test_medicines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import importlib
import sys
import types
import unittest
from pathlib import Path
from unittest import mock


BACKEND_DIR = Path(__file__).resolve().parents[1]
BACKEND_DIR_STR = str(BACKEND_DIR)
ADDED_BACKEND_PATH = BACKEND_DIR_STR not in sys.path
if ADDED_BACKEND_PATH:
sys.path.insert(0, BACKEND_DIR_STR)


def _cleanup_import_state():
sys.modules.pop("routers.medicines", None)
if ADDED_BACKEND_PATH and BACKEND_DIR_STR in sys.path:
sys.path.remove(BACKEND_DIR_STR)


unittest.addModuleCleanup(_cleanup_import_state)


class _APIRouter:
def get(self, *args, **kwargs):
def decorator(func):
return func

return decorator


class _HTTPException(Exception):
pass


class _BaseModel:
pass


fastapi_module = types.ModuleType("fastapi")
fastapi_module.APIRouter = _APIRouter
fastapi_module.HTTPException = _HTTPException

pydantic_module = types.ModuleType("pydantic")
pydantic_module.BaseModel = _BaseModel

database_module = types.ModuleType("database")
database_module.supabase = object()

with mock.patch.dict(
sys.modules,
{
"fastapi": fastapi_module,
"pydantic": pydantic_module,
"database": database_module,
},
):
medicines = importlib.import_module("routers.medicines")


Comment thread
pragnyanramtha marked this conversation as resolved.
class DefaultLocationCacheTest(unittest.TestCase):
def setUp(self):
medicines._fetch_ip_location.cache_clear()

def test_dependency_stubs_are_scoped_to_import(self):
self.assertIsNot(sys.modules.get("fastapi"), fastapi_module)
self.assertIsNot(sys.modules.get("pydantic"), pydantic_module)

def test_default_location_fetch_is_cached_without_mutable_globals(self):
response = mock.Mock()
response.read.return_value = b'{"latitude": 12.34, "longitude": 56.78}'

with mock.patch.object(medicines.urllib.request, "urlopen") as urlopen:
urlopen.return_value.__enter__.return_value = response

self.assertEqual(medicines.get_default_location(), (12.34, 56.78))
self.assertEqual(medicines.get_default_location(), (12.34, 56.78))

self.assertEqual(urlopen.call_count, 1)
self.assertFalse(hasattr(medicines, "LAST_IP_LAT"))
self.assertFalse(hasattr(medicines, "LAST_IP_LON"))
self.assertFalse(hasattr(medicines, "IP_FETCHED"))

def test_default_location_falls_back_without_caching_failure(self):
response = mock.Mock()
response.read.return_value = b'{"latitude": 23.45, "longitude": 67.89}'
successful_lookup = mock.MagicMock()
successful_lookup.__enter__.return_value = response

with mock.patch.object(
medicines.urllib.request,
"urlopen",
side_effect=[OSError("network unavailable"), successful_lookup],
) as urlopen:
self.assertEqual(medicines.get_default_location(), medicines.DEFAULT_LOCATION)
self.assertEqual(medicines.get_default_location(), (23.45, 67.89))

self.assertEqual(urlopen.call_count, 2)

def test_default_location_rejects_invalid_coordinates(self):
response = mock.Mock()
response.read.return_value = b'{"latitude": 1000, "longitude": 67.89}'

with mock.patch.object(medicines.urllib.request, "urlopen") as urlopen:
urlopen.return_value.__enter__.return_value = response

self.assertEqual(medicines.get_default_location(), medicines.DEFAULT_LOCATION)


if __name__ == "__main__":
unittest.main()