From 591d2aebe9bb64d1e557c7c895df6bb9132a26a1 Mon Sep 17 00:00:00 2001 From: "Marianne Angelika B. Santos" <142327703+Akiri017@users.noreply.github.com> Date: Thu, 11 Sep 2025 07:50:49 +0000 Subject: [PATCH 1/4] Checkpoint from VS Code for coding agent session --- .vscode/launch.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2ba986f --- /dev/null +++ b/.vscode/launch.json @@ -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}" + } + ] +} \ No newline at end of file From 5484d3fe898c40ef3a8bcb9629dc082caa5701d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Sep 2025 07:50:56 +0000 Subject: [PATCH 2/4] Initial plan From d6932d757ba47db56381e4169877192eee00e251 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Sep 2025 07:59:32 +0000 Subject: [PATCH 3/4] Implement search and filter functionality for dog breeds Co-authored-by: Akiri017 <142327703+Akiri017@users.noreply.github.com> --- client/src/components/DogList.svelte | 84 +++++++++++++++++++- server/app.py | 30 +++++++- server/test_app.py | 110 ++++++++++++++++++++++++++- 3 files changed, 214 insertions(+), 10 deletions(-) diff --git a/client/src/components/DogList.svelte b/client/src/components/DogList.svelte index b28bc36..4c4aed5 100644 --- a/client/src/components/DogList.svelte +++ b/client/src/components/DogList.svelte @@ -5,16 +5,47 @@ id: number; name: string; breed: string; + status: string; } export let dogs: Dog[] = []; let loading = true; let error: string | null = null; + let breeds: string[] = []; + let selectedBreed = ''; + let availableOnly = false; + + const fetchBreeds = async () => { + try { + const response = await fetch('/api/breeds'); + if(response.ok) { + breeds = await response.json(); + } else { + console.error('Failed to fetch breeds:', response.status); + } + } catch (err) { + console.error('Error fetching breeds:', err); + } + }; const fetchDogs = async () => { loading = true; try { - const response = await fetch('/api/dogs'); + let url = '/api/dogs'; + const params = new URLSearchParams(); + + if (selectedBreed) { + params.append('breed', selectedBreed); + } + if (availableOnly) { + params.append('available', 'true'); + } + + if (params.toString()) { + url += '?' + params.toString(); + } + + const response = await fetch(url); if(response.ok) { dogs = await response.json(); } else { @@ -28,13 +59,53 @@ }; onMount(() => { + fetchBreeds(); fetchDogs(); }); + + // Reactive statement to refetch dogs when filters change + $: if (selectedBreed !== undefined && availableOnly !== undefined) { + fetchDogs(); + }

Available Dogs

+ +
+
+ +
+ + +
+ + +
+ +
+
+
+ {#if loading}
@@ -72,7 +143,16 @@

{dog.name}

-

{dog.breed}

+

{dog.breed}

+
+ {#if dog.status === 'AVAILABLE'} + Available + {:else if dog.status === 'PENDING'} + Pending Adoption + {:else if dog.status === 'ADOPTED'} + Adopted + {/if} +
View details diff --git a/server/app.py b/server/app.py index b6331cc..cb2519e 100644 --- a/server/app.py +++ b/server/app.py @@ -1,6 +1,6 @@ import os from typing import Dict, List, Any, Optional -from flask import Flask, jsonify, Response +from flask import Flask, jsonify, Response, request from models import init_db, db, Dog, Breed # Get the server directory path @@ -15,12 +15,26 @@ @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 models.dog import AdoptionStatus + query = query.filter(Dog.status == AdoptionStatus.AVAILABLE) + dogs_query = query.all() # Convert the result to a list of dictionaries @@ -28,7 +42,8 @@ def get_dogs() -> Response: { '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 \ No newline at end of file diff --git a/server/test_app.py b/server/test_app.py index adbdf5f..ac6bd06 100644 --- a/server/test_app.py +++ b/server/test_app.py @@ -12,13 +12,15 @@ 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 + dog.to_dict.return_value = {'id': dog_id, 'name': name, 'breed': breed, 'status': status} return dog def _setup_query_mock(self, mock_query, dogs): @@ -26,9 +28,16 @@ def _setup_query_mock(self, mock_query, dogs): 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 + def _create_mock_breed(self, name): + """Helper method to create a mock breed""" + breed = MagicMock(spec=['name']) + breed.name = name + return breed + @patch('app.db.session.query') def test_get_dogs_success(self, mock_query): """Test successful retrieval of multiple dogs""" @@ -52,11 +61,13 @@ 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() @@ -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('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('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('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('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('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__': From dd40237bf63d9ea6b7638ae645165bb043aca436 Mon Sep 17 00:00:00 2001 From: "Marianne Angelika B. Santos" <142327703+Akiri017@users.noreply.github.com> Date: Thu, 11 Sep 2025 08:27:20 +0000 Subject: [PATCH 4/4] Tests patch --- server/app.py | 4 ++-- server/test_app.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/server/app.py b/server/app.py index cb2519e..576850e 100644 --- a/server/app.py +++ b/server/app.py @@ -1,7 +1,7 @@ import os from typing import Dict, List, Any, Optional from flask import Flask, jsonify, Response, request -from models import init_db, db, Dog, Breed +from server.models import init_db, db, Dog, Breed # Get the server directory path base_dir: str = os.path.abspath(os.path.dirname(__file__)) @@ -32,7 +32,7 @@ def get_dogs() -> Response: # Apply availability filter if requested if available_only: - from models.dog import AdoptionStatus + from server.models.dog import AdoptionStatus query = query.filter(Dog.status == AdoptionStatus.AVAILABLE) dogs_query = query.all() diff --git a/server/test_app.py b/server/test_app.py index ac6bd06..f878ba4 100644 --- a/server/test_app.py +++ b/server/test_app.py @@ -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): @@ -38,7 +38,7 @@ def _create_mock_breed(self, name): breed.name = name return breed - @patch('app.db.session.query') + @patch('server.app.db.session.query') def test_get_dogs_success(self, mock_query): """Test successful retrieval of multiple dogs""" # Arrange @@ -72,7 +72,7 @@ def test_get_dogs_success(self, mock_query): # 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 @@ -86,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 @@ -102,7 +102,7 @@ def test_get_dogs_structure(self, mock_query): self.assertEqual(len(data), 1) self.assertEqual(set(data[0].keys()), {'id', 'name', 'breed', 'status'}) - @patch('app.db.session.query') + @patch('server.app.db.session.query') def test_get_dogs_breed_filter(self, mock_query): """Test filtering dogs by breed""" # Arrange @@ -120,7 +120,7 @@ def test_get_dogs_breed_filter(self, mock_query): for dog in data: self.assertEqual(dog['breed'], 'Beagle') - @patch('app.db.session.query') + @patch('server.app.db.session.query') def test_get_dogs_available_filter(self, mock_query): """Test filtering dogs by availability""" # Arrange @@ -136,7 +136,7 @@ def test_get_dogs_available_filter(self, mock_query): self.assertEqual(len(data), 1) self.assertEqual(data[0]['status'], 'AVAILABLE') - @patch('app.db.session.query') + @patch('server.app.db.session.query') def test_get_dogs_combined_filters(self, mock_query): """Test filtering dogs by both breed and availability""" # Arrange @@ -153,7 +153,7 @@ def test_get_dogs_combined_filters(self, mock_query): self.assertEqual(data[0]['breed'], 'Beagle') self.assertEqual(data[0]['status'], 'AVAILABLE') - @patch('app.db.session.query') + @patch('server.app.db.session.query') def test_get_breeds_success(self, mock_query): """Test successful retrieval of breed list""" # Arrange @@ -176,7 +176,7 @@ def test_get_breeds_success(self, mock_query): self.assertIn("Labrador", data) self.assertIn("Beagle", data) - @patch('app.db.session.query') + @patch('server.app.db.session.query') def test_get_breeds_empty(self, mock_query): """Test retrieval when no breeds are available""" # Arrange