Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(event-handler): add appsync batch resolvers #1998

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
432093c
update changelog with latest changes
Mar 10, 2023
da1e745
Add batch processing to appsync handler
Mar 3, 2023
9c5cbd9
Extend router to accept List of events. Add functional test
Mar 3, 2023
4793efd
Add e2e tests
Mar 10, 2023
e3ca8c5
Add required package
Mar 10, 2023
d0fe867
Fix linter checks
Mar 10, 2023
bc45703
Refactor appsync resolver
Mar 23, 2023
b7a4391
Refactor code to use composition instead of inheritence
Mar 31, 2023
e1d2caa
Refactor appsync event handler
Apr 7, 2023
b1a49d6
merging from develop
leandrodamascena Apr 14, 2023
f201d25
Merge remote-tracking branch 'upstream/develop' into fix/1303-appsync…
leandrodamascena Apr 27, 2023
8fe6a8b
fix style
leandrodamascena Apr 27, 2023
252a796
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena May 5, 2023
bb87fa3
Merge branch 'develop' into fix/1303-appsync-batch-invoke
heitorlessa Jun 8, 2023
2714b0f
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Jun 8, 2023
986b497
Merge branch 'develop' into fix/1303-appsync-batch-invoke
heitorlessa Jun 12, 2023
b21a0a1
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Jun 28, 2023
a96df34
Merge branch 'develop' into fix/1303-appsync-batch-invoke
mploski Jul 14, 2023
d5bbd09
Add support for async batch processing
Jul 21, 2023
9f8625e
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Aug 15, 2023
659a044
Fixing sonarcloud error
leandrodamascena Aug 15, 2023
13b0bcd
Adding docstring + increasing coverage
leandrodamascena Aug 15, 2023
db74f0a
Adding missing test + increasing coverage
leandrodamascena Aug 15, 2023
1acfe6e
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Aug 29, 2023
b19f4d3
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Sep 4, 2023
c0c45c5
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Sep 5, 2023
56ce1fd
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Sep 27, 2023
5ac0e3c
Start writing docs
Oct 31, 2023
f248769
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Dec 7, 2023
0d2f7e9
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Jan 23, 2024
5f44a2b
Refactoring examples
leandrodamascena Jan 23, 2024
ab7d5b9
Refactoring code + examples + documentation
leandrodamascena Jan 25, 2024
4711cdf
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Jan 25, 2024
50894b9
Moving e2e tests to the right folder
leandrodamascena Jan 25, 2024
3ae6f73
Moving e2e tests to the right folder
leandrodamascena Jan 25, 2024
733b888
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Jan 26, 2024
99b03c5
Adding partial failure
leandrodamascena Jan 26, 2024
32be46c
Adding partial failure
leandrodamascena Jan 26, 2024
4d07d3c
Fixing docstring and examples
leandrodamascena Jan 27, 2024
5644225
Adding documentation about Handling Exceptions
leandrodamascena Jan 27, 2024
78d330b
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Jan 29, 2024
13ac0a1
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Jan 29, 2024
b928996
Fixing docstring
leandrodamascena Jan 29, 2024
45c8a38
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Feb 1, 2024
48ee55f
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Feb 2, 2024
88d2151
Merging from develop
leandrodamascena Feb 6, 2024
f81a003
Adding fine grained control when handling exceptions
leandrodamascena Feb 7, 2024
25fff55
Adding fine grained control when handling exceptions
leandrodamascena Feb 7, 2024
5a335f6
Merge branch 'develop' into fix/1303-appsync-batch-invoke
heitorlessa Feb 7, 2024
cbe7190
docs: add intro diagram
heitorlessa Feb 9, 2024
38a77e8
Merge branch 'develop' into fix/1303-appsync-batch-invoke
heitorlessa Feb 21, 2024
f10a02c
docs: fix wording (Tech debt)
heitorlessa Feb 9, 2024
f0c7b36
refactor: use async_ prefix for async code
heitorlessa Feb 21, 2024
db6d67d
refactor: move router to a separate file to ease maintenance
heitorlessa Feb 21, 2024
50134fc
refactor: rename BasePublic to BaseRouter
heitorlessa Feb 21, 2024
14635f5
refactor: undo router context composition to reduce complexity and ca…
heitorlessa Feb 21, 2024
774a725
refactor: reduce abstractions, use explicit methods over assignments
heitorlessa Feb 21, 2024
e36dd7c
refactor: move registry to a separate file; make it private
heitorlessa Feb 21, 2024
3cddfff
refactor: expand inline if for readability
heitorlessa Feb 21, 2024
17015df
refactor: short circuit upfront, complex after
heitorlessa Feb 22, 2024
54d892b
refactor: simplify arg name
heitorlessa Feb 22, 2024
0ad3383
refactor: add debug statements
heitorlessa Feb 22, 2024
eb340ec
fix(docs): use .context instead of previous ._router.context
heitorlessa Feb 22, 2024
7326582
refactor: use kwargs for explicitness
heitorlessa Feb 22, 2024
26353fa
refactor: use return_exceptions=True to reduce call stack
heitorlessa Feb 23, 2024
b27d497
chore: add notes on the beauty of return_exceptions
heitorlessa Feb 23, 2024
2a1c785
refactor: append suffix in exceptions
heitorlessa Feb 23, 2024
98a8aff
chore: if over elif in short-circuit
heitorlessa Feb 23, 2024
32fd640
chore: improve logging; glad I learned this new f-string trick
heitorlessa Feb 23, 2024
de823a6
chore: fix debug statement location due to null resolvers
heitorlessa Feb 23, 2024
1698778
revert: debug graceful error flag due to non-determinism async
heitorlessa Feb 23, 2024
3e036f9
revert: debug stmt due to mypy; moving elsewhere
heitorlessa Feb 23, 2024
01251bc
docs: docstring resolver (tech debt)
heitorlessa Feb 23, 2024
d280742
docs: minimal batch_resolver docstring
heitorlessa Feb 23, 2024
507d854
chore: complete resolver docstring
heitorlessa Feb 23, 2024
f213510
Merge branch 'develop' into fix/1303-appsync-batch-invoke
heitorlessa Feb 23, 2024
a805ac3
Removing payload exception
Feb 27, 2024
be8b056
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Feb 27, 2024
8592194
Updating poetry
Feb 27, 2024
90e73d0
merging develop
Feb 27, 2024
69fd6ae
Merging from develop
leandrodamascena Mar 14, 2024
e14c73f
Merging from develop
leandrodamascena Mar 14, 2024
857375c
Merging from develop
leandrodamascena Jun 24, 2024
9ed003e
Addressing Heitor's feedback
leandrodamascena Jun 24, 2024
d039211
Merge branch 'develop' into fix/1303-appsync-batch-invoke
leandrodamascena Jun 24, 2024
84c3590
Refactoring to support aggregate events
leandrodamascena Jun 24, 2024
4493165
Refactoring examples + docs
leandrodamascena Jun 24, 2024
a778dcd
Addressing Heitor's feedback
leandrodamascena Jun 25, 2024
615c6d7
Merging from develop
leandrodamascena Jun 25, 2024
b0e1e3e
docs: add diagram to visualize n+1 problem
heitorlessa Jun 26, 2024
fb631ea
docs: improve wording in lambda invoke
heitorlessa Jun 26, 2024
1ebc157
docs: add diagram where n+1 problem shifts to Lambda runtime
heitorlessa Jun 26, 2024
de73d47
docs: add diagram where n+1 problem shifts to Lambda runtime w/ error…
heitorlessa Jun 26, 2024
4414d6b
docs: highlight lambda response for non-errors
heitorlessa Jun 26, 2024
3764819
docs(setup): increase table of contents depth to 5 to help redis and …
heitorlessa Jun 26, 2024
8606136
Adding examples
leandrodamascena Jun 26, 2024
4c84e52
docs: explain N+1 problem and organize content into sub-sections
heitorlessa Jun 26, 2024
06480d4
docs: clean up batch resolvers section; add typing
heitorlessa Jun 26, 2024
925040e
docs: clean up no-aggregate processing section
heitorlessa Jun 26, 2024
2428ebd
docs: clean up raise on error section
heitorlessa Jun 26, 2024
92db4a2
Adding examples
leandrodamascena Jun 26, 2024
8f3af70
docs: clean up async section
heitorlessa Jun 26, 2024
bc6cd12
docs: fix highlights, add missing code annotation
heitorlessa Jun 26, 2024
a4173e0
docs: rename snippets to match advanced section
heitorlessa Jun 26, 2024
ba173b3
Merge branch 'develop' into fix/1303-appsync-batch-invoke
heitorlessa Jun 26, 2024
5d3616a
Merging from develop
leandrodamascena Jun 26, 2024
a612e38
Tests
leandrodamascena Jun 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 57 additions & 5 deletions aws_lambda_powertools/event_handler/appsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import warnings
from typing import Any, Callable, Dict, List, Optional, Type, Union

from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import ResolverNotFoundError
from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import InvalidBatchResponse, ResolverNotFoundError
from aws_lambda_powertools.event_handler.graphql_appsync.router import Router
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
from aws_lambda_powertools.utilities.typing import LambdaContext
Expand Down Expand Up @@ -168,7 +168,12 @@
raise ValueError(f"No resolver found for '{self.current_event.type_name}.{self.current_event.field_name}'")
return resolver["func"](**self.current_event.arguments)

def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = False) -> List[Any]:
def _call_sync_batch_resolver(
self,
resolver: Callable,
raise_on_error: bool = False,
aggregate: bool = True,
) -> List[Any]:
"""
Calls a synchronous batch resolver function for each event in the current batch.

Expand All @@ -179,6 +184,10 @@
raise_on_error: bool
A flag indicating whether to raise an error when processing batches
with failed items. Defaults to False, which means errors are handled without raising exceptions.
aggregate: bool
A flag indicating whether the batch items should be processed at once or individually.
If True (default), the batch resolver will process all items in the batch as a single event.
If False, the batch resolver will process each item in the batch individually.

Returns
-------
Expand All @@ -188,6 +197,17 @@

logger.debug(f"Graceful error handling flag {raise_on_error=}")

# Checks whether the entire batch should be processed at once
if aggregate:
# Process the entire batch
response = resolver(event=self.current_batch_event)

if not isinstance(response, List):
raise InvalidBatchResponse("The response must be a List when using batch resolvers")

return response

# Non aggregated events, so we call this event list x times
# Stop on first exception we encounter
if raise_on_error:
return [
Expand All @@ -206,7 +226,12 @@

return results

async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: bool = False) -> List[Any]:
async def _call_async_batch_resolver(
self,
resolver: Callable,
raise_on_error: bool = False,
aggregate: bool = True,
) -> List[Any]:
"""
Asynchronously call a batch resolver for each event in the current batch.

Expand All @@ -217,6 +242,10 @@
raise_on_error: bool
A flag indicating whether to raise an error when processing batches
with failed items. Defaults to False, which means errors are handled without raising exceptions.
aggregate: bool
A flag indicating whether the batch items should be processed at once or individually.
If True (default), the batch resolver will process all items in the batch as a single event.
If False, the batch resolver will process each item in the batch individually.

Returns
-------
Expand All @@ -225,7 +254,17 @@
"""

logger.debug(f"Graceful error handling flag {raise_on_error=}")
response = []

response: List = []

# Checks whether the entire batch should be processed at once
if aggregate:
# Process the entire batch
response.extend(await asyncio.gather(resolver(event=self.current_batch_event)))
if not isinstance(response[0], List):
raise InvalidBatchResponse("The response must be a List when using batch resolvers")

return response[0]

# Prime coroutines
tasks = [resolver(event=e, **e.arguments) for e in self.current_batch_event]
Expand All @@ -233,7 +272,7 @@
# Aggregate results or raise at first error
if raise_on_error:
response.extend(await asyncio.gather(*tasks))
return response

Check warning on line 275 in aws_lambda_powertools/event_handler/appsync.py

View check run for this annotation

Codecov / codecov/patch

aws_lambda_powertools/event_handler/appsync.py#L275

Added line #L275 was not covered by tests

# Aggregate results and exceptions, then filter them out
# Use `None` upon exception for graceful error handling at GraphQL engine level
Expand Down Expand Up @@ -286,14 +325,19 @@

if resolver:
logger.debug(f"Found sync resolver. {resolver=}, {field_name=}")
return self._call_sync_batch_resolver(resolver=resolver["func"], raise_on_error=resolver["raise_on_error"])
return self._call_sync_batch_resolver(
resolver=resolver["func"],
raise_on_error=resolver["raise_on_error"],
aggregate=resolver["aggregate"],
)

if async_resolver:
logger.debug(f"Found async resolver. {resolver=}, {field_name=}")
return asyncio.run(
self._call_async_batch_resolver(
resolver=async_resolver["func"],
raise_on_error=async_resolver["raise_on_error"],
aggregate=async_resolver["aggregate"],
),
)

Expand Down Expand Up @@ -371,6 +415,7 @@
type_name: str = "*",
field_name: Optional[str] = None,
raise_on_error: bool = False,
aggregate: bool = True,
) -> Callable:
"""Registers batch resolver function for GraphQL type and field name.

Expand All @@ -385,6 +430,10 @@
GraphQL field e.g., getTodo, createTodo, by default None
raise_on_error : bool, optional
Whether to fail entire batch upon error, or handle errors gracefully (None), by default False
aggregate: bool
A flag indicating whether the batch items should be processed at once or individually.
If True (default), the batch resolver will process all items in the batch as a single event.
If False, the batch resolver will process each item in the batch individually.

Returns
-------
Expand All @@ -395,16 +444,19 @@
field_name=field_name,
type_name=type_name,
raise_on_error=raise_on_error,
aggregate=aggregate,
)

def async_batch_resolver(
self,
type_name: str = "*",
field_name: Optional[str] = None,
raise_on_error: bool = False,
aggregate: bool = True,
) -> Callable:
return self._async_batch_resolver_registry.register(
field_name=field_name,
type_name=type_name,
raise_on_error=raise_on_error,
aggregate=aggregate,
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def register(
type_name: str = "*",
field_name: Optional[str] = None,
raise_on_error: bool = False,
aggregate: bool = True,
) -> Callable:
"""Registers the resolver for field_name

Expand All @@ -25,6 +26,10 @@ def register(
raise_on_error: bool
A flag indicating whether to raise an error when processing batches
with failed items. Defaults to False, which means errors are handled without raising exceptions.
aggregate: bool
A flag indicating whether the batch items should be processed at once or individually.
If True (default), the batch resolver will process all items in the batch as a single event.
If False, the batch resolver will process each item in the batch individually.

Return
----------
Expand All @@ -34,7 +39,11 @@ def register(

def _register(func) -> Callable:
logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`")
self.resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error}
self.resolvers[f"{type_name}.{field_name}"] = {
"func": func,
"raise_on_error": raise_on_error,
"aggregate": aggregate,
}
return func

return _register
Expand Down
10 changes: 10 additions & 0 deletions aws_lambda_powertools/event_handler/graphql_appsync/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def batch_resolver(
type_name: str = "*",
field_name: Optional[str] = None,
raise_on_error: bool = False,
aggregate: bool = True,
) -> Callable:
"""
Retrieve a batch resolver function for a specific type and field.
Expand All @@ -62,6 +63,10 @@ def batch_resolver(
raise_on_error: bool
A flag indicating whether to raise an error when processing batches
with failed items. Defaults to False, which means errors are handled without raising exceptions.
aggregate: bool
A flag indicating whether the batch items should be processed at once or individually.
If True (default), the batch resolver will process all items in the batch as a single event.
If False, the batch resolver will process each item in the batch individually.

Examples
--------
Expand Down Expand Up @@ -95,6 +100,7 @@ def async_batch_resolver(
type_name: str = "*",
field_name: Optional[str] = None,
raise_on_error: bool = False,
aggregate: bool = True,
) -> Callable:
"""
Retrieve a batch resolver function for a specific type and field and runs async.
Expand All @@ -108,6 +114,10 @@ def async_batch_resolver(
raise_on_error: bool
A flag indicating whether to raise an error when processing batches
with failed items. Defaults to False, which means errors are handled without raising exceptions.
aggregate: bool
A flag indicating whether the batch items should be processed at once or individually.
If True (default), the batch resolver will process all items in the batch as a single event.
If False, the batch resolver will process each item in the batch individually.

Examples
--------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ class ResolverNotFoundError(Exception):
"""
When a resolver is not found during a lookup.
"""


class InvalidBatchResponse(Exception):
"""
When a batch response something different from a List
"""
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,27 @@ def batch_resolver(
type_name: str = "*",
field_name: Optional[str] = None,
raise_on_error: bool = False,
aggregate: bool = True,
) -> Callable:
return self._batch_resolver_registry.register(
field_name=field_name,
type_name=type_name,
raise_on_error=raise_on_error,
aggregate=aggregate,
)

def async_batch_resolver(
self,
type_name: str = "*",
field_name: Optional[str] = None,
raise_on_error: bool = False,
aggregate: bool = True,
) -> Callable:
return self._async_batch_resolver_registry.register(
field_name=field_name,
type_name=type_name,
raise_on_error=raise_on_error,
aggregate=aggregate,
)

def append_context(self, **additional_context):
Expand Down
4 changes: 3 additions & 1 deletion tests/e2e/event_handler_appsync/files/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ type Post {
downs: Int
relatedPosts: [Post]
relatedPostsAsync: [Post]
}
relatedPostsAggregate: [Post]
relatedPostsAsyncAggregate: [Post]
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class Post(BaseModel):
downs: str


# PROCESSING SINGLE RESOLVERS
@app.resolver(type_name="Query", field_name="getPost")
def get_post(post_id: str = "") -> dict:
post = Post(**posts[post_id]).dict()
Expand All @@ -87,15 +88,27 @@ def all_posts() -> List[dict]:
return list(posts.values())


@app.batch_resolver(type_name="Post", field_name="relatedPosts")
# PROCESSING BATCH WITHOUT AGGREGATION
@app.batch_resolver(type_name="Post", field_name="relatedPosts", aggregate=False)
def related_posts(event: AppSyncResolverEvent) -> Optional[list]:
return posts_related[event.source["post_id"]] if event.source else None


@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsync")
@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsync", aggregate=False)
async def related_posts_async(event: AppSyncResolverEvent) -> Optional[list]:
return posts_related[event.source["post_id"]] if event.source else None


# PROCESSING BATCH WITH AGGREGATION
@app.batch_resolver(type_name="Post", field_name="relatedPostsAggregate")
def related_posts_aggregate(event: List[AppSyncResolverEvent]) -> Optional[list]:
return [posts_related[record.source.get("post_id")] for record in event]


@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsyncAggregate")
async def related_posts_async_aggregate(event: List[AppSyncResolverEvent]) -> Optional[list]:
return [posts_related[record.source.get("post_id")] for record in event]


def lambda_handler(event, context: LambdaContext) -> dict:
return app.resolve(event, context)
14 changes: 14 additions & 0 deletions tests/e2e/event_handler_appsync/infrastructure.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,19 @@ def _create_appsync_endpoint(self, function: Function):
max_batch_size=10,
)

lambda_datasource.create_resolver(
"QueryGetPostRelatedResolverAggregate",
type_name="Post",
field_name="relatedPostsAggregate",
max_batch_size=10,
)

lambda_datasource.create_resolver(
"QueryGetPostRelatedAsyncResolverAggregate",
type_name="Post",
field_name="relatedPostsAsyncAggregate",
max_batch_size=10,
)

CfnOutput(self.stack, "GraphQLHTTPUrl", value=api.graphql_url)
CfnOutput(self.stack, "GraphQLAPIKey", value=api.api_key)
40 changes: 39 additions & 1 deletion tests/e2e/event_handler_appsync/test_appsync_resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_appsync_get_post(appsync_endpoint, appsync_access_key):


@pytest.mark.xdist_group(name="event_handler")
def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key):
def test_appsync_get_related_posts_batch_without_aggregate(appsync_endpoint, appsync_access_key):
# GIVEN
post_id = "2"
related_posts_ids = ["3", "5"]
Expand Down Expand Up @@ -110,3 +110,41 @@ def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key):
assert post["post_id"] in related_posts_ids
for post in data["getPost"]["relatedPostsAsync"]:
assert post["post_id"] in related_posts_ids


@pytest.mark.xdist_group(name="event_handler")
def test_appsync_get_related_posts_batch_with_aggregate(appsync_endpoint, appsync_access_key):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you break the assertions for sync first, then async, and add more details on GIVEN/WHEN as we're doing two tests here, plz?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

# GIVEN
post_id = "2"
related_posts_ids = ["3", "5"]

body = {
"query": f'query MyQuery {{ getPost(post_id: "{post_id}") \
{{ post_id relatedPostsAggregate {{ post_id }} relatedPostsAsyncAggregate {{ post_id }} }} }}',
"variables": None,
"operationName": "MyQuery",
}

# WHEN
response = data_fetcher.get_http_response(
Request(
method="POST",
url=appsync_endpoint,
json=body,
headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"},
),
)

# THEN expect a HTTP 200 response and content return Post id with dependent Posts id's
assert response.status_code == 200
assert response.content is not None

data = json.loads(response.content.decode("ascii"))["data"]

assert data["getPost"]["post_id"] == post_id
assert len(data["getPost"]["relatedPostsAggregate"]) == len(related_posts_ids)
assert len(data["getPost"]["relatedPostsAsyncAggregate"]) == len(related_posts_ids)
for post in data["getPost"]["relatedPostsAggregate"]:
assert post["post_id"] in related_posts_ids
for post in data["getPost"]["relatedPostsAsyncAggregate"]:
assert post["post_id"] in related_posts_ids
Loading