From 49d02c4b9d239e5e25775805ade7d84946073792 Mon Sep 17 00:00:00 2001 From: Michael Herman Date: Wed, 24 Jan 2024 11:30:34 -0700 Subject: [PATCH] updates --- project/app/api/crud.py | 15 ++ project/app/api/summaries.py | 33 ++++- project/app/models/pydantic.py | 8 +- project/tests/test_summaries.py | 250 +++++++++++++++++++++++++++++++- 4 files changed, 299 insertions(+), 7 deletions(-) diff --git a/project/app/api/crud.py b/project/app/api/crud.py index b025098..a746993 100644 --- a/project/app/api/crud.py +++ b/project/app/api/crud.py @@ -23,3 +23,18 @@ async def post(payload: SummaryPayloadSchema) -> int: ) await summary.save() return summary.id + + +async def put(id: int, payload: SummaryPayloadSchema) -> Union[dict, None]: + summary = await TextSummary.filter(id=id).update( + url=payload.url, summary=payload.summary + ) + if summary: + updated_summary = await TextSummary.filter(id=id).first().values() + return updated_summary + return None + + +async def delete(id: int) -> int: + summary = await TextSummary.filter(id=id).first().delete() + return summary diff --git a/project/app/api/summaries.py b/project/app/api/summaries.py index 0257951..6fb3e7e 100644 --- a/project/app/api/summaries.py +++ b/project/app/api/summaries.py @@ -1,16 +1,21 @@ from typing import List -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, Path from app.api import crud -from app.models.pydantic import SummaryPayloadSchema, SummaryResponseSchema from app.models.tortoise import SummarySchema +from app.models.pydantic import ( # isort:skip + SummaryPayloadSchema, + SummaryResponseSchema, + SummaryUpdatePayloadSchema, +) + router = APIRouter() @router.get("/{id}/", response_model=SummarySchema) -async def read_summary(id: int) -> SummarySchema: +async def read_summary(id: int = Path(..., gt=0)) -> SummarySchema: summary = await crud.get(id) if not summary: raise HTTPException(status_code=404, detail="Summary not found") @@ -29,3 +34,25 @@ async def create_summary(payload: SummaryPayloadSchema) -> SummaryResponseSchema response_object = {"id": summary_id, "url": payload.url} return response_object + + +@router.put("/{id}/", response_model=SummarySchema) +async def update_summary( + payload: SummaryUpdatePayloadSchema, id: int = Path(..., gt=0) +) -> SummarySchema: + summary = await crud.put(id, payload) + if not summary: + raise HTTPException(status_code=404, detail="Summary not found") + + return summary + + +@router.delete("/{id}/", response_model=SummaryResponseSchema) +async def delete_summary(id: int = Path(..., gt=0)) -> SummaryResponseSchema: + summary = await crud.get(id) + if not summary: + raise HTTPException(status_code=404, detail="Summary not found") + + await crud.delete(id) + + return summary diff --git a/project/app/models/pydantic.py b/project/app/models/pydantic.py index d60d3fa..0dc2734 100644 --- a/project/app/models/pydantic.py +++ b/project/app/models/pydantic.py @@ -1,9 +1,13 @@ -from pydantic import BaseModel +from pydantic import AnyHttpUrl, BaseModel class SummaryPayloadSchema(BaseModel): - url: str + url: AnyHttpUrl class SummaryResponseSchema(SummaryPayloadSchema): id: int + + +class SummaryUpdatePayloadSchema(SummaryPayloadSchema): + summary: str diff --git a/project/tests/test_summaries.py b/project/tests/test_summaries.py index 2f2684c..d31b5d9 100644 --- a/project/tests/test_summaries.py +++ b/project/tests/test_summaries.py @@ -1,5 +1,7 @@ import json +import pytest + def test_create_summary(test_app_with_db): response = test_app_with_db.post( @@ -7,7 +9,7 @@ def test_create_summary(test_app_with_db): ) assert response.status_code == 201 - assert response.json()["url"] == "https://foo.bar" + assert response.json()["url"] == "https://foo.bar/" def test_create_summaries_invalid_json(test_app): @@ -25,6 +27,12 @@ def test_create_summaries_invalid_json(test_app): ] } + response = test_app.post("/summaries/", data=json.dumps({"url": "invalid://url"})) + assert response.status_code == 422 + assert ( + response.json()["detail"][0]["msg"] == "URL scheme should be 'http' or 'https'" + ) + def test_read_summary(test_app_with_db): response = test_app_with_db.post( @@ -37,7 +45,7 @@ def test_read_summary(test_app_with_db): response_dict = response.json() assert response_dict["id"] == summary_id - assert response_dict["url"] == "https://foo.bar" + assert response_dict["url"] == "https://foo.bar/" assert response_dict["summary"] assert response_dict["created_at"] @@ -47,6 +55,21 @@ def test_read_summary_incorrect_id(test_app_with_db): assert response.status_code == 404 assert response.json()["detail"] == "Summary not found" + response = test_app_with_db.get("/summaries/0/") + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "ctx": {"gt": 0}, + "input": "0", + "loc": ["path", "id"], + "msg": "Input should be greater than 0", + "type": "greater_than", + "url": "https://errors.pydantic.dev/2.5/v/greater_than", + } + ] + } + def test_read_all_summaries(test_app_with_db): response = test_app_with_db.post( @@ -59,3 +82,226 @@ def test_read_all_summaries(test_app_with_db): response_list = response.json() assert len(list(filter(lambda d: d["id"] == summary_id, response_list))) == 1 + + +def test_remove_summary(test_app_with_db): + response = test_app_with_db.post( + "/summaries/", data=json.dumps({"url": "https://foo.bar"}) + ) + summary_id = response.json()["id"] + + response = test_app_with_db.delete(f"/summaries/{summary_id}/") + assert response.status_code == 200 + assert response.json() == {"id": summary_id, "url": "https://foo.bar/"} + + +def test_remove_summary_incorrect_id(test_app_with_db): + response = test_app_with_db.delete("/summaries/999/") + assert response.status_code == 404 + assert response.json()["detail"] == "Summary not found" + + response = test_app_with_db.delete("/summaries/0/") + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "ctx": {"gt": 0}, + "input": "0", + "loc": ["path", "id"], + "msg": "Input should be greater than 0", + "type": "greater_than", + "url": "https://errors.pydantic.dev/2.5/v/greater_than", + } + ] + } + + +def test_update_summary(test_app_with_db): + response = test_app_with_db.post( + "/summaries/", data=json.dumps({"url": "https://foo.bar"}) + ) + summary_id = response.json()["id"] + + response = test_app_with_db.put( + f"/summaries/{summary_id}/", + data=json.dumps({"url": "https://foo.bar", "summary": "updated!"}), + ) + assert response.status_code == 200 + + response_dict = response.json() + assert response_dict["id"] == summary_id + assert response_dict["url"] == "https://foo.bar/" + assert response_dict["summary"] == "updated!" + assert response_dict["created_at"] + + +# def test_update_summary_incorrect_id(test_app_with_db): +# response = test_app_with_db.put( +# "/summaries/999/", +# data=json.dumps({"url": "https://foo.bar", "summary": "updated!"}) +# ) +# assert response.status_code == 404 +# assert response.json()["detail"] == "Summary not found" + +# response = test_app_with_db.put( +# f"/summaries/0/", +# data=json.dumps({"url": "https://foo.bar", "summary": "updated!"}) +# ) +# assert response.status_code == 422 +# assert response.json() == { +# "detail": [ +# { +# "ctx": {"gt": 0}, +# "input": "0", +# "loc": ["path", "id"], +# "msg": "Input should be greater than 0", +# "type": "greater_than", +# "url": "https://errors.pydantic.dev/2.5/v/greater_than", +# } +# ] +# } + + +# def test_update_summary_invalid_json(test_app_with_db): +# response = test_app_with_db.post( +# "/summaries/", data=json.dumps({"url": "https://foo.bar"}) +# ) +# summary_id = response.json()["id"] + +# response = test_app_with_db.put( +# f"/summaries/{summary_id}/", +# data=json.dumps({}) +# ) +# assert response.status_code == 422 +# assert response.json() == { +# "detail": [ +# { +# "input": {}, +# "loc": ["body", "url"], +# "msg": "Field required", +# "type": "missing", +# "url": "https://errors.pydantic.dev/2.5/v/missing", +# }, +# { +# "input": {}, +# "loc": ["body", "summary"], +# "msg": "Field required", +# "type": "missing", +# "url": "https://errors.pydantic.dev/2.5/v/missing", +# } +# ] +# } + + +# def test_update_summary_invalid_keys(test_app_with_db): +# response = test_app_with_db.post( +# "/summaries/", data=json.dumps({"url": "https://foo.bar"}) +# ) +# summary_id = response.json()["id"] + +# response = test_app_with_db.put( +# f"/summaries/{summary_id}/", +# data=json.dumps({"url": "https://foo.bar"}) +# ) +# assert response.status_code == 422 +# assert response.json() == { +# "detail": [ +# { +# "input": {"url": "https://foo.bar"}, +# "loc": ["body", "summary"], +# "msg": "Field required", +# "type": "missing", +# "url": "https://errors.pydantic.dev/2.5/v/missing", +# } +# ] +# } + +# response = test_app_with_db.put( +# f"/summaries/{summary_id}/", +# data=json.dumps({"url": "invalid://url", "summary": "updated!"}) +# ) +# assert response.status_code == 422 +# assert response.json()["detail"][0]["msg"] == "URL scheme should be 'http' or 'https'" + + +@pytest.mark.parametrize( + "summary_id, payload, status_code, detail", + [ + [ + 999, + {"url": "https://foo.bar", "summary": "updated!"}, + 404, + "Summary not found", + ], + [ + 0, + {"url": "https://foo.bar", "summary": "updated!"}, + 422, + [ + { + "type": "greater_than", + "loc": ["path", "id"], + "msg": "Input should be greater than 0", + "input": "0", + "ctx": {"gt": 0}, + "url": "https://errors.pydantic.dev/2.5/v/greater_than", + } + ], + ], + [ + 1, + {}, + 422, + [ + { + "type": "missing", + "loc": ["body", "url"], + "msg": "Field required", + "input": {}, + "url": "https://errors.pydantic.dev/2.5/v/missing", + }, + { + "type": "missing", + "loc": ["body", "summary"], + "msg": "Field required", + "input": {}, + "url": "https://errors.pydantic.dev/2.5/v/missing", + }, + ], + ], + [ + 1, + {"url": "https://foo.bar"}, + 422, + [ + { + "type": "missing", + "loc": ["body", "summary"], + "msg": "Field required", + "input": {"url": "https://foo.bar"}, + "url": "https://errors.pydantic.dev/2.5/v/missing", + } + ], + ], + ], +) +def test_update_summary_invalid( + test_app_with_db, summary_id, payload, status_code, detail +): + response = test_app_with_db.put( + f"/summaries/{summary_id}/", data=json.dumps(payload) + ) + assert response.status_code == status_code + print(response.json()["detail"]) + assert response.json()["detail"] == detail + + +def test_update_summary_invalid_url(test_app): + response = test_app.put( + "/summaries/1/", + data=json.dumps({"url": "invalid://url", "summary": "updated!"}), + ) + assert response.status_code == 422 + assert ( + response.json()["detail"][0]["msg"] == "URL scheme should be 'http' or 'https'" + )