Skip to content

Commit

Permalink
better testability
Browse files Browse the repository at this point in the history
  • Loading branch information
jensens committed Jan 21, 2025
1 parent 1b5b6a1 commit 54b4065
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 20 deletions.
4 changes: 3 additions & 1 deletion examples/edutap_wallet_google_example_callback/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ We hope you get the idea.

There is an example swarm deployment in here in `swarm.yml`.
It can be deployed on the cluster.
The public domain is configured using the environment variable `EDUTAP_WALLET_GOOGLE_EXAMPLE_DOMAIN`.
The public domain must be configured using the environment variable `EDUTAP_WALLET_GOOGLE_EXAMPLE_DOMAIN`.
A TLS certificate will be issued automatically using Lets Encrypt.

Example: `export EDUTAP_WALLET_GOOGLE_EXAMPLE_DOMAIN=edutap-wallet-google-callback.example.com`

```shell
docker stack deploy swarm.yml -c swarm.yml edutap_wallet_google_example_callback
```
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@ callback = [
"httpx",
]
test = [
"pytest",
"edutap.wallet_google[callback]",
"freezegun",
"pytest-cov",
"pytest-explicit",
"pytest",
"requests-mock",
"tox",
"edutap.wallet_google[callback]",
]
typecheck = [
"google-auth-stubs",
Expand Down
53 changes: 37 additions & 16 deletions src/edutap/wallet_google/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import json
import logging
import time
import typing


Expand Down Expand Up @@ -338,6 +339,38 @@ def listing(
break


def _create_payload(models: list[ClassModel | ObjectModel | Reference]) -> JWTPayload:
"""Creates a payload for the JWT."""
payload = JWTPayload()

for model in models:
if isinstance(model, Reference):
if model.model_name is not None:
name = lookup_metadata_by_name(model.model_name)["plural"]
elif model.model_type is not None:
name = lookup_metadata_by_model_type(model.model_type)["plural"]
else:
name = lookup_metadata_by_model_instance(model)["plural"]
if getattr(payload, name) is None:
setattr(payload, name, [])
getattr(payload, name).append(model)
return payload


def _create_claims(
issuer: str,
origins: list[str],
models: list[ClassModel | ObjectModel | Reference],
) -> JWTClaims:
"""Creates a JWTClaims instance based on the given issuer, origins and models."""
return JWTClaims(
iss=issuer,
iat=str(int(time.time())),
origins=origins,
payload=_create_payload(models),
)


def save_link(
models: list[ClassModel | ObjectModel | Reference],
*,
Expand All @@ -362,23 +395,11 @@ def save_link(
messages in the browser console when the origins field is not defined.
:return: Link with JWT to save the resources to the wallet.
"""
payload = JWTPayload()
for model in models:
if isinstance(model, Reference):
if model.model_name is not None:
name = lookup_metadata_by_name(model.model_name)["plural"]
elif model.model_type is not None:
name = lookup_metadata_by_model_type(model.model_type)["plural"]
else:
name = lookup_metadata_by_model_instance(model)["plural"]
if getattr(payload, name) is None:
setattr(payload, name, [])
getattr(payload, name).append(model)

claims = JWTClaims(
iss=session_manager.settings.credentials_info["client_email"],
origins=origins,
payload=payload,
claims = _create_claims(
session_manager.settings.credentials_info["client_email"],
origins,
models,
)
signer = crypt.RSASigner.from_service_account_file(
session_manager.settings.credentials_file
Expand Down
69 changes: 68 additions & 1 deletion tests/test_api_save_link.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
from freezegun import freeze_time


def test_create_payload():

from edutap.wallet_google import api

payload = api._create_payload(
[
api.new(
"Reference", {"id": "test-1.edutap.eu", "model_name": "GenericObject"}
),
api.new(
"OfferObject",
{"id": "test-2.edutap.eu", "classId": "test-class-1.edutap.eu"},
),
]
)
expected = '{"offerObjects":[{"id":"test-2.edutap.eu","classId":"test-class-1.edutap.eu","state":"STATE_UNSPECIFIED","hasLinkedDevice":false,"disableExpirationNotification":false,"notifyPreference":"NOTIFICATION_SETTINGS_FOR_UPDATES_UNSPECIFIED"}],"genericObjects":[{"id":"test-1.edutap.eu"}]}'
assert payload.model_dump_json(exclude_none=True) == expected


@freeze_time("2025-01-17 12:54:00")
def test_create_claims():

from edutap.wallet_google import api

models = [
api.new("Reference", {"id": "test-1.edutap.eu", "model_name": "GenericObject"}),
api.new(
"OfferObject",
{"id": "test-2.edutap.eu", "classId": "test-class-1.edutap.eu"},
),
]
expected = {
"iss": "123456789",
"aud": "google",
"typ": "savettowallet",
"iat": "1737118440",
"payload": {
"offerObjects": [
{
"id": "test-2.edutap.eu",
"classId": "test-class-1.edutap.eu",
"state": "STATE_UNSPECIFIED",
"hasLinkedDevice": False,
"disableExpirationNotification": False,
"notifyPreference": "NOTIFICATION_SETTINGS_FOR_UPDATES_UNSPECIFIED",
}
],
"genericObjects": [{"id": "test-1.edutap.eu"}],
},
"origins": [],
}

claims = api._create_claims("123456789", [], models)

dumped = claims.model_dump(
mode="json",
exclude_unset=False,
exclude_defaults=False,
exclude_none=True,
)
assert expected == dumped


@freeze_time("2025-01-17 12:54:00")
def test_api_save_link(mock_settings):
from edutap.wallet_google.settings import ROOT_DIR

Expand All @@ -21,5 +88,5 @@ def test_api_save_link(mock_settings):
)
assert (
link
== "https://pay.google.com/gp/v/save/eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3OCJ9.eyJpc3MiOiAiZWR1dGFwLXRlc3QtZXhhbXBsZUBzb2RpdW0tcmF5LTEyMzQ1Ni5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsICJhdWQiOiAiZ29vZ2xlIiwgInR5cCI6ICJzYXZldHRvd2FsbGV0IiwgImlhdCI6ICIiLCAicGF5bG9hZCI6IHsib2ZmZXJPYmplY3RzIjogW3siaWQiOiAidGVzdC0yLmVkdXRhcC5ldSIsICJjbGFzc0lkIjogInRlc3QtY2xhc3MtMS5lZHV0YXAuZXUiLCAic3RhdGUiOiAiU1RBVEVfVU5TUEVDSUZJRUQiLCAiaGFzTGlua2VkRGV2aWNlIjogZmFsc2UsICJkaXNhYmxlRXhwaXJhdGlvbk5vdGlmaWNhdGlvbiI6IGZhbHNlLCAibm90aWZ5UHJlZmVyZW5jZSI6ICJOT1RJRklDQVRJT05fU0VUVElOR1NfRk9SX1VQREFURVNfVU5TUEVDSUZJRUQifV0sICJnZW5lcmljT2JqZWN0cyI6IFt7ImlkIjogInRlc3QtMS5lZHV0YXAuZXUifV19LCAib3JpZ2lucyI6IFtdfQ.LEPJBlt7ic9cPWKUvpoxUWe5yvdK0_kqPlBFkHmqFBfO5eeYN-owTHCElCGhnHeE730D4U3XjQWeZXfcaEAQcdBKB8udoT2Tja7Rw_M8M18kpBrSdGDRKT_uXG_-RkG3uVB30Lu5otlJiX2VOJWg9H6NR7wD_pfUt67cLjiBeMILuIVi-h0CDUV0dObEjnOHrRhj6KeKdfqq6izwwmw4iSQxsaQrDxWZtwCZ__pV5UK54Od6-lNrsBQwz241SDYv9kJTXrImrjRZXdoht6xgwqxg-GcuqUJgcczG-TLyN_9aI4FtA2cz8PCXyKPXnd-_HTe9nohi05dfMDeVWsmP6g"
== "https://pay.google.com/gp/v/save/eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3OCJ9.eyJpc3MiOiAiZWR1dGFwLXRlc3QtZXhhbXBsZUBzb2RpdW0tcmF5LTEyMzQ1Ni5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsICJhdWQiOiAiZ29vZ2xlIiwgInR5cCI6ICJzYXZldHRvd2FsbGV0IiwgImlhdCI6ICIxNzM3MTE4NDQwIiwgInBheWxvYWQiOiB7Im9mZmVyT2JqZWN0cyI6IFt7ImlkIjogInRlc3QtMi5lZHV0YXAuZXUiLCAiY2xhc3NJZCI6ICJ0ZXN0LWNsYXNzLTEuZWR1dGFwLmV1IiwgInN0YXRlIjogIlNUQVRFX1VOU1BFQ0lGSUVEIiwgImhhc0xpbmtlZERldmljZSI6IGZhbHNlLCAiZGlzYWJsZUV4cGlyYXRpb25Ob3RpZmljYXRpb24iOiBmYWxzZSwgIm5vdGlmeVByZWZlcmVuY2UiOiAiTk9USUZJQ0FUSU9OX1NFVFRJTkdTX0ZPUl9VUERBVEVTX1VOU1BFQ0lGSUVEIn1dLCAiZ2VuZXJpY09iamVjdHMiOiBbeyJpZCI6ICJ0ZXN0LTEuZWR1dGFwLmV1In1dfSwgIm9yaWdpbnMiOiBbXX0.X5nE4Zh4kvxGZ4LxnEw8_9i2tCq-JJUmSCRxpf8ckQJVNumsA_uQze0uyuCPTBSVsdHcnMxozFdiCktrwIdvBYDjSLf91acoADMtY1n-HIPbCexlpTpHgyDpyC8giVx27lAvkjaDtHzvEoJ1nAkwh-_-322uKwEWOllR_voV162lNLMIE5QY2eDJ9yfcFa5LXqxSW64UDTZSYX0DsJtGf-Oa1HOgnG77D6RZfMYUq18duNmcBp5wREVrE27vPjnVRC2kEsrWfQNw4gkN_aSp6ZhEAG-exQqUQQXeR7nPqYm-DM7uVOCh8pbk8WqwA7fKcsZIRK8dsWnqnDuNdwP7Gw"
)

0 comments on commit 54b4065

Please sign in to comment.