async def connect(self): self._loop = asyncio.get_running_loop() client = TestClient(self._server) async with client.ws_connect('/') as websocket: try: self._websocket = websocket self._connected_event.set_result(True) async for request in websocket: self._messages_queue.append(request) self._message_event.set_result(True) except asyncio.CancelledError: await self.stop()
async def test_route_ws(loop, client: test_utils.TestClient): async with client.ws_connect("/ws") as ws: for i in range(10): await ws.send_json({"hello": "world", "i": i}) response = await ws.receive_json() assert response == {"hello": "world", "i": i, "echo": True} for i in range(10): await ws.send_json({"hello": "world", "i": i}) for i in range(10): response = await ws.receive_json() assert response == {"hello": "world", "i": i, "echo": True}
class TestWeb(asynctest.ClockedTestCase): haproxy_bind: Optional[Tuple[str, int]] = None maxDiff = None async def setUp(self) -> None: self.mc_server = mock.MagicMock() self.mc_server.sensors = SensorSet() self.mc_server.orig_sensors = SensorSet() self.mc_server.sensors.add( Sensor(str, 'products', '', default='["product1", "product2"]', initial_status=Sensor.Status.NOMINAL)) self.mc_server.sensors.add( Sensor(str, 'gui-urls', '', default=json.dumps(ROOT_GUI_URLS), initial_status=Sensor.Status.NOMINAL)) self.mc_server.orig_sensors.add( Sensor(str, 'product1.gui-urls', '', default=json.dumps(PRODUCT1_GUI_URLS), initial_status=Sensor.Status.NOMINAL)) self.mc_server.orig_sensors.add( Sensor(str, 'product2.cal.1.gui-urls', '', default=json.dumps(PRODUCT2_CAL_GUI_URLS), initial_status=Sensor.Status.NOMINAL)) self.mc_server.orig_sensors.add( Sensor(str, 'product2.ingest.1.gui-urls', '', default=json.dumps(PRODUCT2_CAL_GUI_URLS), initial_status=Sensor.Status.UNKNOWN)) for sensor in self.mc_server.orig_sensors.values(): if self.haproxy_bind is not None and sensor.name.endswith( '.gui-urls'): new_value = web.rewrite_gui_urls(EXTERNAL_URL, sensor) new_sensor = Sensor(sensor.stype, sensor.name, sensor.description, sensor.units) new_sensor.set_value(new_value, timestamp=sensor.timestamp, status=sensor.status) self.mc_server.sensors.add(new_sensor) else: self.mc_server.sensors.add(sensor) self.app = web.make_app(self.mc_server, self.haproxy_bind) self.server = TestServer(self.app) self.client = TestClient(self.server) await self.client.start_server() self.addCleanup(self.client.close) self.mc_server.add_interface_changed_callback.assert_called_once() self.dirty_set = self.mc_server.add_interface_changed_callback.mock_calls[ 0][1][0] self.dirty_set() await self.advance(1) async def test_get_guis(self) -> None: guis = web._get_guis(self.mc_server) haproxy = self.haproxy_bind is not None expected = { "general": ROOT_GUI_URLS, "products": { "product1": expected_gui_urls(PRODUCT1_GUI_URLS_OUT, haproxy, True), "product2": expected_gui_urls(PRODUCT2_GUI_URLS_OUT, haproxy, True) } } self.assertEqual(guis, expected) async def test_get_guis_not_nominal(self) -> None: for name in [ 'gui-urls', 'product1.gui-urls', 'product2.cal.1.gui-urls' ]: sensor = self.mc_server.sensors[name] sensor.set_value(sensor.value, status=Sensor.Status.ERROR) guis = web._get_guis(self.mc_server) expected = { "general": [], "products": { "product1": [], "product2": [] } } self.assertEqual(guis, expected) async def test_update_bad_gui_sensor(self) -> None: with self.assertLogs('katsdpcontroller.web', logging.ERROR): self.mc_server.sensors[ 'product1.gui-urls'].value = 'not valid json' await self.advance(1) async def test_index(self) -> None: async with self.client.get('/') as resp: self.assertEqual(resp.status, 200) self.assertEqual(resp.headers['Content-type'], 'text/html; charset=utf-8') self.assertEqual(resp.headers['Cache-control'], 'no-store') async def test_favicon(self) -> None: async with self.client.get('/favicon.ico') as resp: self.assertEqual(resp.status, 200) self.assertEqual(resp.headers['Content-type'], 'image/vnd.microsoft.icon') async def test_prometheus(self) -> None: async with self.client.get('/metrics') as resp: self.assertEqual(resp.status, 200) async def test_rotate(self) -> None: # A good test of this probably needs Selenium plus a whole lot of # extra infrastructure. async with self.client.get('/rotate?width=500&height=600') as resp: text = await resp.text() self.assertEqual(resp.status, 200) self.assertIn('width: 500', text) self.assertIn('height: 600', text) async def test_missing_gui(self) -> None: for path in [ '/gui/product3/service/label', '/gui/product3/service/label/foo' ]: async with self.client.get(path) as resp: text = await resp.text() self.assertEqual(resp.status, 404) self.assertIn('product3', text) async def test_websocket(self) -> None: haproxy = self.haproxy_bind is not None expected = { "general": ROOT_GUI_URLS, "products": { "product1": expected_gui_urls(PRODUCT1_GUI_URLS_OUT, haproxy, False), "product2": expected_gui_urls(PRODUCT2_GUI_URLS_OUT, haproxy, False) } } async with self.client.ws_connect('/ws') as ws: await ws.send_str('guis') guis = await ws.receive_json() self.assertEqual(guis, expected) # Updating should trigger an unsolicited update sensor = self.mc_server.sensors['products'] sensor.value = '["product1"]' del expected["products"]["product2"] # type: ignore self.dirty_set() await self.advance(1) guis = await ws.receive_json() self.assertEqual(guis, expected) await ws.close() async def test_websocket_close_server(self) -> None: """Close the server while a websocket is still connected""" async with self.client.ws_connect('/ws'): await self.server.close()