Merge modifications for more separate backend functions #69
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1,324 +0,0 @@ | |||||||
| """Collection of tests to ensure correct implementation and track progress.""" |  | ||||||
| import time |  | ||||||
| from fastapi.testclient import TestClient |  | ||||||
| import pytest |  | ||||||
|  |  | ||||||
| from .test_utils import load_trip_landmarks, log_trip_details |  | ||||||
| from ..main import app |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture(scope="module") |  | ||||||
| def client(): |  | ||||||
|     """Client used to call the app.""" |  | ||||||
|     return TestClient(app) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_turckheim(client, request):    # pylint: disable=redefined-outer-name |  | ||||||
|     """ |  | ||||||
|     Test n°1 : Custom test in Turckheim to ensure small villages are also supported. |  | ||||||
|  |  | ||||||
|     Args: |  | ||||||
|         client: |  | ||||||
|         request: |  | ||||||
|     """ |  | ||||||
|     start_time = time.time()  # Start timer |  | ||||||
|     duration_minutes = 20 |  | ||||||
|  |  | ||||||
|     response = client.post( |  | ||||||
|         "/trip/new", |  | ||||||
|         json={ |  | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |  | ||||||
|             "nature": {"type": "nature", "score": 0}, |  | ||||||
|             "shopping": {"type": "shopping", "score": 0}, |  | ||||||
|             "max_time_minute": duration_minutes, |  | ||||||
|             "detour_tolerance_minute": 0}, |  | ||||||
|             "start": [48.084588, 7.280405] |  | ||||||
|             # "start": [45.74445023349939, 4.8222687890538865] |  | ||||||
|             # "start": [45.75156398104873, 4.827154464827647] |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     result = response.json() |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Get computation time |  | ||||||
|     comp_time = time.time() - start_time |  | ||||||
|  |  | ||||||
|     # Add details to report |  | ||||||
|     log_trip_details(request, landmarks, result['total_time'], duration_minutes) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # checks : |  | ||||||
|     assert response.status_code == 200  # check for successful planning |  | ||||||
|     assert isinstance(landmarks, list)  # check that the return type is a list |  | ||||||
|     assert len(landmarks) > 2           # check that there is something to visit |  | ||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |  | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     # assert 2!= 3 |  | ||||||
|  |  | ||||||
| def test_bellecour(client, request) :   # pylint: disable=redefined-outer-name |  | ||||||
|     """ |  | ||||||
|     Test n°2 : Custom test in Lyon centre to ensure proper decision making in crowded area. |  | ||||||
|      |  | ||||||
|     Args: |  | ||||||
|         client: |  | ||||||
|         request: |  | ||||||
|     """ |  | ||||||
|     start_time = time.time()  # Start timer |  | ||||||
|     duration_minutes = 120 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     response = client.post( |  | ||||||
|         "/trip/new", |  | ||||||
|         json={ |  | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |  | ||||||
|                             "nature": {"type": "nature", "score": 5}, |  | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |  | ||||||
|                             "max_time_minute": duration_minutes, |  | ||||||
|                             "detour_tolerance_minute": 0}, |  | ||||||
|             "start": [45.7576485, 4.8330241] |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     result = response.json() |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |  | ||||||
|  |  | ||||||
|     # Get computation time |  | ||||||
|     comp_time = time.time() - start_time |  | ||||||
|  |  | ||||||
|     # Add details to report |  | ||||||
|     log_trip_details(request, landmarks, result['total_time'], duration_minutes) |  | ||||||
|  |  | ||||||
|     # checks : |  | ||||||
|     assert response.status_code == 200  # check for successful planning |  | ||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |  | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_cologne(client, request) :   # pylint: disable=redefined-outer-name |  | ||||||
|     """ |  | ||||||
|     Test n°3 : Custom test in Cologne to ensure proper decision making in crowded area. |  | ||||||
|      |  | ||||||
|     Args: |  | ||||||
|         client: |  | ||||||
|         request: |  | ||||||
|     """ |  | ||||||
|     start_time = time.time()  # Start timer |  | ||||||
|     duration_minutes = 240 |  | ||||||
|  |  | ||||||
|     response = client.post( |  | ||||||
|         "/trip/new", |  | ||||||
|         json={ |  | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |  | ||||||
|                             "nature": {"type": "nature", "score": 5}, |  | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |  | ||||||
|                             "max_time_minute": duration_minutes, |  | ||||||
|                             "detour_tolerance_minute": 0}, |  | ||||||
|             "start": [50.942352665, 6.957777972392] |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     result = response.json() |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |  | ||||||
|  |  | ||||||
|     # Get computation time |  | ||||||
|     comp_time = time.time() - start_time |  | ||||||
|  |  | ||||||
|     # Add details to report |  | ||||||
|     log_trip_details(request, landmarks, result['total_time'], duration_minutes) |  | ||||||
|  |  | ||||||
|     # checks : |  | ||||||
|     assert response.status_code == 200  # check for successful planning |  | ||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |  | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_strasbourg(client, request) :   # pylint: disable=redefined-outer-name |  | ||||||
|     """ |  | ||||||
|     Test n°4 : Custom test in Strasbourg to ensure proper decision making in crowded area. |  | ||||||
|      |  | ||||||
|     Args: |  | ||||||
|         client: |  | ||||||
|         request: |  | ||||||
|     """ |  | ||||||
|     start_time = time.time()  # Start timer |  | ||||||
|     duration_minutes = 180 |  | ||||||
|  |  | ||||||
|     response = client.post( |  | ||||||
|         "/trip/new", |  | ||||||
|         json={ |  | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |  | ||||||
|                             "nature": {"type": "nature", "score": 5}, |  | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |  | ||||||
|                             "max_time_minute": duration_minutes, |  | ||||||
|                             "detour_tolerance_minute": 0}, |  | ||||||
|             "start": [48.5846589226, 7.74078715721] |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     result = response.json() |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |  | ||||||
|  |  | ||||||
|     # Get computation time |  | ||||||
|     comp_time = time.time() - start_time |  | ||||||
|  |  | ||||||
|     # Add details to report |  | ||||||
|     log_trip_details(request, landmarks, result['total_time'], duration_minutes) |  | ||||||
|  |  | ||||||
|     # checks : |  | ||||||
|     assert response.status_code == 200  # check for successful planning |  | ||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |  | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_zurich(client, request) :   # pylint: disable=redefined-outer-name |  | ||||||
|     """ |  | ||||||
|     Test n°5 : Custom test in Zurich to ensure proper decision making in crowded area. |  | ||||||
|      |  | ||||||
|     Args: |  | ||||||
|         client: |  | ||||||
|         request: |  | ||||||
|     """ |  | ||||||
|     start_time = time.time()  # Start timer |  | ||||||
|     duration_minutes = 180 |  | ||||||
|  |  | ||||||
|     response = client.post( |  | ||||||
|         "/trip/new", |  | ||||||
|         json={ |  | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |  | ||||||
|                             "nature": {"type": "nature", "score": 5}, |  | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |  | ||||||
|                             "max_time_minute": duration_minutes, |  | ||||||
|                             "detour_tolerance_minute": 0}, |  | ||||||
|             "start": [47.377884227, 8.5395114066] |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     result = response.json() |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |  | ||||||
|  |  | ||||||
|     # Get computation time |  | ||||||
|     comp_time = time.time() - start_time |  | ||||||
|  |  | ||||||
|     # Add details to report |  | ||||||
|     log_trip_details(request, landmarks, result['total_time'], duration_minutes) |  | ||||||
|  |  | ||||||
|     # checks : |  | ||||||
|     assert response.status_code == 200  # check for successful planning |  | ||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |  | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_paris(client, request) :   # pylint: disable=redefined-outer-name |  | ||||||
|     """ |  | ||||||
|     Test n°6 : Custom test in Paris (les Halles) centre to ensure proper decision making in crowded area. |  | ||||||
|      |  | ||||||
|     Args: |  | ||||||
|         client: |  | ||||||
|         request: |  | ||||||
|     """ |  | ||||||
|     start_time = time.time()  # Start timer |  | ||||||
|     duration_minutes = 200 |  | ||||||
|  |  | ||||||
|     response = client.post( |  | ||||||
|         "/trip/new", |  | ||||||
|         json={ |  | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |  | ||||||
|                             "nature": {"type": "nature", "score": 0}, |  | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |  | ||||||
|                             "max_time_minute": duration_minutes, |  | ||||||
|                             "detour_tolerance_minute": 0}, |  | ||||||
|             "start": [48.85468881798671, 2.3423925755998374] |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     result = response.json() |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |  | ||||||
|  |  | ||||||
|     # Get computation time |  | ||||||
|     comp_time = time.time() - start_time |  | ||||||
|  |  | ||||||
|     # Add details to report |  | ||||||
|     log_trip_details(request, landmarks, result['total_time'], duration_minutes) |  | ||||||
|  |  | ||||||
|     # checks : |  | ||||||
|     assert response.status_code == 200  # check for successful planning |  | ||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |  | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_new_york(client, request) :   # pylint: disable=redefined-outer-name |  | ||||||
|     """ |  | ||||||
|     Test n°7 : Custom test in New York to ensure proper decision making in crowded area. |  | ||||||
|      |  | ||||||
|     Args: |  | ||||||
|         client: |  | ||||||
|         request: |  | ||||||
|     """ |  | ||||||
|     start_time = time.time()  # Start timer |  | ||||||
|     duration_minutes = 600 |  | ||||||
|  |  | ||||||
|     response = client.post( |  | ||||||
|         "/trip/new", |  | ||||||
|         json={ |  | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |  | ||||||
|                             "nature": {"type": "nature", "score": 5}, |  | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |  | ||||||
|                             "max_time_minute": duration_minutes, |  | ||||||
|                             "detour_tolerance_minute": 0}, |  | ||||||
|             "start": [40.72592726802, -73.9920434795] |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     result = response.json() |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |  | ||||||
|  |  | ||||||
|     # Get computation time |  | ||||||
|     comp_time = time.time() - start_time |  | ||||||
|  |  | ||||||
|     # Add details to report |  | ||||||
|     log_trip_details(request, landmarks, result['total_time'], duration_minutes) |  | ||||||
|  |  | ||||||
|     # checks : |  | ||||||
|     assert response.status_code == 200  # check for successful planning |  | ||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |  | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_shopping(client, request) :   # pylint: disable=redefined-outer-name |  | ||||||
|     """ |  | ||||||
|     Test n°8 : Custom test in Lyon centre to ensure shopping clusters are found. |  | ||||||
|      |  | ||||||
|     Args: |  | ||||||
|         client: |  | ||||||
|         request: |  | ||||||
|     """ |  | ||||||
|     start_time = time.time()  # Start timer |  | ||||||
|     duration_minutes = 240 |  | ||||||
|  |  | ||||||
|     response = client.post( |  | ||||||
|         "/trip/new", |  | ||||||
|         json={ |  | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 0}, |  | ||||||
|                             "nature": {"type": "nature", "score": 0}, |  | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |  | ||||||
|                             "max_time_minute": duration_minutes, |  | ||||||
|                             "detour_tolerance_minute": 0}, |  | ||||||
|             "start": [45.7576485, 4.8330241] |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     result = response.json() |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |  | ||||||
|  |  | ||||||
|     # Get computation time |  | ||||||
|     comp_time = time.time() - start_time |  | ||||||
|  |  | ||||||
|     # Add details to report |  | ||||||
|     log_trip_details(request, landmarks, result['total_time'], duration_minutes) |  | ||||||
|  |  | ||||||
|     # checks : |  | ||||||
|     assert response.status_code == 200  # check for successful planning |  | ||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |  | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |  | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |  | ||||||
| @@ -14,43 +14,43 @@ def client(): | |||||||
|     return TestClient(app) |     return TestClient(app) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bellecour(client, request) :   # pylint: disable=redefined-outer-name | @pytest.mark.parametrize( | ||||||
|     """ |     "sightseeing, shopping, nature, max_time_minute, start_coords, end_coords", | ||||||
|     Test n°2 : Custom test in Lyon centre to ensure proper decision making in crowded area. |     [ | ||||||
|      |         # Edge cases | ||||||
|     Args: |         (0, 0, 5, 240, [45.7576485, 4.8330241], None),                  # Lyon, Bellecour - test shopping only | ||||||
|         client: |          | ||||||
|         request: |         # Realistic | ||||||
|     """ |         (5, 0, 0, 20, [48.0845881, 7.2804050], None),                   # Turckheim | ||||||
|  |         (5, 5, 5, 120, [45.7576485, 4.8330241], None),                  # Lyon, Bellecour | ||||||
|  |         (5, 5, 5, 240, [50.9423526, 6.9577780], None),                  # Cologne, centre | ||||||
|  |         (5, 5, 5, 180, [48.5846589226, 7.74078715721], None),           # Strasbourg, centre | ||||||
|  |         (5, 5, 5, 180, [47.377884227, 8.5395114066], None),             # Zurich, centre | ||||||
|  |         (5, 0, 5, 200, [48.85468881798671, 2.3423925755998374], None),  # Paris, centre | ||||||
|  |         (5, 5, 5, 600, [40.72592726802, -73.9920434795], None),         # New York, Lower Manhattan | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | def test_trip(client, request, sightseeing, shopping, nature, max_time_minute, start_coords, end_coords): | ||||||
|  |  | ||||||
|     start_time = time.time()  # Start timer |     start_time = time.time()  # Start timer | ||||||
|  |  | ||||||
|     # Step 0: Define the trip preferences |  | ||||||
|     prefs = Preferences( |     prefs = Preferences( | ||||||
|         sightseeing = Preference( |         sightseeing=Preference(type='sightseeing', score=sightseeing), | ||||||
|             type='sightseeing', |         shopping=Preference(type='shopping', score=shopping), | ||||||
|             score=5 |         nature=Preference(type='nature', score=nature), | ||||||
|         ), |         max_time_minute=max_time_minute, | ||||||
|         shopping = Preference( |  | ||||||
|             type='shopping', |  | ||||||
|             score=5 |  | ||||||
|         ), |  | ||||||
|         nature = Preference( |  | ||||||
|             type='nature', |  | ||||||
|             score=5 |  | ||||||
|         ), |  | ||||||
|         max_time_minute=120, |  | ||||||
|         detour_tolerance_minute=0, |         detour_tolerance_minute=0, | ||||||
|     ) |     ) | ||||||
|      |     start = start_coords | ||||||
|     # Define the starting coordinates |     end = end_coords | ||||||
|     start = [45.7576485, 4.8330241] |  | ||||||
|  |  | ||||||
|     # Step 1: request the list of landmarks in the vicinty of the starting point |     # Step 1: request the list of landmarks in the vicinty of the starting point | ||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/get/landmarks", |         "/get/landmarks", | ||||||
|         json={ |         json={ | ||||||
|             "preferences": prefs.model_dump(), |             "preferences": prefs.model_dump(), | ||||||
|             "start": start |             "start": start_coords, | ||||||
|  |             "end": end_coords, | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     landmarks = response.json() |     landmarks = response.json() | ||||||
| @@ -61,7 +61,8 @@ def test_bellecour(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|         json={ |         json={ | ||||||
|             "preferences": prefs.model_dump(), |             "preferences": prefs.model_dump(), | ||||||
|             "landmarks": landmarks, |             "landmarks": landmarks, | ||||||
|             "start": start |             "start": start, | ||||||
|  |             "end": end, | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
| """Helper methods for testing.""" | """Helper methods for testing.""" | ||||||
|  | import time | ||||||
| import logging | import logging | ||||||
|  | from functools import wraps | ||||||
| from fastapi import HTTPException | from fastapi import HTTPException | ||||||
|  |  | ||||||
| from ..structs.landmark import Landmark |  | ||||||
| from ..cache import client as cache_client | from ..cache import client as cache_client | ||||||
|  | from ..structs.landmark import Landmark | ||||||
|  | from ..structs.preferences import Preferences, Preference | ||||||
|  |  | ||||||
|  |  | ||||||
| def landmarks_to_osmid(landmarks: list[Landmark]) -> list[int] : | def landmarks_to_osmid(landmarks: list[Landmark]) -> list[int] : | ||||||
| @@ -91,3 +94,34 @@ def log_trip_details(request, landmarks: list[Landmark], duration: int, target_d | |||||||
|     request.node.trip_details = trip_string |     request.node.trip_details = trip_string | ||||||
|     request.node.trip_duration = str(duration)              # result['total_time'] |     request.node.trip_duration = str(duration)              # result['total_time'] | ||||||
|     request.node.target_duration = str(target_duration) |     request.node.target_duration = str(target_duration) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def trip_params( | ||||||
|  |     sightseeing: int, | ||||||
|  |     shopping: int, | ||||||
|  |     nature: int, | ||||||
|  |     max_time_minute: int, | ||||||
|  |     start_coords: tuple[float, float] = None, | ||||||
|  |     end_coords: tuple[float, float] = None, | ||||||
|  | ): | ||||||
|  |     def decorator(test_func): | ||||||
|  |         @wraps(test_func) | ||||||
|  |         def wrapper(client, request): | ||||||
|  |             prefs = Preferences( | ||||||
|  |                 sightseeing=Preference(type='sightseeing', score=sightseeing), | ||||||
|  |                 shopping=Preference(type='shopping', score=shopping), | ||||||
|  |                 nature=Preference(type='nature', score=nature), | ||||||
|  |                 max_time_minute=max_time_minute, | ||||||
|  |                 detour_tolerance_minute=0, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             start = start_coords | ||||||
|  |             end = end_coords | ||||||
|  |  | ||||||
|  |             # Inject into test function | ||||||
|  |             return test_func(client, request, prefs, start, end) | ||||||
|  |  | ||||||
|  |         return wrapper | ||||||
|  |     return decorator | ||||||
		Reference in New Issue
	
	Block a user