FastAPI pytest with arguments - pytest

I try to test fastAPI get route with pytest and the problem is how i can pass params to client.get
main.py
#app.get('/purpose'):
async def read_purpose(name, date):
"""some logic"""
return {'ok':'ok'}
test.py
client = TestClient(app)
def test_purpose():
response = client.get("/purpose", json={"name":"test_name", "date":"01.01.2020"})
assert response.status_code = 200
My test is failed. it can not find name, and date arguments.
How i can pass this arguments to my test.
Thank you

I have same problem when writing pytest for my first FastAPI demo.
#router.post('/item', tags=['items'], response_model=ShowItem)
async def create_item(item: ItemCreate,
user_id: int,
db: Session = Depends(get_db)):
date_posted = datetime.now().date()
# owner_id = 1
item = Items(**item.dict(),
date_posted=date_posted,
owner_id=user_id)
db.add(item)
db.commit()
db.refresh(item)
return item
You can try "params" instead of "json", because you are passing isolated query parameters
def test_create_item():
# wrong
data = {'title': 'Hot Boat', 'description': 'This is a boat', 'user_id': 1}
resp = client.post('/item', json.dumps(data))
# correct
data = {'title': 'Hot Boat', 'description': 'This is a boat'}
resp = client.post('/item', json.dumps(data), params={"user_id": 2})
assert resp.status_code == 200
Then i can by pass above error.
Try this fix:
client = TestClient(app)
def test_purpose():
response = client.get("/purpose", params={"name":"test_name", "date":"01.01.2020"})
assert response.status_code = 200
More detail refer Question 61383179

Related

Update on sqlalchemy +asyncpg , returns another operation in progress on pytest

I just updated my app with sqlalchemy to create an async connections, no problem, but when writing tests, I can't do the update as opposed to create:
affected function:
#function
#classmethod
async def update_instance(
cls,
instance: InstanceInputOnUpdate
) -> Union['EdaNCEInstance', EdaNCEInstanceOperationError]:
async with get_session() as conn:
result = await conn.execute(
select(Instance).where(Instance.id==eda_nce_instance.id)
)
instance_to_update = result.scalars().unique().first()
if instance_to_update is not None:
instance_to_update.name = instance.name
instance_to_update.host = instance.host
await conn.commit()
return instance_to_update
else:
return InstanceOperationError(
result= False,
message= f"Can't find the instance ID '{instance.id}'"
)
test:
#pytest.mark.asyncio
async def test_04_update_instance():
async with AsyncClient(app=app, base_url="http://test") as c:
res = await Instance.update_eda_nce_instance(
instance=InstanceInputOnUpdate(
id=DUMMY_ID,
name="test_server1",
host="123.123.123.123",
)
)
assert isinstance(res, Instance) == True
assert res.host == "124.123.144.144"
return res
errors:
#...
E sqlalchemy.dialects.postgresql.asyncpg.AsyncAdapt_asyncpg_dbapi.InterfaceError: <class 'asyncpg.exceptions._base.InterfaceError'>: cannot perform operation: another operation is in progress
venv/lib/python3.9/site-packages/sqlalchemy/dialects/postgresql/asyncpg.py:682: InterfaceError
my db.py
SQLALCHEMY_DATABASE_URL = settings.get_settings().postgresql_conn_url
engine = create_async_engine(SQLALCHEMY_DATABASE_URL, echo=False, future=True)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
Base = declarative_base()
async def init_db():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
#asynccontextmanager
async def get_session() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
async with session.begin():
try:
yield session
finally:
await session.close()
In creation I have no problem, and it doesn't change much from the update
#classmethod
async def create_instance(
cls,
instance: InstanceInputOnCreate
) -> Union['Instance', InstanceOperationError]:
async with get_session() as conn:
new_instance = Instance(
name=instance.name,
host=instance.host
)
conn.add(new_instance)
try:
await conn.commit()
return new_instance
except Exception:
conn.rollback()
return InstanceOperationError(
result=False,
message=f"Instance '{instance.name}' already exists"
)
I didn't create mocks, because CI/CDs are set up to create a test database where Alembic can be started first for migrations, then the database is ready to test on it
sqlalchemy==1.4.46
fastapi-utils==0.2.1
aiosqlite==0.18.0
asyncpg==0.27.0
sqlalchemy-utils==0.39.0
pytest==7.2.1
pytest-asyncio==0.20.3
pytest-mock==3.10.0
pydantic~=1.10.4
aiokafka~=0.8.0
requests~=2.28.1
fastapi==0.70.1
If there are typo in the code, it's not a problem I had to change the names to create this question.

How mock requests exception using Pytest fixture for mock_requests?

So I was using the requests-mock library to mock the HTTP requests that I do with the requests library and everything went fine until I had to do a test to catch the exception.
Example of my function to be tested
def get_wordpress_installed_plugins(url: str, user: str, password: str) -> bytes:
try:
credentials = user + ':' + password
token = base64.b64encode(credentials.encode())
header = {'Authorization': 'Basic ' + token.decode('utf-8')}
response = requests.get(url, headers=header, verify=False)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
logger.exception(f"Got response from {url} correctly with error {err}.")
raise CouldNotConnectWithApi(f"Problem retrieving information.")
logger.info(f"Got response from {url} correctly.")
return response.content
And the test to assert function did ok
#mock.patch("checkpluginsversion.logging.Logger.info")
def test_api_wordpress_list_plugins(logger_mock, requests_mock):
user = "user"
password = "password"
url = "https://www.example.com"
expected_result = b'[{"plugin": "akismet\\/akismet", "status": "active", "name": "Akismet Anti-Spam","version": "4.2.2"}]'
requests_mock.get(url,
content=b'[{"plugin": "akismet\\/akismet", "status": "active", "name": "Akismet Anti-Spam","version": "4.2.2"}]')
result = get_wordpress_installed_plugins(url, user, password)
assert result == expected_result
logger_mock.assert_called_with(f"Got response from {url} correctly.")
To be honest, I don't know if using pytest fixture mode of this library is the best way, but ok it is working for me. So the problem I have is when I have to test the function and raise the exception. Eventually, I did a workaround with #mock.patch, and worked for me.
#mock.patch("checkpluginsversion.requests.get")
#mock.patch("checkpluginsversion.logging.Logger.exception")
def test_api_should_return_an_exception(logger_mock,my_request_mock):
user = "user"
password = "password"
url = "https://www.example.com"
expected_result = b'[{"plugin": "akismet\\/akismet", "status": "active", "name": "Akismet Anti-Spam","version": "4.2.2"}]'
my_request_mock.side_effect = requests.exceptions.HTTPError
with pytest.raises(CouldNotConnectWithApi):
result = get_wordpress_installed_plugins(url, user, password)
#assert result == expected_result
logger_mock.assert_called_with(f"Got response from {url} correctly with error .")
But I would know and I will really appreciate it if someone could explain to me how to do a test to raise an exception using the pytest fixture of requests_mock library, thanks!

Misunderstood, an example from the documentation Pytest authorization

I decided to look at Pytest and immediately misunderstood, an example from the documentation, but there is no authorization, the test crashes with a code of 301, can anyone know what is the reason?
def test_with_authenticated_client(client, django_user_model):
username = "TestUser"
password = "1234567"
user = django_user_model.objects.create_user(username=username,
password=password)
# Use this:
client.force_login(user)
response = client.get('/new')
assert response.status_code == 200
def test_with_authenticated_client2(client):
username = "user2"
password = "bar"
# Or this:
client.login(username=username, password=password)
response = client.get('/new')
assert response.status_code == 200
with an unauthorized client, expected code 301
def test_make_not_authorized_user(client):
response = client.get('/new')
assert response.status_code in (302, 301)

Huobi REST API - signing requests - Verification failure

I have problem with Huobi (cryptoexchange) REST API request-signing. Rules are clear (e.g. here). Example for "v1/account/acounts" - GET with no params:
URL params:
AccessKeyId=dbye2sf5t7-d5829459-bf3aee27-67f62&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2020-11-11T12%3A17%3A57
Pre-signing text:
GET\napi.huobi.pro\n/v1/account/accounts\nAccessKeyId=dbye2sf5t7-d5829459-bf3aee27-67f62&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2020-11-11T12%3A17%3A57
Hmac SHA256-Signature of Pre-signing text with private key (verified here):
auZl70i2qsUb7+U9yYEEY1bwzLypWIM7qF1pBAJcvfc=
URL to GET: https://api.huobi.pro/v1/account/accounts?AccessKeyId=dbye2sf5t7-d5829459-bf3aee27-67f62&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2020-11-11T12%3A17%3A57&Signature=auZl70i2qsUb7%2BU9yYEEY1bwzLypWIM7qF1pBAJcvfc%3D
And the response is:
{"status":"error","err-code":"api-signature-not-valid","err-msg":"Signature not valid: Verification failure [校验失败]","data":null}
What I'm making wrong? I have searched thru many code-examples and no error found...
I had this problem with the same endpoint. I'm not sure what code you're using to sign your requests. Here's mine:
class HuobiAuth(requests.auth.AuthBase):
def __init__(self, api_key: str, secret_key: str) -> None:
self.api_key: str = api_key
self.secret_key: str = secret_key
#staticmethod
def create_sign(p_params: Dict, method: str, host_url: str, request_path: str, secret_key: str):
sorted_params = sorted(p_params.items(), key=lambda d: d[0], reverse=False)
# encode_params = urllib.urlencode(sorted_params)
encode_params = urllib.parse.urlencode(sorted_params)
payload = [method, host_url, request_path, encode_params]
payload = '\n'.join(payload)
payload = payload.encode(encoding='UTF8')
secret_key = secret_key.encode(encoding='UTF8')
digest = hmac.new(secret_key, payload, digestmod=hashlib.sha256).digest()
signature = base64.b64encode(digest)
signature = signature.decode()
return signature
def __call__(self, request: requests.PreparedRequest) -> requests.PreparedRequest:
timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')
params_to_sign = {'AccessKeyId': self.api_key,
'SignatureMethod': 'HmacSHA256',
'SignatureVersion': '2',
'Timestamp': timestamp}
host_name = urllib.parse.urlparse(request.url).hostname.lower()
params_to_sign['Signature'] = self.create_sign(params_to_sign, request.method, host_name, request.path_url, self.secret_key)
request.url += '?' + urllib.parse.urlencode(params_to_sign)
return request
My original problem was that instead of using request.method I had a hardcoded "POST", which I must have copied from the original source.
Example usage:
requests.post(url, json=your_data, auth=HuobiAuth(key, secret))

Momoko, Jinja2 and Tornado

there is something fundamentally wrong with my code. These are my
tornado handlers with basic authentication and jinja2 as template
engine. The following works without the momoko db parts.
class BaseHandler(tornado.web.RequestHandler):
#property
def db(self):
return self.application.db
def get_current_user(self):
return self.get_secure_cookie("user")
class TemplateHandler(BaseHandler):
"""Request handler for writing HTML templates."""
def render(self, template_name, **kwargs):
"""Renders a Jinja2 template."""
kwargs['options'] = options.as_dict()
template = templates.environment.get_template(template_name)
html = template.render(kwargs)
self.write(html)
class AuthLoginHandler(TemplateHandler):
def get(self):
try:
errormessage = self.get_argument("error")
except:
errormessage = ""
self.render("login.html", errormessage = errormessage)
def check_permission(self, password, username):
if username == "admin" and password == "admin":
return True
return False
def post(self):
username = self.get_argument("username", "")
password = self.get_argument("password", "")
auth = self.check_permission(password, username)
if auth:
self.set_current_user(username)
self.redirect(self.get_argument("next", u"/"))
else:
error_msg = u"?error=" + tornado.escape.url_escape("Login incorrect")
self.redirect(u"/auth/login/" + error_msg)
def set_current_user(self, user):
if user:
self.set_secure_cookie("user", tornado.escape.json_encode(user))
else:
self.clear_cookie("user")
class AuthLogoutHandler(TemplateHandler):
def get(self):
self.clear_cookie("user")
self.redirect(self.get_argument("next", "/"))
class MainHandler(TemplateHandler):
#gen.engine
def get(self):
username = tornado.escape.xhtml_escape(self.current_user)
try:
cursor = yield momoko.Op(self.db.execute, 'SELECT * FROM products;')
except Exception as error:
self.write(str(error))
res = 'Query results: '+''.join(str(cursor.fetchall()))
self.render("index.html", username = username, cip = self.request.remote_ip, res = res)
For the logged in client, this code should execute a basic query and then print the result to the defined location ( {{ res }} ) within the jinja template. When I try to start the server, I get this:
line 22, in render
kwargs['options'] = options.as_dict()
AttributeError: 'module' object has no attribute 'as_dict'
We'll need to see your "import" statements to know what's wrong for certain. I suspect you have:
from tornado import options
But you need:
from tornado.options import options