1- from unittest import mock
2-
31import django
42from allauth .account .models import EmailAddress
53from django .contrib import auth
64from django .contrib .auth import get_user_model
75from django .contrib .auth .models import Permission
86from django .core import mail
9- from django .core .exceptions import ValidationError as DjangoValidationError
10- from django .test import TestCase
7+ from django .test import TestCase , TransactionTestCase
118from django .urls import reverse
129from django .utils .timezone import localdate , timedelta
1310from swapper import load_model
@@ -140,6 +137,11 @@ def test_patch_disabled_organization_reenable_api(self):
140137 self .assertTrue (org1 .is_active )
141138
142139 def test_reenable_disabled_organization_with_field_edit_api (self ):
140+ """
141+ Re-enabling (is_active) and editing another field in the same
142+ request is rejected: re-enabling and editing must be two separate
143+ requests, matching the admin and the docs.
144+ """
143145 org1 = self ._get_org ()
144146 org1 .is_active = False
145147 org1 .save ()
@@ -153,6 +155,27 @@ def test_reenable_disabled_organization_with_field_edit_api(self):
153155 self .assertEqual (org1 .is_active , False )
154156 self .assertEqual (org1 .name , "test org" )
155157
158+ def test_reenable_disabled_organization_via_put_api (self ):
159+ org1 = self ._get_org ()
160+ org1 .is_active = False
161+ org1 .save ()
162+ path = reverse ("users:organization_detail" , args = (org1 .pk ,))
163+ # a PUT always resends every required field, "name" included; since
164+ # its value is unchanged it must not count as an edit and block the
165+ # re-enable, the way a read-modify-write client would use PUT
166+ data = {
167+ "name" : org1 .name ,
168+ "is_active" : True ,
169+ "slug" : org1 .slug ,
170+ "description" : org1 .description ,
171+ "email" : org1 .email ,
172+ "url" : org1 .url ,
173+ }
174+ response = self .client .put (path , data , content_type = "application/json" )
175+ self .assertEqual (response .status_code , 200 )
176+ org1 .refresh_from_db ()
177+ self .assertTrue (org1 .is_active )
178+
156179 def test_create_organization_owner_api (self ):
157180 user1 = self ._create_user (username = "user1" , email = "user1@email.com" )
158181 org1 = self ._create_org (name = "org1" )
@@ -658,41 +681,6 @@ def test_create_user_with_group_org_user_api(self):
658681 r = self .client .post (path , data , content_type = "application/json" )
659682 self .assertEqual (r .status_code , 201 )
660683
661- def test_create_user_organization_users_disabled_org_api (self ):
662- path = reverse ("users:user_list" )
663- org1 = self ._create_org (name = "disabled-org" , is_active = False )
664- data = {
665- "username" : "tester" ,
666- "email" : "tester@test.com" ,
667- "password" : "password" ,
668- "organization_users" : {"is_admin" : False , "organization" : org1 .pk },
669- }
670- r = self .client .post (path , data , content_type = "application/json" )
671- self .assertEqual (r .status_code , 400 )
672- self .assertEqual (User .objects .filter (username = "tester" ).count (), 0 )
673- self .assertEqual (OrganizationUser .objects .filter (organization = org1 ).count (), 0 )
674-
675- def test_create_user_membership_failure_rolls_back_user_api (self ):
676- # A membership validation failure after the user row is written must
677- # roll the user back instead of leaving a half-created account behind.
678- path = reverse ("users:user_list" )
679- org1 = self ._get_org ()
680- data = {
681- "username" : "rollbackuser" ,
682- "email" : "rollbackuser@test.com" ,
683- "password" : "password" ,
684- "organization_users" : {"is_admin" : False , "organization" : org1 .pk },
685- }
686- with mock .patch .object (
687- OrganizationUser ,
688- "full_clean" ,
689- side_effect = DjangoValidationError ("membership boom" ),
690- ):
691- r = self .client .post (path , data , content_type = "application/json" )
692- self .assertEqual (r .status_code , 400 )
693- self .assertEqual (User .objects .filter (username = "rollbackuser" ).count (), 0 )
694- self .assertEqual (OrganizationUser .objects .filter (organization = org1 ).count (), 0 )
695-
696684 def test_post_with_no_email (self ):
697685 path = reverse ("users:user_list" )
698686 data = {"username" : "" , "email" : "" , "password" : "" }
@@ -968,3 +956,25 @@ def test_expiration_date_none_api(self):
968956 self .assertIsNone (r .data ["expiration_date" ])
969957 user .refresh_from_db ()
970958 self .assertIsNone (user .expiration_date )
959+
960+
961+ class TestUsersApiTransaction (TestOrganizationMixin , TransactionTestCase ):
962+ def setUp (self ):
963+ self .client .force_login (self ._get_admin ())
964+
965+ def test_create_user_organization_users_disabled_org_api (self ):
966+ # A membership validation failure (here, the disabled-organization
967+ # guard) after the user row is written must roll the user back
968+ # instead of leaving a half-created account behind.
969+ path = reverse ("users:user_list" )
970+ org1 = self ._create_org (name = "disabled-org" , is_active = False )
971+ data = {
972+ "username" : "tester" ,
973+ "email" : "tester@test.com" ,
974+ "password" : "password" ,
975+ "organization_users" : {"is_admin" : False , "organization" : org1 .pk },
976+ }
977+ r = self .client .post (path , data , content_type = "application/json" )
978+ self .assertEqual (r .status_code , 400 )
979+ self .assertEqual (User .objects .filter (username = "tester" ).count (), 0 )
980+ self .assertEqual (OrganizationUser .objects .filter (organization = org1 ).count (), 0 )
0 commit comments