Coverage for sm / test_multitenancy_expanded.py: 0%

162 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-24 12:43 +0000

1from django.test import TestCase, Client 

2from django.contrib.auth.models import User, Group, Permission 

3from vendor.models import Model as Vendor 

4from django.urls import reverse 

5from .utils_starterpack import import_starter_pack 

6 

7 

8class MultiTenancyExpandedTest(TestCase): 

9 def setUp(self) -> None: 

10 self.password = "password123" 

11 

12 # Create two groups 

13 self.group_a = Group.objects.create(name="Group A") 

14 self.group_b = Group.objects.create(name="Group B") 

15 

16 # Create profiles 

17 self.profile_a = self.group_a.profile 

18 self.profile_b = self.group_b.profile 

19 

20 # Create users 

21 self.user_a = User.objects.create_user( 

22 username="user_a", password=self.password 

23 ) 

24 self.user_b = User.objects.create_user( 

25 username="user_b", password=self.password 

26 ) 

27 

28 self.user_a.groups.add(self.group_a) 

29 self.user_b.groups.add(self.group_b) 

30 

31 # Set owners 

32 self.profile_a.owner = self.user_a 

33 self.profile_a.save() 

34 self.profile_b.owner = self.user_b 

35 self.profile_b.save() 

36 

37 # Create clients 

38 self.client_a = Client() 

39 self.client_a.login(username="user_a", password=self.password) 

40 self.client_b = Client() 

41 self.client_b.login(username="user_b", password=self.password) 

42 

43 def test_vendor_partitioning(self) -> None: 

44 """Test that vendors are filtered by group.""" 

45 Vendor.objects.create(name="Vendor A", group=self.group_a) 

46 Vendor.objects.create(name="Vendor B", group=self.group_b) 

47 

48 # User A should only see Vendor A 

49 # Give permission first 

50 view_perm = Permission.objects.get( 

51 codename="view_model", content_type__app_label="vendor" 

52 ) 

53 self.group_a.permissions.add(view_perm) 

54 

55 response = self.client_a.get(reverse("vendor:index")) 

56 self.assertContains(response, "Vendor A") 

57 self.assertNotContains(response, "Vendor B") 

58 

59 def test_starter_pack_import(self) -> None: 

60 """Test starter pack utility logic.""" 

61 # Ensure group is empty 

62 Vendor.objects.filter(group=self.group_a).delete() 

63 

64 results = import_starter_pack(self.group_a) 

65 self.assertGreater(results["vendors"], 0) 

66 self.assertGreater(results["os"], 0) 

67 

68 self.assertTrue( 

69 Vendor.objects.filter(group=self.group_a, name="Red Hat").exists() 

70 ) 

71 

72 def test_item_quota_enforcement(self) -> None: 

73 """Test that group item quota is enforced.""" 

74 self.profile_a.max_items = 1 

75 self.profile_a.save() 

76 

77 # Create one vendor 

78 Vendor.objects.create(name="Vendor 1", group=self.group_a) 

79 

80 # Try to create another via view (should fail) 

81 add_perm = Permission.objects.get( 

82 codename="add_model", content_type__app_label="vendor" 

83 ) 

84 self.group_a.permissions.add(add_perm) 

85 

86 response = self.client_a.post( 

87 reverse("vendor:create"), 

88 {"name": "Vendor 2", "is_hardware": True, "is_software": True}, 

89 follow=True, 

90 ) 

91 self.assertContains(response, "Quota exceeded") 

92 self.assertEqual(Vendor.objects.filter(group=self.group_a).count(), 1) 

93 

94 def test_user_quota_enforcement(self) -> None: 

95 """Test that group user quota is enforced.""" 

96 self.profile_a.max_users = 1 # Only user_a 

97 self.profile_a.save() 

98 

99 user_c = User.objects.create_user(username="user_c", password=self.password) 

100 

101 response = self.client_a.post( 

102 reverse("group_member_add", args=[self.group_a.id]), 

103 {"username": "user_c"}, 

104 follow=True, 

105 ) 

106 self.assertContains(response, "User quota exceeded") 

107 self.assertNotIn(user_c, self.group_a.user_set.all()) 

108 

109 def test_group_owner_permissions_management(self) -> None: 

110 """Test that group owner can change group permissions.""" 

111 # Check initial perms (view only by default signal) 

112 from django.contrib.contenttypes.models import ContentType 

113 

114 server_ct = ContentType.objects.get(app_label="server", model="model") 

115 change_perm = Permission.objects.get( 

116 content_type=server_ct, codename="change_model" 

117 ) 

118 

119 self.assertNotIn(change_perm, self.group_a.permissions.all()) 

120 

121 # Toggle edit_server 

122 # The form field name is edit_<app_label> 

123 response = self.client_a.post( 

124 reverse("group_permission_edit", args=[self.group_a.id]), 

125 {"edit_server": True}, 

126 follow=True, 

127 ) 

128 self.assertEqual(response.status_code, 200) 

129 

130 self.assertIn(change_perm, self.group_a.permissions.all()) 

131 

132 # Toggle off 

133 self.client_a.post( 

134 reverse("group_permission_edit", args=[self.group_a.id]), 

135 {"edit_server": False}, 

136 follow=True, 

137 ) 

138 self.assertNotIn(change_perm, self.group_a.permissions.all()) 

139 

140 

141class MultiTenancyEdgeCasesTest(TestCase): 

142 def setUp(self) -> None: 

143 self.password = "password123" 

144 

145 # Create groups 

146 self.group_a = Group.objects.create(name="Group A") 

147 self.group_b = Group.objects.create(name="Group B") 

148 

149 # Create profiles 

150 self.profile_a = self.group_a.profile 

151 self.profile_b = self.group_b.profile 

152 

153 # Create users 

154 self.user_a = User.objects.create_user( 

155 username="user_a", password=self.password 

156 ) 

157 self.user_b = User.objects.create_user( 

158 username="user_b", password=self.password 

159 ) 

160 self.superuser = User.objects.create_superuser( 

161 username="superuser", password=self.password, email="super@example.com" 

162 ) 

163 

164 self.user_a.groups.add(self.group_a) 

165 self.user_b.groups.add(self.group_b) 

166 

167 # Set owners 

168 self.profile_a.owner = self.user_a 

169 self.profile_a.save() 

170 self.profile_b.owner = self.user_b 

171 self.profile_b.save() 

172 

173 # Create clients 

174 self.client_a = Client() 

175 self.client_a.login(username="user_a", password=self.password) 

176 self.client_b = Client() 

177 self.client_b.login(username="user_b", password=self.password) 

178 self.superuser_client = Client() 

179 self.superuser_client.login(username="superuser", password=self.password) 

180 

181 def test_superuser_bypasses_all_restrictions(self) -> None: 

182 """Test that superusers can see and create items in any group.""" 

183 # Create items in different groups 

184 Vendor.objects.create(name="Vendor A", group=self.group_a) 

185 Vendor.objects.create(name="Vendor B", group=self.group_b) 

186 

187 # Superuser should see all items 

188 response = self.superuser_client.get(reverse("vendor:index")) 

189 self.assertContains(response, "Vendor A") 

190 self.assertContains(response, "Vendor B") 

191 

192 # Superuser should be able to create items without group assignment 

193 response = self.superuser_client.post( 

194 reverse("vendor:create"), 

195 {"name": "Vendor Super", "is_hardware": True, "is_software": True}, 

196 follow=True, 

197 ) 

198 self.assertEqual(response.status_code, 200) 

199 

200 # Item should be created without a group (global) 

201 vendor = Vendor.objects.get(name="Vendor Super") 

202 self.assertIsNone(vendor.group) 

203 

204 def test_global_items_visible_to_all_users(self) -> None: 

205 """Test that items with no group are visible to all users.""" 

206 # Create a global vendor (no group) 

207 global_vendor = Vendor.objects.create(name="Global Vendor") 

208 self.assertIsNone(global_vendor.group) 

209 

210 # Both users should see the global vendor 

211 response_a = self.client_a.get(reverse("vendor:index")) 

212 response_b = self.client_b.get(reverse("vendor:index")) 

213 

214 self.assertContains(response_a, "Global Vendor") 

215 self.assertContains(response_b, "Global Vendor") 

216 

217 def test_user_without_group_cannot_create_items(self) -> None: 

218 """Test that users without any groups cannot create items.""" 

219 # Create a user without any groups 

220 user_no_group = User.objects.create_user( 

221 username="user_no_group", password=self.password 

222 ) 

223 client_no_group = Client() 

224 client_no_group.login(username="user_no_group", password=self.password) 

225 

226 # Grant necessary permissions 

227 add_perm = Permission.objects.get( 

228 codename="add_model", content_type__app_label="vendor" 

229 ) 

230 view_perm = Permission.objects.get( 

231 codename="view_model", content_type__app_label="vendor" 

232 ) 

233 user_no_group.user_permissions.add(add_perm, view_perm) 

234 

235 # User should be able to create items but without group assignment 

236 response = client_no_group.post( 

237 reverse("vendor:create"), 

238 {"name": "Vendor No Group", "is_hardware": True, "is_software": True}, 

239 follow=True, 

240 ) 

241 

242 # Should succeed and create item without a group 

243 self.assertEqual(response.status_code, 200) 

244 vendor = Vendor.objects.filter(name="Vendor No Group").first() 

245 self.assertIsNotNone(vendor) 

246 self.assertIsNone(vendor.group) 

247 

248 def test_concurrent_quota_checks(self) -> None: 

249 """Test that concurrent quota checks are handled properly.""" 

250 self.profile_a.max_items = 1 

251 self.profile_a.save() 

252 

253 # Grant add permission 

254 add_perm = Permission.objects.get( 

255 codename="add_model", content_type__app_label="vendor" 

256 ) 

257 self.group_a.permissions.add(add_perm) 

258 

259 # Create one vendor to reach the limit 

260 Vendor.objects.create(name="Vendor 1", group=self.group_a) 

261 

262 # Try to create another vendor (should fail due to quota) 

263 response = self.client_a.post( 

264 reverse("vendor:create"), 

265 {"name": "Vendor 2", "is_hardware": True, "is_software": True}, 

266 follow=True, 

267 ) 

268 self.assertContains(response, "Quota exceeded") 

269 self.assertEqual(Vendor.objects.filter(group=self.group_a).count(), 1) 

270 

271 def test_unique_constraints_per_group(self) -> None: 

272 """Test that unique constraints work correctly per group.""" 

273 # Both groups should be able to have vendors with the same name 

274 Vendor.objects.create(name="Same Name", group=self.group_a) 

275 Vendor.objects.create(name="Same Name", group=self.group_b) 

276 

277 # Should have 2 vendors total 

278 self.assertEqual(Vendor.objects.filter(name="Same Name").count(), 2) 

279 

280 # But each user should only see their own 

281 response_a = self.client_a.get(reverse("vendor:index")) 

282 response_b = self.client_b.get(reverse("vendor:index")) 

283 

284 # Count occurrences in response (this is a simple check) 

285 self.assertContains(response_a, "Same Name") 

286 self.assertContains(response_b, "Same Name") 

287 

288 def test_group_deletion_cascades_to_items(self) -> None: 

289 """Test that deleting a group properly handles related items.""" 

290 # Create some items in group A 

291 Vendor.objects.create(name="Vendor A1", group=self.group_a) 

292 Vendor.objects.create(name="Vendor A2", group=self.group_a) 

293 

294 initial_count = Vendor.objects.count() 

295 

296 # Delete the group (should be protected by on_delete=models.PROTECT) 

297 with self.assertRaises(Exception): 

298 self.group_a.delete() 

299 

300 # Items should still exist 

301 self.assertEqual(Vendor.objects.count(), initial_count) 

302 

303 def test_user_cannot_access_other_group_items_directly(self) -> None: 

304 """Test that users cannot access items from other groups via direct URLs.""" 

305 # Create an item in group B 

306 vendor_b = Vendor.objects.create(name="Vendor B Private", group=self.group_b) 

307 

308 # User A should not be able to access this item's detail page 

309 response = self.client_a.get(reverse("vendor:detail", args=[vendor_b.pk])) 

310 

311 # Should either return 404 or 403 

312 self.assertIn(response.status_code, [403, 404]) 

313 

314 def test_starter_pack_with_existing_data(self) -> None: 

315 """Test starter pack import when group already has some data.""" 

316 # Create some existing data 

317 Vendor.objects.create(name="Existing Vendor", group=self.group_a) 

318 

319 # Import starter pack 

320 results = import_starter_pack(self.group_a) 

321 

322 # Should still import new vendors but not duplicate existing ones 

323 self.assertGreaterEqual(results["vendors"], 0) 

324 

325 # Existing vendor should still be there 

326 self.assertTrue( 

327 Vendor.objects.filter(group=self.group_a, name="Existing Vendor").exists() 

328 ) 

329 

330 def test_quota_zero_allows_no_items(self) -> None: 

331 """Test that setting quota to zero prevents all item creation.""" 

332 self.profile_a.max_items = 0 

333 self.profile_a.save() 

334 

335 # Grant add permission 

336 add_perm = Permission.objects.get( 

337 codename="add_model", content_type__app_label="vendor" 

338 ) 

339 self.group_a.permissions.add(add_perm) 

340 

341 # Try to create an item (should fail) 

342 response = self.client_a.post( 

343 reverse("vendor:create"), 

344 {"name": "Vendor Zero Quota", "is_hardware": True, "is_software": True}, 

345 follow=True, 

346 ) 

347 self.assertContains(response, "Quota exceeded") 

348 self.assertEqual(Vendor.objects.filter(group=self.group_a).count(), 0) 

349 

350 def test_group_profile_creation_on_group_creation(self) -> None: 

351 """Test GroupProfile auto-creation when new group is created.""" 

352 # Create a new group 

353 new_group = Group.objects.create(name="New Group") 

354 

355 # Should have a profile 

356 self.assertTrue(hasattr(new_group, "profile")) 

357 self.assertIsNotNone(new_group.profile) 

358 

359 # Profile should have default values 

360 self.assertEqual(new_group.profile.max_items, 200) 

361 self.assertEqual(new_group.profile.max_users, 2)