Skip to content

Commit

Permalink
[SDESK-7441] Fixes required for planning tests to run/pass (superdesk…
Browse files Browse the repository at this point in the history
…#2758)

* Fix "AttributeError: 'function' object has no attribute 'reset'" error

SDESK-7441

* Remove "basic" word from token and ascii encoding

The token is taken as it comes from the request headers (without decoding) in the `SuperdeskTokenAuth` class so I'm not sure what was the reason of this encoding + the basic word. Removing this would allow the authentication work in the behave tests

SDESK-7441

* Run steps async until complete

SDESK-7441

* Properly create basic token header

This uses `werkzeug.datastructures.Authorization` to generate a proper basic token auth so we don't need to know the internal details of quart/werkzeug implementation.

SDESK-7441

* Fix typo

SDESK-7441

* Skip sending signals in test env until async is supported

SDESK-7441

* Minor fixes

SDESK-7441
  • Loading branch information
eos87 authored Nov 27, 2024
1 parent 4e957bf commit bd1015b
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 23 deletions.
9 changes: 7 additions & 2 deletions apps/archive/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,13 +868,18 @@ def update(self, id, updates, original):

# send signal
# TODO-ASYNC: Support async signals
signals.item_update.send(self, updates=updates, original=original)
superdesk_testing = get_app_config("SUPERDESK_TESTING", False)
if not superdesk_testing:
signals.item_update.send(self, updates=updates, original=original)

super().update(id, updates, original)

updated = copy(original)
updated.update(updates)
signals.item_updated.send(self, item=updated, original=original)

# TODO-ASYNC: Support async signals
if not superdesk_testing:
signals.item_updated.send(self, item=updated, original=original)

if "marked_for_user" in updates:
# TODO-ASYNC: Support async (see superdesk.tests.markers.requires_eve_resource_async_event)
Expand Down
33 changes: 25 additions & 8 deletions superdesk/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# AUTHORS and LICENSE files distributed with this source code, or
# at https://www.sourcefabric.org/superdesk/license

from typing import Dict, Any
from typing import Dict, Any, Tuple
import os
import functools
import logging
Expand All @@ -17,11 +17,11 @@
from dataclasses import dataclass

from copy import deepcopy
from base64 import b64encode
from unittest.mock import patch
from unittest import IsolatedAsyncioTestCase
from quart import Response
from quart.testing import QuartClient
from werkzeug.datastructures import Authorization

from superdesk.core import json
from superdesk.flask import Config
Expand All @@ -35,6 +35,7 @@
from superdesk.core.resources import ResourceModel
from superdesk.storage.amazon_media_storage import AmazonMediaStorage
from superdesk.storage.proxy import ProxyMediaStorage
from superdesk.types import User


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -362,7 +363,7 @@ def inner(*a, **kw):


async def setup(context=None, config=None, app_factory=get_app, reset=False, auto_add_apps: bool = True):
if not hasattr(setup, "app") or setup.reset or config: # type: ignore[attr-defined]
if not hasattr(setup, "app") or hasattr(setup, "reset") or config: # type: ignore[attr-defined]
if hasattr(setup, "app"):
# Close all PyMongo Connections (new ones will be created with ``app_factory`` call)
for key, val in setup.app.extensions["pymongo"].items():
Expand Down Expand Up @@ -393,12 +394,28 @@ async def setup_auth_user(context, user=None):
await setup_db_user(context, user)


def add_to_context(context, token, user, auth_id=None):
context.headers.append(("Authorization", b"basic " + b64encode(token + b":")))
def token_to_basic_auth_header(token: str) -> Tuple[str, str]:
"""
Use werkzeug's Authorization to create a valid basic auth token. This way we don't
need to know how quart/werkzeug handles it convertion to string internally
"""
basic_auth = Authorization("basic", data=dict(username=token, password=""))
return ("Authorization", basic_auth.to_header()) # type: ignore[attr-defined]


def add_user_info_to_context(context: Any, token: str, user: User, auth_id=None):
"""
Add current user's session information to context headers.
It converts the plain string token into a valid basic auth token that
will be converted back to string (internal) by quart/werkzeug Request.
"""
basic_token_header = token_to_basic_auth_header(token)
context.headers.append(basic_token_header)

if getattr(context, "user", None):
context.previous_user = context.user
context.user = user

set_placeholder(context, "CONTEXT_USER_ID", str(user.get("_id")))
set_placeholder(context, "AUTH_ID", str(auth_id))

Expand Down Expand Up @@ -443,9 +460,9 @@ async def setup_db_user(context, user):
)

auth_data = json.loads(await auth_response.get_data())
token = auth_data.get("token").encode("ascii")
token = auth_data.get("token")
auth_id = auth_data.get("_id")
add_to_context(context, token, user, auth_id)
add_user_info_to_context(context, token, user, auth_id)


def setup_ad_user(context, user):
Expand Down Expand Up @@ -492,7 +509,7 @@ def setup_ad_user(context, user):
token = auth_response_as_json.get("token").encode("ascii")
ad_user["_id"] = auth_response_as_json["user"]

add_to_context(context, token, ad_user)
add_user_info_to_context(context, token, ad_user)


class NotificationMock:
Expand Down
38 changes: 25 additions & 13 deletions superdesk/tests/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from datetime import datetime, timedelta, date
from os.path import basename
from re import findall
from unittest.mock import patch
from inspect import isawaitable
from urllib.parse import urlparse
from pathlib import Path

Expand Down Expand Up @@ -68,17 +68,20 @@


async def expect_status(response, code):
assert int(code) == response.status_code, "exptected {expected}, got {code}, reason={reason}".format(
assert int(code) == response.status_code, "expected {expected}, got {code}, reason={reason}".format(
code=response.status_code,
expected=code,
reason=(await response.get_data()).decode("utf-8"),
)


async def expect_status_in(response, codes):
if isawaitable(response):
response = await response

assert response.status_code in [
int(code) for code in codes
], "exptected on of {expected}, got {code}, reason={reason}".format(
], "expected on of {expected}, got {code}, reason={reason}".format(
code=response.status_code,
expected=codes,
reason=(await response.get_data()).decode("utf-8"),
Expand Down Expand Up @@ -1445,6 +1448,11 @@ async def step_impl_then_get_nofield_in_path(context, path):

@then("we get existing resource")
@async_run_until_complete
async def step_impl_then_get_existing_resource(context):
# split into separate function so can be called/awaited independently
await step_impl_then_get_existing(context)


async def step_impl_then_get_existing(context):
await assert_200(context.response)
print("got", get_response_readable(await context.response.get_data()))
Expand Down Expand Up @@ -1952,21 +1960,23 @@ async def we_reset_password_for_user(context):


@when("we switch user")
def when_we_switch_user(context):
@async_run_until_complete
async def when_we_switch_user(context):
user = {
"username": "test-user-2",
"password": "pwd",
"is_active": True,
"needs_activation": False,
"sign_off": "foo",
}
tests.setup_auth_user(context, user)
await tests.setup_auth_user(context, user)
set_placeholder(context, "USERS_ID", str(context.user["_id"]))


@when("we setup test user")
def when_we_setup_test_user(context):
tests.setup_auth_user(context, tests.test_user)
@async_run_until_complete
async def when_we_setup_test_user(context):
await tests.setup_auth_user(context, tests.test_user)


@when('we get my "{url}"')
Expand Down Expand Up @@ -2285,7 +2295,7 @@ async def then_we_get_activity(context):
set_placeholder(context, "USERS_ID", item["user"])


def login_as(context, username, password, user_type):
async def login_as(context, username, password, user_type):
user = {
"username": username,
"password": password,
Expand All @@ -2298,17 +2308,19 @@ def login_as(context, username, password, user_type):
if context.text:
user.update(json.loads(context.text))

tests.setup_auth_user(context, user)
await tests.setup_auth_user(context, user)


@given('we login as user "{username}" with password "{password}" and user type "{user_type}"')
def given_we_login_as_user(context, username, password, user_type):
login_as(context, username, password, user_type)
@async_run_until_complete
async def given_we_login_as_user(context, username, password, user_type):
await login_as(context, username, password, user_type)


@when('we login as user "{username}" with password "{password}" and user type "{user_type}"')
def when_we_login_as_user(context, username, password, user_type):
login_as(context, username, password, user_type)
@async_run_until_complete
async def when_we_login_as_user(context, username, password, user_type):
await login_as(context, username, password, user_type)


def is_user_resource(resource):
Expand Down

0 comments on commit bd1015b

Please sign in to comment.