feat: 2.0 - Backend API - GET /api/habits
This commit is contained in:
207
dashboard/test_habits_api.py
Normal file
207
dashboard/test_habits_api.py
Normal file
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for GET /api/habits endpoint.
|
||||
Validates API response structure, status codes, and error handling.
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# API endpoint - assumes server is running on localhost:8088
|
||||
API_BASE = 'http://localhost:8088'
|
||||
HABITS_FILE = Path(__file__).parent / 'habits.json'
|
||||
|
||||
|
||||
def test_habits_endpoint_exists():
|
||||
"""Test that GET /api/habits endpoint exists and returns 200."""
|
||||
print("Testing endpoint exists and returns 200...")
|
||||
|
||||
try:
|
||||
response = urllib.request.urlopen(f'{API_BASE}/api/habits', timeout=5)
|
||||
status_code = response.getcode()
|
||||
|
||||
assert status_code == 200, f"Expected status 200, got {status_code}"
|
||||
print("✓ Endpoint returns 200 status")
|
||||
except urllib.error.HTTPError as e:
|
||||
raise AssertionError(f"Endpoint returned HTTP {e.code}: {e.reason}")
|
||||
except urllib.error.URLError as e:
|
||||
raise AssertionError(f"Could not connect to API server: {e.reason}")
|
||||
|
||||
|
||||
def test_habits_response_is_json():
|
||||
"""Test that response is valid JSON."""
|
||||
print("Testing response is valid JSON...")
|
||||
|
||||
try:
|
||||
response = urllib.request.urlopen(f'{API_BASE}/api/habits', timeout=5)
|
||||
content = response.read().decode('utf-8')
|
||||
|
||||
try:
|
||||
data = json.loads(content)
|
||||
print("✓ Response is valid JSON")
|
||||
return data
|
||||
except json.JSONDecodeError as e:
|
||||
raise AssertionError(f"Response is not valid JSON: {e}")
|
||||
except urllib.error.URLError as e:
|
||||
raise AssertionError(f"Could not connect to API server: {e.reason}")
|
||||
|
||||
|
||||
def test_habits_response_structure():
|
||||
"""Test that response has correct structure: habits array and lastUpdated."""
|
||||
print("Testing response structure...")
|
||||
|
||||
data = test_habits_response_is_json()
|
||||
|
||||
# Check for habits array
|
||||
assert 'habits' in data, "Response missing 'habits' field"
|
||||
assert isinstance(data['habits'], list), "'habits' field must be an array"
|
||||
print("✓ Response contains 'habits' array")
|
||||
|
||||
# Check for lastUpdated timestamp
|
||||
assert 'lastUpdated' in data, "Response missing 'lastUpdated' field"
|
||||
print("✓ Response contains 'lastUpdated' field")
|
||||
|
||||
|
||||
def test_habits_lastupdated_is_iso():
|
||||
"""Test that lastUpdated is a valid ISO timestamp."""
|
||||
print("Testing lastUpdated is valid ISO timestamp...")
|
||||
|
||||
data = test_habits_response_is_json()
|
||||
last_updated = data.get('lastUpdated')
|
||||
|
||||
assert last_updated, "lastUpdated field is empty"
|
||||
|
||||
try:
|
||||
# Try to parse as ISO datetime
|
||||
dt = datetime.fromisoformat(last_updated.replace('Z', '+00:00'))
|
||||
print(f"✓ lastUpdated is valid ISO timestamp: {last_updated}")
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise AssertionError(f"lastUpdated is not a valid ISO timestamp: {e}")
|
||||
|
||||
|
||||
def test_empty_habits_returns_empty_array():
|
||||
"""Test that empty habits.json returns empty array, not error."""
|
||||
print("Testing empty habits file returns empty array...")
|
||||
|
||||
# Backup original file
|
||||
backup = None
|
||||
if HABITS_FILE.exists():
|
||||
backup = HABITS_FILE.read_text()
|
||||
|
||||
try:
|
||||
# Write empty habits file
|
||||
HABITS_FILE.write_text(json.dumps({
|
||||
'lastUpdated': datetime.now().isoformat(),
|
||||
'habits': []
|
||||
}))
|
||||
|
||||
# Request habits
|
||||
response = urllib.request.urlopen(f'{API_BASE}/api/habits', timeout=5)
|
||||
data = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
assert data['habits'] == [], "Empty habits.json should return empty array"
|
||||
print("✓ Empty habits.json returns empty array (not error)")
|
||||
|
||||
finally:
|
||||
# Restore backup
|
||||
if backup:
|
||||
HABITS_FILE.write_text(backup)
|
||||
|
||||
|
||||
def test_habits_with_data():
|
||||
"""Test that habits with data are returned correctly."""
|
||||
print("Testing habits with data are returned...")
|
||||
|
||||
# Backup original file
|
||||
backup = None
|
||||
if HABITS_FILE.exists():
|
||||
backup = HABITS_FILE.read_text()
|
||||
|
||||
try:
|
||||
# Write test habits
|
||||
test_data = {
|
||||
'lastUpdated': '2026-02-10T10:00:00.000Z',
|
||||
'habits': [
|
||||
{
|
||||
'id': 'test-habit-1',
|
||||
'name': 'Bazin',
|
||||
'frequency': 'daily',
|
||||
'createdAt': '2026-02-10T10:00:00.000Z',
|
||||
'completions': ['2026-02-10T10:00:00.000Z']
|
||||
}
|
||||
]
|
||||
}
|
||||
HABITS_FILE.write_text(json.dumps(test_data, indent=2))
|
||||
|
||||
# Request habits
|
||||
response = urllib.request.urlopen(f'{API_BASE}/api/habits', timeout=5)
|
||||
data = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
assert len(data['habits']) == 1, "Should return 1 habit"
|
||||
habit = data['habits'][0]
|
||||
assert habit['name'] == 'Bazin', f"Expected habit name 'Bazin', got '{habit['name']}'"
|
||||
assert habit['frequency'] == 'daily', f"Expected frequency 'daily', got '{habit['frequency']}'"
|
||||
print("✓ Habits with data are returned correctly")
|
||||
|
||||
finally:
|
||||
# Restore backup
|
||||
if backup:
|
||||
HABITS_FILE.write_text(backup)
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all tests and report results."""
|
||||
print("=" * 60)
|
||||
print("Running GET /api/habits endpoint tests")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
tests = [
|
||||
test_habits_endpoint_exists,
|
||||
test_habits_response_is_json,
|
||||
test_habits_response_structure,
|
||||
test_habits_lastupdated_is_iso,
|
||||
test_empty_habits_returns_empty_array,
|
||||
test_habits_with_data,
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for test in tests:
|
||||
try:
|
||||
test()
|
||||
passed += 1
|
||||
print()
|
||||
except AssertionError as e:
|
||||
print(f"✗ FAILED: {e}")
|
||||
print()
|
||||
failed += 1
|
||||
except Exception as e:
|
||||
print(f"✗ ERROR: {e}")
|
||||
print()
|
||||
failed += 1
|
||||
|
||||
print("=" * 60)
|
||||
print(f"Results: {passed} passed, {failed} failed")
|
||||
print("=" * 60)
|
||||
|
||||
return failed == 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
# Check if API server is running
|
||||
try:
|
||||
urllib.request.urlopen(f'{API_BASE}/api/status', timeout=2)
|
||||
except urllib.error.URLError:
|
||||
print("ERROR: API server is not running on localhost:8088")
|
||||
print("Start the server with: python3 dashboard/api.py")
|
||||
sys.exit(1)
|
||||
|
||||
success = run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
Reference in New Issue
Block a user