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

Request organization account #11184

Merged
merged 47 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a9f6c66
admin-new-organization-requested email template
divbzero Apr 8, 2022
aa2a939
new-orgnization-requested email template
divbzero Apr 12, 2022
0bde7fa
Test admin-new-organization-requested email
divbzero Apr 12, 2022
6fb99e7
Test new-organization-requested email
divbzero Apr 12, 2022
e8f1b5c
Translate *-organization-requested consistently
divbzero Apr 12, 2022
d7d7923
`make translations` for *-organization-requested
divbzero Apr 12, 2022
bec7058
Remove translations from admin-* emails
divbzero Apr 13, 2022
08fb166
Initial cut at db model architecture
sterbo Apr 8, 2022
28edd0f
Ran code thru make reformat and lint (pypa#11070)
sterbo Apr 10, 2022
9de28f6
Added tests for new db models (pypa#11070)
sterbo Apr 10, 2022
6efcb99
Numerous tweaks to db models
sterbo Apr 12, 2022
086e2c7
Require value for is_active and ran reformat.
sterbo Apr 12, 2022
df033eb
Regenerate migrations for Organization models
divbzero Apr 13, 2022
054b6c7
Numerous tweaks to alembic scripts
sterbo Apr 12, 2022
540bce5
Initial implementation of organization service.
sterbo Apr 13, 2022
0a95425
Implementing organization events functionality.
sterbo Apr 14, 2022
e0eb6e2
Added tests for organization services.
sterbo Apr 14, 2022
0b0acc5
Added OrganizationFactory class for model.
sterbo Apr 14, 2022
8d9cb8f
Blank /manage/organizations/ page
divbzero Apr 8, 2022
99975f9
Create organization form on /manage/organizations/
divbzero Apr 12, 2022
c9fb07d
Register DatabaseOrganizationService
divbzero Apr 14, 2022
e33b8ca
POST /manage/organizations/ database updates
divbzero Apr 14, 2022
b2b0ec2
Add .get_admins method to user service
divbzero Apr 14, 2022
d4664d3
POST /manage/organizations/ email notifications
divbzero Apr 14, 2022
5d9bcc5
Blank /admin/organizations/approve/ page
divbzero Apr 14, 2022
88663d7
Test GET /manage/organizations/
divbzero Apr 14, 2022
f29a97d
Translations for /manage/organizations/
divbzero Apr 14, 2022
e15e280
Fixed OrganizationType enum.
sterbo Apr 14, 2022
67d3a28
Test POST /manage/organizations/
divbzero Apr 14, 2022
acbfac6
Test CreateOrganizationForm
divbzero Apr 15, 2022
71d2ce0
Placeholder to test /admin/organizations/approve/
divbzero Apr 15, 2022
b4aa6ec
Test `DatabaseUserService.get_admins()`
divbzero Apr 15, 2022
232f638
NFC: Add comments about intentionally blank page
divbzero Apr 15, 2022
3b373b4
Record events for POST /manage/organizations/
divbzero Apr 15, 2022
d9de8b3
Test record events for POST /manage/organizations/
divbzero Apr 15, 2022
1f40800
Functional test for POST /manage/organizations/
divbzero Apr 15, 2022
faa73f4
Comment out `OrganizationFactory` for future use
divbzero Apr 16, 2022
1163b5e
NFC: Fix camel case for class names
divbzero Apr 16, 2022
eecf5de
Converted OrganizationRoleType to SQLAlchemy Enum
sterbo Apr 18, 2022
5034ac0
Add disable-organizations global admin flag
sterbo Apr 18, 2022
161ab19
Add `AdminFlagValue.DISABLE_ORGANIZATIONS`
divbzero Apr 18, 2022
0cb26b0
Modified org name catalog to store normalized name
sterbo Apr 19, 2022
354f24d
Merge branch 'main' into feature/request-organization-account
divbzero Apr 20, 2022
c73674c
{OrganizationEvents => Organization.Events}
divbzero Apr 20, 2022
db0ec20
Store id instead of username in new events
divbzero Apr 20, 2022
49fafee
Display CreateOrganizationForm errors
divbzero Apr 20, 2022
c37c786
Tweak naming in events data {*_id => *_user_id}
divbzero Apr 20, 2022
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
97 changes: 97 additions & 0 deletions tests/common/db/organizations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime

import factory
import faker

from warehouse.organizations.models import (
Organization,
OrganizationInvitation,
OrganizationNameCatalog,
OrganizationProject,
OrganizationRole,
)

from .accounts import UserFactory
from .base import WarehouseFactory
from .packaging import ProjectFactory

fake = faker.Faker()


class OrganizationFactory(WarehouseFactory):
class Meta:
model = Organization

id = factory.Faker("uuid4", cast_to=None)
name = factory.Faker("word")
normalized_name = factory.Faker("word")
display_name = factory.Faker("word")
orgtype = "Community"
link_url = factory.Faker("uri")
description = factory.Faker("sentence")
is_active = True
is_approved = False
created = factory.Faker(
"date_time_between_dates",
datetime_start=datetime.datetime(2020, 1, 1),
datetime_end=datetime.datetime(2022, 1, 1),
)
date_approved = factory.Faker(
"date_time_between_dates", datetime_start=datetime.datetime(2020, 1, 1)
)


class OrganizationEventFactory(WarehouseFactory):
class Meta:
model = Organization.Event

source = factory.SubFactory(OrganizationFactory)


class OrganizationNameCatalogFactory(WarehouseFactory):
class Meta:
model = OrganizationNameCatalog

name = factory.Faker("orgname")
organization_id = factory.Faker("uuid4", cast_to=None)


class OrganizationRoleFactory(WarehouseFactory):
class Meta:
model = OrganizationRole

role_name = "Owner"
user = factory.SubFactory(UserFactory)
organization = factory.SubFactory(OrganizationFactory)


class OrganizationInvitationFactory(WarehouseFactory):
class Meta:
model = OrganizationInvitation

invite_status = "pending"
token = "test_token"
user = factory.SubFactory(UserFactory)
organization = factory.SubFactory(OrganizationFactory)


class OrganizationProjectFactory(WarehouseFactory):
class Meta:
model = OrganizationProject

id = factory.Faker("uuid4", cast_to=None)
is_active = True
organization = factory.SubFactory(OrganizationFactory)
project = factory.SubFactory(ProjectFactory)
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from warehouse.email.interfaces import IEmailSender
from warehouse.macaroons import services as macaroon_services
from warehouse.metrics import IMetricsService
from warehouse.organizations import services as organization_services

from .common.db import Session

Expand Down Expand Up @@ -285,6 +286,13 @@ def macaroon_service(db_session):
return macaroon_services.DatabaseMacaroonService(db_session)


@pytest.fixture
def organization_service(db_session, remote_addr):
return organization_services.DatabaseOrganizationService(
db_session, remote_addr=remote_addr
)


@pytest.fixture
def token_service(app_config):
return account_services.TokenService(secret="secret", salt="salt", max_age=21600)
Expand Down
67 changes: 66 additions & 1 deletion tests/functional/manage/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,91 @@
from webob.multidict import MultiDict

from warehouse.accounts.interfaces import IPasswordBreachedService, IUserService
from warehouse.admin.flags import AdminFlagValue
from warehouse.manage import views
from warehouse.organizations.interfaces import IOrganizationService
from warehouse.organizations.models import OrganizationType

from ...common.db.accounts import EmailFactory, UserFactory


class TestManageAccount:
def test_save_account(self, pyramid_services, user_service, db_request):
breach_service = pretend.stub()
organization_service = pretend.stub()
pyramid_services.register_service(user_service, IUserService, None)
pyramid_services.register_service(
breach_service, IPasswordBreachedService, None
)
pyramid_services.register_service(
organization_service, IOrganizationService, None
)
user = UserFactory.create(name="old name")
EmailFactory.create(primary=True, verified=True, public=True, user=user)
db_request.user = user
db_request.method = "POST"
db_request.path = "/manage/accounts/"
db_request.POST = MultiDict({"name": "new name", "public_email": ""})
views.ManageAccountViews(db_request).save_account()

views.ManageAccountViews(db_request).save_account()
user = user_service.get_user(user.id)

assert user.name == "new name"
assert user.public_email is None


class TestManageOrganizations:
def test_create_organization(
self,
pyramid_services,
user_service,
organization_service,
db_request,
monkeypatch,
):
pyramid_services.register_service(user_service, IUserService, None)
pyramid_services.register_service(
organization_service, IOrganizationService, None
)
user = UserFactory.create(name="old name")
EmailFactory.create(primary=True, verified=True, public=True, user=user)
db_request.user = user
db_request.method = "POST"
db_request.path = "/manage/organizations/"
db_request.POST = MultiDict(
{
"name": "psf",
"display_name": "Python Software Foundation",
"orgtype": "Community",
"link_url": "https://www.python.org/psf/",
"description": (
"To promote, protect, and advance the Python programming "
"language, and to support and facilitate the growth of a "
"diverse and international community of Python programmers"
),
}
)
monkeypatch.setattr(
db_request,
"flags",
pretend.stub(enabled=pretend.call_recorder(lambda *a: False)),
)
send_email = pretend.call_recorder(lambda *a, **kw: None)
monkeypatch.setattr(
views, "send_admin_new_organization_requested_email", send_email
)
monkeypatch.setattr(views, "send_new_organization_requested_email", send_email)

views.ManageOrganizationsViews(db_request).create_organization()
organization = organization_service.get_organization_by_name(
db_request.POST["name"]
)

assert db_request.flags.enabled.calls == [
pretend.call(AdminFlagValue.DISABLE_ORGANIZATIONS),
]
assert organization.name == db_request.POST["name"]
assert organization.display_name == db_request.POST["display_name"]
assert organization.orgtype == OrganizationType[db_request.POST["orgtype"]]
assert organization.link_url == db_request.POST["link_url"]
assert organization.description == db_request.POST["description"]
8 changes: 8 additions & 0 deletions tests/unit/accounts/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,14 @@ def test_get_user_by_email_failure(self, user_service):

assert found_user is None

def test_get_admins(self, user_service):
admin = UserFactory.create(is_superuser=True)
user = UserFactory.create(is_superuser=False)
admins = user_service.get_admins()

assert admin in admins
assert user not in admins

def test_disable_password(self, user_service):
user = UserFactory.create()

Expand Down
5 changes: 5 additions & 0 deletions tests/unit/admin/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ def test_includeme():

assert config.add_route.calls == [
pretend.call("admin.dashboard", "/admin/", domain=warehouse),
pretend.call(
"admin.organization.approve",
"/admin/organizations/approve/",
domain=warehouse,
),
pretend.call("admin.user.list", "/admin/users/", domain=warehouse),
pretend.call("admin.user.detail", "/admin/users/{user_id}/", domain=warehouse),
pretend.call(
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/admin/views/test_organizations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pretend

from warehouse.admin.views import organizations as views


class TestOrganizations:
def test_approve(self):
assert views.approve(pretend.stub()) == {}
Loading