def test_validation_list_calls(self, EnumMock): test = "tests/backend/data_schemas/list.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) EnumMock.assert_called_once_with(values=["on", "off", "null"]) data = '{"leds": ["on", "off", "null"], "id": 1}' validate = MagicMock(return_value=True) dsd.descriptors["leds"].delegate.validate = validate self.assertEqual(dsd.validate(data), True) self.assertEqual(validate.call_count, 3)
def test_generate_nested_list_dict(self): test = "tests/backend/data_schemas/list_dict.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) data = dsd.generate(1) jdata = json.loads(data) self.assertEqual(set(jdata.keys()), set(["motor", "id"])) self.assertEqual(len(jdata["motor"]), 2) self.assertEqual(set(jdata["motor"][0].keys()), set(["speed", "turn_radius"])) self.assertEqual(set(jdata["motor"][1].keys()), set(["speed", "turn_radius"]))
def test_validation_enum(self): test = "tests/backend/data_schemas/enum.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) data = '{"interesting_name": 1, "id": 1}' self.assertEqual(dsd.validate(data), True) data = '{"interesting_name": 2, "id": 1}' self.assertEqual(dsd.validate(data), True) data = '{"interesting_name": 3, "id": 1}' self.assertEqual(dsd.validate(data), True) data = '{"interesting_name": 4, "id": 1}' self.assertEqual(dsd.validate(data), False)
def test_validation_dict_calls(self, NumberMock, EnumMock): test = "tests/backend/data_schemas/dict.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) EnumMock.assert_called_once_with(values=[0, 3], type="enum") NumberMock.assert_called_once_with(range=[0, 5], type="number") data = '{"motor": {"speed": 4, "turn_radius": 3}, "id": 1}' v_enum = MagicMock(return_value=True) v_number = MagicMock(return_value=True) dsd.descriptors["motor"].delegates["speed"].validate = v_number dsd.descriptors["motor"].delegates["turn_radius"].validate = v_enum self.assertEqual(dsd.validate(data), True) v_enum.assert_called_once_with(3) v_number.assert_called_once_with(4)
def test_validaton_nested_list_dict(self, MockNumber): mock_validate = MagicMock(return_value=True) MockNumber().validate = mock_validate test = "tests/backend/data_schemas/list_dict.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) data = """ { "id": 1, "motor": [{"speed": 4, "turn_radius": 3}, {"speed": 3, "turn_radius": 2}] } """ dsd.validate(data) self.assertEqual(mock_validate.called, True)
def setUp(self): self.cwd = settings["lupulo_cwd"] test = "tests/backend/data_schemas/complete.json" self.fp = open(os.path.join(self.cwd, test), "r") self.old_inotify = settings['activate_inotify'] settings['activate_inotify'] = False self.valid_schema_desc = DataSchemaManager(self.fp)
def __init__(self): """ @prop subscribers are the requests which should be updated when new information is published to the sse_resource. """ self.subscribers = set() # The device ids who have sent a message through this sse resource self.ids = [] fp = open(settings["data_schema"], "r") self.data_schema_manager = DataSchemaManager(fp) self.data_schema_manager.register_inotify_callback(self.schema_changed) fp = open(settings["layout"], "r") self.layout_manager = LayoutManager(fp, self.data_schema_manager) self.layout_manager.register_inotify_callback(self.layout_changed) self.layout_manager.compile() if settings['activate_mongo']: self.mongo_client = MongoClient(settings['mongo_host']) self.db = self.mongo_client[settings['mongo_db']] reactor.addSystemEventTrigger('after', 'shutdown', self.clean_up)
class TestDataSchemaGenerations(unittest.TestCase): def setUp(self): self.cwd = settings["lupulo_cwd"] test = "tests/backend/data_schemas/complete.json" self.fp = open(os.path.join(self.cwd, test), "r") self.old_inotify = settings['activate_inotify'] settings['activate_inotify'] = False self.valid_schema_desc = DataSchemaManager(self.fp) def tearDown(self): self.fp.close() settings['activate_inotify'] = self.old_inotify def test_generate_complete(self): data = self.valid_schema_desc.generate(1) jdata = json.loads(data) valid_keys = set(self.valid_schema_desc.descriptors.keys()) valid_keys.add("id") self.assertEqual(set(jdata.keys()), valid_keys) def test_generate_partial(self): data = self.valid_schema_desc.generate(1, ["battery", "date"]) jdata = json.loads(data) self.assertEqual(["battery", "date", "id"], jdata.keys()) def test_generate_null(self): data = self.valid_schema_desc.generate([]) jdata = json.loads(data) valid_keys = set(self.valid_schema_desc.descriptors.keys()) valid_keys.add("id") self.assertEqual(set(jdata.keys()), valid_keys) data = self.valid_schema_desc.generate(1, ["nothing"]) jdata = json.loads(data) self.assertEqual(jdata.keys(), ["id"]) def test_generate_list(self): data = self.valid_schema_desc.generate(1, ["distances"]) jdata = json.loads(data) self.assertEqual(set(["distances", "id"]), set(jdata.keys())) self.assertEqual(len(jdata["distances"]), 8) def test_generate_dict(self): data = self.valid_schema_desc.generate(1, ["motor"]) jdata = json.loads(data) self.assertEqual(set(["motor", "id"]), set(jdata.keys())) self.assertEqual(type(jdata["motor"]), dict) self.assertEqual(set(["turn_radius", "speed"]), set(jdata["motor"].keys())) self.assertEqual(type(jdata["motor"]["speed"]), float) self.assertEqual(type(jdata["motor"]["turn_radius"]), float) def test_generate_nested_list_dict(self): test = "tests/backend/data_schemas/list_dict.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) data = dsd.generate(1) jdata = json.loads(data) self.assertEqual(set(jdata.keys()), set(["motor", "id"])) self.assertEqual(len(jdata["motor"]), 2) self.assertEqual(set(jdata["motor"][0].keys()), set(["speed", "turn_radius"])) self.assertEqual(set(jdata["motor"][1].keys()), set(["speed", "turn_radius"]))
class SSEResource(resource.Resource): """ Twisted web resource that will work as the SSE server. """ isLeaf = True def __init__(self): """ @prop subscribers are the requests which should be updated when new information is published to the sse_resource. """ self.subscribers = set() # The device ids who have sent a message through this sse resource self.ids = [] fp = open(settings["data_schema"], "r") self.data_schema_manager = DataSchemaManager(fp) self.data_schema_manager.register_inotify_callback(self.schema_changed) fp = open(settings["layout"], "r") self.layout_manager = LayoutManager(fp, self.data_schema_manager) self.layout_manager.register_inotify_callback(self.layout_changed) self.layout_manager.compile() if settings['activate_mongo']: self.mongo_client = MongoClient(settings['mongo_host']) self.db = self.mongo_client[settings['mongo_db']] reactor.addSystemEventTrigger('after', 'shutdown', self.clean_up) def clean_up(self): log.msg("SSEResource cleanup.") self.data_schema_manager.fp.close() self.layout_manager.fp.close() def removeSubscriber(self, subscriber): """ When the request is finished for some reason, the request is finished and the subscriber is removed from the set. """ if subscriber in self.subscribers: subscriber.finish() self.subscribers.remove(subscriber) def render_GET(self, request): """ Called when twisted wants to render the page, this method is asynchronous and therefore returns NOT_DONE_YET. """ def wrap(x): return '"' + str(x) + '"' request.setHeader('Content-Type', 'text/event-stream; charset=utf-8') request.setResponseCode(200) self.subscribers.add(request) d = request.notifyFinish() d.addBoth(self.removeSubscriber) msg = [] if len(self.ids) > 0: msg.append('event: new_devices\n') msg.append('data: [%s]\n\n' % ",".join(map(wrap, self.ids))) request.write("".join(msg)) msg = [] widgets = self.layout_manager.get_widgets() msg.append('event: new_widgets\n') msg.append('data: %s\n\n' % widgets) request.write("".join(msg)) msg = [] events = self.data_schema_manager.get_events() jdata = json.dumps({'added': events, 'removed': []}) msg.append('event: new_event_sources\n') msg.append('data: %s\n\n' % jdata) request.write("".join(msg)) log.msg("SSE connection made by %s" % request.getClientIP()) return server.NOT_DONE_YET def publish(self, data): """ When data arrives it is written to every request which is in the subscribers set. """ if self.data_schema_manager.validate(data): jdata = json.loads(data) iid = jdata["id"] iid_encoded = iid.encode('ascii', 'ignore') if settings["activate_mongo"]: self.store(data) msg = [] if iid not in self.ids: log.msg("New connection from device %s" % iid) self.ids.append(iid) msg.append('event: new_devices\n') msg.append('data: ["%s"]\n\n' % iid_encoded) for event, data in jdata.items(): if event in ["id"]: continue event_name = event.encode('ascii', 'ignore') msg.append("event: id%s-%s\n" % (iid_encoded, event_name)) msg.append("data: %s\n\n" % json.dumps(data)) for subscriber in self.subscribers: subscriber.write("".join(msg)) def store(self, data): """ Store the data in the data collection. A string is passed as attribute to avoid pollution of the argument. """ jdata = json.loads(data) jdata['timestamp'] = datetime.utcnow() self.db.data.insert(jdata) def broadcast(self, event_source, data): msg = [] jdata = json.dumps(data) msg.append('event: %s\n' % event_source) msg.append('data: %s\n\n' % jdata) for subscriber in self.subscribers: subscriber.write("".join(msg)) def layout_changed(self, data): self.broadcast('new_widgets', data) def schema_changed(self, data): self.broadcast('new_event_sources', data)
class TestDataSchemaValidations(unittest.TestCase): def setUp(self): self.cwd = settings["lupulo_cwd"] test = "tests/backend/data_schemas/complete.json" self.fp = open(os.path.join(self.cwd, test), "r") self.old_inotify = settings["activate_inotify"] settings["activate_inotify"] = False self.valid_schema_desc = DataSchemaManager(self.fp) def tearDown(self): settings["activate_inotify"] = self.old_inotify self.fp.close() def test_validation_number(self): data = '{"rotation": 180, "id": 1}' self.assertEqual(self.valid_schema_desc.validate(data), True) data = '{"rotation": 400, "id": 1}' self.assertEqual(self.valid_schema_desc.validate(data), False) data = '{"direction": 180, "id": 1}' self.assertEqual(self.valid_schema_desc.validate(data), False) def test_validation_enum(self): test = "tests/backend/data_schemas/enum.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) data = '{"interesting_name": 1, "id": 1}' self.assertEqual(dsd.validate(data), True) data = '{"interesting_name": 2, "id": 1}' self.assertEqual(dsd.validate(data), True) data = '{"interesting_name": 3, "id": 1}' self.assertEqual(dsd.validate(data), True) data = '{"interesting_name": 4, "id": 1}' self.assertEqual(dsd.validate(data), False) def test_validation_list(self): data = """ { "id": 1, "leds": ["on", "off", "null", "on", "null", "off", "null", "on"] } """ self.assertEqual(self.valid_schema_desc.validate(data), True) data = """ { "id": 1, "leds": ["on", "off", "null", "on", "null", "off", "null", "on", "off"] } """ self.assertEqual(self.valid_schema_desc.validate(data), False) data = """ { "id": 1, "leds": ["shit", "off", "null", "on", "null", "off", "null", "on"] } """ self.assertEqual(self.valid_schema_desc.validate(data), False) data = '{"leds": ["off"], "id": 1}' self.assertEqual(self.valid_schema_desc.validate(data), False) @patch("lupulo.descriptors.enum.Enum") def test_validation_list_calls(self, EnumMock): test = "tests/backend/data_schemas/list.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) EnumMock.assert_called_once_with(values=["on", "off", "null"]) data = '{"leds": ["on", "off", "null"], "id": 1}' validate = MagicMock(return_value=True) dsd.descriptors["leds"].delegate.validate = validate self.assertEqual(dsd.validate(data), True) self.assertEqual(validate.call_count, 3) def test_validation_dict(self): data = '{"motor": {"speed": 1.45, "turn_radius": 2.32}, "id": 1}' self.assertEqual(self.valid_schema_desc.validate(data), True) data = '{"motor": {"turn_radius": 2.32}, "id": 1}' self.assertEqual(self.valid_schema_desc.validate(data), False) data = """ { "id": 1, "motor": {"speed": 1.45, "turn_radius": 2.32, "something": 5.55} } """ self.assertEqual(self.valid_schema_desc.validate(data), False) data = '{"motor": {"speed": 1000, "turn_radius": 2.32}, "id": 1}' self.assertEqual(self.valid_schema_desc.validate(data), False) @patch("lupulo.descriptors.enum.Enum") @patch("lupulo.descriptors.number.Number") def test_validation_dict_calls(self, NumberMock, EnumMock): test = "tests/backend/data_schemas/dict.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) EnumMock.assert_called_once_with(values=[0, 3], type="enum") NumberMock.assert_called_once_with(range=[0, 5], type="number") data = '{"motor": {"speed": 4, "turn_radius": 3}, "id": 1}' v_enum = MagicMock(return_value=True) v_number = MagicMock(return_value=True) dsd.descriptors["motor"].delegates["speed"].validate = v_number dsd.descriptors["motor"].delegates["turn_radius"].validate = v_enum self.assertEqual(dsd.validate(data), True) v_enum.assert_called_once_with(3) v_number.assert_called_once_with(4) @patch("lupulo.descriptors.number.Number") def test_validaton_nested_list_dict(self, MockNumber): mock_validate = MagicMock(return_value=True) MockNumber().validate = mock_validate test = "tests/backend/data_schemas/list_dict.json" ifp = open(os.path.join(self.cwd, test), "r") dsd = DataSchemaManager(ifp) data = """ { "id": 1, "motor": [{"speed": 4, "turn_radius": 3}, {"speed": 3, "turn_radius": 2}] } """ dsd.validate(data) self.assertEqual(mock_validate.called, True)