From 5a8b88202ef3d4be0a519053e42ad83ba1db2b28 Mon Sep 17 00:00:00 2001 From: Peery Date: Sat, 23 Oct 2021 15:29:35 +0200 Subject: [PATCH] Added Unit Tests to test the WebService Added scripts to test various API calls of the webservice by creating, modifying and deleting entries from a test database of ArtNet. Do NOT use those on production. --- tests/__init__.py | 0 tests/createAPI/__init__.py | 0 tests/createAPI/create_delete_art.py | 180 ++++++++++++++++++++++ tests/createAPI/create_delete_artist.py | 81 ++++++++++ tests/createAPI/create_delete_presence.py | 85 ++++++++++ tests/relationAPI/__init__.py | 0 tests/relationAPI/relate_art.py | 114 ++++++++++++++ tests/run_tests.py | 47 ++++++ 8 files changed, 507 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/createAPI/__init__.py create mode 100644 tests/createAPI/create_delete_art.py create mode 100644 tests/createAPI/create_delete_artist.py create mode 100644 tests/createAPI/create_delete_presence.py create mode 100644 tests/relationAPI/__init__.py create mode 100644 tests/relationAPI/relate_art.py create mode 100644 tests/run_tests.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/createAPI/__init__.py b/tests/createAPI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/createAPI/create_delete_art.py b/tests/createAPI/create_delete_art.py new file mode 100644 index 0000000..82c6e32 --- /dev/null +++ b/tests/createAPI/create_delete_art.py @@ -0,0 +1,180 @@ +import requests +import json +from typing import List, Tuple + +from tests.createAPI.create_delete_artist import list_artists, create_artist_entries, delete_artist_entries +from tests.createAPI.create_delete_presence import test_presence_entries, list_presences, create_presence_entries + + +test_art_entries = [ + {"hash": "hash1", "path": "artist1/image1", "title": "Image Title 1", "link": "http://localhost/artist1/image1.png", + "presences": [(test_presence_entries[0]["name"], test_presence_entries[0]["domain"])]}, + {"hash": "hash2", "path": "artist1/image2", "title": "Image Title 2", "link": "http://localhost/artist1/image2.png", + "presences": [(test_presence_entries[1]["name"], test_presence_entries[1]["domain"])]}, + {"hash": "hash3", "path": "artist2/image1", "title": "Image Title 3", "link": "http://localhost/artist2/image3.png", + "presences": [(test_presence_entries[0]["name"], test_presence_entries[0]["domain"]), + (test_presence_entries[1]["name"], test_presence_entries[1]["domain"])]}, +] + + +def list_art(url: str, port: int): + """ + Return a list of all art known to the database + :param url: + :param port: + :return: + """ + r = requests.get(f"http://{url}:{port}/artnet/metadata/art") + if r.status_code != 200: + raise Exception("Failed querying for art!") + art = json.loads(r.text) + return art + + +def create_art_entries(url: str, port: int): + """ + Creates many art entries for testing purposes + :param url: + :param port: + :return: + """ + for i in range(len(test_art_entries)): + r = create_art(url, port, md5_hash=test_art_entries[i]["hash"], path=test_art_entries[i]["path"], + title=test_art_entries[i]["title"], link=test_art_entries[i]["link"], + presences=test_art_entries[i]["presences"]) + if r.status_code != 200: + print(f"Create Art Entry Test Nr.{i}: failed with {r.status_code} and reason {r.text}") + raise Exception("Create Art Entry Test: FAILED") + else: + test_art_entries[i]["ID"] = json.loads(r.text)["id"] + + print("Created art entries: SUCCESS") + + +def update_art_entries(url: str, port: int): + for i in range(len(test_art_entries)): + r = update_art(url, port, md5_hash=test_art_entries[i]["hash"]+"moreHash", + path=test_art_entries[i]["path"]+"evenBetterPath", + title=test_art_entries[i]["title"]+" updated", + presences=test_art_entries[i]["presences"], art_id=test_art_entries[i]["ID"]) + + if r.status_code != 200: + print(f"Update Art Entry Test Nr.{i}: failed with {r.status_code} and reason {r.text}") + raise Exception("Update Art Entry Test: FAILED") + + for i in range(len(test_art_entries)): + r = requests.get(f"http://{url}:{port}/artnet/metadata/art?id={test_art_entries[i]['ID']}") + if json.loads(r.text)["title"].split(" ")[-1] != "updated": + raise Exception("Update Art Entry Test: Failed (unexpected or no updated title)") + if json.loads(r.text)["link"] != test_art_entries[i]["link"]: + raise Exception("Update Art Entry Test: Failed (unexpected link)") + + print("Updated art entries: SUCCESS") + + +def delete_art_entries(url: str, port: int): + arts = list_art(url, port) + + for i in range(len(arts)): + if "id" in arts[i].keys(): + r = delete_art_by_id(url, port, art_id=arts[i]["id"]) + else: + try: + r = delete_art_by_hash(url, port, md5_hash=arts[i]["hash"]) + except ValueError: + r = None + + if r is not None and r.status_code != 200: + raise Exception(f"Could not delete the art! {r.status_code}, {r.text}") + + print("Deleted art entries: SUCCESS") + + +def create_art(url: str, port: int, md5_hash: str, path: str, title: str, link: str, presences: List[Tuple[str, str]]): + r = requests.post(f"http://{url}:{port}/artnet/metadata/art", + json={"hash": md5_hash, "path": path, "title": title, "link": link, + "presences": [{"name": presence[0], "domain": presence[1]} for presence in presences]}) + + return r + + +def update_art(url: str, port: int, art_id: int, md5_hash: str = None, path: str = None, title: str = None, + link: str = None, presences: List[Tuple[str, str]] = None): + body = {} + if md5_hash is not None: + body["hash"] = md5_hash + if path is not None: + body["path"] = path + if title is not None: + body["title"] = title + if link is not None: + body["link"] = link + if presences is not None: + body["presences"] = [{"name": presence[0], "domain": presence[1]} for presence in presences] + + r = requests.post(f"http://{url}:{port}/artnet/metadata/art?id={art_id}", + json=body) + + return r + + +def delete_art_by_id(url: str, port: int, art_id: int): + r = requests.delete(f"http://{url}:{port}/artnet/metadata/art?id={art_id}") + + return r + + +def delete_art_by_hash(url: str, port: int, md5_hash: str): + r1 = requests.get(f"http://{url}:{port}/artnet/metadata/art?hash={md5_hash}") + if r1.status_code != 200: + raise ValueError("Could not delete art because it could not be found!") + data = json.loads(str(r1.text)) + art_id = data['id'] + + r = requests.delete(f"http://{url}:{port}/artnet/metadata/art?id={art_id}") + + return r + + +def run_art_test(url: str, port: int): + print() + print("----------------") + + print(f"Starting presence test with " + f"({len(list_artists(url, port))}) artists, ({len(list_presences(url, port))}) presences, " + f"({len(list_art(url, port))}) art!") + print("Setting up artist and presences for the art test ...") + create_artist_entries(url, port) + create_presence_entries(url, port) + + print("Starting the art test ...") + if len(list_art(url, port)) > 0: + print("Deleting previous art entries of tests ...") + delete_art_entries(url, port) + + print(f"Creating {len(test_art_entries)} art entries ...") + create_art_entries(url, port) + create_art_result = False if not len(list_art(url, port)) == 3 else True + print(f"Found {len(list_art(url, port))} art entries!") + if not create_art_result: + print(f"Failure! Unexpected number of art entries!") + + print("Updating all art entries with new data ...") + update_art_entries(url, port) + update_art_result = True # given, the update function would raise an exception on error + print(f"Found {len(list_art(url, port))} art entries!") + + # TODO write test for updating presence-art relation + + print("Deleting all art entries ...") + delete_art_entries(url, port) + delete_art_result = False if not len(list_art(url, port)) == 0 else True + print(f"Found {len(list_art(url, port))} art entries!") + + print("Art test complete! Cleaning up setup ...") + delete_artist_entries(url, port) + print("----------------") + print() + + return create_art_result, update_art_result, delete_art_result + diff --git a/tests/createAPI/create_delete_artist.py b/tests/createAPI/create_delete_artist.py new file mode 100644 index 0000000..215ddfa --- /dev/null +++ b/tests/createAPI/create_delete_artist.py @@ -0,0 +1,81 @@ +import requests +from typing import List +import json + +# DB Schema Version 2.0 | 2021-10-10 + +test_artist_entries = [ + {"id": None, "name": "Artist1", "topics": []}, + {"id": None, "name": "Artist2", "topics": []}, + {"id": None, "name": "Artist3", "topics": []} +] + + +def list_artists(url: str, port: int): + """ + Return a list of all artist known to the database + :param url: + :param port: + :return: + """ + r = requests.get(f"http://{url}:{port}/artnet/metadata/artist") + if r.status_code != 200: + print(r.text) + raise Exception("Failed querying for artists!") + artists = json.loads(r.text) + return artists + + +def create_artist_entries(url: str, port: int): + for i in range(len(test_artist_entries)): + r = create_artist(url, port, test_artist_entries[i]["name"], test_artist_entries[i]["topics"]) + + if r.status_code != 200: + print(f"Status:{r.status_code} Text:{r.text}") + raise Exception("Artist Entry creation failed!") + + artist_id = int(r.text) + test_artist_entries[i]["id"] = artist_id + + +def delete_artist_entries(url: str, port: int): + artists = list_artists(url, port) + for i in range(len(artists)): + r = delete_artist(url, port, artists[i]["id"]) + if r.status_code != 200: + print(f"Deleted artist entry #{artists[i]['id']}, {artists[i]['name']}: FAILURE") + else: + print(f"Deleted artist entry #{artists[i]['id']}, {artists[i]['name']}: SUCCESS") + + +def create_artist(url: str, port: int, name: str, topics: List[int]): + r = requests.post(f"http://{url}:{port}/artnet/metadata/artist", + json={"name": name, "topics": topics}) + + return r + + +def delete_artist(url: str, port: int, id: int): + r = requests.delete(f"http://{url}:{port}/artnet/metadata/artist?id={id}") + return r + + +def run_artist_test(url: str, port: int): + print() + print("----------------") + print(f"Starting the artist test with ({len(list_artists(url, port))}) artists!") + print("Starting the artist test ...") + print(f"Creating {len(test_artist_entries)} artists ...") + create_artist_entries(url, port) + print(f"Found {len(list_artists(url, port))} artist entries") + create_artist_result = False if not len(list_artists(url, port)) == 3 else True + + print("Deleting all artists ...") + delete_artist_entries(url, port) + delete_artist_result = False if not len(list_artists(url, port)) == 0 else True + print(f"Found {len(list_artists(url, port))} artist entries") + print("Completed the artist test!") + print("----------------") + print() + + return create_artist_result, delete_artist_result diff --git a/tests/createAPI/create_delete_presence.py b/tests/createAPI/create_delete_presence.py new file mode 100644 index 0000000..a697158 --- /dev/null +++ b/tests/createAPI/create_delete_presence.py @@ -0,0 +1,85 @@ +import requests +import json +from typing import List, Tuple + +from tests.createAPI.create_delete_artist import test_artist_entries, list_artists, create_artist_entries, delete_artist_entries + +test_presence_entries = [ + {"name": "presence_name1", "domain": "presence_domain1", "artist_id": 0}, + {"name": "presence_name2", "domain": "presence_domain1", "artist_id": 1}, + {"name": "presence_name3", "domain": "presence_domain2", "artist_id": 1}, +] + + +def create_presence_entries(url: str, port: int): + for i in range(len(test_presence_entries)): + r = create_presence(url, port, name=test_presence_entries[i]["name"], domain=test_presence_entries[i]["domain"], + artist_id=test_artist_entries[test_presence_entries[i]["artist_id"]]["id"]) + if r.status_code != 200: + print(f"Tried to create presence with name:{test_presence_entries[i]['name']} " + f"domain:{test_presence_entries[i]['domain']} artist_id:{test_presence_entries[i]['artist_id']}") + print(f"Create Presence failed with {r.status_code} and reason {r.text}!") + raise Exception("Create Presence Entry Test: Failed") + + +def create_presence(url: str, port: int, name: str, domain: str, artist_id: int): + r = requests.post(f"http://{url}:{port}/artnet/metadata/presence", + json={"name": name, "domain": domain, "artist_id": artist_id}) + + return r + + +def delete_presence(url: str, port: int, name: str, domain: str): + r = requests.delete(f"http://{url}:{port}/artnet/metadata/presence?name={name}&domain={domain}") + + if r.status_code != 200: + print(f"Deleting Presence entry {name}@{domain}: FAILED") + return False + else: + print(f"Deleting Presence entry {name}@{domain}: SUCCESS") + return True + + +def list_presences(url: str, port: int): + """ + Return a list of all presences known to the database + :param url: + :param port: + :return: + """ + r = requests.get(f"http://{url}:{port}/artnet/metadata/presence") + if r.status_code != 200: + raise Exception("Failed querying for presences!") + presences = json.loads(r.text) + return presences + + +def run_presence_test(url: str, port: int): + print() + print("----------------") + print(f"Starting presence test with " + f"({len(list_artists(url, port))}) artists, ({len(list_presences(url, port))}) presences!") + print("Setting up artists for the presence test ...") + create_artist_entries(url, port) + + print("Starting the presence test ...") + print(f"Creating {len(test_presence_entries)} presences ...") + create_presence_entries(url, port) + create_presence_result = False if not len(list_presences(url, port)) == 3 else True + print(f"Found {len(list_presences(url, port))} presence entries!") + + print(f"Deleting (1) specific presence entry ({test_presence_entries[0]['name']}, " + f"{test_presence_entries[0]['domain']}) ...") + delete_presence_result = delete_presence(url, port, test_presence_entries[0]["name"], + test_presence_entries[0]["domain"]) + print(f"Found {len(list_presences(url, port))} presence entries!") + + print(f"Deleting leftover presences ({len(list_presences(url, port))}) via cascading ...") + delete_artist_entries(url, port) # artist deletion should cascade into the presences + print(f"Found {len(list_presences(url, port))} presence entries!") + cascade_delete_presence_result = False if not len(list_presences(url, port)) == 0 else True + print("Completed the presence test!") + print("----------------") + print() + + return create_presence_result, delete_presence_result, cascade_delete_presence_result diff --git a/tests/relationAPI/__init__.py b/tests/relationAPI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/relationAPI/relate_art.py b/tests/relationAPI/relate_art.py new file mode 100644 index 0000000..95e2754 --- /dev/null +++ b/tests/relationAPI/relate_art.py @@ -0,0 +1,114 @@ +import requests +import json +from typing import List, Tuple + +from tests.createAPI.create_delete_artist import list_artists, create_artist_entries, delete_artist_entries +from tests.createAPI.create_delete_presence import test_presence_entries, list_presences, create_presence_entries +from tests.createAPI.create_delete_art import test_art_entries, list_art, create_art_entries, delete_art_entries + +test_art_presence_relations = [ + {"art_hash": test_art_entries[0]["hash"], "presence_name": test_presence_entries[0]["name"], "presence_domain": test_presence_entries[0]["domain"]}, + {"art_hash": test_art_entries[1]["hash"], "presence_name": test_presence_entries[0]["name"], "presence_domain": test_presence_entries[0]["domain"]}, + {"art_hash": test_art_entries[2]["hash"], "presence_name": test_presence_entries[0]["name"], "presence_domain": test_presence_entries[0]["domain"]}, + {"art_hash": test_art_entries[2]["hash"], "presence_name": test_presence_entries[1]["name"], "presence_domain": test_presence_entries[1]["domain"]}, +] + + +def list_art_to_presence_relations(url: str, port: int): + """ + List all relations between art and presence entries + :param url: + :param port: + :return: + """ + results = [] + + arts = list_art(url, port) + for art in arts: + r = requests.get(f"http://{url}:{port}/artnet/metadata/presence?art_id={art['id']}") + if r.status_code != 200: + raise Exception("Failed to query presences with art id!") + + result = json.loads(r.text) + results.append(result) + + return results + + +def art_hash_to_id(arts: List[dict]): + """ + Converts a list of arts into the format of {hash: id, hash1: id1} + :param arts: + :return: + """ + result = {} + for i in range(len(arts)): + result[arts[i]["md5_hash"].strip()] = arts[i]["id"] + return result + + +def create_art_presence_relations(url: str, port: int): + arts = list_art(url, port) + art_hash_id = art_hash_to_id(arts) + for i in range(len(test_art_presence_relations)): + print(f"Creating art-presence relation with id:{art_hash_id[test_art_presence_relations[i]['art_hash']]} " + f"presence_name:{test_art_presence_relations[i]['presence_name']} " + f"presence_domain:{test_art_presence_relations[i]['presence_domain']}") + r = create_art_presence_relation(url, port, art_id=art_hash_id[test_art_presence_relations[i]["art_hash"]], + presence_name=test_art_presence_relations[i]["presence_name"], + presence_domain=test_art_presence_relations[i]["presence_domain"]) + if r.status_code != 200: + print(f"Tried to create art-presence relation with name:{test_art_presence_relations[i]['presence_name']} " + f"domain:{test_art_presence_relations[i]['presence_domain']}" + f" art_id:{test_art_presence_relations[i]['art_id']}") + print(f"Create Relation failed with {r.status_code} and reason {r.text}!") + raise Exception("Couldn't create art-presence relation!") + + l = list_art_to_presence_relations(url, port) + if len(l) == 3 and len(l[-1]) == 2: + return True + else: + return False + + +def create_art_presence_relation(url: str, port: int, presence_name: str, presence_domain: str, art_id: int): + r = requests.post(f"http://{url}:{port}/artnet/metadata/art?id={art_id}", + json={"presences": [{"name": presence_name, "domain": presence_domain}]}) + return r + + +def run_art_presence_relation_test(url, port): + print() + print("----------------") + if len(list_presences(url, port)) > 0: + print("Deleting leftover presence entries via cascading from artists ...") + delete_artist_entries(url, port) # deleting presences via cascading + if len(list_art(url, port)) > 0: + print("Deleting leftover art entries ...") + delete_art_entries(url, port) + + print(f"Starting presence test with " + f"({len(list_presences(url, port))}) presences, " + f"({len(list_art(url, port))}) art!") + + print("Setting up art and presences for the art-presence-relation test ...") + create_artist_entries(url, port) + create_presence_entries(url, port) + create_art_entries(url, port) + + art = list_art(url, port) # debug + presences = list_presences(url, port) # debug + + print() + print("Starting the art-presence test ...") + if sum([len(x) for x in list_art_to_presence_relations(url, port)]) != 4: # 3 expected from create_art_entries() + print("Anomalous amount of presence-art relations ...") + exit(1) + + print("Creating art-presence relations ...") + create_result = create_art_presence_relations(url, port) + print(f"Found {sum([len(x) for x in list_art_to_presence_relations(url, port)])} art-presence relations!") + + # TODO delete art-presence relation test + + return create_result diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100644 index 0000000..c0def89 --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,47 @@ +from tests.createAPI.create_delete_art import run_art_test, list_art, delete_art_entries +from tests.createAPI.create_delete_presence import run_presence_test, list_presences +from tests.createAPI.create_delete_artist import delete_artist_entries, list_artists, run_artist_test + +from tests.relationAPI.relate_art import run_art_presence_relation_test + + +def run_tests(url: str, port: int): + """ + Run unit tests against all API interfaces + :param url: + :param port: + :return: + """ + if len(list_artists(url, port)) > 0: + print("Deleting leftover artists ...") + delete_artist_entries(url, port) + if len(list_art(url, port)) > 0: + print("Deleting leftover art ...") + delete_art_entries(url, port) + + create_artist_result, delete_artist_result = run_artist_test(url, port) + create_presence_result, delete_presence_result, cascade_delete_presence_result = run_presence_test(url, port) + create_art_result, update_art_result, delete_art_result = run_art_test(url, port) + + print() + print("-------- Test Results ---------") + print("Leftover DB Objects:") + r = list_artists(url, port) + print(f"Artists: {len(r)}, {r}") + r = list_presences(url, port) + print(f"Presences: {len(r)}, {r}") + r = list_art(url, port) + print(f"Art: {len(r)}, {r}") + print() + print("Functions:") + print(f"Artists: \tCreate: {create_artist_result} \tUpdate: {'N/A'} \tDelete: {delete_artist_result} \t(Direct)") + print(f"Presences: \tCreate: {create_presence_result} \tUpdate: {'N/A'} " + f"\tDelete: {delete_presence_result} \t(Direct), {cascade_delete_presence_result} \t(cascade@artist)") + print(f"Art: \t\tCreate: {create_art_result} \tUpdate: {update_art_result} " + f"\tDelete: {delete_art_result} \t(Direct)") + print("-------------------------------") + + +if __name__ == "__main__": + #run_tests("127.0.0.1", 8000) + run_art_presence_relation_test("127.0.0.1", 8000)