test_weather_card.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. # -*- coding: utf-8 -*-
  2. import threading
  3. import mock
  4. import pygame
  5. import requests
  6. from pie_time.application import PieTime
  7. from pie_time.cards import weather as card_module
  8. class Test_WeatherCard(object):
  9. def _dummy_card(self, city='Wroclaw,PL', api_key='API key', **settings):
  10. app = PieTime(None, screen_size=(320, 240))
  11. app._logger = mock.Mock()
  12. app.path_for_resource = mock.Mock(
  13. side_effect=lambda resource: resource
  14. )
  15. card = card_module.WeatherCard()
  16. card.set_app(app)
  17. if city:
  18. settings['city'] = city
  19. if api_key:
  20. settings['api_key'] = api_key
  21. card.set_settings(settings)
  22. return card
  23. def _ok_payload(self):
  24. return {
  25. "base": "cmc stations",
  26. "clouds": {
  27. "all": 20
  28. },
  29. "cod": 200,
  30. "coord": {
  31. "lat": 51.1,
  32. "lon": 17.03
  33. },
  34. "dt": 1413815400,
  35. "id": 3081368,
  36. "main": {
  37. "humidity": 59,
  38. "pressure": 1013,
  39. "temp": 17,
  40. "temp_max": 17,
  41. "temp_min": 17
  42. },
  43. "name": "Wroclaw",
  44. "sys": {
  45. "country": "PL",
  46. "id": 5375,
  47. "message": 0.1316,
  48. "sunrise": 1413782666,
  49. "sunset": 1413820124,
  50. "type": 1
  51. },
  52. "weather": [
  53. {
  54. "description": "few clouds",
  55. "icon": "02d",
  56. "id": 801,
  57. "main": "Clouds"
  58. }
  59. ],
  60. "wind": {
  61. "deg": 280,
  62. "speed": 6.2
  63. }
  64. }
  65. def test_initialize_no_city(self):
  66. card = self._dummy_card(city=None)
  67. try:
  68. card.initialize()
  69. except Exception as exc:
  70. assert isinstance(exc, AssertionError)
  71. assert exc.args[0] == 'Configuration error: missing city'
  72. else:
  73. assert False, 'Nothing was raised :('
  74. def test_initialize_no_api_key(self):
  75. card = self._dummy_card(api_key=None)
  76. try:
  77. card.initialize()
  78. except Exception as exc:
  79. assert isinstance(exc, AssertionError)
  80. assert exc.args[0] == 'Configuration error: missing API key'
  81. else:
  82. assert False, 'Nothing was raised :('
  83. def test_initialize(self):
  84. with mock.patch.object(card_module.pygame.font, 'Font',
  85. spec=pygame.font.Font):
  86. card = self._dummy_card()
  87. card._refresh_conditions = mock.Mock()
  88. card.initialize()
  89. assert card._text_font is not None
  90. assert card._temp_font is not None
  91. assert card._icon_font is not None
  92. assert card._timer is None
  93. assert card._current_conditions is None
  94. assert card._refresh_conditions.called
  95. def test_refresh_conditions_requests_error(self):
  96. fake_timer = mock.Mock(spec=threading.Timer)
  97. fake_timer.start = mock.Mock()
  98. with mock.patch.object(card_module.pygame.font, 'Font',
  99. spec=pygame.font.Font):
  100. with mock.patch.object(card_module.requests, 'get',
  101. side_effect=RuntimeError('TODO')):
  102. with mock.patch.object(card_module, 'Timer',
  103. return_value=fake_timer):
  104. card = self._dummy_card()
  105. card.initialize(refresh=False)
  106. card._refresh_conditions()
  107. assert card_module.requests.get.called
  108. assert card._current_conditions is None
  109. assert card._timer is not None
  110. card_module.Timer.assert_called_with(
  111. card.REFRESH_INTERVAL, card._refresh_conditions
  112. )
  113. assert card._timer.start.called
  114. def test_refresh_conditions_bad_status_code(self):
  115. fake_response = mock.Mock(spec=requests.Response)
  116. fake_response.status_code = 404
  117. fake_timer = mock.Mock(spec=threading.Timer)
  118. fake_timer.start = mock.Mock()
  119. with mock.patch.object(card_module.pygame.font, 'Font',
  120. spec=pygame.font.Font):
  121. with mock.patch.object(card_module.requests, 'get',
  122. return_value=fake_response):
  123. with mock.patch.object(card_module, 'Timer',
  124. return_value=fake_timer):
  125. card = self._dummy_card()
  126. card.initialize(refresh=False)
  127. card._refresh_conditions()
  128. assert card_module.requests.get.called
  129. assert card._current_conditions is None
  130. assert card._timer is not None
  131. card_module.Timer.assert_called_with(
  132. card.REFRESH_INTERVAL, card._refresh_conditions
  133. )
  134. assert card._timer.start.called
  135. def test_refresh_conditions_missing_weather_object(self):
  136. payload = self._ok_payload()
  137. payload['weather'] = []
  138. fake_response = mock.Mock(spec=requests.Response)
  139. fake_response.json = mock.Mock(return_value=payload)
  140. fake_response.status_code = 200
  141. fake_timer = mock.Mock(spec=threading.Timer)
  142. fake_timer.start = mock.Mock()
  143. with mock.patch.object(card_module.pygame.font, 'Font',
  144. spec=pygame.font.Font):
  145. with mock.patch.object(card_module.requests, 'get',
  146. return_value=fake_response):
  147. with mock.patch.object(card_module, 'Timer',
  148. return_value=fake_timer):
  149. card = self._dummy_card()
  150. card.initialize(refresh=False)
  151. card._refresh_conditions()
  152. assert card_module.requests.get.called
  153. assert card._current_conditions is None
  154. assert card._timer is not None
  155. card_module.Timer.assert_called_with(
  156. card.REFRESH_INTERVAL, card._refresh_conditions
  157. )
  158. assert card._timer.start.called
  159. def test_refresh_conditions_missing_conditions(self):
  160. payload = self._ok_payload()
  161. payload['weather'][0].pop('main')
  162. fake_response = mock.Mock(spec=requests.Response)
  163. fake_response.json = mock.Mock(return_value=payload)
  164. fake_response.status_code = 200
  165. fake_timer = mock.Mock(spec=threading.Timer)
  166. fake_timer.start = mock.Mock()
  167. with mock.patch.object(card_module.pygame.font, 'Font',
  168. spec=pygame.font.Font):
  169. with mock.patch.object(card_module.requests, 'get',
  170. return_value=fake_response):
  171. with mock.patch.object(card_module, 'Timer',
  172. return_value=fake_timer):
  173. card = self._dummy_card()
  174. card.initialize(refresh=False)
  175. card._refresh_conditions()
  176. assert card_module.requests.get.called
  177. assert card._current_conditions is None
  178. assert card._timer is not None
  179. card_module.Timer.assert_called_with(
  180. card.REFRESH_INTERVAL, card._refresh_conditions
  181. )
  182. assert card._timer.start.called
  183. def test_refresh_conditions_missing_temp(self):
  184. payload = self._ok_payload()
  185. payload['main'].pop('temp')
  186. fake_response = mock.Mock(spec=requests.Response)
  187. fake_response.json = mock.Mock(return_value=payload)
  188. fake_response.status_code = 200
  189. fake_timer = mock.Mock(spec=threading.Timer)
  190. fake_timer.start = mock.Mock()
  191. with mock.patch.object(card_module.pygame.font, 'Font',
  192. spec=pygame.font.Font):
  193. with mock.patch.object(card_module.requests, 'get',
  194. return_value=fake_response):
  195. with mock.patch.object(card_module, 'Timer',
  196. return_value=fake_timer):
  197. card = self._dummy_card()
  198. card.initialize(refresh=False)
  199. card._refresh_conditions()
  200. assert card_module.requests.get.called
  201. assert card._current_conditions is None
  202. assert card._timer is not None
  203. card_module.Timer.assert_called_with(
  204. card.REFRESH_INTERVAL, card._refresh_conditions
  205. )
  206. assert card._timer.start.called
  207. def test_refresh_conditions(self):
  208. payload = self._ok_payload()
  209. fake_response = mock.Mock(spec=requests.Response)
  210. fake_response.json = mock.Mock(return_value=payload)
  211. fake_response.status_code = 200
  212. fake_timer = mock.Mock(spec=threading.Timer)
  213. fake_timer.start = mock.Mock()
  214. with mock.patch.object(card_module.pygame.font, 'Font',
  215. spec=pygame.font.Font):
  216. with mock.patch.object(card_module.requests, 'get',
  217. return_value=fake_response):
  218. with mock.patch.object(card_module, 'Timer',
  219. return_value=fake_timer):
  220. card = self._dummy_card()
  221. card.initialize(refresh=False)
  222. card._refresh_conditions()
  223. assert card_module.requests.get.called
  224. assert card._current_conditions == {
  225. 'conditions': payload['weather'][0]['main'],
  226. 'icon': payload['weather'][0]['icon'],
  227. 'temperature': payload['main']['temp'],
  228. }
  229. assert card._timer is not None
  230. card_module.Timer.assert_called_with(
  231. card.REFRESH_INTERVAL, card._refresh_conditions
  232. )
  233. assert card._timer.start.called
  234. def test_refresh_conditions_no_icon(self):
  235. payload = self._ok_payload()
  236. payload['weather'][0].pop('icon')
  237. fake_response = mock.Mock(spec=requests.Response)
  238. fake_response.json = mock.Mock(return_value=payload)
  239. fake_response.status_code = 200
  240. fake_timer = mock.Mock(spec=threading.Timer)
  241. fake_timer.start = mock.Mock()
  242. with mock.patch.object(card_module.pygame.font, 'Font',
  243. spec=pygame.font.Font):
  244. with mock.patch.object(card_module.requests, 'get',
  245. return_value=fake_response):
  246. with mock.patch.object(card_module, 'Timer',
  247. return_value=fake_timer):
  248. card = self._dummy_card()
  249. card.initialize(refresh=False)
  250. card._refresh_conditions()
  251. assert card_module.requests.get.called
  252. assert card._current_conditions == {
  253. 'conditions': payload['weather'][0]['main'],
  254. 'icon': None,
  255. 'temperature': payload['main']['temp'],
  256. }
  257. assert card._timer is not None
  258. card_module.Timer.assert_called_with(
  259. card.REFRESH_INTERVAL, card._refresh_conditions
  260. )
  261. assert card._timer.start.called
  262. def test_render_city(self):
  263. card = self._dummy_card()
  264. with mock.patch.object(card_module.pygame.font, 'Font',
  265. spec=pygame.font.Font):
  266. card.initialize(refresh=False)
  267. card._text_font.render = mock.Mock(
  268. return_value=mock.Mock(spec=pygame.surface.Surface)
  269. )
  270. surface = card._render_city()
  271. card._text_font.render.assert_called_with(
  272. card._settings['city'], True, card.WHITE
  273. )
  274. assert isinstance(surface, pygame.surface.Surface)
  275. def test_render_city_override_color(self):
  276. card = self._dummy_card(city_color=(255, 0, 0))
  277. with mock.patch.object(card_module.pygame.font, 'Font',
  278. spec=pygame.font.Font):
  279. card.initialize(refresh=False)
  280. card._text_font.render = mock.Mock(
  281. return_value=mock.Mock(spec=pygame.surface.Surface)
  282. )
  283. surface = card._render_city()
  284. card._text_font.render.assert_called_with(
  285. card._settings['city'], True, (255, 0, 0)
  286. )
  287. assert isinstance(surface, pygame.surface.Surface)
  288. def test_render_conditions(self):
  289. card = self._dummy_card()
  290. with mock.patch.object(card_module.pygame.font, 'Font',
  291. spec=pygame.font.Font):
  292. card.initialize(refresh=False)
  293. card._current_conditions = {
  294. 'conditions': 'clouds'
  295. }
  296. card._text_font.render = mock.Mock(
  297. return_value=mock.Mock(spec=pygame.surface.Surface)
  298. )
  299. surface = card._render_conditions()
  300. card._text_font.render.assert_called_with(
  301. 'Clouds', True, card.WHITE
  302. )
  303. assert isinstance(surface, pygame.surface.Surface)
  304. def test_render_conditions_override_color(self):
  305. card = self._dummy_card(conditions_color=(255, 0, 0))
  306. with mock.patch.object(card_module.pygame.font, 'Font',
  307. spec=pygame.font.Font):
  308. card.initialize(refresh=False)
  309. card._current_conditions = {
  310. 'conditions': 'clouds'
  311. }
  312. card._text_font.render = mock.Mock(
  313. return_value=mock.Mock(spec=pygame.surface.Surface)
  314. )
  315. surface = card._render_conditions()
  316. card._text_font.render.assert_called_with(
  317. 'Clouds', True, (255, 0, 0)
  318. )
  319. assert isinstance(surface, pygame.surface.Surface)
  320. def test_render_icon(self):
  321. card = self._dummy_card()
  322. with mock.patch.object(card_module.pygame.font, 'Font',
  323. spec=pygame.font.Font):
  324. card.initialize(refresh=False)
  325. icon_code = '01d'
  326. card._current_conditions = {'icon': icon_code}
  327. card._text_font.render = mock.Mock(
  328. return_value=mock.Mock(spec=pygame.surface.Surface)
  329. )
  330. surface = card._render_icon()
  331. card._icon_font.render.assert_called_with(
  332. card.WEATHER_CODE_TO_ICON[icon_code], True, card.WHITE
  333. )
  334. assert isinstance(surface, pygame.surface.Surface)
  335. def test_render_icon_override_color(self):
  336. card = self._dummy_card(icon_color=(255, 0, 0))
  337. with mock.patch.object(card_module.pygame.font, 'Font',
  338. spec=pygame.font.Font):
  339. card.initialize(refresh=False)
  340. icon_code = '01d'
  341. card._current_conditions = {'icon': icon_code}
  342. card._text_font.render = mock.Mock(
  343. return_value=mock.Mock(spec=pygame.surface.Surface)
  344. )
  345. surface = card._render_icon()
  346. card._icon_font.render.assert_called_with(
  347. card.WEATHER_CODE_TO_ICON[icon_code], True, (255, 0, 0)
  348. )
  349. assert isinstance(surface, pygame.surface.Surface)
  350. def test_render_icon_no_icon(self):
  351. card = self._dummy_card()
  352. with mock.patch.object(card_module.pygame.font, 'Font',
  353. spec=pygame.font.Font):
  354. card.initialize(refresh=False)
  355. icon_code = None
  356. card._current_conditions = {'icon': icon_code}
  357. card._text_font.render = mock.Mock(
  358. return_value=mock.Mock(spec=pygame.surface.Surface)
  359. )
  360. surface = card._render_icon()
  361. assert card._icon_font.render.called is False
  362. assert surface is None
  363. def test_render_icon_unknown_icon(self):
  364. card = self._dummy_card()
  365. with mock.patch.object(card_module.pygame.font, 'Font',
  366. spec=pygame.font.Font):
  367. card.initialize(refresh=False)
  368. icon_code = 'spam'
  369. card._current_conditions = {'icon': icon_code}
  370. card._text_font.render = mock.Mock(
  371. return_value=mock.Mock(spec=pygame.surface.Surface)
  372. )
  373. surface = card._render_icon()
  374. assert card._icon_font.render.called is False
  375. assert surface is None
  376. def test_render_temperature(self):
  377. card = self._dummy_card()
  378. with mock.patch.object(card_module.pygame.font, 'Font',
  379. spec=pygame.font.Font):
  380. card.initialize(refresh=False)
  381. card._current_conditions = {
  382. 'temperature': 17
  383. }
  384. card._text_font.render = mock.Mock(
  385. return_value=mock.Mock(spec=pygame.surface.Surface)
  386. )
  387. surface = card._render_temperature()
  388. card._text_font.render.assert_called_with(
  389. u'17°', True, card.WHITE
  390. )
  391. assert isinstance(surface, pygame.surface.Surface)
  392. def test_render_temperature_override_color(self):
  393. card = self._dummy_card(temperature_color=(255, 0, 0))
  394. with mock.patch.object(card_module.pygame.font, 'Font',
  395. spec=pygame.font.Font):
  396. card.initialize(refresh=False)
  397. card._current_conditions = {
  398. 'temperature': 17
  399. }
  400. card._text_font.render = mock.Mock(
  401. return_value=mock.Mock(spec=pygame.surface.Surface)
  402. )
  403. surface = card._render_temperature()
  404. card._text_font.render.assert_called_with(
  405. u'17°', True, (255, 0, 0)
  406. )
  407. assert isinstance(surface, pygame.surface.Surface)
  408. def test_quit(self):
  409. card = self._dummy_card()
  410. card._timer = None
  411. card.quit()
  412. card = self._dummy_card()
  413. card._timer = mock.Mock(spec=threading.Timer)
  414. card._timer.cancel = mock.Mock()
  415. card.quit()
  416. assert card._timer.cancel.called
  417. def test_tick_redraw(self):
  418. card = self._dummy_card()
  419. with mock.patch.object(card_module.pygame.font, 'Font',
  420. spec=pygame.font.Font):
  421. card.initialize(refresh=False)
  422. card._current_conditions = {
  423. 'conditions': 'Clouds',
  424. 'icon': '01d',
  425. 'temperature': 17
  426. }
  427. card._surface = mock.Mock(spec=pygame.surface.Surface)
  428. city_surface = mock.Mock(spec=pygame.surface.Surface)
  429. city_surface.get_size = mock.Mock(return_value=(120, 40))
  430. card._render_city = mock.Mock(return_value=city_surface)
  431. conditions_surface = mock.Mock(spec=pygame.surface.Surface)
  432. conditions_surface.get_size = mock.Mock(return_value=(200, 40))
  433. card._render_conditions = mock.Mock(
  434. return_value=conditions_surface
  435. )
  436. icon_surface = mock.Mock(spec=pygame.surface.Surface)
  437. icon_surface.get_size = mock.Mock(return_value=(128, 128))
  438. card._render_icon = mock.Mock(return_value=icon_surface)
  439. temperature_surface = mock.Mock(spec=pygame.surface.Surface)
  440. temperature_surface.get_size = mock.Mock(return_value=(100, 82))
  441. card._render_temperature = mock.Mock(
  442. return_value=temperature_surface
  443. )
  444. card.tick()
  445. card.surface.blit.assert_any_call(
  446. city_surface, (100, 0, 120, 40)
  447. )
  448. card.surface.blit.assert_any_call(
  449. conditions_surface, (60, 200, 200, 40)
  450. )
  451. card.surface.blit.assert_any_call(
  452. icon_surface, (34, 56, 128, 128)
  453. )
  454. card.surface.blit.assert_any_call(
  455. temperature_surface, (186, 79, 100, 82)
  456. )
  457. assert card._should_redraw is False
  458. def test_tick_dont_redraw(self):
  459. card = self._dummy_card()
  460. with mock.patch.object(card_module.pygame.font, 'Font',
  461. spec=pygame.font.Font):
  462. card.initialize(refresh=False)
  463. card._should_redraw = False
  464. card._surface = mock.Mock(spec=pygame.surface.Surface)
  465. city_surface = mock.Mock(spec=pygame.surface.Surface)
  466. city_surface.get_size = mock.Mock(return_value=(120, 40))
  467. card._render_city = mock.Mock(return_value=city_surface)
  468. conditions_surface = mock.Mock(spec=pygame.surface.Surface)
  469. conditions_surface.get_size = mock.Mock(return_value=(200, 40))
  470. card._render_conditions = mock.Mock(
  471. return_value=conditions_surface
  472. )
  473. icon_surface = mock.Mock(spec=pygame.surface.Surface)
  474. icon_surface.get_size = mock.Mock(return_value=(128, 128))
  475. card._render_icon = mock.Mock(return_value=icon_surface)
  476. temperature_surface = mock.Mock(spec=pygame.surface.Surface)
  477. temperature_surface.get_size = mock.Mock(return_value=(100, 82))
  478. card._render_temperature = mock.Mock(
  479. return_value=temperature_surface
  480. )
  481. card.tick()
  482. assert card._render_city.called is False
  483. assert card._render_conditions.called is False
  484. assert card._render_icon.called is False
  485. assert card._render_temperature.called is False
  486. assert card.surface.blit.called is False
  487. assert card._should_redraw is False
  488. def test_tick_without_icon(self):
  489. card = self._dummy_card()
  490. with mock.patch.object(card_module.pygame.font, 'Font',
  491. spec=pygame.font.Font):
  492. card.initialize(refresh=False)
  493. card._current_conditions = {
  494. 'conditions': 'Clouds',
  495. 'icon': '01d',
  496. 'temperature': 17
  497. }
  498. card._surface = mock.Mock(spec=pygame.surface.Surface)
  499. city_surface = mock.Mock(spec=pygame.surface.Surface)
  500. city_surface.get_size = mock.Mock(return_value=(120, 40))
  501. card._render_city = mock.Mock(return_value=city_surface)
  502. conditions_surface = mock.Mock(spec=pygame.surface.Surface)
  503. conditions_surface.get_size = mock.Mock(return_value=(200, 40))
  504. card._render_conditions = mock.Mock(
  505. return_value=conditions_surface
  506. )
  507. card._render_icon = mock.Mock(return_value=None)
  508. temperature_surface = mock.Mock(spec=pygame.surface.Surface)
  509. temperature_surface.get_size = mock.Mock(return_value=(100, 82))
  510. card._render_temperature = mock.Mock(
  511. return_value=temperature_surface
  512. )
  513. card.tick()
  514. card.surface.blit.assert_any_call(
  515. city_surface, (100, 0, 120, 40)
  516. )
  517. card.surface.blit.assert_any_call(
  518. conditions_surface, (60, 200, 200, 40)
  519. )
  520. card.surface.blit.assert_any_call(
  521. temperature_surface, (110, 79, 100, 82)
  522. )
  523. assert card._should_redraw is False
  524. def test_tick_without_conditions(self):
  525. card = self._dummy_card()
  526. with mock.patch.object(card_module.pygame.font, 'Font',
  527. spec=pygame.font.Font):
  528. card.initialize(refresh=False)
  529. card._surface = mock.Mock(spec=pygame.surface.Surface)
  530. city_surface = mock.Mock(spec=pygame.surface.Surface)
  531. city_surface.get_size = mock.Mock(return_value=(120, 40))
  532. card._render_city = mock.Mock(return_value=city_surface)
  533. card._render_conditions = mock.Mock()
  534. card._render_icon = mock.Mock()
  535. card._render_temperature = mock.Mock()
  536. card.tick()
  537. assert card.surface.blit.call_count == 1
  538. card.surface.blit.assert_called_with(
  539. city_surface, (100, 0, 120, 40)
  540. )
  541. assert card._render_conditions.called is False
  542. assert card._render_icon.called is False
  543. assert card._render_temperature.called is False
  544. assert card._should_redraw is False