-
Notifications
You must be signed in to change notification settings - Fork 92
Implementation of Filter Functionality for Dog Breeds and Adoption Availability #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
591d2ae
5484d3f
d6932d7
791b204
dd40237
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"type": "chrome", | ||
"request": "launch", | ||
"name": "Launch Chrome against localhost", | ||
"url": "http://localhost:8080", | ||
"webRoot": "${workspaceFolder}" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import os | ||
from typing import Dict, List, Any, Optional | ||
from flask import Flask, jsonify, Response | ||
from models import init_db, db, Dog, Breed | ||
from flask import Flask, jsonify, Response, request | ||
from server.models import init_db, db, Dog, Breed | ||
|
||
# Get the server directory path | ||
base_dir: str = os.path.abspath(os.path.dirname(__file__)) | ||
|
@@ -15,20 +15,35 @@ | |
|
||
@app.route('/api/dogs', methods=['GET']) | ||
def get_dogs() -> Response: | ||
# Get query parameters for filtering | ||
breed_filter = request.args.get('breed') | ||
available_only = request.args.get('available') == 'true' | ||
|
||
query = db.session.query( | ||
Dog.id, | ||
Dog.name, | ||
Breed.name.label('breed') | ||
Breed.name.label('breed'), | ||
Dog.status | ||
).join(Breed, Dog.breed_id == Breed.id) | ||
|
||
# Apply breed filter if provided | ||
if breed_filter: | ||
query = query.filter(Breed.name == breed_filter) | ||
|
||
# Apply availability filter if requested | ||
if available_only: | ||
from server.models.dog import AdoptionStatus | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The import statement is inside the function scope. For better maintainability and performance, consider moving this import to the top of the file with the other imports. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
query = query.filter(Dog.status == AdoptionStatus.AVAILABLE) | ||
|
||
dogs_query = query.all() | ||
|
||
# Convert the result to a list of dictionaries | ||
dogs_list: List[Dict[str, Any]] = [ | ||
{ | ||
'id': dog.id, | ||
'name': dog.name, | ||
'breed': dog.breed | ||
'breed': dog.breed, | ||
'status': dog.status.name if dog.status else 'UNKNOWN' | ||
} | ||
for dog in dogs_query | ||
] | ||
|
@@ -65,7 +80,14 @@ def get_dog(id: int) -> tuple[Response, int] | Response: | |
|
||
return jsonify(dog) | ||
|
||
## HERE | ||
@app.route('/api/breeds', methods=['GET']) | ||
def get_breeds() -> Response: | ||
breeds_query = db.session.query(Breed.name).distinct().all() | ||
|
||
# Convert the result to a list of breed names | ||
breeds_list: List[str] = [breed.name for breed in breeds_query] | ||
|
||
return jsonify(breeds_list) | ||
|
||
if __name__ == '__main__': | ||
app.run(debug=True, port=5100) # Port 5100 to avoid macOS conflicts |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import unittest | ||
from unittest.mock import patch, MagicMock | ||
import json | ||
from app import app # Changed from relative import to absolute import | ||
from server.app import app # Changed to absolute import for running from project root | ||
|
||
# filepath: server/test_app.py | ||
class TestApp(unittest.TestCase): | ||
|
@@ -12,24 +12,33 @@ def setUp(self): | |
# Turn off database initialization for tests | ||
app.config['TESTING'] = True | ||
|
||
def _create_mock_dog(self, dog_id, name, breed): | ||
def _create_mock_dog(self, dog_id, name, breed, status='AVAILABLE'): | ||
"""Helper method to create a mock dog with standard attributes""" | ||
dog = MagicMock(spec=['to_dict', 'id', 'name', 'breed']) | ||
dog = MagicMock(spec=['to_dict', 'id', 'name', 'breed', 'status']) | ||
dog.id = dog_id | ||
dog.name = name | ||
dog.breed = breed | ||
dog.to_dict.return_value = {'id': dog_id, 'name': name, 'breed': breed} | ||
dog.status = MagicMock() | ||
dog.status.name = status | ||
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The mock setup creates a nested MagicMock for the status attribute. This could be simplified by directly setting Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
dog.to_dict.return_value = {'id': dog_id, 'name': name, 'breed': breed, 'status': status} | ||
return dog | ||
|
||
def _setup_query_mock(self, mock_query, dogs): | ||
"""Helper method to configure the query mock""" | ||
mock_query_instance = MagicMock() | ||
mock_query.return_value = mock_query_instance | ||
mock_query_instance.join.return_value = mock_query_instance | ||
mock_query_instance.filter.return_value = mock_query_instance | ||
mock_query_instance.all.return_value = dogs | ||
return mock_query_instance | ||
|
||
@patch('app.db.session.query') | ||
def _create_mock_breed(self, name): | ||
"""Helper method to create a mock breed""" | ||
breed = MagicMock(spec=['name']) | ||
breed.name = name | ||
return breed | ||
|
||
@patch('server.app.db.session.query') | ||
def test_get_dogs_success(self, mock_query): | ||
"""Test successful retrieval of multiple dogs""" | ||
# Arrange | ||
|
@@ -52,16 +61,18 @@ def test_get_dogs_success(self, mock_query): | |
self.assertEqual(data[0]['id'], 1) | ||
self.assertEqual(data[0]['name'], "Buddy") | ||
self.assertEqual(data[0]['breed'], "Labrador") | ||
self.assertEqual(data[0]['status'], "AVAILABLE") | ||
|
||
# Verify second dog | ||
self.assertEqual(data[1]['id'], 2) | ||
self.assertEqual(data[1]['name'], "Max") | ||
self.assertEqual(data[1]['breed'], "German Shepherd") | ||
self.assertEqual(data[1]['status'], "AVAILABLE") | ||
|
||
# Verify query was called | ||
mock_query.assert_called_once() | ||
|
||
@patch('app.db.session.query') | ||
@patch('server.app.db.session.query') | ||
def test_get_dogs_empty(self, mock_query): | ||
"""Test retrieval when no dogs are available""" | ||
# Arrange | ||
|
@@ -75,7 +86,7 @@ def test_get_dogs_empty(self, mock_query): | |
data = json.loads(response.data) | ||
self.assertEqual(data, []) | ||
|
||
@patch('app.db.session.query') | ||
@patch('server.app.db.session.query') | ||
def test_get_dogs_structure(self, mock_query): | ||
"""Test the response structure for a single dog""" | ||
# Arrange | ||
|
@@ -89,7 +100,98 @@ def test_get_dogs_structure(self, mock_query): | |
data = json.loads(response.data) | ||
self.assertTrue(isinstance(data, list)) | ||
self.assertEqual(len(data), 1) | ||
self.assertEqual(set(data[0].keys()), {'id', 'name', 'breed'}) | ||
self.assertEqual(set(data[0].keys()), {'id', 'name', 'breed', 'status'}) | ||
|
||
@patch('server.app.db.session.query') | ||
def test_get_dogs_breed_filter(self, mock_query): | ||
"""Test filtering dogs by breed""" | ||
# Arrange | ||
beagle1 = self._create_mock_dog(1, "Buddy", "Beagle") | ||
beagle2 = self._create_mock_dog(2, "Max", "Beagle") | ||
self._setup_query_mock(mock_query, [beagle1, beagle2]) | ||
|
||
# Act | ||
response = self.app.get('/api/dogs?breed=Beagle') | ||
|
||
# Assert | ||
self.assertEqual(response.status_code, 200) | ||
data = json.loads(response.data) | ||
self.assertEqual(len(data), 2) | ||
for dog in data: | ||
self.assertEqual(dog['breed'], 'Beagle') | ||
|
||
@patch('server.app.db.session.query') | ||
def test_get_dogs_available_filter(self, mock_query): | ||
"""Test filtering dogs by availability""" | ||
# Arrange | ||
available_dog = self._create_mock_dog(1, "Buddy", "Labrador", "AVAILABLE") | ||
self._setup_query_mock(mock_query, [available_dog]) | ||
|
||
# Act | ||
response = self.app.get('/api/dogs?available=true') | ||
|
||
# Assert | ||
self.assertEqual(response.status_code, 200) | ||
data = json.loads(response.data) | ||
self.assertEqual(len(data), 1) | ||
self.assertEqual(data[0]['status'], 'AVAILABLE') | ||
|
||
@patch('server.app.db.session.query') | ||
def test_get_dogs_combined_filters(self, mock_query): | ||
"""Test filtering dogs by both breed and availability""" | ||
# Arrange | ||
available_beagle = self._create_mock_dog(1, "Buddy", "Beagle", "AVAILABLE") | ||
self._setup_query_mock(mock_query, [available_beagle]) | ||
|
||
# Act | ||
response = self.app.get('/api/dogs?breed=Beagle&available=true') | ||
|
||
# Assert | ||
self.assertEqual(response.status_code, 200) | ||
data = json.loads(response.data) | ||
self.assertEqual(len(data), 1) | ||
self.assertEqual(data[0]['breed'], 'Beagle') | ||
self.assertEqual(data[0]['status'], 'AVAILABLE') | ||
|
||
@patch('server.app.db.session.query') | ||
def test_get_breeds_success(self, mock_query): | ||
"""Test successful retrieval of breed list""" | ||
# Arrange | ||
breed1 = self._create_mock_breed("Labrador") | ||
breed2 = self._create_mock_breed("Beagle") | ||
mock_breeds = [breed1, breed2] | ||
|
||
mock_query_instance = MagicMock() | ||
mock_query.return_value = mock_query_instance | ||
mock_query_instance.distinct.return_value = mock_query_instance | ||
mock_query_instance.all.return_value = mock_breeds | ||
|
||
# Act | ||
response = self.app.get('/api/breeds') | ||
|
||
# Assert | ||
self.assertEqual(response.status_code, 200) | ||
data = json.loads(response.data) | ||
self.assertEqual(len(data), 2) | ||
self.assertIn("Labrador", data) | ||
self.assertIn("Beagle", data) | ||
|
||
@patch('server.app.db.session.query') | ||
def test_get_breeds_empty(self, mock_query): | ||
"""Test retrieval when no breeds are available""" | ||
# Arrange | ||
mock_query_instance = MagicMock() | ||
mock_query.return_value = mock_query_instance | ||
mock_query_instance.distinct.return_value = mock_query_instance | ||
mock_query_instance.all.return_value = [] | ||
|
||
# Act | ||
response = self.app.get('/api/breeds') | ||
|
||
# Assert | ||
self.assertEqual(response.status_code, 200) | ||
data = json.loads(response.data) | ||
self.assertEqual(data, []) | ||
|
||
|
||
if __name__ == '__main__': | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reactive statement will trigger
fetchDogs()
on component initialization when both variables are defined, causing an unnecessary duplicate API call sincefetchDogs()
is already called inonMount()
. Consider checking if the component has been mounted or if the values have actually changed from their initial state.Copilot uses AI. Check for mistakes.