async def test_subscribers(core_client: ApiClient) -> None: # provide a clean slate for subscriber in core_client.subscribers(): core_client.delete_subscriber(subscriber.id) sub_id = rnd_str() # add subscription subscriber = core_client.add_subscription(sub_id, rc.Subscription("test")) assert subscriber.id == sub_id assert len(subscriber.subscriptions) == 1 assert subscriber.subscriptions["test"] is not None # delete subscription subscriber = core_client.delete_subscription(sub_id, rc.Subscription("test")) assert subscriber.id == sub_id assert len(subscriber.subscriptions) == 0 # update subscriber updated = core_client.update_subscriber(sub_id, [rc.Subscription("test"), rc.Subscription("rest")]) assert updated is not None assert updated.id == sub_id assert len(updated.subscriptions) == 2 # subscriber for message type assert core_client.subscribers_for_event("test") == [updated] assert core_client.subscribers_for_event("rest") == [updated] assert core_client.subscribers_for_event("does_not_exist") == [] # get subscriber sub = core_client.subscriber(sub_id) assert sub is not None
async def test_config(core_client: ApiClient, foo_kinds: List[Kind]) -> None: # make sure we have a clean slate for config in await core_client.configs(): await core_client.delete_config(config) # define a config model model = await core_client.update_configs_model(foo_kinds) assert "foo" in model assert "bla" in model # get the config model again get_model = await core_client.get_configs_model() assert len(model) == len(get_model) # define config validation validation = ConfigValidation("external.validated.config", external_validation=True) assert await core_client.put_config_validation(validation) == validation # get the config validation assert await core_client.get_config_validation(validation.id) == validation # put config cfg_id = rnd_str() # put a config with schema that is violated with pytest.raises(AttributeError) as ex: await core_client.put_config(cfg_id, {"foo": {"some_int": "abc"}}) assert "Expected number but got abc" in str(ex.value) # put a config with schema that is violated, but turn validation off await core_client.put_config(cfg_id, {"foo": { "some_int": "abc" }}, validate=False) # set a simple state assert await core_client.put_config(cfg_id, {"a": 1}) == {"a": 1} # patch config assert await core_client.patch_config(cfg_id, {"a": 1}) == {"a": 1} assert await core_client.patch_config(cfg_id, {"b": 2}) == {"a": 1, "b": 2} assert await core_client.patch_config(cfg_id, {"c": 3}) == { "a": 1, "b": 2, "c": 3 } # get config assert await core_client.config(cfg_id) == {"a": 1, "b": 2, "c": 3} # list configs assert await core_client.configs() == [cfg_id] # delete config await core_client.delete_config(cfg_id) assert await core_client.configs() == []
async def execute(self, request: Request) -> StreamResponse: temp_dir: Optional[str] = None try: ctx = self.cli_context_from_request(request) if request.content_type.startswith("text"): command = (await request.text()).strip() elif request.content_type.startswith("multipart"): command = request.headers["Resoto-Shell-Command"].strip() temp = tempfile.mkdtemp() temp_dir = temp files = {} # for now we assume that all multi-parts are file uploads async for part in MultipartReader(request.headers, request.content): name = part.name if not name: raise AttributeError( "Multipart request: content disposition name is required!" ) path = os.path.join( temp, rnd_str()) # use random local path to avoid clashes files[name] = path with open(path, "wb") as writer: while not part.at_eof(): writer.write(await part.read_chunk()) ctx = replace(ctx, uploaded_files=files) else: raise AttributeError( f"Not able to handle: {request.content_type}") # we want to eagerly evaluate the command, so that parse exceptions will throw directly here parsed = await self.cli.evaluate_cli_command(command, ctx) return await self.execute_parsed(request, command, parsed) finally: if temp_dir: shutil.rmtree(temp_dir)
async def test_graph_api(core_client: ApiClient) -> None: # make sure we have a clean slate with suppress(Exception): core_client.delete_graph(g) # create a new graph graph = AccessJson(core_client.create_graph(g)) assert graph.id == "root" assert graph.reported.kind == "graph_root" # list all graphs graphs = core_client.list_graphs() assert g in graphs # get one specific graph graph: AccessJson = AccessJson(core_client.get_graph(g)) # type: ignore assert graph.id == "root" assert graph.reported.kind == "graph_root" # wipe the data in the graph assert core_client.delete_graph(g, truncate=True) == "Graph truncated." assert g in core_client.list_graphs() # create a node in the graph uid = rnd_str() node = AccessJson(core_client.create_node("root", uid, {"identifier": uid, "kind": "child", "name": "max"}, g)) assert node.id == uid assert node.reported.name == "max" # update a node in the graph node = AccessJson(core_client.patch_node(uid, {"name": "moritz"}, "reported", g)) assert node.id == uid assert node.reported.name == "moritz" # get the node node = AccessJson(core_client.get_node(uid, g)) assert node.id == uid assert node.reported.name == "moritz" # delete the node core_client.delete_node(uid, g) with pytest.raises(AttributeError): # node can not be found core_client.get_node(uid, g) # merge a complete graph merged = core_client.merge_graph(graph_to_json(create_graph("test")), g) assert merged == rc.GraphUpdate(112, 1, 0, 212, 0, 0) # batch graph update and commit batch1_id, batch1_info = core_client.add_to_batch(graph_to_json(create_graph("hello")), "batch1", g) assert batch1_info == rc.GraphUpdate(0, 100, 0, 0, 0, 0) assert batch1_id == "batch1" batch_infos = AccessJson.wrap_list(core_client.list_batches(g)) assert len(batch_infos) == 1 # assert batch_infos[0].id == batch1_id assert batch_infos[0].affected_nodes == ["collector"] # replace node assert batch_infos[0].is_batch is True core_client.commit_batch(batch1_id, g) # batch graph update and abort batch2_id, batch2_info = core_client.add_to_batch(graph_to_json(create_graph("bonjour")), "batch2", g) assert batch2_info == rc.GraphUpdate(0, 100, 0, 0, 0, 0) assert batch2_id == "batch2" core_client.abort_batch(batch2_id, g) # update nodes update = [{"id": node["id"], "reported": {"name": "bruce"}} for _, node in create_graph("foo").nodes(data=True)] updated_nodes = core_client.patch_nodes(update, g) assert len(updated_nodes) == 113 for n in updated_nodes: assert n.get("reported", {}).get("name") == "bruce" # create the raw search raw = core_client.search_graph_raw('id("3")', g) assert raw == { "query": "LET filter0 = (FOR m0 in graphtest FILTER m0._key == @b0 RETURN m0) " 'FOR result in filter0 RETURN UNSET(result, ["flat"])', "bind_vars": {"b0": "3"}, } # estimate the search cost = core_client.search_graph_explain('id("3")', g) assert cost.full_collection_scan is False assert cost.rating == rc.EstimatedQueryCostRating.simple # search list result_list = list(core_client.search_list('id("3") -[0:]->', graph=g)) assert len(result_list) == 11 # one parent node and 10 child nodes assert result_list[0].get("id") == "3" # first node is the parent node # search graph result_graph = list(core_client.search_graph('id("3") -[0:]->', graph=g)) assert len(result_graph) == 21 # 11 nodes + 10 edges assert result_list[0].get("id") == "3" # first node is the parent node # aggregate result_aggregate = core_client.search_aggregate("aggregate(reported.kind as kind: sum(1) as count): all", g) assert {r["group"]["kind"]: r["count"] for r in result_aggregate} == { "bla": 100, "cloud": 1, "foo": 11, "graph_root": 1, } # delete the graph assert core_client.delete_graph(g) == "Graph deleted." assert g not in core_client.list_graphs()
def test_random_str() -> None: assert rnd_str() != rnd_str()