diff --git a/dashboard/tests/test_habits_api.py b/dashboard/tests/test_habits_api.py index 968d573..39507d2 100644 --- a/dashboard/tests/test_habits_api.py +++ b/dashboard/tests/test_habits_api.py @@ -39,14 +39,15 @@ def cleanup_test_env(temp_dir): api.HABITS_FILE = original_habits_file shutil.rmtree(temp_dir) -def start_test_server(port=8765): - """Start test server in background thread.""" - server = HTTPServer(('localhost', port), api.TaskBoardHandler) +def start_test_server(): + """Start test server in background thread with random available port.""" + server = HTTPServer(('localhost', 0), api.TaskBoardHandler) # Port 0 = random + port = server.server_address[1] # Get actual assigned port thread = threading.Thread(target=server.serve_forever) thread.daemon = True thread.start() - time.sleep(0.5) # Give server time to start - return server + time.sleep(0.3) # Give server time to start + return server, port def http_get(path, port=8765): """Make HTTP GET request.""" @@ -103,10 +104,10 @@ def http_delete(path, port=8765): # Test 1: GET /api/habits returns empty array when no habits def test_get_habits_empty(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: - status, data = http_get('/api/habits') + status, data = http_get('/api/habits', port) assert status == 200, f"Expected 200, got {status}" assert data == [], f"Expected empty array, got {data}" print("✓ Test 1: GET /api/habits returns empty array") @@ -117,7 +118,7 @@ def test_get_habits_empty(): # Test 2: POST /api/habits creates new habit with valid input def test_post_habit_valid(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: habit_data = { @@ -133,7 +134,7 @@ def test_post_habit_valid(): } } - status, data = http_post('/api/habits', habit_data) + status, data = http_post('/api/habits', habit_data, port) assert status == 201, f"Expected 201, got {status}" assert 'id' in data, "Response should include habit id" assert data['name'] == 'Morning Exercise', f"Name mismatch: {data['name']}" @@ -149,10 +150,10 @@ def test_post_habit_valid(): # Test 3: POST validates name is required def test_post_habit_missing_name(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: - status, data = http_post('/api/habits', {}) + status, data = http_post('/api/habits', {}, port) assert status == 400, f"Expected 400, got {status}" assert 'error' in data, "Response should include error" assert 'name' in data['error'].lower(), f"Error should mention name: {data['error']}" @@ -164,10 +165,10 @@ def test_post_habit_missing_name(): # Test 4: POST validates name max 100 chars def test_post_habit_name_too_long(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: - status, data = http_post('/api/habits', {'name': 'x' * 101}) + status, data = http_post('/api/habits', {'name': 'x' * 101}, port) assert status == 400, f"Expected 400, got {status}" assert 'error' in data, "Response should include error" assert '100' in data['error'], f"Error should mention max length: {data['error']}" @@ -179,13 +180,13 @@ def test_post_habit_name_too_long(): # Test 5: POST validates color hex format def test_post_habit_invalid_color(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: status, data = http_post('/api/habits', { 'name': 'Test', 'color': 'not-a-hex-color' - }) + }, port) assert status == 400, f"Expected 400, got {status}" assert 'error' in data, "Response should include error" assert 'color' in data['error'].lower(), f"Error should mention color: {data['error']}" @@ -197,13 +198,13 @@ def test_post_habit_invalid_color(): # Test 6: POST validates frequency type def test_post_habit_invalid_frequency(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: status, data = http_post('/api/habits', { 'name': 'Test', 'frequency': {'type': 'invalid_type'} - }) + }, port) assert status == 400, f"Expected 400, got {status}" assert 'error' in data, "Response should include error" assert 'frequency' in data['error'].lower(), f"Error should mention frequency: {data['error']}" @@ -215,15 +216,15 @@ def test_post_habit_invalid_frequency(): # Test 7: GET /api/habits returns habits with stats enriched def test_get_habits_with_stats(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit first habit_data = {'name': 'Daily Reading', 'frequency': {'type': 'daily'}} - http_post('/api/habits', habit_data) + http_post('/api/habits', habit_data, port) # Get habits - status, data = http_get('/api/habits') + status, data = http_get('/api/habits', port) assert status == 200, f"Expected 200, got {status}" assert len(data) == 1, f"Expected 1 habit, got {len(data)}" @@ -240,16 +241,16 @@ def test_get_habits_with_stats(): # Test 8: GET /api/habits sorts by priority ascending def test_get_habits_sorted_by_priority(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create habits with different priorities - http_post('/api/habits', {'name': 'Low Priority', 'priority': 10}) - http_post('/api/habits', {'name': 'High Priority', 'priority': 1}) - http_post('/api/habits', {'name': 'Medium Priority', 'priority': 5}) + http_post('/api/habits', {'name': 'Low Priority', 'priority': 10}, port) + http_post('/api/habits', {'name': 'High Priority', 'priority': 1}, port) + http_post('/api/habits', {'name': 'Medium Priority', 'priority': 5}, port) # Get habits - status, data = http_get('/api/habits') + status, data = http_get('/api/habits', port) assert status == 200, f"Expected 200, got {status}" assert len(data) == 3, f"Expected 3 habits, got {len(data)}" @@ -265,10 +266,10 @@ def test_get_habits_sorted_by_priority(): # Test 9: POST returns 400 for invalid JSON def test_post_habit_invalid_json(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: - url = f'http://localhost:8765/api/habits' + url = f'http://localhost:{port}/api/habits' req = urllib.request.Request( url, data=b'invalid json{', @@ -287,10 +288,10 @@ def test_post_habit_invalid_json(): # Test 10: POST initializes streak.current=0 def test_post_habit_initial_streak(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: - status, data = http_post('/api/habits', {'name': 'Test Habit'}) + status, data = http_post('/api/habits', {'name': 'Test Habit'}, port) assert status == 201, f"Expected 201, got {status}" assert data['streak']['current'] == 0, "Initial streak.current should be 0" assert data['streak']['best'] == 0, "Initial streak.best should be 0" @@ -303,7 +304,7 @@ def test_post_habit_initial_streak(): # Test 12: PUT /api/habits/{id} updates habit successfully def test_put_habit_valid(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit first @@ -313,7 +314,7 @@ def test_put_habit_valid(): 'color': '#10b981', 'priority': 3 } - status, created_habit = http_post('/api/habits', habit_data) + status, created_habit = http_post('/api/habits', habit_data, port) habit_id = created_habit['id'] # Update the habit @@ -324,7 +325,7 @@ def test_put_habit_valid(): 'priority': 1, 'notes': 'New notes' } - status, updated_habit = http_put(f'/api/habits/{habit_id}', update_data) + status, updated_habit = http_put(f'/api/habits/{habit_id}', update_data, port) assert status == 200, f"Expected 200, got {status}" assert updated_habit['name'] == 'Updated Name', "Name not updated" @@ -341,12 +342,12 @@ def test_put_habit_valid(): # Test 13: PUT /api/habits/{id} does not allow editing protected fields def test_put_habit_protected_fields(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit first habit_data = {'name': 'Test Habit'} - status, created_habit = http_post('/api/habits', habit_data) + status, created_habit = http_post('/api/habits', habit_data, port) habit_id = created_habit['id'] original_created_at = created_habit['createdAt'] @@ -359,7 +360,7 @@ def test_put_habit_protected_fields(): 'lives': 10, 'completions': [{'date': '2025-01-01'}] } - status, updated_habit = http_put(f'/api/habits/{habit_id}', update_data) + status, updated_habit = http_put(f'/api/habits/{habit_id}', update_data, port) assert status == 200, f"Expected 200, got {status}" assert updated_habit['name'] == 'Updated Name', "Name should be updated" @@ -376,11 +377,11 @@ def test_put_habit_protected_fields(): # Test 14: PUT /api/habits/{id} returns 404 for non-existent habit def test_put_habit_not_found(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: update_data = {'name': 'Updated Name'} - status, response = http_put('/api/habits/non-existent-id', update_data) + status, response = http_put('/api/habits/non-existent-id', update_data, port) assert status == 404, f"Expected 404, got {status}" assert 'error' in response, "Expected error message" @@ -392,27 +393,27 @@ def test_put_habit_not_found(): # Test 15: PUT /api/habits/{id} validates input def test_put_habit_invalid_input(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit first habit_data = {'name': 'Test Habit'} - status, created_habit = http_post('/api/habits', habit_data) + status, created_habit = http_post('/api/habits', habit_data, port) habit_id = created_habit['id'] # Test invalid color update_data = {'color': 'not-a-hex-color'} - status, response = http_put(f'/api/habits/{habit_id}', update_data) + status, response = http_put(f'/api/habits/{habit_id}', update_data, port) assert status == 400, f"Expected 400 for invalid color, got {status}" # Test empty name update_data = {'name': ''} - status, response = http_put(f'/api/habits/{habit_id}', update_data) + status, response = http_put(f'/api/habits/{habit_id}', update_data, port) assert status == 400, f"Expected 400 for empty name, got {status}" # Test name too long update_data = {'name': 'x' * 101} - status, response = http_put(f'/api/habits/{habit_id}', update_data) + status, response = http_put(f'/api/habits/{habit_id}', update_data, port) assert status == 400, f"Expected 400 for long name, got {status}" print("✓ Test 15: PUT /api/habits/{id} validates input") @@ -423,24 +424,24 @@ def test_put_habit_invalid_input(): # Test 16: DELETE /api/habits/{id} removes habit successfully def test_delete_habit_success(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit first habit_data = {'name': 'Habit to Delete'} - status, created_habit = http_post('/api/habits', habit_data) + status, created_habit = http_post('/api/habits', habit_data, port) habit_id = created_habit['id'] # Verify habit exists - status, habits = http_get('/api/habits') + status, habits = http_get('/api/habits', port) assert len(habits) == 1, "Should have 1 habit" # Delete the habit - status, _ = http_delete(f'/api/habits/{habit_id}') + status, _ = http_delete(f'/api/habits/{habit_id}', port) assert status == 204, f"Expected 204, got {status}" # Verify habit is deleted - status, habits = http_get('/api/habits') + status, habits = http_get('/api/habits', port) assert len(habits) == 0, "Should have 0 habits after deletion" print("✓ Test 16: DELETE /api/habits/{id} removes habit successfully") finally: @@ -450,10 +451,10 @@ def test_delete_habit_success(): # Test 17: DELETE /api/habits/{id} returns 404 for non-existent habit def test_delete_habit_not_found(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: - status, response = http_delete('/api/habits/non-existent-id') + status, response = http_delete('/api/habits/non-existent-id', port) assert status == 404, f"Expected 404, got {status}" assert 'error' in response, "Expected error message" @@ -465,11 +466,11 @@ def test_delete_habit_not_found(): # Test 18: do_OPTIONS includes PUT and DELETE methods def test_options_includes_put_delete(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Make OPTIONS request - url = 'http://localhost:8765/api/habits' + url = f'http://localhost:{port}/api/habits' req = urllib.request.Request(url, method='OPTIONS') with urllib.request.urlopen(req) as response: allowed_methods = response.headers.get('Access-Control-Allow-Methods', '') @@ -483,19 +484,19 @@ def test_options_includes_put_delete(): # Test 20: POST /api/habits/{id}/check adds completion entry def test_check_in_basic(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a daily habit status, habit = http_post('/api/habits', { 'name': 'Morning Exercise', 'frequency': {'type': 'daily'} - }) + }, port) assert status == 201, f"Failed to create habit: {status}" habit_id = habit['id'] # Check in on the habit - status, updated_habit = http_post(f'/api/habits/{habit_id}/check', {}) + status, updated_habit = http_post(f'/api/habits/{habit_id}/check', {}, port) assert status == 200, f"Expected 200, got {status}" assert len(updated_habit['completions']) == 1, "Expected 1 completion" @@ -509,14 +510,14 @@ def test_check_in_basic(): # Test 21: Check-in accepts optional note, rating, mood def test_check_in_with_details(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a daily habit status, habit = http_post('/api/habits', { 'name': 'Meditation', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] # Check in with details @@ -524,7 +525,7 @@ def test_check_in_with_details(): 'note': 'Felt very relaxed today', 'rating': 5, 'mood': 'happy' - }) + }, port) assert status == 200, f"Expected 200, got {status}" completion = updated_habit['completions'][0] @@ -539,10 +540,10 @@ def test_check_in_with_details(): # Test 22: Check-in returns 404 if habit not found def test_check_in_not_found(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: - status, response = http_post('/api/habits/non-existent-id/check', {}) + status, response = http_post('/api/habits/non-existent-id/check', {}, port) assert status == 404, f"Expected 404, got {status}" assert 'error' in response @@ -554,7 +555,7 @@ def test_check_in_not_found(): # Test 23: Check-in returns 400 if habit not relevant for today def test_check_in_not_relevant(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit for specific days (e.g., Monday only) @@ -568,11 +569,11 @@ def test_check_in_not_relevant(): 'type': 'specific_days', 'days': [different_day] } - }) + }, port) habit_id = habit['id'] # Try to check in - status, response = http_post(f'/api/habits/{habit_id}/check', {}) + status, response = http_post(f'/api/habits/{habit_id}/check', {}, port) assert status == 400, f"Expected 400, got {status}" assert 'not relevant' in response.get('error', '').lower() @@ -584,22 +585,22 @@ def test_check_in_not_relevant(): # Test 24: Check-in returns 409 if already checked today def test_check_in_already_checked(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a daily habit status, habit = http_post('/api/habits', { 'name': 'Water Plants', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] # Check in once - status, _ = http_post(f'/api/habits/{habit_id}/check', {}) + status, _ = http_post(f'/api/habits/{habit_id}/check', {}, port) assert status == 200, "First check-in should succeed" # Try to check in again - status, response = http_post(f'/api/habits/{habit_id}/check', {}) + status, response = http_post(f'/api/habits/{habit_id}/check', {}, port) assert status == 409, f"Expected 409, got {status}" assert 'already checked' in response.get('error', '').lower() @@ -611,18 +612,18 @@ def test_check_in_already_checked(): # Test 25: Streak is recalculated after check-in def test_check_in_updates_streak(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a daily habit status, habit = http_post('/api/habits', { 'name': 'Read', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] # Check in - status, updated_habit = http_post(f'/api/habits/{habit_id}/check', {}) + status, updated_habit = http_post(f'/api/habits/{habit_id}/check', {}, port) assert status == 200, f"Expected 200, got {status}" assert updated_habit['streak']['current'] == 1, f"Expected streak 1, got {updated_habit['streak']['current']}" @@ -635,21 +636,21 @@ def test_check_in_updates_streak(): # Test 26: lastCheckIn is updated after check-in def test_check_in_updates_last_check_in(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a daily habit status, habit = http_post('/api/habits', { 'name': 'Floss', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] # Initially lastCheckIn should be None assert habit['streak']['lastCheckIn'] is None # Check in - status, updated_habit = http_post(f'/api/habits/{habit_id}/check', {}) + status, updated_habit = http_post(f'/api/habits/{habit_id}/check', {}, port) today = datetime.now().date().isoformat() assert updated_habit['streak']['lastCheckIn'] == today @@ -661,14 +662,14 @@ def test_check_in_updates_last_check_in(): # Test 27: Lives are restored after 7 consecutive check-ins def test_check_in_life_restore(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a daily habit and manually set up 6 previous check-ins status, habit = http_post('/api/habits', { 'name': 'Yoga', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] # Manually add 6 previous check-ins and reduce lives to 2 @@ -687,7 +688,7 @@ def test_check_in_life_restore(): api.HABITS_FILE.write_text(json.dumps(habits_data, indent=2)) # Check in for today (7th consecutive) - status, updated_habit = http_post(f'/api/habits/{habit_id}/check', {}) + status, updated_habit = http_post(f'/api/habits/{habit_id}/check', {}, port) assert status == 200, f"Expected 200, got {status}" assert updated_habit['lives'] == 3, f"Expected 3 lives restored, got {updated_habit['lives']}" @@ -699,20 +700,20 @@ def test_check_in_life_restore(): # Test 28: Check-in validates rating range def test_check_in_invalid_rating(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a daily habit status, habit = http_post('/api/habits', { 'name': 'Journal', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] # Try to check in with invalid rating status, response = http_post(f'/api/habits/{habit_id}/check', { 'rating': 10 # Invalid, should be 1-5 - }) + }, port) assert status == 400, f"Expected 400, got {status}" assert 'rating' in response.get('error', '').lower() @@ -724,20 +725,20 @@ def test_check_in_invalid_rating(): # Test 29: Check-in validates mood values def test_check_in_invalid_mood(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a daily habit status, habit = http_post('/api/habits', { 'name': 'Gratitude', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] # Try to check in with invalid mood status, response = http_post(f'/api/habits/{habit_id}/check', { 'mood': 'excited' # Invalid, should be happy/neutral/sad - }) + }, port) assert status == 400, f"Expected 400, got {status}" assert 'mood' in response.get('error', '').lower() @@ -749,19 +750,19 @@ def test_check_in_invalid_mood(): # Test 30: Skip basic - decrements lives def test_skip_basic(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) assert status == 201 habit_id = habit['id'] # Skip a day - status, response = http_post(f'/api/habits/{habit_id}/skip', {}) + status, response = http_post(f'/api/habits/{habit_id}/skip', {}, port) assert status == 200, f"Expected 200, got {status}" assert response['lives'] == 2, f"Expected 2 lives, got {response['lives']}" @@ -780,28 +781,28 @@ def test_skip_basic(): # Test 31: Skip preserves streak def test_skip_preserves_streak(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) assert status == 201 habit_id = habit['id'] # Check in to build a streak - http_post(f'/api/habits/{habit_id}/check', {}) + http_post(f'/api/habits/{habit_id}/check', {}, port) # Get current streak - status, habits = http_get('/api/habits') + status, habits = http_get('/api/habits', port) current_streak = habits[0]['current_streak'] assert current_streak > 0 # Skip the next day (simulate by adding skip manually and checking streak doesn't break) # Since we can't time travel, we'll verify that skip doesn't recalculate streak - status, response = http_post(f'/api/habits/{habit_id}/skip', {}) + status, response = http_post(f'/api/habits/{habit_id}/skip', {}, port) assert status == 200, f"Expected 200, got {status}" # Verify lives decremented @@ -821,10 +822,10 @@ def test_skip_preserves_streak(): # Test 32: Skip returns 404 for non-existent habit def test_skip_not_found(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: - status, response = http_post('/api/habits/nonexistent-id/skip', {}) + status, response = http_post('/api/habits/nonexistent-id/skip', {}, port) assert status == 404, f"Expected 404, got {status}" assert 'not found' in response.get('error', '').lower() @@ -837,25 +838,25 @@ def test_skip_not_found(): # Test 33: Skip returns 400 when no lives remaining def test_skip_no_lives(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) assert status == 201 habit_id = habit['id'] # Use all 3 lives for i in range(3): - status, response = http_post(f'/api/habits/{habit_id}/skip', {}) + status, response = http_post(f'/api/habits/{habit_id}/skip', {}, port) assert status == 200, f"Skip {i+1} failed with status {status}" assert response['lives'] == 2 - i, f"Expected {2-i} lives, got {response['lives']}" # Try to skip again with no lives - status, response = http_post(f'/api/habits/{habit_id}/skip', {}) + status, response = http_post(f'/api/habits/{habit_id}/skip', {}, port) assert status == 400, f"Expected 400, got {status}" assert 'no lives remaining' in response.get('error', '').lower() @@ -868,20 +869,20 @@ def test_skip_no_lives(): # Test 34: Skip returns updated habit with new lives count def test_skip_returns_updated_habit(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) assert status == 201 habit_id = habit['id'] original_updated_at = habit['updatedAt'] # Skip a day - status, response = http_post(f'/api/habits/{habit_id}/skip', {}) + status, response = http_post(f'/api/habits/{habit_id}/skip', {}, port) assert status == 200 assert response['id'] == habit_id @@ -899,26 +900,26 @@ def test_skip_returns_updated_habit(): # Test 35: DELETE uncheck - removes completion for specified date def test_uncheck_removes_completion(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) assert status == 201 habit_id = habit['id'] # Check in on a specific date today = datetime.now().date().isoformat() - status, response = http_post(f'/api/habits/{habit_id}/check', {}) + status, response = http_post(f'/api/habits/{habit_id}/check', {}, port) assert status == 200 assert len(response['completions']) == 1 assert response['completions'][0]['date'] == today # Uncheck the habit for today - status, response = http_delete(f'/api/habits/{habit_id}/check?date={today}') + status, response = http_delete(f'/api/habits/{habit_id}/check?date={today}', port) assert status == 200 assert len(response['completions']) == 0, "Completion should be removed" assert response['id'] == habit_id @@ -931,20 +932,20 @@ def test_uncheck_removes_completion(): # Test 36: DELETE uncheck - returns 404 if no completion for date def test_uncheck_no_completion_for_date(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit (but don't check in) status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) assert status == 201 habit_id = habit['id'] # Try to uncheck a date with no completion today = datetime.now().date().isoformat() - status, response = http_delete(f'/api/habits/{habit_id}/check?date={today}') + status, response = http_delete(f'/api/habits/{habit_id}/check?date={today}', port) assert status == 404 assert 'error' in response assert 'No completion found' in response['error'] @@ -957,11 +958,11 @@ def test_uncheck_no_completion_for_date(): # Test 37: DELETE uncheck - returns 404 if habit not found def test_uncheck_habit_not_found(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: today = datetime.now().date().isoformat() - status, response = http_delete(f'/api/habits/nonexistent-id/check?date={today}') + status, response = http_delete(f'/api/habits/nonexistent-id/check?date={today}', port) assert status == 404 assert 'error' in response assert 'Habit not found' in response['error'] @@ -974,14 +975,14 @@ def test_uncheck_habit_not_found(): # Test 38: DELETE uncheck - recalculates streak correctly def test_uncheck_recalculates_streak(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) assert status == 201 habit_id = habit['id'] @@ -999,14 +1000,14 @@ def test_uncheck_recalculates_streak(): json.dump(data, f) # Get habit to verify streak is 3 - status, habit = http_get('/api/habits') + status, habit = http_get('/api/habits', port) assert status == 200 habit = [h for h in habit if h['id'] == habit_id][0] assert habit['current_streak'] == 3 # Uncheck the middle day middle_date = (today - timedelta(days=1)).isoformat() - status, response = http_delete(f'/api/habits/{habit_id}/check?date={middle_date}') + status, response = http_delete(f'/api/habits/{habit_id}/check?date={middle_date}', port) assert status == 200 # Streak should now be 1 (only today counts) @@ -1020,21 +1021,21 @@ def test_uncheck_recalculates_streak(): # Test 39: DELETE uncheck - returns updated habit object def test_uncheck_returns_updated_habit(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create and check in status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] today = datetime.now().date().isoformat() - status, _ = http_post(f'/api/habits/{habit_id}/check', {}) + status, _ = http_post(f'/api/habits/{habit_id}/check', {}, port) # Uncheck and verify response structure - status, response = http_delete(f'/api/habits/{habit_id}/check?date={today}') + status, response = http_delete(f'/api/habits/{habit_id}/check?date={today}', port) assert status == 200 assert 'id' in response assert 'name' in response @@ -1050,18 +1051,18 @@ def test_uncheck_returns_updated_habit(): # Test 40: DELETE uncheck - requires date parameter def test_uncheck_requires_date(): temp_dir = setup_test_env() - server = start_test_server() + server, port = start_test_server() try: # Create a habit status, habit = http_post('/api/habits', { 'name': 'Daily Exercise', 'frequency': {'type': 'daily'} - }) + }, port) habit_id = habit['id'] # Try to uncheck without date parameter - status, response = http_delete(f'/api/habits/{habit_id}/check') + status, response = http_delete(f'/api/habits/{habit_id}/check', port) assert status == 400 assert 'error' in response assert 'date parameter is required' in response['error']