test_api.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import pytest
  2. import httpx
  3. from unittest.mock import patch, AsyncMock
  4. from datetime import date
  5. from room_cal.api import create_app
  6. from room_cal.api._routes import _serialize
  7. # ── _serialize ─────────────────────────────────────────────────────────────────
  8. def test_serialize_date():
  9. assert _serialize(date(2026, 1, 15)) == '2026-01-15'
  10. def test_serialize_nested_dict():
  11. result = _serialize({'a': date(2026, 1, 1), 'b': 42})
  12. assert result == {'a': '2026-01-01', 'b': 42}
  13. def test_serialize_list():
  14. result = _serialize([date(2026, 2, 1), 'x', 7])
  15. assert result == ['2026-02-01', 'x', 7]
  16. def test_serialize_passthrough():
  17. assert _serialize(42) == 42
  18. assert _serialize('hello') == 'hello'
  19. assert _serialize(None) is None
  20. # ── app factory ────────────────────────────────────────────────────────────────
  21. def test_create_app():
  22. from fastapi import FastAPI
  23. assert isinstance(create_app(), FastAPI)
  24. # ── HTTP tests ─────────────────────────────────────────────────────────────────
  25. @pytest.fixture
  26. def app():
  27. return create_app()
  28. @pytest.fixture
  29. async def client(app):
  30. async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url='http://test') as c:
  31. yield c
  32. _AUTHED = {'sub': 'u', 'email': 'u@test.com', 'name': 'User'}
  33. @pytest.mark.asyncio
  34. async def test_calc_net_to_list(client):
  35. r = await client.get('/calc/net_to_list?service=airbnb&amount=100')
  36. assert r.status_code == 200
  37. data = r.json()
  38. assert data['net'] == 100
  39. assert data['list'] > data['net']
  40. assert 'retail' in data
  41. assert 'details' in data
  42. @pytest.mark.asyncio
  43. async def test_calc_unknown_transformation(client):
  44. r = await client.get('/calc/bad_transform?service=airbnb&amount=100')
  45. assert r.status_code == 404
  46. @pytest.mark.asyncio
  47. async def test_calc_bad_service(client):
  48. r = await client.get('/calc/net_to_list?service=nonexistent&amount=100')
  49. assert r.status_code == 400
  50. @pytest.mark.asyncio
  51. async def test_auth_middleware_calc_is_public(client):
  52. # /calc/ prefix is public — no auth cookie needed
  53. r = await client.get('/calc/net_to_list?service=airbnb&amount=50')
  54. assert r.status_code == 200
  55. @pytest.mark.asyncio
  56. async def test_auth_middleware_private_api_returns_401(client):
  57. r = await client.get('/prices/2026-01-01')
  58. assert r.status_code == 401
  59. @pytest.mark.asyncio
  60. async def test_auth_middleware_html_path_redirects_to_login(client):
  61. r = await client.get('/calendar', follow_redirects=False)
  62. assert r.status_code in (302, 307)
  63. assert '/auth/login' in r.headers['location']
  64. @pytest.mark.asyncio
  65. async def test_workflow_discount_invalid_type(client):
  66. with patch('room_cal.api.get_user', return_value=_AUTHED):
  67. r = await client.post('/workflow/discount/2026-01-15', json={'type': 'bad', 'value': 10})
  68. assert r.status_code == 422
  69. @pytest.mark.asyncio
  70. async def test_action_dispatch_unknown_action(client):
  71. with patch('room_cal.api.get_user', return_value=_AUTHED):
  72. r = await client.post('/action/nonexistent-action/2026-01-15')
  73. assert r.status_code == 404
  74. @pytest.mark.asyncio
  75. async def test_reload_clears_caches(client):
  76. import room_cal.store as _cal
  77. cal = _cal.get_calendar()
  78. cal._event_cache[('fake_key',)] = []
  79. cal._cached_url = 'http://example.com/cal'
  80. with patch('room_cal.api.get_user', return_value=_AUTHED):
  81. r = await client.post('/reload')
  82. assert r.status_code == 200
  83. data = r.json()
  84. assert 'cleared_calendars' in data
  85. assert data['cleared_calendars'] >= 1
  86. assert cal._event_cache == {}
  87. assert cal._cached_url is None