Skip to content

Commit

Permalink
feat(api): adding multiple models to the passport analysis endpoint (#…
Browse files Browse the repository at this point in the history
…631)

* feat: uncommented tests code + attempting to fix failing tests

* fix(api): reorder imports

* comment out test

* disable SSL

* force port 80

* test with requests library

* fix(api): using standard requests library

* test fix

* fix test

* fix import order

* multiple model types

* fix tests

* chore: test updated model endpoint and pull lambda endpoints from env

* chore: update remaining tests

---------

Co-authored-by: Lucian Hymer <lucian@gitcoin.co>
Co-authored-by: schultztimothy <schultz.timothy52@gmail.com>
  • Loading branch information
3 people authored Jul 10, 2024
1 parent d95f0c2 commit 3736df9
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 220 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/api-ci-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,7 @@ jobs:
ROUTE_53_ZONE_FOR_PUBLIC_DATA: ${{ secrets.ROUTE_53_ZONE_FOR_PUBLIC_DATA_REVIEW }}
DOMAIN: ${{ secrets.DOMAIN_REVIEW }}
SCORER_SERVER_SSM_ARN: ${{ secrets.SCORER_SERVER_SSM_ARN_REVIEW }}

ETHEREUM_MODEL_ENDPOINT: ${{ secrets.ETHEREUM_MODEL_ENDPOINT_REVIEW }}
NFT_MODEL_ENDPOINT: ${{ secrets.NFT_MODEL_ENDPOINT_REVIEW }}
ZKSYNC_MODEL_ENDPOINT: ${{ secrets.ZKSYNC_MODEL_ENDPOINT_REVIEW }}
4 changes: 4 additions & 0 deletions .github/workflows/api-promote-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,7 @@ jobs:
SCORER_RDS_SECRET_ARN: ${{ secrets.SCORER_RDS_SECRET_ARN }}

PAGERDUTY_INTEGRATION_ENDPOINT: ${{ secrets.PAGERDUTY_INTEGRATION_ENDPOINT }}

ETHEREUM_MODEL_ENDPOINT: ${{ secrets.ETHEREUM_MODEL_ENDPOINT }}
NFT_MODEL_ENDPOINT: ${{ secrets.NFT_MODEL_ENDPOINT }}
ZKSYNC_MODEL_ENDPOINT: ${{ secrets.ZKSYNC_MODEL_ENDPOINT }}
4 changes: 4 additions & 0 deletions .github/workflows/api-promote-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,7 @@ jobs:
REDASH_MAIL_USERNAME: ${{ secrets.REDASH_MAIL_USERNAME }}
REDASH_SECRET_KEY: ${{ secrets.REDASH_SECRET_KEY }}
SCORER_RDS_SECRET_ARN: ${{ secrets.SCORER_RDS_SECRET_ARN }}

ETHEREUM_MODEL_ENDPOINT: ${{ secrets.ETHEREUM_MODEL_ENDPOINT_STAGING }}
NFT_MODEL_ENDPOINT: ${{ secrets.NFT_MODEL_ENDPOINT_STAGING }}
ZKSYNC_MODEL_ENDPOINT: ${{ secrets.ZKSYNC_MODEL_ENDPOINT_STAGING }}
16 changes: 3 additions & 13 deletions api/aws_lambdas/passport/analysis_GET.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,22 @@
This module provides a handler to manage API requests in AWS Lambda.
"""

import asyncio
from aws_lambdas.utils import with_api_request_exception_handling # isort:skip

from django.db import close_old_connections
from passport.api import handle_get_analysis

from aws_lambdas.utils import (
with_api_request_exception_handling,
)


@with_api_request_exception_handling
def _handler(event, _context, _request, _user_account, _body):
"""
Handles the incoming events and translates them into Django's context.
"""

print(f"EVENT: \n***************\n{event}\n***************\n")
address = event["path"].split("/")[-1]
model_list = event.get("queryStringParameters", {}).get("model_list", "")

loop = asyncio.get_event_loop()
# DynamoDB resource defined above is attached to this loop:
# if you use asyncio.run instead
# you will encounter "Event loop closed" exception
analysis = loop.run_until_complete(handle_get_analysis(address))

return analysis
return handle_get_analysis(address, model_list)


def handler(*args, **kwargs):
Expand Down
131 changes: 104 additions & 27 deletions api/aws_lambdas/passport/tests/test_passport_analysis_lambda.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# pylint: disable=no-value-for-parameter
# pyright: reportGeneralTypeIssues=false
import json
from unittest.mock import Mock

import pytest
from passport.test.test_analysis import MockLambdaClient
from passport.api import MODEL_ENDPOINTS

from aws_lambdas.scorer_api_passport.tests.helpers import MockContext

Expand All @@ -14,7 +15,42 @@
address = "0x06e3c221011767FE816D0B8f5B16253E43e4Af7D"


def test_successful_analysis(
def mock_post_response(url, json, headers):
# Create a mock response object
mock_response = Mock()
mock_response.status_code = 200

# Define different responses based on the model (which we can infer from the URL)
responses = {
"ethereum": {
"data": {"human_probability": 75},
"metadata": {"model_name": "ethereum_activity", "version": "1.0"},
},
"nft": {
"data": {"human_probability": 85},
"metadata": {"model_name": "social_media", "version": "2.0"},
},
"zksync": {
"data": {"human_probability": 95},
"metadata": {"model_name": "transaction_history", "version": "1.5"},
},
}

# Determine which model is being requested
for model, endpoint in MODEL_ENDPOINTS.items():
if endpoint in url:
response_data = responses.get(model, {"data": {"human_probability": 0}})
break
else:
response_data = {"error": "Unknown model"}

# Set the json method of the mock response
mock_response.json = lambda: response_data

return mock_response


def test_successful_analysis_eth(
scorer_api_key,
mocker,
):
Expand All @@ -25,55 +61,74 @@ def test_successful_analysis(
event = {
"headers": {"x-api-key": scorer_api_key},
"path": f"/passport/analysis/{address}",
"queryStringParameters": {},
"queryStringParameters": {"model_list": "ethereum"},
"isBase64Encoded": False,
}
mocker.patch(
"passport.api.get_lambda_client",
MockLambdaClient,
)
response = _handler(event, MockContext())
with mocker.patch("requests.post", side_effect=mock_post_response):
response = _handler(event, MockContext())

# TODO: geri uncomment this
# assert response is not None
# assert response["statusCode"] == 200
assert response is not None
assert response["statusCode"] == 200

# body = json.loads(response["body"])
body = json.loads(response["body"])

# assert body["address"] == address
# assert body["details"]["models"]["ethereum_activity"]["score"] == 50
assert body["address"] == address
assert body["details"]["models"]["ethereum"]["score"] == 75


def test_bad_auth(
def test_successful_analysis_zksync(
scorer_api_key,
mocker,
):
"""
Tests that analysis can be requested successfully.
"""

event = {
"headers": {"x-api-key": scorer_api_key},
"path": f"/passport/analysis/{address}",
"queryStringParameters": {"model_list": "zksync"},
"isBase64Encoded": False,
}
with mocker.patch("requests.post", side_effect=mock_post_response):
response = _handler(event, MockContext())

assert response is not None
assert response["statusCode"] == 200

body = json.loads(response["body"])

assert body["address"] == address
assert body["details"]["models"]["zksync"]["score"] == 95


def test_bad_auth(
mocker,
):
"""
Tests that error is thrown if auth is bad
"""

event = {
"headers": {"x-api-key": "bad_auth"},
"path": f"/passport/analysis/{address}",
"queryStringParameters": {},
"isBase64Encoded": False,
}
mocker.patch(
"passport.api.get_lambda_client",
MockLambdaClient,
)

response = _handler(event, MockContext())

assert response is not None
assert response["statusCode"] == 403
assert json.loads(response["body"])["error"] == "Unauthorized"
assert response["statusCode"] == 401
assert json.loads(response["body"])["error"] == "Invalid API Key."


def test_bad_address(
scorer_api_key,
mocker,
):
"""
Tests that analysis can be requested successfully.
Tests that error is thrown is addrss is bad
"""

bad_address = address[:-1] + "d"
Expand All @@ -84,12 +139,34 @@ def test_bad_address(
"queryStringParameters": {},
"isBase64Encoded": False,
}
mocker.patch(
"passport.api.get_lambda_client",
MockLambdaClient,
)
response = _handler(event, MockContext())

assert response is not None
assert response["statusCode"] == 400
assert json.loads(response["body"])["error"] == "Invalid address"
assert json.loads(response["body"])["error"] == "Invalid address."


def test_bad_model(
scorer_api_key,
mocker,
):
"""
Tests that error is thrown if unsupported model is requested
"""

model = "bad_model"
event = {
"headers": {"x-api-key": scorer_api_key},
"path": f"/passport/analysis/{address}",
"queryStringParameters": {"model_list": model},
"isBase64Encoded": False,
}

response = _handler(event, MockContext())

assert response is not None
assert response["statusCode"] == 400
assert (
json.loads(response["body"])["error"]
== f"Invalid model name(s): {', '.join([model])}. Must be one of {', '.join(MODEL_ENDPOINTS.keys())}"
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

import pytest
from account.models import AccountAPIKeyAnalytics
from registry.test.test_passport_submission import mock_passport

from aws_lambdas.scorer_api_passport.tests.helpers import MockContext
from aws_lambdas.scorer_api_passport.utils import strip_event
from registry.test.test_passport_submission import mock_passport

from ..submit_passport import _handler

Expand Down Expand Up @@ -179,7 +180,7 @@ def test_unsucessfull_auth(scorer_account, scorer_community_with_binary_scorer):
response = _handler(event, MockContext())

assert response is not None
assert response["statusCode"] == 403
assert response["statusCode"] == 401


def test_strip_event():
Expand Down Expand Up @@ -362,7 +363,7 @@ def test_failed_authentication_and_analytics_logging(
response = _handler(event, MockContext())

assert response is not None
assert response["statusCode"] == 403
assert response["statusCode"] == 401

# Check for the proper analytics entry
analytics_entry_count = AccountAPIKeyAnalytics.objects.order_by(
Expand Down Expand Up @@ -423,7 +424,7 @@ def test_bad_scorer_id_and_analytics_logging(
response = _handler(event, MockContext())

assert response is not None
assert response["statusCode"] == 400
assert response["statusCode"] == 404
# Check for the proper analytics entry
analytics_entry = AccountAPIKeyAnalytics.objects.order_by("-created_at")[0]
assert analytics_entry.path == event["path"]
Expand Down
20 changes: 5 additions & 15 deletions api/aws_lambdas/tests/test_with_api_request_exception_handling.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import json

import pytest
from registry.test.test_passport_submission import mock_passport

from aws_lambdas.exceptions import InvalidRequest
from aws_lambdas.scorer_api_passport.tests.helpers import MockContext
from aws_lambdas.submit_passport.tests.test_submit_passport_lambda import (
make_test_event,
)
from aws_lambdas.utils import with_api_request_exception_handling
from registry.test.test_passport_submission import mock_passport
from aws_lambdas.exceptions import InvalidRequest


pytestmark = pytest.mark.django_db

Expand All @@ -31,15 +31,13 @@ def test_with_api_request_exception_handling_success(
passport_holder_addresses,
mocker,
):

with mocker.patch(
"registry.atasks.aget_passport",
return_value=mock_passport,
):
with mocker.patch(
"registry.atasks.validate_credential", side_effect=[[], [], []]
):

wrapped_func = with_api_request_exception_handling(func_to_test)

address = passport_holder_addresses[0]["address"].lower()
Expand All @@ -59,15 +57,13 @@ def test_with_api_request_exception_handling_bad_api_key(
passport_holder_addresses,
mocker,
):

with mocker.patch(
"registry.atasks.aget_passport",
return_value=mock_passport,
):
with mocker.patch(
"registry.atasks.validate_credential", side_effect=[[], [], []]
):

wrapped_func = with_api_request_exception_handling(func_to_test)

address = passport_holder_addresses[0]["address"].lower()
Expand All @@ -77,8 +73,8 @@ def test_with_api_request_exception_handling_bad_api_key(

ret = wrapped_func(test_event, MockContext())

assert ret["statusCode"] == 403
assert ret["body"] == '{"error": "Unauthorized"}'
assert ret["statusCode"] == 401
assert ret["body"] == '{"error": "Invalid API Key."}'


def test_with_api_request_exception_handling_bad_request(
Expand All @@ -87,15 +83,13 @@ def test_with_api_request_exception_handling_bad_request(
passport_holder_addresses,
mocker,
):

with mocker.patch(
"registry.atasks.aget_passport",
return_value=mock_passport,
):
with mocker.patch(
"registry.atasks.validate_credential", side_effect=[[], [], []]
):

wrapped_func = with_api_request_exception_handling(func_to_test_bad_request)

address = passport_holder_addresses[0]["address"].lower()
Expand All @@ -115,15 +109,13 @@ def test_with_api_request_exception_handling_unexpected_error(
passport_holder_addresses,
mocker,
):

with mocker.patch(
"registry.atasks.aget_passport",
return_value=mock_passport,
):
with mocker.patch(
"registry.atasks.validate_credential", side_effect=[[], [], []]
):

wrapped_func = with_api_request_exception_handling(
func_to_test_unexpected_error
)
Expand All @@ -142,15 +134,13 @@ def test_with_api_request_exception_handling_unexpected_error(
def test_with_api_request_exception_handling_bad_event(
mocker,
):

with mocker.patch(
"registry.atasks.aget_passport",
return_value=mock_passport,
):
with mocker.patch(
"registry.atasks.validate_credential", side_effect=[[], [], []]
):

wrapped_func = with_api_request_exception_handling(
func_to_test_unexpected_error
)
Expand Down
Loading

0 comments on commit 3736df9

Please sign in to comment.