Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 12 additions & 18 deletions services/invitations/src/simcore_service_invitations/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getpass
import logging
import os

import typer
from cryptography.fernet import Fernet
Expand All @@ -14,7 +15,6 @@
print_as_envfile,
)

from . import web_server
from ._meta import PROJECT_NAME, __version__
from .core.settings import ApplicationSettings, MinimalApplicationSettings
from .services.invitations import (
Expand Down Expand Up @@ -50,7 +50,7 @@ def generate_key(
export INVITATIONS_SECRET_KEY=$(invitations-maker generate-key)
"""
assert ctx # nosec
print(Fernet.generate_key().decode()) # noqa: T201
typer.echo(Fernet.generate_key().decode())


@main.command()
Expand Down Expand Up @@ -106,6 +106,10 @@ def invite(
None,
help=InvitationInputs.model_fields["trial_account_days"].description,
),
extra_credits_in_usd: int = typer.Option(
None,
help=InvitationInputs.model_fields["extra_credits_in_usd"].description,
),
product: str = typer.Option(
None,
help=InvitationInputs.model_fields["product"].description,
Expand All @@ -119,7 +123,7 @@ def invite(
issuer=issuer,
guest=TypeAdapter(EmailStr).validate_python(email),
trial_account_days=trial_account_days,
extra_credits_in_usd=None,
extra_credits_in_usd=extra_credits_in_usd,
product=product,
)

Expand All @@ -129,7 +133,7 @@ def invite(
base_url=settings.INVITATIONS_OSPARC_URL,
default_product=settings.INVITATIONS_DEFAULT_PRODUCT,
)
print(invitation_link) # noqa: T201
typer.echo(invitation_link)


@main.command()
Expand All @@ -149,18 +153,8 @@ def extract(ctx: typer.Context, invitation_url: str):
)
assert invitation.product is not None # nosec

print(invitation.model_dump_json(indent=1)) # noqa: T201

except (InvalidInvitationCodeError, ValidationError):
_err_console.print("[bold red]Invalid code[/bold red]")

typer.echo(invitation.model_dump_json(indent=1))

@main.command()
def serve(
ctx: typer.Context,
*,
reload: bool = False,
):
"""Starts server with http API"""
assert ctx # nosec
web_server.start(log_level="info", reload=reload)
except (InvalidInvitationCodeError, ValidationError) as err:
typer.secho("Invalid code", fg=typer.colors.RED, bold=True, err=True)
raise typer.Exit(os.EX_DATAERR) from err
3 changes: 3 additions & 0 deletions services/invitations/tests/unit/api/test_api_invitations.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def test_create_invitation(
assert invitation.issuer == invitation_input.issuer
assert invitation.guest == invitation_input.guest
assert invitation.trial_account_days == invitation_input.trial_account_days
assert invitation.extra_credits_in_usd == invitation_input.extra_credits_in_usd

# checks issue with `//` reported in https://github.com/ITISFoundation/osparc-simcore/issues/7055
assert invitation.invitation_url
Expand All @@ -61,6 +62,7 @@ def test_check_invitation(
"issuer": invitation_data.issuer,
"guest": invitation_data.guest,
"trial_account_days": invitation_data.trial_account_days,
"extra_credits_in_usd": invitation_data.extra_credits_in_usd,
},
auth=basic_auth,
)
Expand All @@ -85,6 +87,7 @@ def test_check_invitation(
assert invitation.issuer == invitation_data.issuer
assert invitation.guest == invitation_data.guest
assert invitation.trial_account_days == invitation_data.trial_account_days
assert invitation.extra_credits_in_usd == invitation_data.extra_credits_in_usd


def test_check_valid_invitation(
Expand Down
14 changes: 13 additions & 1 deletion services/invitations/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from cryptography.fernet import Fernet
from faker import Faker
from models_library.products import ProductName
from pydantic import PositiveInt, TypeAdapter
from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict
from pytest_simcore.helpers.typing_env import EnvVarsDict
from simcore_service_invitations.services.invitations import InvitationInputs
Expand Down Expand Up @@ -85,6 +86,11 @@ def is_trial_account(request: pytest.FixtureRequest) -> bool:
return request.param


@pytest.fixture
def extra_credits_in_usd(is_trial_account: bool) -> PositiveInt | None:
return TypeAdapter(PositiveInt).validate_python(123) if is_trial_account else None


@pytest.fixture
def default_product() -> ProductName:
return "s4llite"
Expand All @@ -98,7 +104,10 @@ def product(request: pytest.FixtureRequest) -> ProductName | None:

@pytest.fixture
def invitation_data(
is_trial_account: bool, faker: Faker, product: ProductName | None
is_trial_account: bool,
faker: Faker,
product: ProductName | None,
extra_credits_in_usd: PositiveInt | None,
) -> InvitationInputs:
# first version
kwargs = {
Expand All @@ -110,4 +119,7 @@ def invitation_data(
if product:
kwargs["product"] = product

if extra_credits_in_usd is not None:
kwargs["extra_credits_in_usd"] = extra_credits_in_usd

return InvitationInputs.model_validate(kwargs)
36 changes: 31 additions & 5 deletions services/invitations/tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,24 @@ def test_invite_user_and_check_invitation(
"INVITATIONS_DEFAULT_PRODUCT": default_product,
}

expected = {
expected_invitation = {
**invitation_data.model_dump(exclude={"product"}),
"product": environs["INVITATIONS_DEFAULT_PRODUCT"],
}

# invitations-maker invite [email protected] --issuer=me --trial-account-days=3
trial_account = ""
other_options = ""
if invitation_data.trial_account_days:
trial_account = f"--trial-account-days={invitation_data.trial_account_days}"
other_options = f"--trial-account-days={invitation_data.trial_account_days}"

if invitation_data.extra_credits_in_usd:
other_options += (
f" --extra-credits-in-usd={invitation_data.extra_credits_in_usd}"
)

result = cli_runner.invoke(
main,
f"invite {invitation_data.guest} --issuer={invitation_data.issuer} {trial_account}",
f"invite {invitation_data.guest} --issuer={invitation_data.issuer} {other_options}",
env=environs,
)
assert result.exit_code == os.EX_OK, result.output
Expand All @@ -73,7 +78,7 @@ def test_invite_user_and_check_invitation(
)
assert result.exit_code == os.EX_OK, result.output
assert (
expected
expected_invitation
== TypeAdapter(InvitationInputs).validate_json(result.stdout).model_dump()
)

Expand All @@ -99,3 +104,24 @@ def test_list_settings(cli_runner: CliRunner, app_environment: EnvVarsDict):
print(result.output)
settings = ApplicationSettings.model_validate_json(result.output)
assert settings == ApplicationSettings.create_from_envs()


def test_extract_invalid_invitation_code(
cli_runner: CliRunner, faker: Faker, app_environment: EnvVarsDict
):
"""Test that extract command handles invalid invitation codes properly"""
# Create an invalid invitation URL
invalid_invitation_url = f"{faker.url()}#invitation=invalid_code_123"

# Run extract command with invalid invitation URL
result = cli_runner.invoke(
main,
f'extract "{invalid_invitation_url}"',
env=app_environment,
)

# Verify command exits with correct error code
assert result.exit_code == os.EX_DATAERR

# Verify error message is displayed via stderr
assert "Invalid code" in result.stdout
2 changes: 2 additions & 0 deletions services/invitations/tests/unit/test_core_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ def test_valid_application_settings(app_environment: EnvVarsDict):
assert settings

assert settings == ApplicationSettings.create_from_envs()

assert settings.LOG_LEVEL == "INFO"
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

async def _auto_add_default_wallet(
app: web.Application,
*,
user_id: UserID,
product_name: ProductName,
extra_credits_in_usd: PositiveInt | None = None,
Expand Down
Loading