You've already forked hotpocket
This commit is contained in:
3
services/backend/testing/README.md
Normal file
3
services/backend/testing/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# HotPocket by BTHLabs
|
||||
|
||||
This repository contains the _HotPocket Backend Testing_ project.
|
||||
21
services/backend/testing/hotpocket_backend_testing/dto/ui.py
Normal file
21
services/backend/testing/hotpocket_backend_testing/dto/ui.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
import pydantic
|
||||
|
||||
|
||||
class PocketImportSaveSpec(pydantic.BaseModel):
|
||||
title: str | None
|
||||
url: str
|
||||
time_added: datetime.datetime
|
||||
tags: str = pydantic.Field(default='')
|
||||
status: str = pydantic.Field(default='unread')
|
||||
|
||||
def dict(self, *args, **kwargs):
|
||||
result = super().dict(*args, **kwargs)
|
||||
|
||||
result['time_added'] = int(self.time_added.strftime('%s'))
|
||||
|
||||
return result
|
||||
@@ -0,0 +1 @@
|
||||
from . import accounts # noqa: F401
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
import factory
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import Account
|
||||
|
||||
|
||||
class AccountFactory(factory.django.DjangoModelFactory):
|
||||
username = factory.LazyFunction(uuid.uuid4)
|
||||
first_name = factory.Faker('first_name')
|
||||
last_name = factory.Faker('last_name')
|
||||
email = factory.Faker('email')
|
||||
is_staff = False
|
||||
is_active = True
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
@@ -0,0 +1,2 @@
|
||||
from .association import AssociationFactory # noqa: F401,F403
|
||||
from .save import SaveFactory # noqa: F401,F403
|
||||
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import factory
|
||||
|
||||
from hotpocket_backend.apps.saves.models import Association
|
||||
|
||||
|
||||
class AssociationFactory(factory.django.DjangoModelFactory):
|
||||
account_uuid = None
|
||||
deleted_at = None
|
||||
archived_at = None
|
||||
starred_at = None
|
||||
target_meta = factory.LazyFunction(dict)
|
||||
target_title = None
|
||||
target_description = None
|
||||
|
||||
target = None
|
||||
|
||||
class Meta:
|
||||
model = Association
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
|
||||
import factory
|
||||
|
||||
from hotpocket_backend.apps.saves.models import Save
|
||||
|
||||
|
||||
class SaveFactory(factory.django.DjangoModelFactory):
|
||||
account_uuid = None
|
||||
deleted_at = None
|
||||
key = factory.LazyAttribute(lambda obj: hashlib.sha256(obj.url.encode('utf-8')).hexdigest())
|
||||
url = None
|
||||
content = None
|
||||
title = None
|
||||
description = None
|
||||
last_processed_at = None
|
||||
is_netloc_banned = False
|
||||
|
||||
class Meta:
|
||||
model = Save
|
||||
@@ -0,0 +1,4 @@
|
||||
from .accounts import * # noqa F401
|
||||
from .core import * # noqa: F401,F403
|
||||
from .saves import * # noqa: F401,F403
|
||||
from .ui import * # noqa: F401,F403
|
||||
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def account_password():
|
||||
return 'thisisntright'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def account():
|
||||
from hotpocket_backend_testing.factories.accounts import AccountFactory
|
||||
return AccountFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def federated_account():
|
||||
from hotpocket_backend.apps.accounts.models import Account
|
||||
from hotpocket_backend_testing.factories.accounts import AccountFactory
|
||||
|
||||
result: Account = AccountFactory()
|
||||
result.set_unusable_password()
|
||||
result.save()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def account_with_password(account, account_password):
|
||||
account.set_password(account_password)
|
||||
account.save()
|
||||
|
||||
return account
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inactive_account():
|
||||
from hotpocket_backend_testing.factories.accounts import AccountFactory
|
||||
return AccountFactory(is_active=False)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account():
|
||||
from hotpocket_backend_testing.factories.accounts import AccountFactory
|
||||
return AccountFactory()
|
||||
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from __future__ import annotations
|
||||
|
||||
from django.test import Client
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def authenticated_client(client: Client, account) -> Client:
|
||||
client.force_login(account)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def federated_account_client(client: Client, federated_account) -> Client:
|
||||
client.force_login(federated_account)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def account_with_password_client(client: Client, account_with_password):
|
||||
client.force_login(account_with_password)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inactive_account_client(client: Client, inactive_account) -> Client:
|
||||
client.force_login(inactive_account)
|
||||
|
||||
return client
|
||||
@@ -0,0 +1,2 @@
|
||||
from .association import * # noqa: F401,F403
|
||||
from .save import * # noqa: F401,F403
|
||||
@@ -0,0 +1,165 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from django.utils.timezone import now
|
||||
import pytest
|
||||
|
||||
from hotpocket_soa.dto.associations import AssociationWithTargetOut
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def association_factory(request: pytest.FixtureRequest):
|
||||
default_account = request.getfixturevalue('account')
|
||||
default_target = request.getfixturevalue('save')
|
||||
|
||||
def factory(account=None, target=None, **kwargs):
|
||||
from hotpocket_backend_testing.factories.saves import AssociationFactory
|
||||
|
||||
return AssociationFactory(
|
||||
account_uuid=(
|
||||
account.pk
|
||||
if account is not None
|
||||
else default_account.pk
|
||||
),
|
||||
target=target or default_target,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def association(association_factory):
|
||||
return association_factory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def association_out(association):
|
||||
return AssociationWithTargetOut.model_validate(
|
||||
association, from_attributes=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def deleted_association(association_factory):
|
||||
return association_factory(deleted_at=now())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def deleted_association_out(deleted_association):
|
||||
return AssociationWithTargetOut.model_validate(
|
||||
deleted_association, from_attributes=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def archived_association(association_factory):
|
||||
return association_factory(archived_at=now())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def archived_association_out(archived_association):
|
||||
return AssociationWithTargetOut.model_validate(
|
||||
archived_association, from_attributes=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def starred_association(association_factory):
|
||||
return association_factory(starred_at=now())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def starred_association_out(starred_association):
|
||||
return AssociationWithTargetOut.model_validate(
|
||||
starred_association, from_attributes=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_association(association_factory, other_account):
|
||||
return association_factory(account=other_account)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_association_out(other_account_association):
|
||||
return AssociationWithTargetOut.model_validate(
|
||||
other_account_association, from_attributes=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_deleted_association(association_factory, other_account):
|
||||
return association_factory(account=other_account, deleted_at=now())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_deleted_association_out(other_account_deleted_association):
|
||||
return AssociationWithTargetOut.model_validate(
|
||||
other_account_deleted_association, from_attributes=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_archived_association(association_factory, other_account):
|
||||
return association_factory(account=other_account, archived_at=now())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_archived_association_out(other_account_archived_association):
|
||||
return AssociationWithTargetOut.model_validate(
|
||||
other_account_archived_association, from_attributes=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_starred_association(association_factory, other_account):
|
||||
return association_factory(account=other_account, starred_at=now())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_starred_association_out(other_account_starred_association):
|
||||
return AssociationWithTargetOut.model_validate(
|
||||
other_account_starred_association, from_attributes=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def browsable_associations(association,
|
||||
deleted_association,
|
||||
archived_association,
|
||||
starred_association,
|
||||
other_account_association,
|
||||
):
|
||||
return [
|
||||
association,
|
||||
starred_association,
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def browsable_association_outs(browsable_associations):
|
||||
return [
|
||||
AssociationWithTargetOut.model_validate(obj, from_attributes=True)
|
||||
for obj
|
||||
in browsable_associations
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def paginatable_associations(association_factory):
|
||||
result = [
|
||||
association_factory()
|
||||
for _ in range(0, 14)
|
||||
]
|
||||
|
||||
return result[::-1]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def paginatable_association_outs(paginatable_associations):
|
||||
return [
|
||||
AssociationWithTargetOut.model_validate(obj, from_attributes=True)
|
||||
for obj
|
||||
in paginatable_associations
|
||||
]
|
||||
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from django.utils.timezone import now
|
||||
import pytest
|
||||
|
||||
from hotpocket_soa.dto.saves import SaveOut
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def save_url_factory():
|
||||
def factory():
|
||||
return f'https://{uuid.uuid4()}.local/'
|
||||
|
||||
return factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def save_url(save_url_factory):
|
||||
return save_url_factory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def save_factory(request: pytest.FixtureRequest):
|
||||
default_account = request.getfixturevalue('account')
|
||||
default_url = request.getfixturevalue('save_url')
|
||||
|
||||
def factory(account=None, **kwargs):
|
||||
from hotpocket_backend_testing.factories.saves import SaveFactory
|
||||
|
||||
if 'url' not in kwargs:
|
||||
kwargs['url'] = default_url
|
||||
|
||||
return SaveFactory(
|
||||
account_uuid=(
|
||||
account.pk
|
||||
if account is not None
|
||||
else default_account.pk
|
||||
),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def save(save_factory):
|
||||
return save_factory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def save_out(save):
|
||||
return SaveOut.model_validate(save, from_attributes=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_save(save_factory, save_url_factory):
|
||||
return save_factory(url=save_url_factory())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_save_out(other_save):
|
||||
return SaveOut.model_validate(other_save, from_attributes=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def processed_save(save_factory, save_url_factory):
|
||||
return save_factory(url=save_url_factory(), last_processed_at=now())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def processed_save_out(processed_save):
|
||||
return SaveOut.model_validate(processed_save, from_attributes=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def netloc_banned_save(save_factory, save_url_factory):
|
||||
return save_factory(url=save_url_factory(), is_netloc_banned=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def netloc_banned_save_out(netloc_banned_save):
|
||||
return SaveOut.model_validate(netloc_banned_save, from_attributes=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def deleted_save(save_factory, save_url_factory):
|
||||
return save_factory(url=save_url_factory(), deleted_at=now())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def deleted_save_out(deleted_save):
|
||||
return SaveOut.model_validate(deleted_save, from_attributes=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_save(save_factory, other_account, save_url_factory):
|
||||
return save_factory(account=other_account, url=save_url_factory())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_account_save_out(other_account_save):
|
||||
return SaveOut.model_validate(other_account_save, from_attributes=True)
|
||||
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import datetime
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
||||
from hotpocket_backend_testing.dto.ui import PocketImportSaveSpec
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pocket_import_created_save_spec():
|
||||
return PocketImportSaveSpec.model_validate({
|
||||
'title': 'Ziomek',
|
||||
'url': 'https://www.ziomek.dog/',
|
||||
'time_added': datetime.datetime(
|
||||
1985, 12, 12, 8, 0, 0, 0, tzinfo=datetime.UTC,
|
||||
),
|
||||
'tags': '',
|
||||
'status': 'unread',
|
||||
})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pocket_import_reused_save_spec(save_out):
|
||||
return PocketImportSaveSpec.model_validate({
|
||||
'title': save_out.title,
|
||||
'url': save_out.url,
|
||||
'time_added': datetime.datetime(
|
||||
1987, 10, 3, 8, 0, 0, 0, tzinfo=datetime.UTC,
|
||||
),
|
||||
'tags': '',
|
||||
'status': 'unread',
|
||||
})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pocket_import_other_account_save_spec(other_account_save_out):
|
||||
return PocketImportSaveSpec.model_validate({
|
||||
'title': other_account_save_out.title,
|
||||
'url': other_account_save_out.url,
|
||||
'time_added': datetime.datetime(
|
||||
2019, 12, 6, 8, 0, 0, 0, tzinfo=datetime.UTC,
|
||||
),
|
||||
'tags': '',
|
||||
'status': 'unread',
|
||||
})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pocket_import_banned_netloc_save_spec():
|
||||
return PocketImportSaveSpec.model_validate({
|
||||
'title': 'Nyan Cat! [Official]',
|
||||
'url': 'https://www.youtube.com/watch?v=2yJgwwDcgV8',
|
||||
'time_added': datetime.datetime(
|
||||
2021, 9, 17, 8, 0, 0, 0, tzinfo=datetime.UTC,
|
||||
),
|
||||
'tags': '',
|
||||
'status': 'unread',
|
||||
})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pocket_csv_content(pocket_import_created_save_spec,
|
||||
pocket_import_reused_save_spec,
|
||||
pocket_import_other_account_save_spec,
|
||||
pocket_import_banned_netloc_save_spec,
|
||||
):
|
||||
with io.StringIO() as csv_f:
|
||||
field_names = [
|
||||
'title', 'url', 'time_added', 'tags', 'status',
|
||||
]
|
||||
|
||||
writer = csv.DictWriter(csv_f, field_names, dialect=csv.excel)
|
||||
writer.writeheader()
|
||||
writer.writerows([
|
||||
pocket_import_created_save_spec.dict(),
|
||||
pocket_import_reused_save_spec.dict(),
|
||||
pocket_import_other_account_save_spec.dict(),
|
||||
pocket_import_banned_netloc_save_spec.dict(),
|
||||
])
|
||||
|
||||
csv_f.seek(0)
|
||||
|
||||
yield csv_f.getvalue()
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from .fixtures import * # noqa: F401,F403
|
||||
@@ -0,0 +1 @@
|
||||
from .ui import UITestingService # noqa: F401,F403
|
||||
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
import uuid
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import Account
|
||||
|
||||
|
||||
class AccountsTestingService:
|
||||
EDITABLE_FIELDS = [
|
||||
'first_name', 'last_name', 'email',
|
||||
]
|
||||
|
||||
def assert_edited(self, *, pk: uuid.UUID, update: dict, reference: typing.Any):
|
||||
account = Account.objects.get(pk=pk)
|
||||
|
||||
for field in self.EDITABLE_FIELDS:
|
||||
expected_value = update.get(field, '') or None
|
||||
actual_value = getattr(account, field)
|
||||
assert actual_value == expected_value, (
|
||||
f'Value mismatch: field=`{field}` '
|
||||
f'expected_value=`{expected_value}` '
|
||||
f'actual_value=`{actual_value}`'
|
||||
)
|
||||
|
||||
assert account.updated_at > reference.updated_at
|
||||
|
||||
def assert_password_changed(self,
|
||||
*,
|
||||
pk: uuid.UUID,
|
||||
reference: typing.Any,
|
||||
):
|
||||
account = Account.objects.get(pk=pk)
|
||||
assert account.password != reference.password
|
||||
|
||||
assert account.updated_at > reference.updated_at
|
||||
|
||||
def assert_settings_edited(self,
|
||||
*,
|
||||
pk: uuid.UUID,
|
||||
update: dict,
|
||||
reference: typing.Any,
|
||||
):
|
||||
account = Account.objects.get(pk=pk)
|
||||
assert account.raw_settings == update
|
||||
|
||||
assert account.updated_at > reference.updated_at
|
||||
@@ -0,0 +1,3 @@
|
||||
from .associations import AssociationsTestingService # noqa: F401,F403
|
||||
from .save_processor import SaveProcessorTestingService # noqa: F401,F403
|
||||
from .saves import SavesTestingService # noqa: F401,F403
|
||||
@@ -0,0 +1,112 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import typing
|
||||
import uuid
|
||||
|
||||
from django.utils.timezone import get_current_timezone
|
||||
|
||||
from hotpocket_backend.apps.saves.models import Association
|
||||
|
||||
|
||||
class AssociationsTestingService:
|
||||
EDITABLE_FIELDS = [
|
||||
'target_title', 'target_description',
|
||||
]
|
||||
|
||||
def assert_archived(self, *, pk: uuid.UUID, reference: typing.Any = None):
|
||||
association = Association.objects.get(pk=pk)
|
||||
assert association.archived_at is not None
|
||||
|
||||
if reference is not None:
|
||||
assert association.updated_at > reference.updated_at
|
||||
|
||||
def assert_not_archived(self, *, pk: uuid.UUID, reference: typing.Any = None):
|
||||
association = Association.objects.get(pk=pk)
|
||||
assert association.archived_at is None
|
||||
|
||||
if reference is not None:
|
||||
assert association.updated_at > reference.updated_at
|
||||
|
||||
def assert_starred(self, *, pk: uuid.UUID, reference: typing.Any = None):
|
||||
association = Association.objects.get(pk=pk)
|
||||
assert association.starred_at is not None
|
||||
|
||||
if reference is not None:
|
||||
assert association.updated_at > reference.updated_at
|
||||
|
||||
def assert_not_starred(self, *, pk: uuid.UUID, reference: typing.Any = None):
|
||||
association = Association.objects.get(pk=pk)
|
||||
assert association.starred_at is None
|
||||
|
||||
if reference is not None:
|
||||
assert association.updated_at > reference.updated_at
|
||||
|
||||
def assert_edited(self, *, pk: uuid.UUID, update: dict, reference: typing.Any):
|
||||
association = Association.objects.get(pk=pk)
|
||||
|
||||
for field in self.EDITABLE_FIELDS:
|
||||
expected_value = update.get(field, '') or None
|
||||
actual_value = getattr(association, field)
|
||||
assert actual_value == expected_value, (
|
||||
f'Value mismatch: field=`{field}` '
|
||||
f'expected_value=`{expected_value}` '
|
||||
f'actual_value=`{actual_value}`'
|
||||
)
|
||||
|
||||
assert association.updated_at > reference.updated_at
|
||||
assert association.target.url == reference.target.url
|
||||
|
||||
def assert_created(self,
|
||||
*,
|
||||
pk: uuid.UUID,
|
||||
account_uuid: uuid.UUID,
|
||||
target_uuid: uuid.UUID,
|
||||
):
|
||||
association = Association.objects.get(pk=pk)
|
||||
assert association.account_uuid == account_uuid
|
||||
assert association.target_id == target_uuid
|
||||
|
||||
for field in self.EDITABLE_FIELDS:
|
||||
actual_value = getattr(association, field)
|
||||
assert actual_value is None, (
|
||||
f'Value mismatch: field=`{field}` '
|
||||
f'actual_value=`{actual_value}`'
|
||||
)
|
||||
|
||||
assert association.created_at is not None
|
||||
assert association.updated_at is not None
|
||||
|
||||
def assert_reused(self,
|
||||
pk: uuid.UUID,
|
||||
reference: typing.Any,
|
||||
):
|
||||
association = Association.objects.get(pk=pk)
|
||||
assert association.pk == reference.pk
|
||||
assert association.account_uuid == reference.account_uuid
|
||||
assert association.target_id == reference.target_uuid
|
||||
|
||||
for field in self.EDITABLE_FIELDS:
|
||||
expected_value = getattr(reference, field)
|
||||
actual_value = getattr(association, field)
|
||||
assert actual_value is expected_value, (
|
||||
f'Value mismatch: field=`{field}` '
|
||||
f'actual_value=`{actual_value}` '
|
||||
f'expected_value=`{expected_value}`'
|
||||
)
|
||||
|
||||
assert association.created_at == reference.created_at
|
||||
assert association.updated_at == reference.updated_at
|
||||
|
||||
def assert_imported(self,
|
||||
*,
|
||||
pk: uuid.UUID,
|
||||
account_uuid: uuid.UUID,
|
||||
target_uuid: uuid.UUID,
|
||||
created_at: datetime.datetime,
|
||||
):
|
||||
self.assert_created(pk=pk, account_uuid=account_uuid, target_uuid=target_uuid)
|
||||
|
||||
association = Association.objects.get(pk=pk)
|
||||
assert association.created_at == created_at.astimezone(get_current_timezone())
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest_mock
|
||||
|
||||
|
||||
class SaveProcessorTestingService:
|
||||
def mock_process_save_task_apply_async(self,
|
||||
*,
|
||||
mocker: pytest_mock.MockFixture,
|
||||
async_result: mock.Mock,
|
||||
) -> mock.Mock:
|
||||
return mocker.patch(
|
||||
'hotpocket_backend.apps.saves.tasks.process_save.apply_async',
|
||||
return_value=async_result,
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
import uuid
|
||||
|
||||
from hotpocket_backend.apps.saves.models import Save
|
||||
from hotpocket_testing.asserts import assert_datetimes_are_really_close
|
||||
|
||||
|
||||
class SavesTestingService:
|
||||
def assert_created(self,
|
||||
*,
|
||||
pk: uuid.UUID,
|
||||
account_uuid: uuid.UUID,
|
||||
url: str,
|
||||
is_netloc_banned: bool,
|
||||
**kwargs,
|
||||
):
|
||||
save = Save.objects.get(pk=pk)
|
||||
assert save.account_uuid == account_uuid
|
||||
assert save.url == url
|
||||
assert save.last_processed_at is None
|
||||
assert save.is_netloc_banned == is_netloc_banned
|
||||
|
||||
for field, expected_value in kwargs.items():
|
||||
actual_value = getattr(save, field)
|
||||
assert actual_value == expected_value, (
|
||||
f'Value mismatch: field=`{field}` '
|
||||
f'expected_value=`{expected_value}` '
|
||||
f'actual_value=`{actual_value}`'
|
||||
)
|
||||
|
||||
assert_datetimes_are_really_close(
|
||||
save.created_at, save.updated_at,
|
||||
)
|
||||
|
||||
def assert_reused(self,
|
||||
pk: uuid.UUID,
|
||||
reference: typing.Any,
|
||||
):
|
||||
save = Save.objects.get(pk=pk)
|
||||
assert save.pk == reference.pk
|
||||
assert save.account_uuid == reference.account_uuid
|
||||
assert save.url == reference.url
|
||||
assert save.title == reference.title
|
||||
assert save.last_processed_at == reference.last_processed_at
|
||||
assert save.is_netloc_banned == reference.is_netloc_banned
|
||||
|
||||
assert save.created_at == reference.created_at
|
||||
assert save.updated_at == reference.updated_at
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest_mock
|
||||
|
||||
|
||||
class UITestingService:
|
||||
def mock_import_from_pocket_task_apply_async(self,
|
||||
*,
|
||||
mocker: pytest_mock.MockFixture,
|
||||
async_result: mock.Mock,
|
||||
) -> mock.Mock:
|
||||
return mocker.patch(
|
||||
'hotpocket_backend.apps.ui.tasks.import_from_pocket.apply_async',
|
||||
return_value=async_result,
|
||||
)
|
||||
18
services/backend/testing/pyproject.toml
Normal file
18
services/backend/testing/pyproject.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[tool.poetry]
|
||||
name = "hotpocket-backend-testing"
|
||||
version = "1.0.0.dev0"
|
||||
description = "HotPocket Backend Testing"
|
||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
pydantic = "2.11.7"
|
||||
python = "^3.12"
|
||||
|
||||
[tool.poetry.plugins.pytest11]
|
||||
hotpocket_backend = "hotpocket_backend_testing.plugin"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
Reference in New Issue
Block a user