Skip to content

Commit 6b72688

Browse files
committed
Updated instructions files
1 parent e948956 commit 6b72688

File tree

5 files changed

+803
-1
lines changed

5 files changed

+803
-1
lines changed

.github/copilot-instructions.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The Tailspin Shelter is a full-stack web application that showcases a fictional
1010
- **Styling**: [Tailwind CSS](https://tailwindcss.com/) v4.0+ - Utility-first CSS framework
1111
- **Language**: TypeScript - Strongly typed JavaScript
1212
- **Adapter**: Node.js adapter for server-side rendering
13+
- **E2E Testing**: [Playwright](https://playwright.dev/) v1.49+ - End-to-end testing framework
1314

1415
### Backend (Server)
1516
- **Framework**: [Flask](https://flask.palletsprojects.com/) - Python web framework
@@ -26,7 +27,8 @@ pets-workshop/
2627
│ ├── src/components/ # Svelte components (DogList, DogDetails)
2728
│ ├── src/layouts/ # Astro layout templates
2829
│ ├── src/pages/ # Astro pages (routing)
29-
│ └── src/styles/ # Global CSS and Tailwind imports
30+
│ ├── src/styles/ # Global CSS and Tailwind imports
31+
│ └── e2e-tests/ # Playwright end-to-end tests
3032
├── server/ # Flask backend API
3133
│ ├── models/ # SQLAlchemy models (Dog, Breed)
3234
│ ├── tests/ # Python unit tests
@@ -49,6 +51,7 @@ pets-workshop/
4951
### Use Scripts, Not Direct Commands
5052
**IMPORTANT**: Always prefer using the provided scripts in the `scripts/` directory rather than running commands directly:
5153
- **Testing**: Use `./scripts/run-server-tests.sh` instead of `python -m unittest`
54+
- **E2E Testing**: Use `npm run test:e2e` in the `client/` directory for Playwright tests
5255
- **Environment Setup**: Use `./scripts/setup-environment.sh` for initial setup
5356
- **Application Start**: Use `./scripts/start-app.sh` to launch the application
5457

@@ -68,6 +71,13 @@ pets-workshop/
6871
- **Queries**: Prefer SQLAlchemy query syntax over raw SQL
6972
- **Data Seeding**: Use the utilities in `utils/seed_database.py`
7073

74+
### Testing Patterns
75+
- **E2E Tests**: Playwright tests in `client/e2e-tests/` cover full user workflows
76+
- **Test Structure**: Organize tests by page/feature (homepage, dog-details, API integration)
77+
- **Test Commands**: Use `npm run test:e2e` for all tests, `npm run test:e2e:ui` for debugging
78+
- **Server Tests**: Python unittest framework for backend API testing
79+
- **Test Coverage**: Include tests for user interactions, API responses, and error handling
80+
7181
## Coding Standards
7282

7383
### Python (Backend)
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
---
2+
applyTo: "**/test_*.py"
3+
---
4+
5+
# Flask Testing Guidelines for Tailspin Shelter
6+
7+
Essential patterns for writing unit tests for Flask APIs using Python's unittest framework.
8+
9+
## Core Principles
10+
11+
1. **Mock Database Queries** - Use `unittest.mock` to isolate API logic
12+
2. **Test HTTP Responses** - Verify status codes, JSON structure, error messages
13+
3. **Helper Methods** - Create reusable mock objects and setup methods
14+
15+
## Basic Test Structure
16+
17+
```python
18+
import unittest
19+
from unittest.mock import patch, MagicMock
20+
import json
21+
from app import app
22+
23+
class TestDogAPI(unittest.TestCase):
24+
def setUp(self):
25+
self.app = app.test_client()
26+
self.app.testing = True
27+
app.config['TESTING'] = True
28+
```
29+
30+
## Testing GET Endpoints
31+
32+
### List Endpoint
33+
```python
34+
@patch('app.db.session.query')
35+
def test_get_dogs_success(self, mock_query):
36+
# Setup mock
37+
mock_dog = MagicMock()
38+
mock_dog.id = 1
39+
mock_dog.name = "Buddy"
40+
mock_dog.breed = "Golden Retriever"
41+
42+
mock_query_instance = MagicMock()
43+
mock_query.return_value = mock_query_instance
44+
mock_query_instance.join.return_value = mock_query_instance
45+
mock_query_instance.all.return_value = [mock_dog]
46+
47+
response = self.app.get('/api/dogs')
48+
49+
self.assertEqual(response.status_code, 200)
50+
data = json.loads(response.data)
51+
self.assertEqual(len(data), 1)
52+
self.assertEqual(data[0]['name'], "Buddy")
53+
```
54+
55+
### Single Resource
56+
```python
57+
@patch('app.db.session.query')
58+
def test_get_dog_by_id_success(self, mock_query):
59+
mock_dog = MagicMock()
60+
mock_dog.id = 1
61+
mock_dog.name = "Buddy"
62+
mock_dog.status.name = "AVAILABLE"
63+
64+
mock_query_instance = MagicMock()
65+
mock_query.return_value = mock_query_instance
66+
mock_query_instance.join.return_value = mock_query_instance
67+
mock_query_instance.filter.return_value = mock_query_instance
68+
mock_query_instance.first.return_value = mock_dog
69+
70+
response = self.app.get('/api/dogs/1')
71+
72+
self.assertEqual(response.status_code, 200)
73+
data = json.loads(response.data)
74+
self.assertEqual(data['name'], "Buddy")
75+
76+
@patch('app.db.session.query')
77+
def test_get_dog_not_found(self, mock_query):
78+
mock_query_instance = MagicMock()
79+
mock_query.return_value = mock_query_instance
80+
mock_query_instance.join.return_value = mock_query_instance
81+
mock_query_instance.filter.return_value = mock_query_instance
82+
mock_query_instance.first.return_value = None
83+
84+
response = self.app.get('/api/dogs/999')
85+
86+
self.assertEqual(response.status_code, 404)
87+
data = json.loads(response.data)
88+
self.assertEqual(data['error'], "Dog not found")
89+
```
90+
91+
## Testing POST Endpoints
92+
93+
```python
94+
@patch('app.db.session')
95+
@patch('app.Dog')
96+
def test_create_dog_success(self, mock_dog_class, mock_session):
97+
mock_dog_instance = MagicMock()
98+
mock_dog_instance.id = 1
99+
mock_dog_instance.name = "New Dog"
100+
mock_dog_class.return_value = mock_dog_instance
101+
102+
dog_data = {'name': 'New Dog', 'breed_id': 1, 'age': 2}
103+
104+
response = self.app.post('/api/dogs',
105+
data=json.dumps(dog_data),
106+
content_type='application/json')
107+
108+
self.assertEqual(response.status_code, 201)
109+
data = json.loads(response.data)
110+
self.assertEqual(data['name'], "New Dog")
111+
mock_session.add.assert_called_once()
112+
mock_session.commit.assert_called_once()
113+
114+
def test_create_dog_missing_fields(self):
115+
dog_data = {'name': 'Incomplete Dog'} # Missing breed_id
116+
117+
response = self.app.post('/api/dogs',
118+
data=json.dumps(dog_data),
119+
content_type='application/json')
120+
121+
self.assertEqual(response.status_code, 400)
122+
data = json.loads(response.data)
123+
self.assertIn('Missing required fields', data['error'])
124+
```
125+
126+
## Testing PUT/DELETE Endpoints
127+
128+
```python
129+
@patch('app.Dog.query')
130+
@patch('app.db.session')
131+
def test_update_dog_success(self, mock_session, mock_query):
132+
mock_dog = MagicMock()
133+
mock_dog.id = 1
134+
mock_query.get.return_value = mock_dog
135+
136+
update_data = {'name': 'Updated Name', 'age': 4}
137+
138+
response = self.app.put('/api/dogs/1',
139+
data=json.dumps(update_data),
140+
content_type='application/json')
141+
142+
self.assertEqual(response.status_code, 200)
143+
self.assertEqual(mock_dog.name, "Updated Name")
144+
mock_session.commit.assert_called_once()
145+
146+
@patch('app.Dog.query')
147+
@patch('app.db.session')
148+
def test_delete_dog_success(self, mock_session, mock_query):
149+
mock_dog = MagicMock()
150+
mock_query.get.return_value = mock_dog
151+
152+
response = self.app.delete('/api/dogs/1')
153+
154+
self.assertEqual(response.status_code, 200)
155+
mock_session.delete.assert_called_once_with(mock_dog)
156+
mock_session.commit.assert_called_once()
157+
```
158+
159+
## Error Handling Tests
160+
161+
```python
162+
@patch('app.db.session')
163+
def test_database_error_handling(self, mock_session):
164+
mock_session.commit.side_effect = Exception("Database error")
165+
166+
dog_data = {'name': 'Test Dog', 'breed_id': 1}
167+
168+
response = self.app.post('/api/dogs',
169+
data=json.dumps(dog_data),
170+
content_type='application/json')
171+
172+
self.assertEqual(response.status_code, 500)
173+
data = json.loads(response.data)
174+
self.assertIn('Internal server error', data['error'])
175+
mock_session.rollback.assert_called_once()
176+
```
177+
178+
## Helper Methods
179+
180+
```python
181+
def _create_mock_dog(self, dog_id: int, name: str, breed: str):
182+
mock_dog = MagicMock()
183+
mock_dog.id = dog_id
184+
mock_dog.name = name
185+
mock_dog.breed = breed
186+
mock_dog.status.name = "AVAILABLE"
187+
return mock_dog
188+
189+
def _setup_query_mock(self, mock_query, return_data):
190+
mock_query_instance = MagicMock()
191+
mock_query.return_value = mock_query_instance
192+
mock_query_instance.join.return_value = mock_query_instance
193+
mock_query_instance.filter.return_value = mock_query_instance
194+
mock_query_instance.all.return_value = return_data
195+
mock_query_instance.first.return_value = return_data[0] if return_data else None
196+
return mock_query_instance
197+
```
198+
199+
## Running Tests
200+
201+
```bash
202+
# Run all tests
203+
python -m unittest discover tests/
204+
205+
# Run with project script
206+
./scripts/run-server-tests.sh
207+
```
208+
209+
## Key Testing Patterns
210+
211+
- **Mock database operations** to isolate API logic
212+
- **Test both success and error cases** for each endpoint
213+
- **Verify HTTP status codes** and response JSON structure
214+
- **Use helper methods** to reduce code duplication
215+
- **Assert database operations** like `add()`, `commit()`, `rollback()`

0 commit comments

Comments
 (0)