Repository: pypi/warehouse

Issue:
Role.role_name can be null
Pairing with @ewdurbin on #2745 we noticed that `Role.role_name` can be null:

https://github.com/pypa/warehouse/blob/32f621a359bfa7ecb84d8b0ff77e675aae2cd9e1/warehouse/packaging/models.py#L64

Enforcing this to be `nullable=False` causes a lot of our tests to fail due to our `ProjectFactory` and `UserFactory` implicitly creating roles without names, since they bypass the `RoleFactory`.

We should update our test fixtures to allow us to set `nullable=False`.

Some possible leads: https://factoryboy.readthedocs.io/en/latest/reference.html 
Role.role_name can be null
Pairing with @ewdurbin on #2745 we noticed that `Role.role_name` can be null:

https://github.com/pypa/warehouse/blob/32f621a359bfa7ecb84d8b0ff77e675aae2cd9e1/warehouse/packaging/models.py#L64

Enforcing this to be `nullable=False` causes a lot of our tests to fail due to our `ProjectFactory` and `UserFactory` implicitly creating roles without names, since they bypass the `RoleFactory`.

We should update our test fixtures to allow us to set `nullable=False`.

Some possible leads: https://factoryboy.readthedocs.io/en/latest/reference.html 


Discussion:
After #7689, it looks like only 4 tests are left depending on the implicit Role creation here.

I'm not sure if supporting

`test_project = ProjectFactory(users=[test_user0, test_user1])`
or
`test_user = UserFactory(projects=[test_project0, test_project1])`

is worth it really.
After #7689, it looks like only 4 tests are left depending on the implicit Role creation here.

I'm not sure if supporting

`test_project = ProjectFactory(users=[test_user0, test_user1])`
or
`test_user = UserFactory(projects=[test_project0, test_project1])`

is worth it really.

Resolving patch:
diff --git a/warehouse/migrations/versions/6af76ffb9612_role_role_name_should_not_be_nullable.py b/warehouse/migrations/versions/6af76ffb9612_role_role_name_should_not_be_nullable.py
new file mode 100644
--- /dev/null
+++ b/warehouse/migrations/versions/6af76ffb9612_role_role_name_should_not_be_nullable.py
@@ -0,0 +1,33 @@
+# 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.
+"""
+Role.role_name should not be nullable
+
+Revision ID: 6af76ffb9612
+Revises: aaa60e8ea12e
+Create Date: 2020-03-28 01:20:30.453875
+"""
+
+import sqlalchemy as sa
+
+from alembic import op
+
+revision = "6af76ffb9612"
+down_revision = "aaa60e8ea12e"
+
+
+def upgrade():
+    op.alter_column("roles", "role_name", existing_type=sa.TEXT(), nullable=False)
+
+
+def downgrade():
+    op.alter_column("roles", "role_name", existing_type=sa.TEXT(), nullable=True)
diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py
--- a/warehouse/packaging/models.py
+++ b/warehouse/packaging/models.py
@@ -64,7 +64,7 @@ class Role(db.Model):
 
     __repr__ = make_repr("role_name")
 
-    role_name = Column(Text)
+    role_name = Column(Text, nullable=False)
     user_id = Column(
         ForeignKey("users.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False
     )


Test patch:
diff --git a/tests/unit/malware/checks/package_turnover/test_check.py b/tests/unit/malware/checks/package_turnover/test_check.py
--- a/tests/unit/malware/checks/package_turnover/test_check.py
+++ b/tests/unit/malware/checks/package_turnover/test_check.py
@@ -21,7 +21,7 @@
 
 from .....common.db.accounts import UserFactory
 from .....common.db.malware import MalwareCheckFactory
-from .....common.db.packaging import ProjectFactory, ReleaseFactory
+from .....common.db.packaging import ProjectFactory, ReleaseFactory, RoleFactory
 
 
 def test_initializes(db_session):
@@ -90,7 +90,8 @@ def test_user_posture_verdicts_has_2fa(db_session):
 
 def test_user_turnover_verdicts(db_session):
     user = UserFactory.create()
-    project = ProjectFactory.create(users=[user])
+    project = ProjectFactory.create()
+    RoleFactory.create(user=user, project=project, role_name="Owner")
 
     project.record_event(
         tag="project:role:add",
@@ -117,7 +118,8 @@ def test_user_turnover_verdicts(db_session):
 
 def test_user_turnover_verdicts_no_turnover(db_session):
     user = UserFactory.create()
-    project = ProjectFactory.create(users=[user])
+    project = ProjectFactory.create()
+    RoleFactory.create(user=user, project=project, role_name="Owner")
 
     MalwareCheckFactory.create(
         name="PackageTurnoverCheck", state=MalwareCheckState.Enabled,
@@ -130,7 +132,8 @@ def test_user_turnover_verdicts_no_turnover(db_session):
 
 def test_scan(db_session, monkeypatch):
     user = UserFactory.create()
-    project = ProjectFactory.create(users=[user])
+    project = ProjectFactory.create()
+    RoleFactory.create(user=user, project=project, role_name="Owner")
 
     for _ in range(3):
         ReleaseFactory.create(project=project)
@@ -157,7 +160,8 @@ def test_scan(db_session, monkeypatch):
 
 def test_scan_too_few_releases(db_session, monkeypatch):
     user = UserFactory.create()
-    project = ProjectFactory.create(users=[user])
+    project = ProjectFactory.create()
+    RoleFactory.create(user=user, project=project, role_name="Owner")
     ReleaseFactory.create(project=project)
 
     MalwareCheckFactory.create(