Skip to content

Commit 6de5a8a

Browse files
committed
Small adjustments for superdesk-planning behave tests
SDESK-7497
1 parent 268e00f commit 6de5a8a

File tree

6 files changed

+79
-12
lines changed

6 files changed

+79
-12
lines changed

apps/publish/content/common.py

+3
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,12 @@ def update(self, id, updates, original):
242242
# race condition.
243243
published_service = get_resource_service(PUBLISHED)
244244
assert published_service is not None
245+
246+
# TODO-ASYNC: superdesk-planning tests break because of difference between `_id` and `guid`
245247
published_service.patch(id, {QUEUE_STATE: PUBLISH_STATE.PUSHED})
246248
from apps.publish.enqueue import push_publish
247249

250+
# TODO-ASYNC: update this service so task below completes eagearly and tests pass
248251
push_publish.apply_async(str(id))
249252

250253
push_notification(

apps/publish/published_item.py

+11
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ class PublishedItemService(BaseService, HighlightsSearchMixin):
137137

138138
SEQ_KEY_NAME = "published_item_sequence_no"
139139

140+
def patch(self, id, updates):
141+
"""
142+
FIXME: Temporary workaround until we find out why _id and guid are not in sync
143+
when running tests from `superdesk-planning`. Without this, many tests fill fail.
144+
"""
145+
item = self.find_one(req=None, guid=id)
146+
if item:
147+
id = item[ID_FIELD]
148+
149+
return super().patch(id, updates)
150+
140151
def on_fetched(self, docs):
141152
"""
142153
Overriding this to enhance the published article with the one in archive collection

superdesk/core/resources/resource_rest_endpoints.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
# at https://www.sourcefabric.org/superdesk/license
1010

1111
import math
12+
import logging
13+
import traceback
14+
1215
from typing import Optional, cast, Any, Literal
1316
from copy import deepcopy
1417

@@ -44,6 +47,9 @@
4447
from .utils import combine_projection_args
4548

4649

50+
logger = logging.getLogger("superdesk")
51+
52+
4753
@dataclass
4854
class RestParentLink:
4955
#: Name of the resource this parent link belongs to
@@ -368,7 +374,7 @@ async def create_item(self, request: Request) -> Response:
368374
issues = []
369375
return_code = 201
370376
for value in deepcopy(payload):
371-
# Validate the provided item,
377+
# Validate the provided item
372378
try:
373379
for parent_link in self.endpoint_config.parent_links or []:
374380
parent_item = parent_items.get(parent_link.resource_name)
@@ -385,6 +391,8 @@ async def create_item(self, request: Request) -> Response:
385391
ISSUES: get_field_errors_from_pydantic_validation_error(validation_error),
386392
}
387393
)
394+
self._log_traceback(validation_error, "Validation error while creating item")
395+
388396
except SuperdeskError as superdesk_error:
389397
return_code = superdesk_error.status_code
390398
# Let the Superdesk exception populate the issue dictionary
@@ -402,6 +410,7 @@ async def create_item(self, request: Request) -> Response:
402410
ISSUES: {"exception": str(exception)},
403411
}
404412
)
413+
self._log_traceback(exception, "Unexpected exception while creating item")
405414

406415
if self.endpoint_config.exclude_fields_in_response:
407416
# If projection is enabled, we fetch all newly created items with projection applied
@@ -413,7 +422,14 @@ async def create_item(self, request: Request) -> Response:
413422
results: list[dict]
414423
results = [
415424
{
416-
**self._populate_item_hateoas(request, model_instance.to_dict()),
425+
**self._populate_item_hateoas(
426+
request,
427+
# make sure to include unset and default values
428+
model_instance.to_dict(
429+
exclude_unset=False,
430+
exclude_defaults=False,
431+
),
432+
),
417433
STATUS: STATUS_OK,
418434
}
419435
for model_instance in model_instances
@@ -499,6 +515,8 @@ async def update_item(
499515
except ValidationError as validation_error:
500516
return_code = 400
501517
issues = get_field_errors_from_pydantic_validation_error(validation_error)
518+
self._log_traceback(validation_error, "Validation error while updating item")
519+
502520
except SuperdeskError as superdesk_error:
503521
return_code = superdesk_error.status_code
504522
# Let the Superdesk exception populate the issue dictionary
@@ -730,3 +748,8 @@ async def on_fetched_item(self, request: Request, doc: dict) -> None:
730748

731749
async def on_fetched(self, request: Request, doc: RestGetResponse) -> None:
732750
pass
751+
752+
def _log_traceback(self, exception: Exception, message: str = "Exception occurred") -> None:
753+
"""Logs the exception message and the full traceback."""
754+
logger.warn(f"{message}: {exception}")
755+
logger.warn(traceback.format_exc())

superdesk/tests/__init__.py

+15
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,9 @@ async def setup(context=None, config=None, app_factory=get_app, reset=False, aut
403403

404404
if context:
405405
context.app = app
406+
app.test_client_class = TestClient
406407
context.client = app.test_client()
408+
407409
if not hasattr(context, "BEHAVE") and not hasattr(context, "test_context"):
408410
context.test_context = app.test_request_context("/")
409411
await context.test_context.push()
@@ -434,6 +436,10 @@ def add_user_info_to_context(context: Any, token: str, user: User, auth_id=None)
434436
will be converted back to string (internal) by quart/werkzeug Request.
435437
"""
436438
basic_token_header = token_to_basic_auth_header(token)
439+
440+
# remove any existing authorization header. Updates the list (in-place)
441+
# to preserve any references to context.headers elsewhere
442+
context.headers[:] = [h for h in context.headers if h[0] != "Authorization"]
437443
context.headers.append(basic_token_header)
438444

439445
if getattr(context, "user", None):
@@ -607,6 +613,15 @@ def assertDictContains(self, source: dict, contains: dict):
607613

608614

609615
class TestClient(QuartClient):
616+
async def open(self, *args, **kwargs) -> Response:
617+
"""
618+
Appends the request path to the response object for later debugging
619+
"""
620+
621+
response = await super().open(*args, **kwargs)
622+
response.request_path = kwargs.get("path", args[0] if args else "/")
623+
return response
624+
610625
def model_instance_to_json(self, model_instance: ResourceModel):
611626
return model_instance.to_dict(mode="json")
612627

superdesk/tests/publish_steps.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@
2828

2929

3030
@when("we enqueue published")
31-
def step_impl_when_auth(context):
32-
enqueue_published.apply_async()
31+
@async_run_until_complete
32+
async def step_impl_when_auth(context):
33+
await enqueue_published.apply_async()
3334

3435

3536
@then("we get formatted item")

superdesk/tests/steps.py

+22-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111

1212
import os
13+
import io
1314
import time
1415
import arrow
1516
import celery
@@ -20,8 +21,9 @@
2021
from unittest import mock
2122
from copy import deepcopy
2223
from base64 import b64encode
23-
from datetime import datetime, timedelta, date
2424
from os.path import basename
25+
from datetime import datetime, timedelta, date
26+
from quart.datastructures import FileStorage
2527
from re import findall
2628
from inspect import isawaitable
2729
from urllib.parse import urlparse
@@ -82,10 +84,11 @@ async def expect_status_in(response, codes):
8284

8385
assert response.status_code in [
8486
int(code) for code in codes
85-
], "expected on of {expected}, got {code}, reason={reason}".format(
87+
], "expected one of {expected}, got {code}, reason={reason}, url='{requested_path}'".format(
8688
code=response.status_code,
8789
expected=codes,
8890
reason=(await response.get_data()).decode("utf-8"),
91+
requested_path=response.request_path,
8992
)
9093

9194

@@ -1230,15 +1233,28 @@ async def when_upload_patch_dictionary(context):
12301233
async def upload_file(context, dest, filename, file_field, extra_data=None, method="post", user_headers=None):
12311234
if user_headers is None:
12321235
user_headers = []
1236+
12331237
with open(get_fixture_path(context, filename), "rb") as f:
1234-
data = {file_field: f}
1235-
if extra_data:
1236-
data.update(extra_data)
1238+
file_content = f.read()
1239+
files = {
1240+
file_field: FileStorage(
1241+
io.BytesIO(file_content),
1242+
filename=os.path.basename(filename),
1243+
name=file_field,
1244+
)
1245+
}
1246+
12371247
headers = [("Content-Type", "multipart/form-data")]
12381248
headers.extend(user_headers)
12391249
headers = unique_headers(headers, context.headers)
12401250
url = get_prefixed_url(context.app, dest)
1241-
context.response = await getattr(context.client, method)(url, data=data, headers=headers)
1251+
1252+
context.response = await getattr(context.client, method)(
1253+
url,
1254+
files=files,
1255+
form=extra_data,
1256+
headers=headers,
1257+
)
12421258
await assert_ok(context.response)
12431259
await store_placeholder(context, url)
12441260

@@ -1298,7 +1314,6 @@ async def _step_impl_then_get_error(context, code):
12981314
async def step_impl_then_get_error(context, code):
12991315
await expect_status(context.response, int(code))
13001316
if context.text:
1301-
print("got", (await context.response.get_data()).decode("utf-8"))
13021317
await test_json(context)
13031318

13041319

@@ -1469,7 +1484,6 @@ async def step_impl_then_get_existing_resource(context):
14691484

14701485
async def step_impl_then_get_existing(context):
14711486
await assert_200(context.response)
1472-
print("got", get_response_readable(await context.response.get_data()))
14731487
await test_json(context)
14741488

14751489

0 commit comments

Comments
 (0)