-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(JSONObjectAgg): expand tests to cover use of more internal types
- Loading branch information
Showing
2 changed files
with
137 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,156 @@ | ||
"""Test JSONObjectAgg aggregator.""" | ||
|
||
from __future__ import annotations | ||
|
||
from functools import partial | ||
from itertools import chain | ||
from typing import TYPE_CHECKING | ||
|
||
import pytest | ||
from django.db.models import F | ||
from django.db.models import DateTimeField | ||
from django.db.models import JSONField | ||
|
||
from json_agg import JSONObjectAgg | ||
from tests.models import Author | ||
from tests.models import Post | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_aggregate_integer(faker): | ||
"""Test JSONObjectAgg over a integer value (Post.year).""" | ||
expected_value_per_author_name = { | ||
faker.slug(): { | ||
faker.slug(): faker.pyint(min_value=1900, max_value=3500) for _ in range(20) | ||
} | ||
for _ in range(10) | ||
if TYPE_CHECKING: | ||
from typing import Callable | ||
|
||
from faker import Faker | ||
|
||
|
||
def post_factory( | ||
faker: Faker, | ||
*, | ||
value_name: str, | ||
value_factory: Callable, | ||
number_of_posts: int = 20, | ||
number_of_authors: int = 10, | ||
): | ||
"""Factory to create author and posts in this test module.""" | ||
posts_per_author_name = { | ||
faker.slug(): {faker.slug(): value_factory() for _ in range(number_of_posts)} | ||
for _ in range(number_of_authors) | ||
} | ||
|
||
for author_name, post_dict in expected_value_per_author_name.items(): | ||
for author_name, post_dict in posts_per_author_name.items(): | ||
author = Author.objects.create(name=author_name) | ||
post_list = [] | ||
for post_title, year in post_dict.items(): | ||
post_list.append(Post(title=post_title, year=year, author=author)) | ||
for post_title, value in post_dict.items(): | ||
post_list.append( | ||
Post(title=post_title, author=author, **{value_name: value}) | ||
) | ||
Post.objects.bulk_create(post_list) | ||
return posts_per_author_name | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_post_factory(faker: Faker): | ||
"""Test helper function "post_factory".""" | ||
|
||
class _DummyFactory: | ||
_dummy_value = 0 | ||
|
||
def __call__(self): | ||
self._dummy_value += 1 | ||
return self._dummy_value | ||
|
||
posts_per_author = post_factory( | ||
faker, | ||
value_name="year", | ||
value_factory=_DummyFactory(), | ||
number_of_authors=2, | ||
number_of_posts=2, | ||
) | ||
assert isinstance(posts_per_author, dict) | ||
assert len(posts_per_author) == 2 | ||
assert all(isinstance(posts, dict) for posts in posts_per_author.values()) | ||
assert all(len(posts) == 2 for posts in posts_per_author.values()) | ||
|
||
expected_values = [1, 2, 3, 4] | ||
|
||
# since author name and post title are random, let's only check expected values | ||
assert set( | ||
chain.from_iterable(posts.values() for posts in posts_per_author.values()) | ||
) == set(expected_values) | ||
|
||
# ensure the values were persisted in db | ||
qs = Post.objects.order_by("year").values_list("year").all() | ||
assert list(chain.from_iterable(qs)) == expected_values | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_aggregate_integer(faker: Faker): | ||
"""Test JSONObjectAgg over a integer value (Post.year).""" | ||
expected_value_per_author_name = post_factory( | ||
faker, | ||
value_name="year", | ||
value_factory=partial(faker.pyint, min_value=1900, max_value=3500), | ||
) | ||
|
||
queryset = Author.objects.annotate( | ||
post_years=JSONObjectAgg(F("posts__title"), F("posts__year")) | ||
post_years=JSONObjectAgg("posts__title", "posts__year") | ||
).all() | ||
|
||
result_as_dict = {author.name: author.post_years for author in queryset} | ||
assert result_as_dict == expected_value_per_author_name | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_aggregate_text(faker: Faker): | ||
"""Test JSONObjectAgg over a integer value (Post.content).""" | ||
expected_value_per_author_name = post_factory( | ||
faker, | ||
value_name="content", | ||
value_factory=partial(faker.paragraph, variable_nb_sentences=True), | ||
) | ||
|
||
queryset = Author.objects.annotate( | ||
post_content=JSONObjectAgg("posts__title", "posts__content") | ||
).all() | ||
|
||
result_as_dict = {author.name: author.post_content for author in queryset} | ||
assert result_as_dict == expected_value_per_author_name | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_aggregate_datetime(faker: Faker): | ||
"""Test JSONObjectAgg over a datetime value (Post.updated_at).""" | ||
expected_value_per_author_name = post_factory( | ||
faker, | ||
value_name="updated_at", | ||
value_factory=partial(faker.date_time), | ||
) | ||
|
||
queryset = Author.objects.annotate( | ||
post_updated_at=JSONObjectAgg( | ||
"posts__title", "posts__updated_at", nested_output_field=DateTimeField() | ||
) | ||
).all() | ||
|
||
result_as_dict = {author.name: author.post_updated_at for author in queryset} | ||
assert result_as_dict == expected_value_per_author_name | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_aggregate_json(faker: Faker): | ||
"""Test JSONObjectAgg over a json value (Post.metadata).""" | ||
expected_value_per_author_name = post_factory( | ||
faker, | ||
value_name="metadata", | ||
value_factory=partial(faker.pydict, allowed_types=(str, int)), | ||
) | ||
|
||
queryset = Author.objects.annotate( | ||
post_metadata=JSONObjectAgg( | ||
"posts__title", | ||
"posts__metadata", | ||
nested_output_field=JSONField(), | ||
# sqlite_func="json", | ||
) | ||
).all() | ||
|
||
result_as_dict = {author.name: author.post_metadata for author in queryset} | ||
assert result_as_dict == expected_value_per_author_name |