Coverage for src/meshadmin/server/networks/tests/test_views.py: 100%

368 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-10 16:08 +0200

1from datetime import timedelta 

2 

3import pytest 

4from django.urls import reverse 

5from django.utils import timezone 

6 

7from meshadmin.common.utils import create_keys 

8from meshadmin.server.networks.models import ( 

9 CA, 

10 ConfigRollout, 

11 Group, 

12 Host, 

13 HostConfig, 

14 Network, 

15 Rule, 

16 Template, 

17) 

18from meshadmin.server.networks.services import create_group, create_template 

19 

20 

21class TestRolloutViews: 

22 def test_rollout_creation_with_hosts(self, auth_client, test_network): 

23 client, user = auth_client() 

24 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

25 hosts = [] 

26 for i in range(3): 

27 host = Host.objects.create( 

28 network=test_net, 

29 name=f"test-host-{i}", 

30 assigned_ip=f"100.100.64.{i + 1}", 

31 ) 

32 hosts.append(host) 

33 

34 data = { 

35 "name": "new-rollout", 

36 "notes": "Test notes", 

37 "hosts": [host.id for host in hosts[:2]], 

38 } 

39 

40 response = client.post( 

41 reverse( 

42 "networks:network-rollout-create", kwargs={"network_id": test_net.id} 

43 ), 

44 data, 

45 ) 

46 

47 assert response.status_code == 302 

48 rollout = ConfigRollout.objects.get(name="new-rollout") 

49 assert rollout.network == test_net 

50 assert rollout.notes == "Test notes" 

51 assert rollout.status == "PENDING" 

52 assert list(rollout.target_hosts.all()) == hosts[:2] 

53 for host in hosts[:2]: 

54 host.refresh_from_db() 

55 assert host.config_freeze is True 

56 

57 def test_rollout_unfreeze(self, auth_client, test_network): 

58 client, user = auth_client() 

59 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

60 hosts = [] 

61 for i in range(2): 

62 host = Host.objects.create( 

63 network=test_net, 

64 name=f"test-host-{i}", 

65 assigned_ip=f"100.100.64.{i + 1}", 

66 config_freeze=True, 

67 ) 

68 hosts.append(host) 

69 

70 rollout = ConfigRollout.objects.create( 

71 name="test-rollout", 

72 network=test_net, 

73 status="PENDING", 

74 ) 

75 rollout.target_hosts.add(*hosts) 

76 

77 # Test single host unfreeze 

78 response = client.post( 

79 reverse("networks:rollout-unfreeze", kwargs={"pk": rollout.pk}), 

80 {"host_id": hosts[0].id}, 

81 ) 

82 

83 assert response.status_code == 302 

84 rollout.refresh_from_db() 

85 assert rollout.status == "PENDING" 

86 assert list(rollout.completed_hosts.all()) == [hosts[0]] 

87 hosts[0].refresh_from_db() 

88 assert hosts[0].config_freeze is False 

89 hosts[1].refresh_from_db() 

90 assert hosts[1].config_freeze is True 

91 

92 # Test complete rollout unfreeze 

93 response = client.post( 

94 reverse("networks:rollout-unfreeze", kwargs={"pk": rollout.pk}), 

95 ) 

96 assert response.status_code == 302 

97 rollout.refresh_from_db() 

98 assert rollout.status == "COMPLETED" 

99 assert set(rollout.completed_hosts.all()) == set(hosts) 

100 for host in hosts: 

101 host.refresh_from_db() 

102 assert host.config_freeze is False 

103 

104 def test_rollout_update(self, auth_client, test_network): 

105 client, user = auth_client() 

106 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

107 hosts = [] 

108 for i in range(3): 

109 host = Host.objects.create( 

110 network=test_net, 

111 name=f"test-host-{i}", 

112 assigned_ip=f"100.100.64.{i + 1}", 

113 ) 

114 hosts.append(host) 

115 

116 rollout = ConfigRollout.objects.create( 

117 name="test-rollout", 

118 network=test_net, 

119 status="PENDING", 

120 ) 

121 rollout.target_hosts.add(*hosts[:2]) 

122 

123 data = { 

124 "name": "updated-rollout", 

125 "notes": "Updated notes", 

126 "hosts": [hosts[0].id, hosts[2].id], 

127 } 

128 

129 response = client.post( 

130 reverse("networks:rollout-edit", kwargs={"pk": rollout.pk}), 

131 data, 

132 ) 

133 

134 assert response.status_code == 302 

135 rollout.refresh_from_db() 

136 assert rollout.name == "updated-rollout" 

137 assert rollout.notes == "Updated notes" 

138 assert list(rollout.target_hosts.all()) == [hosts[0], hosts[2]] 

139 hosts[0].refresh_from_db() 

140 hosts[1].refresh_from_db() 

141 hosts[2].refresh_from_db() 

142 assert hosts[0].config_freeze is True 

143 assert hosts[1].config_freeze is False 

144 assert hosts[2].config_freeze is True 

145 

146 def test_rollout_delete(self, auth_client, test_network): 

147 client, user = auth_client() 

148 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

149 hosts = [] 

150 for i in range(2): 

151 host = Host.objects.create( 

152 network=test_net, 

153 name=f"test-host-{i}", 

154 assigned_ip=f"100.100.64.{i + 1}", 

155 config_freeze=True, 

156 ) 

157 hosts.append(host) 

158 

159 rollout = ConfigRollout.objects.create( 

160 name="test-rollout", 

161 network=test_net, 

162 status="PENDING", 

163 ) 

164 rollout.target_hosts.add(*hosts) 

165 

166 response = client.post( 

167 reverse("networks:rollout-delete", kwargs={"pk": rollout.pk}), 

168 ) 

169 

170 assert response.status_code == 302 

171 assert not ConfigRollout.objects.filter(pk=rollout.pk).exists() 

172 for host in hosts: 

173 host.refresh_from_db() 

174 assert host.config_freeze is False 

175 

176 

177class TestHostViews: 

178 def test_host_refresh_config_with_rollout(self, auth_client, test_network): 

179 client, user = auth_client() 

180 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

181 _, public_key = create_keys() 

182 host = Host.objects.create( 

183 network=test_net, 

184 name="test-host", 

185 assigned_ip="100.100.64.1", 

186 config_freeze=True, 

187 public_key=public_key, 

188 interface="nebula1", 

189 ) 

190 rollout = ConfigRollout.objects.create( 

191 name="test-rollout", 

192 network=test_net, 

193 status="PENDING", 

194 ) 

195 rollout.target_hosts.add(host) 

196 response = client.post( 

197 reverse( 

198 "networks:host-refresh-config", 

199 kwargs={"pk": host.pk, "rollout_id": rollout.pk}, 

200 ), 

201 ) 

202 assert response.status_code == 302 

203 assert response.url == reverse( 

204 "networks:rollout-detail", kwargs={"pk": rollout.pk} 

205 ) 

206 host.refresh_from_db() 

207 assert host.hostconfig_set.count() > 0 

208 latest_config = host.hostconfig_set.latest("created_at") 

209 assert "pki" in latest_config.config 

210 assert "lighthouse" in latest_config.config 

211 

212 def test_config_diff_view(self, auth_client, test_network): 

213 client, user = auth_client() 

214 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

215 host = Host.objects.create(name="test_host", network=test_net) 

216 

217 # Create two configs with different content 

218 config1 = HostConfig.objects.create( 

219 host=host, config="test: config1\nline: 1", sha256="abc123" 

220 ) 

221 config2 = HostConfig.objects.create( 

222 host=host, config="test: config2\nline: 2", sha256="def456" 

223 ) 

224 

225 # Test successful diff 

226 response = client.get( 

227 reverse( 

228 "networks:config-diff", 

229 kwargs={"base_id": config1.id, "compare_id": config2.id}, 

230 ) 

231 ) 

232 assert response.status_code == 200 

233 assert b"-test: config1" in response.content 

234 assert b"+test: config2" in response.content 

235 assert b"-line: 1" in response.content 

236 assert b"+line: 2" in response.content 

237 

238 # Test no changes between identical configs 

239 config3 = HostConfig.objects.create( 

240 host=host, config="test: config1\nline: 1", sha256="abc123" 

241 ) 

242 response = client.get( 

243 reverse( 

244 "networks:config-diff", 

245 kwargs={"base_id": config1.id, "compare_id": config3.id}, 

246 ) 

247 ) 

248 assert response.status_code == 200 

249 assert b"No differences found" in response.content 

250 

251 # Test non-existent config 

252 response = client.get( 

253 reverse( 

254 "networks:config-diff", 

255 kwargs={"base_id": 99999, "compare_id": config2.id}, 

256 ) 

257 ) 

258 assert response.status_code == 200 

259 assert b"Error" in response.content 

260 

261 def test_make_signing_ca(self, auth_client, test_network): 

262 client, user = auth_client() 

263 network = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

264 new_ca = CA.objects.create( 

265 network=network, 

266 name="new_signing_ca", 

267 key="test_key", 

268 cert="test_cert", 

269 ) 

270 original_signing_ca = network.signingca.ca 

271 response = client.post( 

272 reverse("networks:ca-make-signing", kwargs={"pk": new_ca.pk}), 

273 ) 

274 assert response.status_code == 200 

275 network.refresh_from_db() 

276 assert network.signingca.ca == new_ca 

277 assert network.signingca.ca != original_signing_ca 

278 

279 

280class TestCRUDWithParentNetwork: 

281 @pytest.mark.parametrize( 

282 "url_name,data,expected_model,expected_fields", 

283 [ 

284 ( 

285 "networks:network-ca-create", 

286 {"name": "test_ca"}, 

287 CA, 

288 {"name": "test_ca", "cert__isnull": False, "key__isnull": False}, 

289 ), 

290 ( 

291 "networks:network-group-create", 

292 {"name": "test_group"}, 

293 Group, 

294 {"name": "test_group"}, 

295 ), 

296 ], 

297 ) 

298 def test_entity_creation_with_parent_network( 

299 self, 

300 auth_client, 

301 test_network, 

302 url_name, 

303 data, 

304 expected_model, 

305 expected_fields, 

306 ): 

307 client, user = auth_client() 

308 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

309 data["network"] = test_net.id 

310 

311 response = client.post( 

312 reverse(url_name, kwargs={"network_id": test_net.id}), 

313 data, 

314 ) 

315 assert response.status_code == 302 

316 filters = {**expected_fields, "network": test_net} 

317 assert expected_model.objects.filter(**filters).exists() 

318 

319 @pytest.mark.parametrize( 

320 "url_name,model,update_data", 

321 [ 

322 ("networks:ca-edit", CA, {"name": "updated_name"}), 

323 ("networks:group-edit", Group, {"name": "updated_name"}), 

324 ], 

325 ) 

326 def test_entity_update_with_parent_network( 

327 self, auth_client, test_network, url_name, model, update_data 

328 ): 

329 client, user = auth_client() 

330 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

331 

332 obj = model.objects.create( 

333 network=test_net, name=f"test_{model._meta.model_name}" 

334 ) 

335 update_data["network"] = test_net.id 

336 

337 response = client.post( 

338 reverse(url_name, kwargs={"pk": obj.id}), 

339 update_data, 

340 ) 

341 assert response.status_code == 302 

342 obj.refresh_from_db() 

343 assert obj.name == update_data["name"] 

344 

345 @pytest.mark.parametrize( 

346 "url_name,model", 

347 [ 

348 ("networks:ca-delete", CA), 

349 ("networks:group-delete", Group), 

350 ], 

351 ) 

352 def test_entity_deletion_with_parent_network( 

353 self, auth_client, test_network, url_name, model 

354 ): 

355 client, user = auth_client() 

356 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

357 obj = model.objects.create( 

358 network=test_net, 

359 name=f"test_{model._meta.model_name}", 

360 ) 

361 response = client.post( 

362 reverse(url_name, kwargs={"pk": obj.id}), 

363 ) 

364 assert response.status_code == 302 

365 assert not model.objects.filter(id=obj.id).exists() 

366 

367 

368class TestRuleViews: 

369 def test_add_rule_to_group_success(self, auth_client, test_network): 

370 client, user = auth_client() 

371 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

372 

373 # First create a security group 

374 group_data = {"name": "test_group", "description": "Test security group"} 

375 response = client.post( 

376 reverse( 

377 "networks:network-group-create", kwargs={"network_id": test_net.id} 

378 ), 

379 group_data, 

380 ) 

381 assert response.status_code == 302 

382 security_group = Group.objects.get(name="test_group") 

383 

384 # create target groups 

385 target_group1 = create_group(test_net.pk, "target_group1") 

386 target_group2 = create_group(test_net.pk, "target_group2") 

387 target_group3 = create_group(test_net.pk, "target_group3") 

388 

389 # add a rule to the group 

390 rule_data = { 

391 "security_group": security_group.id, 

392 "direction": "I", 

393 "proto": "tcp", 

394 "port": "80", 

395 "cidr": "0.0.0.0/0", 

396 "group": target_group1.id, 

397 "groups": [target_group2.id, target_group3.id], 

398 "local_cidr": "192.168.1.0/24", 

399 } 

400 

401 response = client.post( 

402 reverse("networks:group-add-rule"), 

403 rule_data, 

404 ) 

405 

406 assert response.status_code == 200 

407 assert Rule.objects.filter(security_group=security_group).exists() 

408 rule = Rule.objects.get(security_group=security_group) 

409 assert rule.direction == "I" 

410 assert rule.proto == "tcp" 

411 assert rule.port == "80" 

412 assert rule.cidr == "0.0.0.0/0" 

413 assert rule.group == target_group1 

414 assert set(rule.groups.all()) == {target_group2, target_group3} 

415 assert rule.local_cidr == "192.168.1.0/24" 

416 

417 def test_add_rule_validation_no_target(self, auth_client, test_network): 

418 client, user = auth_client() 

419 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

420 group = create_group(test_net.pk, "test_group") 

421 rule_data = { 

422 "security_group": group.id, 

423 "direction": "I", 

424 "proto": "tcp", 

425 "port": "80", 

426 } 

427 response = client.post( 

428 reverse("networks:group-add-rule"), 

429 rule_data, 

430 ) 

431 assert response.status_code == 200 

432 assert "At least one of group, groups, or CIDR" in response.content.decode() 

433 assert not Rule.objects.filter(security_group=group).exists() 

434 

435 def test_add_rule_validation_invalid_port(self, auth_client, test_network): 

436 client, user = auth_client() 

437 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

438 group = create_group(test_net.pk, "test_group") 

439 rule_data = { 

440 "security_group": group.id, 

441 "direction": "I", 

442 "proto": "tcp", 

443 "port": "80-invalid", 

444 "cidr": "0.0.0.0/0", 

445 } 

446 

447 response = client.post( 

448 reverse("networks:group-add-rule"), 

449 rule_data, 

450 ) 

451 

452 assert response.status_code == 200 

453 assert "Port range must be two" in response.content.decode() 

454 assert not Rule.objects.filter(security_group=group).exists() 

455 

456 # Port out of range 

457 rule_data["port"] = "70000" 

458 response = client.post( 

459 reverse("networks:group-add-rule"), 

460 rule_data, 

461 ) 

462 assert response.status_code == 200 

463 assert "Port must be" in response.content.decode() 

464 assert not Rule.objects.filter(security_group=group).exists() 

465 

466 def test_add_rule_validation_invalid_cidr(self, auth_client, test_network): 

467 client, user = auth_client() 

468 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

469 group = create_group(test_net.pk, "test_group") 

470 rule_data = { 

471 "security_group": group.id, 

472 "direction": "I", 

473 "proto": "tcp", 

474 "port": "80", 

475 "cidr": "invalid-cidr", 

476 } 

477 response = client.post( 

478 reverse("networks:group-add-rule"), 

479 rule_data, 

480 ) 

481 assert response.status_code == 200 

482 assert "Invalid CIDR format" in response.content.decode() 

483 assert not Rule.objects.filter(security_group=group).exists() 

484 

485 # Invalid local CIDR format 

486 rule_data = { 

487 "security_group": group.id, 

488 "direction": "I", 

489 "proto": "tcp", 

490 "port": "80", 

491 "cidr": "0.0.0.0/0", 

492 "local_cidr": "invalid-local-cidr", 

493 } 

494 response = client.post( 

495 reverse("networks:group-add-rule"), 

496 rule_data, 

497 ) 

498 assert response.status_code == 200 

499 assert "Invalid CIDR format" in response.content.decode() 

500 assert not Rule.objects.filter(security_group=group).exists() 

501 

502 

503class TestNetworkViews: 

504 def test_network_list(self, auth_client, test_network): 

505 client, user = auth_client() 

506 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

507 response = client.get(reverse("networks:network-list")) 

508 assert response.status_code == 200 

509 assert test_net.name.encode() in response.content 

510 assert test_net.cidr.encode() in response.content 

511 

512 def test_network_detail(self, auth_client, test_network): 

513 client, user = auth_client() 

514 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

515 ca = CA.objects.create(network=test_net, name="test_ca") 

516 group = create_group(test_net.pk, "test_group") 

517 

518 response = client.get( 

519 reverse("networks:network-detail", kwargs={"pk": test_net.id}) 

520 ) 

521 

522 assert response.status_code == 200 

523 assert test_net.name.encode() in response.content 

524 assert test_net.cidr.encode() in response.content 

525 assert ca.name.encode() in response.content 

526 assert group.name.encode() in response.content 

527 

528 @pytest.mark.parametrize( 

529 "test_data,expected_status", 

530 [ 

531 ({"name": "test_net", "cidr": "192.168.1.0/24", "update_interval": 5}, 302), 

532 ({"name": "test_net", "cidr": "10.0.0.0/16", "update_interval": 5}, 302), 

533 ({"name": "test_net", "cidr": "100.64.0.0/24", "update_interval": 5}, 302), 

534 ( 

535 {"name": "test_net", "cidr": "199.100.69.0/24", "update_interval": 5}, 

536 200, 

537 ), 

538 ], 

539 ) 

540 def test_network_cidr_validation(self, auth_client, test_data, expected_status): 

541 client, _ = auth_client() 

542 response = client.post(reverse("networks:network-create"), test_data) 

543 

544 assert response.status_code == expected_status 

545 

546 if expected_status == 302: 

547 assert Network.objects.filter(name=test_data["name"]).exists() 

548 else: 

549 assert not Network.objects.filter(name=test_data["name"]).exists() 

550 

551 

552class TestTemplateViews: 

553 def test_template_creation_with_security_group(self, auth_client, test_network): 

554 client, user = auth_client() 

555 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

556 group1 = create_group(test_net.pk, "group1") 

557 group2 = create_group(test_net.pk, "group2") 

558 security_group = create_group( 

559 test_net.pk, 

560 "security_group", 

561 "Security group for testing", 

562 ) 

563 Rule.objects.create( 

564 security_group=security_group, 

565 group=group1, 

566 direction="I", 

567 proto="tcp", 

568 port="80", 

569 ) 

570 data = { 

571 "name": "test_template", 

572 "network": test_net.id, 

573 "is_lighthouse": False, 

574 "is_relay": False, 

575 "use_relay": True, 

576 "groups": [group1.id, group2.id, security_group.id], 

577 } 

578 response = client.post( 

579 reverse( 

580 "networks:network-template-create", kwargs={"network_id": test_net.id} 

581 ), 

582 data, 

583 ) 

584 assert response.status_code == 302 

585 template = Template.objects.get(name="test_template") 

586 assert list(template.groups.all()) == [group1, group2, security_group] 

587 

588 def test_template_deletion(self, auth_client, test_network): 

589 client, user = auth_client() 

590 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

591 security_group = create_group( 

592 test_net.pk, "test_security_group", "Test security group" 

593 ) 

594 template = create_template( 

595 "test_template", 

596 test_net.name, 

597 groups=[security_group.name], 

598 ) 

599 

600 response = client.post( 

601 reverse("networks:template-delete", kwargs={"pk": template.id}), 

602 ) 

603 

604 assert response.status_code == 302 

605 assert not Template.objects.filter(id=template.id).exists() 

606 assert Group.objects.filter(id=security_group.id).exists() 

607 

608 def test_template_creation_with_all_settings(self, auth_client, test_network): 

609 client, user = auth_client() 

610 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

611 data = { 

612 "name": "test_template_with_settings", 

613 "network": test_net.id, 

614 "is_lighthouse": False, 

615 "is_relay": False, 

616 "use_relay": True, 

617 "reusable": False, 

618 "usage_limit": 5, 

619 "expiry_days": 30, 

620 "ephemeral_peers": True, 

621 } 

622 

623 response = client.post( 

624 reverse( 

625 "networks:network-template-create", kwargs={"network_id": test_net.id} 

626 ), 

627 data, 

628 ) 

629 

630 assert response.status_code == 302 

631 template = Template.objects.get(name="test_template_with_settings") 

632 assert template.reusable is False 

633 assert template.usage_limit == 5 

634 assert template.ephemeral_peers is True 

635 assert template.expires_at is not None 

636 

637 # The expiry_days field should be converted to an expires_at datetime 

638 # Check that it's approximately 30 days in the future (within 1 day tolerance) 

639 expected_expiry = timezone.now() + timedelta(days=30) 

640 assert ( 

641 abs((template.expires_at - expected_expiry).total_seconds()) < 86400 

642 ) # 1 day in seconds 

643 

644 def test_template_update_with_enrollment_settings(self, auth_client, test_network): 

645 client, user = auth_client() 

646 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

647 template = create_template( 

648 "template_to_update", 

649 test_net.name, 

650 reusable=True, 

651 usage_limit=None, 

652 ephemeral_peers=False, 

653 ) 

654 data = { 

655 "name": "updated_template", 

656 "network": test_net.id, 

657 "is_lighthouse": False, 

658 "is_relay": False, 

659 "use_relay": True, 

660 "reusable": False, 

661 "usage_limit": 10, 

662 "expiry_days": 15, 

663 "ephemeral_peers": True, 

664 } 

665 response = client.post( 

666 reverse("networks:template-edit", kwargs={"pk": template.id}), 

667 data, 

668 ) 

669 assert response.status_code == 302 

670 template.refresh_from_db() 

671 assert template.name == "updated_template" 

672 assert template.reusable is False 

673 assert template.usage_limit == 10 

674 assert template.ephemeral_peers is True 

675 assert template.expires_at is not None 

676 

677 # Check expiry date is approximately 15 days in the future 

678 expected_expiry = timezone.now() + timedelta(days=15) 

679 assert abs((template.expires_at - expected_expiry).total_seconds()) < 86400 

680 

681 def test_template_update_remove_expiry(self, auth_client, test_network): 

682 client, user = auth_client() 

683 test_net = test_network(name="testnet", cidr="100.100.64.0/24", user=user) 

684 template = Template.objects.create( 

685 name="template_with_expiry", 

686 network=test_net, 

687 expires_at=timezone.now() + timedelta(days=30), 

688 ) 

689 data = { 

690 "name": "template_without_expiry", 

691 "network": test_net.id, 

692 "is_lighthouse": False, 

693 "is_relay": False, 

694 "use_relay": True, 

695 "reusable": True, 

696 "usage_limit": "", # Empty string for no limit 

697 "expiry_days": "", # Empty string for no expiration 

698 "ephemeral_peers": False, 

699 } 

700 response = client.post( 

701 reverse("networks:template-edit", kwargs={"pk": template.id}), 

702 data, 

703 ) 

704 assert response.status_code == 302 

705 template.refresh_from_db() 

706 assert template.name == "template_without_expiry" 

707 assert template.expires_at is None 

708 

709 

710class TestNetworkMembershipViews: 

711 def test_add_member(self, auth_client, test_network, create_user): 

712 client, admin_user = auth_client() 

713 network = test_network(name="testnet", cidr="100.100.64.0/24", user=admin_user) 

714 new_member = create_user(email="member@example.com", username="member") 

715 url = reverse("networks:network-member-add", kwargs={"network_id": network.id}) 

716 data = { 

717 "email": new_member.email, 

718 "role": "MEMBER", 

719 } 

720 response = client.post(url, data) 

721 assert response.status_code == 302 

722 membership = network.memberships.get(user=new_member) 

723 assert membership.role == "MEMBER" 

724 

725 def test_add_duplicate_member(self, auth_client, test_network, create_user): 

726 client, admin_user = auth_client() 

727 network = test_network(name="testnet", cidr="100.100.64.0/24", user=admin_user) 

728 existing_member = create_user(email="member@example.com", username="member") 

729 network.memberships.create(user=existing_member, role="MEMBER") 

730 url = reverse("networks:network-member-add", kwargs={"network_id": network.id}) 

731 data = { 

732 "email": existing_member.email, 

733 "role": "MEMBER", 

734 } 

735 response = client.post(url, data) 

736 assert response.status_code == 200 

737 assert ( 

738 "This user is already a member of the network" in response.content.decode() 

739 ) 

740 

741 def test_edit_member_role(self, auth_client, test_network, create_user): 

742 client, admin_user = auth_client() 

743 network = test_network(name="testnet", cidr="100.100.64.0/24", user=admin_user) 

744 member = create_user(email="member@example.com", username="member") 

745 membership = network.memberships.create(user=member, role="MEMBER") 

746 url = reverse( 

747 "networks:network-member-edit", 

748 kwargs={"network_id": network.id, "pk": membership.pk}, 

749 ) 

750 headers = {"HX-Request": "true"} 

751 data = "role=ADMIN" 

752 response = client.put( 

753 url, data=data, content_type="application/x-www-form-urlencoded", **headers 

754 ) 

755 assert response.status_code == 200 

756 membership.refresh_from_db() 

757 assert membership.role == "ADMIN" 

758 assert "membership-row-" in response.content.decode() 

759 

760 def test_delete_member(self, auth_client, test_network, create_user): 

761 client, admin_user = auth_client() 

762 network = test_network(name="testnet", cidr="100.100.64.0/24", user=admin_user) 

763 member = create_user(email="member@example.com", username="member") 

764 membership = network.memberships.create(user=member, role="MEMBER") 

765 url = reverse( 

766 "networks:network-member-delete", 

767 kwargs={"network_id": network.id, "pk": membership.pk}, 

768 ) 

769 headers = {"HX-Request": "true"} 

770 response = client.delete(url, **headers) 

771 assert response.status_code == 404 

772 assert not network.memberships.filter(pk=membership.pk).exists() 

773 

774 def test_unauthorized_member_operations( 

775 self, auth_client, test_network, create_user 

776 ): 

777 client, _ = auth_client() 

778 admin_user = create_user(email="admin@example.com", username="admin") 

779 network = test_network(name="testnet", cidr="100.100.64.0/24", user=admin_user) 

780 member = create_user(email="member@example.com", username="member") 

781 membership = network.memberships.create(user=member, role="MEMBER") 

782 add_url = reverse( 

783 "networks:network-member-add", kwargs={"network_id": network.id} 

784 ) 

785 response = client.post(add_url, {"email": "new@example.com", "role": "MEMBER"}) 

786 assert response.status_code == 403 

787 edit_url = reverse( 

788 "networks:network-member-edit", 

789 kwargs={"network_id": network.id, "pk": membership.pk}, 

790 ) 

791 response = client.put( 

792 edit_url, 

793 data="role=ADMIN", 

794 content_type="application/x-www-form-urlencoded", 

795 ) 

796 assert response.status_code == 403 

797 delete_url = reverse( 

798 "networks:network-member-delete", 

799 kwargs={"network_id": network.id, "pk": membership.pk}, 

800 ) 

801 response = client.delete(delete_url) 

802 assert response.status_code == 403