Skip to content

Commit

Permalink
feat: WIP - adding expiration date to score (#616)
Browse files Browse the repository at this point in the history
* feat: WIP - adding expiration date to score

* fix: failing tests

* feat: add test to check that expiration data is none if no stamp exists

* feat: return the score expiration_date in the score get API

* feat: rescore the passport if the score is expired in the 'ceramic-cache/score' request

* fix: unde removal of print in recalculate_scores, as it is used in the test

* feat: adding tests regarding score_expiration time and ascore_passport

---------

Co-authored-by: Gerald Iakobinyi-Pich <[email protected]>
  • Loading branch information
nutrina and Gerald Iakobinyi-Pich authored Jul 1, 2024
1 parent cb8a55e commit a38eeb2
Show file tree
Hide file tree
Showing 26 changed files with 642 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,5 @@ def test_bad_address(
response = _handler(event, MockContext())

assert response is not None
print(response)
assert response["statusCode"] == 400
assert json.loads(response["body"])["error"] == "Invalid address"
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_patch(
assert body["stamps"][0]["provider"] == "Google"
assert int(body["score"]["evidence"]["rawScore"]) > 0
assert body["score"]["status"] == "DONE"
assert body["success"] == True
assert body["success"] is True


def test_delete(
Expand Down Expand Up @@ -74,7 +74,7 @@ def test_delete(
assert len(body["stamps"]) == 0
assert int(body["score"]["evidence"]["rawScore"]) == 0
assert body["score"]["status"] == "DONE"
assert body["success"] == True
assert body["success"] is True


def test_post(
Expand Down Expand Up @@ -107,4 +107,4 @@ def test_post(
assert body["stamps"][0]["provider"] == "Google"
assert int(body["score"]["evidence"]["rawScore"]) > 0
assert body["score"]["status"] == "DONE"
assert body["success"] == True
assert body["success"] is True
66 changes: 57 additions & 9 deletions api/ceramic_cache/api/v1.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Ceramic Cache API"""

from datetime import datetime, timedelta
from datetime import timedelta
from typing import Any, Dict, List, Type, Optional

import api_logging as logging
import tos.api
import tos.schema
from account.models import Account, Nonce
from account.models import Account, Nonce, Community
from asgiref.sync import async_to_sync
from django.conf import settings
from django.contrib.auth import get_user_model
Expand All @@ -27,7 +27,6 @@
ErrorMessageResponse,
SubmitPassportPayload,
ahandle_submit_passport,
handle_get_score,
)
from registry.models import Score
from stake.api import handle_get_gtc_stake
Expand All @@ -52,16 +51,21 @@
GetStampResponse,
GetStampsWithScoreResponse,
)
from ceramic_cache.utils import get_utc_time
from registry.api.utils import (
is_valid_address,
)
from registry.exceptions import (
InvalidAddressException,
InvalidCommunityScoreRequestException,
NotFoundApiException,
)

log = logging.getLogger(__name__)

router = Router()


def get_utc_time():
return datetime.utcnow()


def get_address_from_did(did: str):
return did.split(":")[-1]

Expand Down Expand Up @@ -418,8 +422,52 @@ def handle_get_ui_score(
address: str, alternate_scorer_id: Optional[int]
) -> DetailedScoreResponse:
scorer_id = alternate_scorer_id or settings.CERAMIC_CACHE_SCORER_ID
account = get_object_or_404(Account, community__id=scorer_id)
return handle_get_score(address, scorer_id, account)
lower_address = address.lower()

if not is_valid_address(lower_address):
raise InvalidAddressException()

try:
# Get community object, for the configured scorer
user_community = Community.objects.get(id=scorer_id)

score = None
try:
score = Score.objects.get(
passport__address=lower_address, passport__community=user_community
)
except Score.DoesNotExist:
pass

# If score is expired re-calculate it
now = get_utc_time()

if score is None or (
score.expiration_date is not None and score.expiration_date < now
):
# This will re-calculate the score and update the expiration date
ret = get_detailed_score_response_for_address(address)

return ret

return DetailedScoreResponse.from_orm(score)

except Community.DoesNotExist as e:
raise NotFoundApiException(
"Community matching the configured scorer id does not exist! This is probably a misconfiguration!",
code=500,
) from e
except Score.DoesNotExist as e:
raise NotFoundApiException(
"No score could be found matching the request!"
) from e
except Exception as e:
log.error(
"Error getting passport scores. scorer_id=%s",
scorer_id,
exc_info=True,
)
raise InvalidCommunityScoreRequestException() from e


@router.post(
Expand Down
27 changes: 13 additions & 14 deletions api/ceramic_cache/test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import pytest
from django.conf import settings
from scorer.test.conftest import (
api_key,
sample_address,
sample_provider,
sample_token,
scorer_account,
scorer_community_with_binary_scorer,
scorer_user,
ui_scorer,
verifiable_credential,
api_key, # noqa
sample_address, # noqa
sample_provider, # noqa
sample_token, # noqa
scorer_account, # noqa
scorer_community, # noqa
scorer_passport, # noqa
scorer_community_with_binary_scorer, # noqa
scorer_user, # noqa
ui_scorer, # noqa
verifiable_credential, # noqa
)


Expand All @@ -33,8 +35,5 @@ def sample_stamps():


def pytest_configure():
try:
settings.CERAMIC_CACHE_API_KEY = "supersecret"
settings.CERAMIC_CACHE_SCORER_ID = ""
except:
pass
settings.CERAMIC_CACHE_API_KEY = "supersecret"
settings.CERAMIC_CACHE_SCORER_ID = ""
1 change: 1 addition & 0 deletions api/ceramic_cache/test/test_cmd_scorer_dump_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def upload_file(self, file_name, *args, **kwargs):
"passport",
"score",
"last_score_timestamp",
"expiration_date",
"status",
"error",
"evidence",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ def mocked_upload_to_s3(output_file, _s3_folder, _s3_bucket_name, _extra_args):
] == list(df.columns)
assert df.shape == (5, 5)
for idx, (_, r) in enumerate(df.iterrows()):
print(f"Row: {r}")
assert expected_results[idx]["passport_address"] == r.iloc[0]
assert (
expected_results[idx]["last_score_timestamp"]
Expand Down
183 changes: 183 additions & 0 deletions api/ceramic_cache/test/test_get_score.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
from datetime import datetime, timezone, timedelta

import pytest
from ceramic_cache.models import CeramicCache
from registry.models import Passport, Score
from django.test import Client
from registry.api.schema import DetailedScoreResponse
from decimal import Decimal

pytestmark = pytest.mark.django_db

client = Client()

future_expiration_date = datetime.now(timezone.utc) + timedelta(days=5)
past_expiration_date = datetime.now(timezone.utc) - timedelta(days=5)
last_score_timestamp = past_expiration_date


class TestBulkStampUpdates:
base_url = "/ceramic-cache"
stamp_version = CeramicCache.StampType.V1

def test_get_score_when_no_stamps(
self, scorer_passport, sample_token, mocker, ui_scorer
):
mocker.patch("registry.atasks.get_utc_time", return_value=last_score_timestamp)

score_get_response = client.get(
f"{self.base_url}/score/{scorer_passport.address}",
**{"HTTP_AUTHORIZATION": f"Bearer {sample_token}"},
)

assert score_get_response.status_code == 200

response_data = score_get_response.json()

assert response_data == {
"address": scorer_passport.address.lower(),
"score": "0",
"status": "DONE",
"last_score_timestamp": last_score_timestamp.isoformat(),
"expiration_date": None,
"evidence": {
"type": "ThresholdScoreCheck",
"success": False,
"rawScore": "0",
"threshold": "75.00000",
},
"error": None,
"stamp_scores": {},
}

def test_get_score_when_score_not_expired(
self, sample_address, sample_token, ui_scorer, mocker
):
passport = Passport.objects.create(
address=sample_address,
community_id=ui_scorer,
)

# Create an score with expiration date in the future
score = Score.objects.create(
passport=passport,
score=1,
status=Score.Status.DONE,
last_score_timestamp=past_expiration_date,
expiration_date=future_expiration_date,
error=None,
stamp_scores=[],
evidence={
"rawScore": "10",
"type": "binary",
"success": True,
"threshold": "5",
},
)

mocked_score_response_data = dict(
address=passport.address.lower(),
score="0.000000000",
status=Score.Status.DONE,
last_score_timestamp=future_expiration_date.isoformat(),
expiration_date=future_expiration_date.isoformat(),
error=None,
stamp_scores=[],
evidence={
"rawScore": "7",
"type": "binary",
"success": True,
"threshold": "5",
},
)

mocked_score_response = DetailedScoreResponse(**mocked_score_response_data)

mocker.patch(
"ceramic_cache.api.v1.get_detailed_score_response_for_address",
side_effect=[mocked_score_response],
)

score_get_response = client.get(
f"{self.base_url}/score/{passport.address}",
**{"HTTP_AUTHORIZATION": f"Bearer {sample_token}"},
)

assert score_get_response.status_code == 200

response_data = score_get_response.json()

assert response_data == {
"address": "0xc79bfbf4e4824cdb65c71f2eeb2d7f2db5da1fb8",
"error": None,
"evidence": {
"rawScore": "10",
"success": True,
"threshold": "5",
"type": "binary",
},
"expiration_date": score.expiration_date.isoformat(),
"last_score_timestamp": score.last_score_timestamp.isoformat(),
"score": "1.000000000",
"stamp_scores": {},
"status": "DONE",
}

def test_get_score_when_score_expired(
self, sample_address, sample_token, ui_scorer, mocker
):
passport = Passport.objects.create(
address=sample_address,
community_id=ui_scorer,
)

# Create an expired score.
Score.objects.create(
passport=passport,
score=Decimal("1.000000000"),
status=Score.Status.DONE,
last_score_timestamp=past_expiration_date,
expiration_date=past_expiration_date, # This is an expired score
error=None,
stamp_scores={},
evidence={
"rawScore": "10",
"type": "binary",
"success": True,
"threshold": "5",
},
)

mocked_score_response_data = dict(
address=passport.address.lower(),
score="0.000000000",
status=Score.Status.DONE,
last_score_timestamp=future_expiration_date.isoformat(),
expiration_date=future_expiration_date.isoformat(),
error=None,
stamp_scores={},
evidence={
"rawScore": "7",
"type": "binary",
"success": True,
"threshold": "5",
},
)

mocked_score_response = DetailedScoreResponse(**mocked_score_response_data)

mocker.patch(
"ceramic_cache.api.v1.get_detailed_score_response_for_address",
side_effect=[mocked_score_response],
)

score_get_response = client.get(
f"{self.base_url}/score/{passport.address}",
**{"HTTP_AUTHORIZATION": f"Bearer {sample_token}"},
)

assert score_get_response.status_code == 200

response_data = score_get_response.json()

assert response_data == mocked_score_response_data
Loading

0 comments on commit a38eeb2

Please sign in to comment.