Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Orgs: Collect terms of service agreement before billing activation #17376

Merged
merged 5 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
61 changes: 56 additions & 5 deletions tests/unit/manage/views/test_organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1066,16 +1066,67 @@ def test_activate_subscription(
self,
db_request,
organization,
monkeypatch,
):
organization_activate_billing_form_obj = pretend.stub()
organization_activate_billing_form_cls = pretend.call_recorder(
lambda *a, **kw: organization_activate_billing_form_obj
)
monkeypatch.setattr(
org_views,
"OrganizationActivateBillingForm",
organization_activate_billing_form_cls,
)
db_request.POST = MultiDict()

view = org_views.ManageOrganizationBillingViews(organization, db_request)

# We're not ready for companies to activate their own subscriptions yet.
with pytest.raises(HTTPNotFound):
assert view.activate_subscription()
result = view.activate_subscription()

assert result == {
"organization": organization,
"form": organization_activate_billing_form_obj,
}

@pytest.mark.usefixtures("_enable_organizations")
def test_post_activate_subscription_valid(
self,
db_request,
organization,
monkeypatch,
):
db_request.method = "POST"
db_request.POST = MultiDict({"terms_of_service_agreement": "1"})

# result = view.activate_subscription()
db_request.route_path = pretend.call_recorder(
lambda *a, **kw: "mock-billing-url"
)

view = org_views.ManageOrganizationBillingViews(organization, db_request)

result = view.activate_subscription()

assert isinstance(result, HTTPSeeOther)
assert result.headers["Location"] == "mock-billing-url"

@pytest.mark.usefixtures("_enable_organizations")
def test_post_activate_subscription_invalid(
self,
db_request,
organization,
monkeypatch,
):
db_request.method = "POST"
db_request.POST = MultiDict()

view = org_views.ManageOrganizationBillingViews(organization, db_request)

# assert result == {"organization": organization}
result = view.activate_subscription()

assert result["organization"] == organization
assert result["form"].terms_of_service_agreement.errors == [
"Terms of Service must be accepted."
]

@pytest.mark.usefixtures("_enable_organizations")
def test_create_subscription(
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/organizations/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
OrganizationRoleType,
OrganizationStripeCustomer,
OrganizationStripeSubscription,
OrganizationTermsOfServiceAgreement,
OrganizationType,
Team,
TeamProjectRole,
Expand Down Expand Up @@ -634,6 +635,42 @@ def test_delete_organization_project(self, organization_service, db_request):
.count()
)

def test_add_organization_terms_of_service_agreement(
self, organization_service, db_request
):
organization = OrganizationFactory.create()
assert organization.terms_of_service_agreements == []
organization_service.add_organization_terms_of_service_agreement(
organization.id
)
assert (
db_request.db.query(OrganizationTermsOfServiceAgreement)
.filter(
OrganizationTermsOfServiceAgreement.organization_id == organization.id,
OrganizationTermsOfServiceAgreement.agreed.isnot(None),
OrganizationTermsOfServiceAgreement.notified.is_(None),
)
.count()
) == 1

def test_add_organization_terms_of_service_agreement_notified(
self, organization_service, db_request
):
organization = OrganizationFactory.create()
assert organization.terms_of_service_agreements == []
organization_service.add_organization_terms_of_service_agreement(
organization.id, notified=True
)
assert (
db_request.db.query(OrganizationTermsOfServiceAgreement)
.filter(
OrganizationTermsOfServiceAgreement.organization_id == organization.id,
OrganizationTermsOfServiceAgreement.agreed.is_(None),
OrganizationTermsOfServiceAgreement.notified.isnot(None),
)
.count()
) == 1

def test_add_organization_subscription(self, organization_service, db_request):
organization = OrganizationFactory.create()
stripe_customer = StripeCustomerFactory.create()
Expand Down
80 changes: 46 additions & 34 deletions warehouse/locale/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ msgid ""
msgstr ""

#: warehouse/accounts/forms.py:410 warehouse/manage/forms.py:139
#: warehouse/manage/forms.py:730
#: warehouse/manage/forms.py:742
msgid "The name is too long. Choose a name with 100 characters or less."
msgstr ""

Expand Down Expand Up @@ -345,102 +345,102 @@ msgstr ""
msgid "Banner Preview"
msgstr ""

#: warehouse/manage/forms.py:408
#: warehouse/manage/forms.py:420
msgid "Choose an organization account name with 50 characters or less."
msgstr ""

#: warehouse/manage/forms.py:416
#: warehouse/manage/forms.py:428
msgid ""
"The organization account name is invalid. Organization account names must"
" be composed of letters, numbers, dots, hyphens and underscores. And must"
" also start and finish with a letter or number. Choose a different "
"organization account name."
msgstr ""

#: warehouse/manage/forms.py:439
#: warehouse/manage/forms.py:451
msgid ""
"This organization account name has already been used. Choose a different "
"organization account name."
msgstr ""

#: warehouse/manage/forms.py:454
#: warehouse/manage/forms.py:466
msgid ""
"You have already submitted an application for that name. Choose a "
"different organization account name."
msgstr ""

#: warehouse/manage/forms.py:490
#: warehouse/manage/forms.py:502
msgid "Select project"
msgstr ""

#: warehouse/manage/forms.py:495 warehouse/oidc/forms/_core.py:23
#: warehouse/manage/forms.py:507 warehouse/oidc/forms/_core.py:23
#: warehouse/oidc/forms/gitlab.py:57
msgid "Specify project name"
msgstr ""

#: warehouse/manage/forms.py:498
#: warehouse/manage/forms.py:510
msgid ""
"Start and end with a letter or numeral containing only ASCII numeric and "
"'.', '_' and '-'."
msgstr ""

#: warehouse/manage/forms.py:505
#: warehouse/manage/forms.py:517
msgid "This project name has already been used. Choose a different project name."
msgstr ""

#: warehouse/manage/forms.py:578
#: warehouse/manage/forms.py:590
msgid ""
"The organization name is too long. Choose a organization name with 100 "
"characters or less."
msgstr ""

#: warehouse/manage/forms.py:590
#: warehouse/manage/forms.py:602
msgid ""
"The organization URL is too long. Choose a organization URL with 400 "
"characters or less."
msgstr ""

#: warehouse/manage/forms.py:597
#: warehouse/manage/forms.py:609
msgid "The organization URL must start with http:// or https://"
msgstr ""

#: warehouse/manage/forms.py:608
#: warehouse/manage/forms.py:620
msgid ""
"The organization description is too long. Choose a organization "
"description with 400 characters or less."
msgstr ""

#: warehouse/manage/forms.py:643
#: warehouse/manage/forms.py:655
msgid "You have already submitted the maximum number of "
msgstr ""

#: warehouse/manage/forms.py:673
#: warehouse/manage/forms.py:685
msgid "Choose a team name with 50 characters or less."
msgstr ""

#: warehouse/manage/forms.py:679
#: warehouse/manage/forms.py:691
msgid ""
"The team name is invalid. Team names cannot start or end with a space, "
"period, underscore, hyphen, or slash. Choose a different team name."
msgstr ""

#: warehouse/manage/forms.py:707
#: warehouse/manage/forms.py:719
msgid "This team name has already been used. Choose a different team name."
msgstr ""

#: warehouse/manage/forms.py:726
#: warehouse/manage/forms.py:738
msgid "Specify your alternate repository name"
msgstr ""

#: warehouse/manage/forms.py:740
#: warehouse/manage/forms.py:752
msgid "Specify your alternate repository URL"
msgstr ""

#: warehouse/manage/forms.py:744
#: warehouse/manage/forms.py:756
msgid "The URL is too long. Choose a URL with 400 characters or less."
msgstr ""

#: warehouse/manage/forms.py:758
#: warehouse/manage/forms.py:770
msgid ""
"The description is too long. Choose a description with 400 characters or "
"less."
Expand Down Expand Up @@ -579,12 +579,12 @@ msgid ""
msgstr ""

#: warehouse/manage/views/__init__.py:2817
#: warehouse/manage/views/organizations.py:878
#: warehouse/manage/views/organizations.py:887
msgid "User '${username}' already has an active invite. Please try again later."
msgstr ""

#: warehouse/manage/views/__init__.py:2882
#: warehouse/manage/views/organizations.py:943
#: warehouse/manage/views/organizations.py:952
msgid "Invitation sent to '${username}'"
msgstr ""

Expand All @@ -597,30 +597,30 @@ msgid "Invitation already expired."
msgstr ""

#: warehouse/manage/views/__init__.py:2958
#: warehouse/manage/views/organizations.py:1130
#: warehouse/manage/views/organizations.py:1139
msgid "Invitation revoked from '${username}'."
msgstr ""

#: warehouse/manage/views/organizations.py:854
#: warehouse/manage/views/organizations.py:863
msgid "User '${username}' already has ${role_name} role for organization"
msgstr ""

#: warehouse/manage/views/organizations.py:865
#: warehouse/manage/views/organizations.py:874
msgid ""
"User '${username}' does not have a verified primary email address and "
"cannot be added as a ${role_name} for organization"
msgstr ""

#: warehouse/manage/views/organizations.py:1026
#: warehouse/manage/views/organizations.py:1068
#: warehouse/manage/views/organizations.py:1035
#: warehouse/manage/views/organizations.py:1077
msgid "Could not find organization invitation."
msgstr ""

#: warehouse/manage/views/organizations.py:1036
#: warehouse/manage/views/organizations.py:1045
msgid "Organization invitation could not be re-sent."
msgstr ""

#: warehouse/manage/views/organizations.py:1083
#: warehouse/manage/views/organizations.py:1092
msgid "Expired invitation for '${username}' deleted."
msgstr ""

Expand Down Expand Up @@ -1432,6 +1432,7 @@ msgstr ""
#: warehouse/templates/manage/account/token.html:150
#: warehouse/templates/manage/account/totp-provision.html:69
#: warehouse/templates/manage/account/webauthn-provision.html:44
#: warehouse/templates/manage/organization/activate_subscription.html:34
#: warehouse/templates/manage/organization/projects.html:128
#: warehouse/templates/manage/organization/projects.html:151
#: warehouse/templates/manage/organization/roles.html:270
Expand Down Expand Up @@ -4059,7 +4060,7 @@ msgstr ""

#: warehouse/templates/manage/manage_base.html:366
#: warehouse/templates/manage/manage_base.html:418
#: warehouse/templates/manage/organization/activate_subscription.html:32
#: warehouse/templates/manage/organization/activate_subscription.html:52
msgid "Cancel"
msgstr ""

Expand Down Expand Up @@ -5124,17 +5125,28 @@ msgid ""
msgstr ""

#: warehouse/templates/manage/organization/activate_subscription.html:17
#: warehouse/templates/manage/organization/activate_subscription.html:21
#: warehouse/templates/manage/organization/activate_subscription.html:35
#: warehouse/templates/manage/organization/activate_subscription.html:22
#: warehouse/templates/manage/organization/activate_subscription.html:54
msgid "Activate Subscription"
msgstr ""

#: warehouse/templates/manage/organization/activate_subscription.html:27
#: warehouse/templates/manage/organization/activate_subscription.html:26
msgid ""
"Company accounts require an active subscription. Please enter up-to-date "
"billing information to enable the account."
msgstr ""

#: warehouse/templates/manage/organization/activate_subscription.html:33
msgid "Terms of Service"
msgstr ""

#: warehouse/templates/manage/organization/activate_subscription.html:37
#, python-format
msgid ""
"I agree to the PyPI <a href=\"%(tos_url)s\">Terms of Service</a> on "
"behalf of the %(organization_name)s organization."
msgstr ""

#: warehouse/templates/manage/organization/history.html:20
#: warehouse/templates/manage/project/history.html:20
#: warehouse/templates/manage/team/history.html:20
Expand Down
12 changes: 12 additions & 0 deletions warehouse/manage/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,18 @@ def validate_macaroon_id(self, field):
# /manage/organizations/ forms


class OrganizationActivateBillingForm(wtforms.Form):
terms_of_service_agreement = wtforms.BooleanField(
validators=[
wtforms.validators.AnyOf(
[True],
message="Terms of Service must be accepted.",
),
ewdurbin marked this conversation as resolved.
Show resolved Hide resolved
],
default=False,
)


class OrganizationRoleNameMixin:
role_name = wtforms.SelectField(
"Select role",
Expand Down
Loading