Skip to content
Merged
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
33 changes: 3 additions & 30 deletions cityfeel/api/spec/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -466,12 +466,6 @@ paths:
name: name
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- in: query
name: radius
schema:
Expand All @@ -485,7 +479,9 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedLocationListList'
type: array
items:
$ref: '#/components/schemas/LocationList'
description: ''
/api/locations/{id}/:
get:
Expand Down Expand Up @@ -891,29 +887,6 @@ components:
type: array
items:
$ref: '#/components/schemas/EmotionPoint'
PaginatedLocationListList:
type: object
required:
- count
- results
properties:
count:
type: integer
example: 123
next:
type: string
nullable: true
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
$ref: '#/components/schemas/LocationList'
PatchedEmotionPoint:
type: object
properties:
Expand Down
58 changes: 22 additions & 36 deletions cityfeel/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,8 @@ def test_list_locations_authenticated(self):
response = self.client.get(self.url)

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('results', response.data) # Pagination
self.assertEqual(len(response.data['results']), 3)
self.assertIsInstance(response.data, list) # No pagination - direct array
self.assertEqual(len(response.data), 3)

def test_list_locations_unauthenticated(self):
"""Test GET /api/locations/ - unauthorized returns 401/403."""
Expand All @@ -660,7 +660,7 @@ def test_location_response_structure(self):
self.client.force_authenticate(user=self.user)
response = self.client.get(self.url)

location_data = response.data['results'][0]
location_data = response.data[0]
required_fields = ['id', 'name', 'coordinates', 'avg_emotional_value']

for field in required_fields:
Expand All @@ -676,9 +676,9 @@ def test_avg_emotional_value_calculation(self):
self.client.force_authenticate(user=self.user)
response = self.client.get(self.url)

# Znajdź location1 w results
# Znajdź location1 w response
location1_data = next(
(loc for loc in response.data['results'] if loc['id'] == self.location1.id),
(loc for loc in response.data if loc['id'] == self.location1.id),
None
)

Expand All @@ -693,7 +693,7 @@ def test_avg_emotional_value_includes_all_points(self):

# location3 ma: prywatny=4, publiczny=3, avg=(4+3)/2=3.5
location3_data = next(
(loc for loc in response.data['results'] if loc['id'] == self.location3.id),
(loc for loc in response.data if loc['id'] == self.location3.id),
None
)

Expand All @@ -706,7 +706,7 @@ def test_avg_emotional_value_null_for_no_emotions(self):
response = self.client.get(self.url)

location2_data = next(
(loc for loc in response.data['results'] if loc['id'] == self.location2.id),
(loc for loc in response.data if loc['id'] == self.location2.id),
None
)

Expand All @@ -722,16 +722,16 @@ def test_filter_by_name_exact(self):
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Powinny być 2 lokalizacje z "Gdańsk" w nazwie (location1)
# Uwaga: plan wspominał o 2, ale w setup mamy tylko 1
self.assertGreaterEqual(len(response.data['results']), 1)
for loc in response.data['results']:
self.assertGreaterEqual(len(response.data), 1)
for loc in response.data:
self.assertIn('Gdańsk', loc['name'])

def test_filter_by_name_case_insensitive(self):
"""Test czy filtrowanie jest case-insensitive."""
self.client.force_authenticate(user=self.user)
response = self.client.get(self.url, {'name': 'gdańsk'})

self.assertGreaterEqual(len(response.data['results']), 1)
self.assertGreaterEqual(len(response.data), 1)

def test_filter_by_radius(self):
"""Test ?lat=54.35&lon=18.64&radius=1000 (1km)."""
Expand All @@ -746,8 +746,8 @@ def test_filter_by_radius(self):

self.assertEqual(response.status_code, status.HTTP_200_OK)
# Tylko location1 powinna być w tym promieniu
self.assertEqual(len(response.data['results']), 1)
self.assertEqual(response.data['results'][0]['id'], self.location1.id)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]['id'], self.location1.id)

def test_filter_by_radius_large_area(self):
"""Test radius=25000 (25km) - wszystkie 3 lokalizacje."""
Expand All @@ -761,7 +761,7 @@ def test_filter_by_radius_large_area(self):
'radius': 25000 # 25km
})

self.assertEqual(len(response.data['results']), 3)
self.assertEqual(len(response.data), 3)

def test_filter_by_radius_missing_params(self):
"""Test z brakującymi parametrami - powinno zwrócić wszystkie."""
Expand All @@ -773,7 +773,7 @@ def test_filter_by_radius_missing_params(self):
'lon': self.gdansk_lon,
})

self.assertEqual(len(response.data['results']), 3)
self.assertEqual(len(response.data), 3)

def test_filter_by_radius_invalid_values(self):
"""Test z nieprawidłowymi wartościami - zwraca błąd lub pusty queryset."""
Expand All @@ -789,7 +789,7 @@ def test_filter_by_radius_invalid_values(self):
# Filter może zwrócić 400 (błąd walidacji) lub 200 z pustym queryset
# Oba są akceptowalne
if response.status_code == status.HTTP_200_OK:
self.assertEqual(len(response.data['results']), 0)
self.assertEqual(len(response.data), 0)
else:
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Expand All @@ -803,19 +803,19 @@ def test_filter_by_bbox(self):
})

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 2)
self.assertEqual(len(response.data), 2)

def test_filter_by_bbox_invalid_format(self):
"""Test bbox z nieprawidłowym formatem - pusty queryset."""
self.client.force_authenticate(user=self.user)

# Zbyt mało wartości
response = self.client.get(self.url, {'bbox': '18.5,54.3,18.65'})
self.assertEqual(len(response.data['results']), 0)
self.assertEqual(len(response.data), 0)

# Nieprawidłowy format
response = self.client.get(self.url, {'bbox': 'invalid,bbox,format,here'})
self.assertEqual(len(response.data['results']), 0)
self.assertEqual(len(response.data), 0)

# --- READ-ONLY TESTS ---
def test_post_not_allowed(self):
Expand Down Expand Up @@ -851,22 +851,8 @@ def test_delete_not_allowed(self):
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)

# --- PAGINATION TESTS ---
def test_pagination_default_page_size(self):
"""Test paginacji (15 lokalizacji, page_size=10)."""
self.client.force_authenticate(user=self.user)

# Utwórz 12 dodatkowych lokalizacji (12 + 3 istniejące = 15)
for i in range(12):
Location.objects.create(
name=f'Location {i}',
coordinates=Point(18.0 + i*0.01, 54.0 + i*0.01, srid=4326)
)

response = self.client.get(self.url)

self.assertEqual(len(response.data['results']), 10) # PAGE_SIZE=10
self.assertIn('next', response.data)
self.assertIsNotNone(response.data['next'])
# UWAGA: Paginacja dla LocationViewSet została wyłączona (pagination_class = None),
# więc testy paginacji zostały usunięte. API zwraca prostą tablicę bez paginacji.

# --- EDGE CASES ---
def test_location_with_mixed_privacy_emotion_points(self):
Expand All @@ -878,7 +864,7 @@ def test_location_with_mixed_privacy_emotion_points(self):
response = self.client.get(self.url)

location3_data = next(
(loc for loc in response.data['results'] if loc['id'] == self.location3.id),
(loc for loc in response.data if loc['id'] == self.location3.id),
None
)

Expand All @@ -890,7 +876,7 @@ def test_ordering_by_avg_emotional_value(self):
self.client.force_authenticate(user=self.user)
response = self.client.get(self.url)

results = response.data['results']
results = response.data

# Sprawdź czy lokalizacje są posortowane po avg_emotional_value (malejąco)
# Locations z wartościami powinny być przed null
Expand Down
5 changes: 3 additions & 2 deletions cityfeel/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ class EmotionPointViewSet(ModelViewSet):
class LocationViewSet(ReadOnlyModelViewSet):
"""
ViewSet dla endpointu /api/locations/ (READ-ONLY).

Zwraca lokalizacje z agregowaną średnią wartością emocjonalną (avg_emotional_value).
Średnia liczy ze WSZYSTKICH emotion_points (zarówno publicznych jak i prywatnych).

Filtrowanie:
- ?name=Gdańsk - filtrowanie po nazwie (icontains)
- ?lat=54.35&lon=18.64&radius=1000 - filtrowanie po promieniu (metry)
Expand All @@ -55,6 +55,7 @@ class LocationViewSet(ReadOnlyModelViewSet):
permission_classes = [IsAuthenticated]
filter_backends = [DjangoFilterBackend]
filterset_class = LocationFilter
pagination_class = None # Wyłącz paginację - wszystkie lokalizacje w bounding box

def get_queryset(self):
return (
Expand Down
12 changes: 6 additions & 6 deletions cityfeel/emotions/tests_privacy_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def test_both_types_visible_on_map_api(self):

response = self.client.get('/api/locations/')

location_data = next((loc for loc in response.data['results'] if loc['id'] == self.location.id), None)
location_data = next((loc for loc in response.data if loc['id'] == self.location.id), None)
self.assertEqual(float(location_data['avg_emotional_value']), 4.0)
self.assertEqual(location_data['emotion_points_count'], 2)

Expand All @@ -112,7 +112,7 @@ def test_both_types_affect_avg_emotional_value(self):
)

response = self.client.get('/api/locations/')
loc = response.data['results'][0]
loc = response.data[0]
self.assertEqual(float(loc['avg_emotional_value']), 3.0)

def test_public_emotion_visible_in_api_emotion_points(self):
Expand Down Expand Up @@ -176,7 +176,7 @@ def test_comments_only_on_public_emotion_points(self):
)

response = self.client.get('/api/locations/')
loc = response.data['results'][0]
loc = response.data[0]
# Oczekujemy 1, bo drugi jest prywatny
self.assertEqual(loc['comments_count'], 1)

Expand Down Expand Up @@ -213,7 +213,7 @@ def test_latest_comment_only_from_public_emotion_points(self):
)

response = self.client.get('/api/locations/')
loc = response.data['results'][0]
loc = response.data[0]
# Ponieważ 'Visible' został utworzony później, on jest najnowszy.
# Gdyby 'Secret' był nowszy, API zwróciłoby go jako 'Anonim' (zgodnie z nową logiką)
# Ten test weryfikuje głównie poprawne sortowanie i dostępność
Expand Down Expand Up @@ -243,7 +243,7 @@ def test_comments_count_only_public_emotion_points(self):
)

response = self.client.get('/api/locations/')
self.assertEqual(response.data['results'][0]['comments_count'], 2)
self.assertEqual(response.data[0]['comments_count'], 2)

# =========================================================================
# 2. PHOTOS
Expand Down Expand Up @@ -456,5 +456,5 @@ def test_photos_not_counted_in_emotion_stats(self):
Photo.objects.create(user=self.user, location=self.location, image='p.jpg')

response = self.client.get('/api/locations/')
loc = response.data['results'][0]
loc = response.data[0]
self.assertEqual(loc['emotion_points_count'], 0)
Loading