-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
25 changed files
with
910 additions
and
72 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
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 |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from datetime import datetime, timedelta | ||
|
||
from async_factory_boy.factory.sqlalchemy import AsyncSQLAlchemyFactory | ||
from factory import SubFactory | ||
from sqlalchemy import select | ||
|
||
from app.infrastructure.database.models import GPStamps, User | ||
from tests.v2.factories.base import FactorySetBase | ||
from tests.v2.factories.users import UserFactory | ||
|
||
|
||
class GPStampsFactory(AsyncSQLAlchemyFactory): | ||
class Meta: | ||
model = GPStamps | ||
sqlalchemy_session_persistence = "commit" | ||
|
||
user_id = SubFactory(UserFactory) | ||
score = 1.0 | ||
expires_at = datetime.now() + timedelta(days=30) | ||
stamps = "[]" # Default empty JSON array as string | ||
|
||
|
||
class GPStampsFactorySet(FactorySetBase): | ||
_factories = {"gp_stamps": GPStampsFactory} | ||
|
||
async def create( | ||
self, | ||
user: User = None, | ||
score: float = None, | ||
expires_at: datetime = None, | ||
stamps: str = None, | ||
) -> GPStamps: | ||
factory_kwargs = {} | ||
|
||
if user is not None: | ||
factory_kwargs["user_id"] = user.id | ||
if score is not None: | ||
factory_kwargs["score"] = score | ||
if expires_at is not None: | ||
factory_kwargs["expires_at"] = expires_at | ||
if stamps is not None: | ||
factory_kwargs["stamps"] = stamps | ||
|
||
gp_stamps = await GPStampsFactory.create(**factory_kwargs) | ||
return gp_stamps | ||
|
||
async def get_or_create( | ||
self, | ||
user: User, | ||
score: float = None, | ||
expires_at: datetime = None, | ||
stamps: str = None, | ||
) -> GPStamps: | ||
gp_stamps = await self.session.scalar( | ||
select(GPStamps).filter(GPStamps.user_id == user.id) | ||
) | ||
|
||
if not gp_stamps: | ||
gp_stamps = await self.create( | ||
user=user, | ||
score=score, | ||
expires_at=expires_at, | ||
stamps=stamps, | ||
) | ||
return gp_stamps |
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 |
---|---|---|
@@ -0,0 +1,56 @@ | ||
from decimal import Decimal | ||
from typing import Optional | ||
|
||
from async_factory_boy.factory.sqlalchemy import AsyncSQLAlchemyFactory | ||
from factory import SubFactory | ||
from sqlalchemy import select | ||
|
||
from app.infrastructure.database.models import UniquenessQuotient, User | ||
from tests.v2.factories.base import FactorySetBase | ||
from tests.v2.factories.users import UserFactory | ||
|
||
|
||
class UniquenessQuotientFactory(AsyncSQLAlchemyFactory): | ||
class Meta: | ||
model = UniquenessQuotient | ||
sqlalchemy_session_persistence = "commit" | ||
|
||
user = SubFactory(UserFactory) | ||
epoch = 1 | ||
score = "1.0" | ||
|
||
|
||
class UniquenessQuotientFactorySet(FactorySetBase): | ||
_factories = {"uniqueness_quotient": UniquenessQuotientFactory} | ||
|
||
async def create( | ||
self, | ||
user: Optional[User] = None, | ||
epoch: Optional[int] = None, | ||
score: Optional[Decimal] = None, | ||
) -> UniquenessQuotient: | ||
factory_kwargs = {} | ||
|
||
if user is not None: | ||
factory_kwargs["user"] = user | ||
if epoch is not None: | ||
factory_kwargs["epoch"] = epoch | ||
if score is not None: | ||
factory_kwargs["score"] = str(score) | ||
|
||
uq = await UniquenessQuotientFactory.create(**factory_kwargs) | ||
return uq | ||
|
||
async def get_or_create( | ||
self, user: User, epoch: int, score: Optional[Decimal] = None | ||
) -> UniquenessQuotient: | ||
uq = await self.session.scalar( | ||
select(UniquenessQuotient).filter( | ||
UniquenessQuotient.user_id == user.id, | ||
UniquenessQuotient.epoch == epoch, | ||
) | ||
) | ||
|
||
if not uq: | ||
uq = await self.create(user=user, epoch=epoch, score=score) | ||
return uq |
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 |
---|---|---|
@@ -0,0 +1,170 @@ | ||
from datetime import datetime, timedelta, timezone | ||
from http import HTTPStatus | ||
from fastapi import FastAPI | ||
import pytest | ||
from httpx import AsyncClient | ||
from sqlalchemy.ext.asyncio import AsyncSession | ||
|
||
from app.constants import ( | ||
LOW_UQ_SCORE, | ||
MAX_UQ_SCORE, | ||
NULLIFIED_UQ_SCORE, | ||
UQ_THRESHOLD_MAINNET, | ||
) | ||
from v2.uniqueness_quotients.dependencies import get_uq_score_getter | ||
from v2.uniqueness_quotients.services import UQScoreGetter | ||
from tests.v2.factories import FactoriesAggregator | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_get_antisybil_status_normal_user( | ||
fast_app: FastAPI, | ||
fast_client: AsyncClient, | ||
fast_session: AsyncSession, | ||
factories: FactoriesAggregator, | ||
): | ||
"""Should return the antisybil status for a normal user""" | ||
alice = await factories.users.get_or_create_alice() | ||
|
||
# Create a gp stamps for the user | ||
expires_at = datetime.now(timezone.utc) + timedelta(days=30) | ||
expires_ts = int(expires_at.timestamp()) | ||
await factories.gp_stamps.create( | ||
user=alice, | ||
score=7.0, | ||
expires_at=expires_at, | ||
) | ||
|
||
# Override uq_score_getter | ||
fake_uq_score_getter = UQScoreGetter( | ||
session=fast_session, | ||
uq_score_threshold=UQ_THRESHOLD_MAINNET, | ||
max_uq_score=MAX_UQ_SCORE, | ||
low_uq_score=LOW_UQ_SCORE, | ||
null_uq_score=NULLIFIED_UQ_SCORE, | ||
guest_list=set(), | ||
timeout_list=set(), | ||
) | ||
fast_app.dependency_overrides[get_uq_score_getter] = lambda: fake_uq_score_getter | ||
|
||
async with fast_client as client: | ||
resp = await client.get(f"user/{alice.address}/antisybil-status") | ||
assert resp.status_code == HTTPStatus.OK | ||
assert resp.json() == { | ||
"status": "Known", | ||
"score": "7.0", | ||
"expiresAt": str(expires_ts), | ||
"isOnTimeOutList": False, | ||
} | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_get_antisybil_status_timeout_list_user( | ||
fast_app: FastAPI, | ||
fast_client: AsyncClient, | ||
fast_session: AsyncSession, | ||
factories: FactoriesAggregator, | ||
): | ||
"""Should return the antisybil status for a user on timeout list""" | ||
bob = await factories.users.get_or_create_bob() | ||
|
||
# Create a gp stamps for the user | ||
expires_at = datetime.now(timezone.utc) + timedelta(days=30) | ||
expires_ts = int(expires_at.timestamp()) | ||
await factories.gp_stamps.create( | ||
user=bob, | ||
score=100.0, # High score but on timeout list | ||
expires_at=expires_at, | ||
) | ||
|
||
# Override uq_score_getter with bob on timeout list | ||
fake_uq_score_getter = UQScoreGetter( | ||
session=fast_session, | ||
uq_score_threshold=UQ_THRESHOLD_MAINNET, | ||
max_uq_score=MAX_UQ_SCORE, | ||
low_uq_score=LOW_UQ_SCORE, | ||
null_uq_score=NULLIFIED_UQ_SCORE, | ||
guest_list=set(), | ||
timeout_list=set([bob.address]), | ||
) | ||
fast_app.dependency_overrides[get_uq_score_getter] = lambda: fake_uq_score_getter | ||
|
||
async with fast_client as client: | ||
resp = await client.get(f"user/{bob.address}/antisybil-status") | ||
assert resp.status_code == HTTPStatus.OK | ||
assert resp.json() == { | ||
"status": "Known", | ||
"score": "0.0", | ||
"expiresAt": str(expires_ts), | ||
"isOnTimeOutList": True, | ||
} | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_get_antisybil_status_guest_list_user( | ||
fast_app: FastAPI, | ||
fast_client: AsyncClient, | ||
fast_session: AsyncSession, | ||
factories: FactoriesAggregator, | ||
): | ||
"""Should return the antisybil status for a user on guest list""" | ||
charlie = await factories.users.get_or_create_charlie() | ||
|
||
# Create a gp stamps for the user | ||
expires_at = datetime.now(timezone.utc) + timedelta(days=30) | ||
expires_ts = int(expires_at.timestamp()) | ||
await factories.gp_stamps.create( | ||
user=charlie, | ||
score=0.0, # Low score but on guest list | ||
expires_at=expires_at, | ||
) | ||
|
||
# Override uq_score_getter with charlie on guest list | ||
fake_uq_score_getter = UQScoreGetter( | ||
session=fast_session, | ||
uq_score_threshold=UQ_THRESHOLD_MAINNET, | ||
max_uq_score=MAX_UQ_SCORE, | ||
low_uq_score=LOW_UQ_SCORE, | ||
null_uq_score=NULLIFIED_UQ_SCORE, | ||
guest_list=set([charlie.address]), | ||
timeout_list=set(), | ||
) | ||
fast_app.dependency_overrides[get_uq_score_getter] = lambda: fake_uq_score_getter | ||
|
||
async with fast_client as client: | ||
resp = await client.get(f"user/{charlie.address}/antisybil-status") | ||
assert resp.status_code == HTTPStatus.OK | ||
assert resp.json() == { | ||
"status": "Known", | ||
"score": "21.0", | ||
"expiresAt": str(expires_ts), | ||
"isOnTimeOutList": False, | ||
} | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_get_antisybil_status_no_gp_stamps( | ||
fast_app: FastAPI, | ||
fast_client: AsyncClient, | ||
fast_session: AsyncSession, | ||
factories: FactoriesAggregator, | ||
): | ||
"""Should return 404 when user has no GPStamps record""" | ||
# Create user but don't create any GPStamps for them | ||
alice = await factories.users.get_or_create_alice() | ||
|
||
# Override uq_score_getter with empty lists | ||
fake_uq_score_getter = UQScoreGetter( | ||
session=fast_session, | ||
uq_score_threshold=UQ_THRESHOLD_MAINNET, | ||
max_uq_score=MAX_UQ_SCORE, | ||
low_uq_score=LOW_UQ_SCORE, | ||
null_uq_score=NULLIFIED_UQ_SCORE, | ||
guest_list=set(), | ||
timeout_list=set(), | ||
) | ||
fast_app.dependency_overrides[get_uq_score_getter] = lambda: fake_uq_score_getter | ||
|
||
async with fast_client as client: | ||
resp = await client.get(f"user/{alice.address}/antisybil-status") | ||
assert resp.status_code == HTTPStatus.NOT_FOUND |
Oops, something went wrong.