diff --git a/Pipfile.lock b/Pipfile.lock index b9a2f5e..6cc74c5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "668d309d10cf7c6f25e160cda7e377ba5b513b8da1cd536de45267aba5547f30" + "sha256": "b223a827ff9d6bbb9ca6b61946b012b87f5f9c739a113800d28756741d8845c9" }, "pipfile-spec": 6, "requires": { @@ -18,10 +18,10 @@ "default": { "certifi": { "hashes": [ - "sha256:4c1d68a1408dd090d2f3a869aa94c3947cc1d967821d1ed303208c9f41f0f2f4", - "sha256:b6e8b28b2b7e771a41ecdd12d4d43262ecab52adebbafa42c77d6b57fb6ad3a4" + "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", + "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" ], - "version": "==2018.8.13" + "version": "==2018.8.24" }, "chardet": { "hashes": [ @@ -84,7 +84,7 @@ "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version < '4' and python_version != '3.0.*' and python_version >= '2.6'", + "markers": "python_version < '4' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.6'", "version": "==1.23" }, "werkzeug": { @@ -96,19 +96,92 @@ } }, "develop": { + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.0" + }, "atomicwrites": { "hashes": [ - "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", - "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6" + "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", + "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" ], - "version": "==1.1.5" + "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==1.2.1" }, "attrs": { "hashes": [ - "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", - "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, + "backcall": { + "hashes": [ + "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", + "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" + ], + "version": "==0.1.0" + }, + "black": { + "hashes": [ + "sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", + "sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191" + ], + "index": "pypi", + "version": "==18.6b4" + }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "version": "==6.7" + }, + "decorator": { + "hashes": [ + "sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82", + "sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c" + ], + "version": "==4.3.0" + }, + "ipdb": { + "hashes": [ + "sha256:7081c65ed7bfe7737f83fa4213ca8afd9617b42ff6b3f1daf9a3419839a2a00a" + ], + "index": "pypi", + "version": "==0.11" + }, + "ipython": { + "hashes": [ + "sha256:007dcd929c14631f83daff35df0147ea51d1af420da303fd078343878bd5fb62", + "sha256:b0f2ef9eada4a68ef63ee10b6dde4f35c840035c50fd24265f8052c98947d5a4" + ], + "version": "==6.5.0" + }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" ], - "version": "==18.1.0" + "version": "==0.2.0" + }, + "jedi": { + "hashes": [ + "sha256:b409ed0f6913a701ed474a614a3bb46e6953639033e31f769ca7581da5bd1ec1", + "sha256:c254b135fb39ad76e78d4d8f92765ebc9bf92cbc76f49e97ade1d5f5121e1f6f" + ], + "version": "==0.12.1" }, "more-itertools": { "hashes": [ @@ -118,29 +191,79 @@ ], "version": "==4.3.0" }, + "parso": { + "hashes": [ + "sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2", + "sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24" + ], + "version": "==0.3.1" + }, + "pexpect": { + "hashes": [ + "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", + "sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b" + ], + "markers": "sys_platform != 'win32'", + "version": "==4.6.0" + }, + "pickleshare": { + "hashes": [ + "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b", + "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5" + ], + "version": "==0.7.4" + }, "pluggy": { "hashes": [ "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" ], - "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*'", + "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.3.*'", "version": "==0.7.1" }, + "prompt-toolkit": { + "hashes": [ + "sha256:1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381", + "sha256:3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4", + "sha256:858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917" + ], + "version": "==1.0.15" + }, + "ptyprocess": { + "hashes": [ + "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", + "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" + ], + "version": "==0.6.0" + }, "py": { "hashes": [ - "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", - "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" + "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", + "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" + ], + "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" ], - "markers": "python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.2.*'", - "version": "==1.5.4" + "version": "==2.2.0" }, "pytest": { "hashes": [ - "sha256:86a8dbf407e437351cef4dba46736e9c5a6e3c3ac71b2e942209748e76ff2086", - "sha256:e74466e97ac14582a8188ff4c53e6cc3810315f342f6096899332ae864c1d432" + "sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", + "sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d" ], "index": "pypi", - "version": "==3.7.1" + "version": "==3.8.0" + }, + "simplegeneric": { + "hashes": [ + "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" + ], + "version": "==0.8.1" }, "six": { "hashes": [ @@ -148,6 +271,27 @@ "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" ], "version": "==1.11.0" + }, + "toml": { + "hashes": [ + "sha256:380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42", + "sha256:a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957" + ], + "version": "==0.9.6" + }, + "traitlets": { + "hashes": [ + "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", + "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + ], + "version": "==4.3.2" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" } } } diff --git a/app.py b/app.py index 314e633..9a8b04f 100644 --- a/app.py +++ b/app.py @@ -5,12 +5,19 @@ app = Flask(__name__) +ENDPOINT_MAIN = "/" +ENDPOINT_MIRROR = "/mirror" +ENDPOINT_USERS = "/users" +ENDPOINT_USERS_BY_ID = "/users/" + +TYPE_USER = "users" + def create_response( data: dict = None, status: int = 200, message: str = "" ) -> Tuple[Response, int]: """Wraps response in a consistent format throughout the API. - + Format inspired by https://medium.com/@shazow/how-i-design-json-api-responses-71900f00f2db Modifications included: - make success a boolean since there's only 2 values @@ -41,7 +48,7 @@ def create_response( """ -@app.route("/") +@app.route(ENDPOINT_MAIN) def hello_world(): return create_response({"content": "hello world!"}) @@ -52,7 +59,73 @@ def mirror(name): return create_response(data) -# TODO: Implement the rest of the API here! +@app.route(ENDPOINT_USERS, methods=["GET"]) +def get_users(): + """ + Query parameters + ---------------- + team: Get list of users belonging to that team. If no users belong to that team, + responds with an empty list. + """ + team = request.args.get("team") + if team is None: + return create_response({"users": db.get(TYPE_USER)}) + else: + return create_response({"users": db.getByTeam(TYPE_USER, team)}) + + +@app.route(ENDPOINT_USERS_BY_ID, methods=["GET"]) +def get_user_by_id(user_id): + user = db.getById(TYPE_USER, user_id) + if user is None: + return create_response( + status=404, message="Could not find a user with id {}".format(user_id) + ) + return create_response({"user": user}) + + +@app.route(ENDPOINT_USERS, methods=["POST"]) +def create_user(): + data = request.get_json() + if data is None: + return create_response(status=422, message="No JSON body provided") + + required_keys = ["name", "age", "team"] + missing_keys = [k for k in required_keys if k not in data] + if len(missing_keys) > 0: + msg = "Body lacks the following data: {}".format(", ".join(missing_keys)) + return create_response(status=422, message=msg) + + user_data = {k: data[k] for k in required_keys} + created_user = db.create(TYPE_USER, user_data) + return create_response(data={"user": created_user}, status=201) + + +@app.route(ENDPOINT_USERS_BY_ID, methods=["PUT"]) +def update_user_by_id(user_id): + """ + Only name, age and team can be updated. + """ + data = request.get_json() + allowed_keys = ["name", "age", "team"] + user_data = {k: data[k] for k in allowed_keys if k in data} + updated_user = db.updateById(TYPE_USER, user_id, user_data) + if updated_user is None: + return create_response( + status=404, message="Could not find a user with id {}".format(user_id) + ) + return create_response({"user": updated_user}) + + +@app.route(ENDPOINT_USERS_BY_ID, methods=["DELETE"]) +def delete_user_by_id(user_id): + if db.getById(TYPE_USER, user_id) is None: + return create_response( + status=404, message="Could not find a user with id {}".format(user_id) + ) + db.deleteById(TYPE_USER, user_id) + return create_response(message="Deleted user with id {}".format(user_id)) + """ ~~~~~~~~~~~~ END API ~~~~~~~~~~~~ diff --git a/mockdb/mockdb_interface.py b/mockdb/mockdb_interface.py index e059be8..50bef85 100644 --- a/mockdb/mockdb_interface.py +++ b/mockdb/mockdb_interface.py @@ -12,6 +12,10 @@ def getById(type, id): return next((i for i in get(type) if i["id"] == id), None) +def getByTeam(type, team): + return [i for i in get(type) if i["team"] == team] + + def create(type, payload): last_id = max([i["id"] for i in get(type)]) new_id = last_id + 1 diff --git a/test_app.py b/test_app.py index 586aba5..80c6dba 100644 --- a/test_app.py +++ b/test_app.py @@ -33,9 +33,61 @@ def tests_get_users_with_team(client): def test_get_user_id(client): + # Test successful request res = client.get("/users/1") assert res.status_code == 200 res_user = res.json["result"]["user"] assert res_user["name"] == "Aria" assert res_user["age"] == 19 + + # Test bad request + res = client.get("/users/0") + assert res.status_code == 404 + assert len(res.json["message"]) > 0 + + +def test_create_user(client): + # Test successful request + body = {"name": "Lato", "age": 28, "team": "C2TC"} + res = client.post("/users", json=body) + assert res.status_code == 201 + + res_user = res.json["result"]["user"] + assert res_user["name"] == "Lato" + assert res_user["id"] == 5 + + # Test bad request + body = {"name": "Lato", "age": 28} + res = client.post("/users", json=body) + assert res.status_code == 422 + assert len(res.json["message"]) > 0 + + +def test_update_user_by_id(client): + body = {"name": "Angel"} + + # Test successful request + res = client.put("/users/1", json=body) + assert res.status_code == 200 + + res_user = res.json["result"]["user"] + assert res_user["name"] == "Angel" + assert res_user["age"] == 19 + + # Test bad request + res = client.put("/users/0", json=body) + assert res.status_code == 404 + assert len(res.json["message"]) > 0 + + +def test_delete_user_by_id(client): + # Test successful request + res = client.delete("/users/1") + assert res.status_code == 200 + assert len(res.json["message"]) > 0 + + # Test bad request + res = client.delete("/users/1") + assert res.status_code == 404 + assert len(res.json["message"]) > 0