@@ -320,89 +320,40 @@ def test_role_types_and_permissions_payload_shape(user_api_client):
320
320
class TestCreatedByAnsibleIdAllowNull :
321
321
"""Test that created_by_ansible_id field accepts null values and omissions"""
322
322
323
- def test_service_user_assignment_with_null_created_by (self , admin_api_client , rando , inv_rd , inventory ):
324
- """Test that ServiceRoleUserAssignmentSerializer accepts null created_by_ansible_id"""
325
- url = get_relative_url ('serviceuserassignment-assign' )
326
- data = {
327
- "role_definition" : inv_rd .name ,
328
- "user_ansible_id" : str (rando .resource .ansible_id ),
329
- "object_id" : inventory .pk ,
330
- "created_by_ansible_id" : "" , # Use empty string instead of None
331
- }
332
-
333
- response = admin_api_client .post (url , data = data )
334
- assert response .status_code == 201 , response .data
335
- assert rando .has_obj_perm (inventory , 'change' )
336
-
337
- def test_service_user_assignment_without_created_by (self , admin_api_client , rando , inv_rd , inventory ):
338
- """Test that ServiceRoleUserAssignmentSerializer works when created_by_ansible_id is omitted"""
339
- url = get_relative_url ('serviceuserassignment-assign' )
340
- data = {
341
- "role_definition" : inv_rd .name ,
342
- "user_ansible_id" : str (rando .resource .ansible_id ),
343
- "object_id" : inventory .pk ,
344
- # created_by_ansible_id is intentionally omitted
345
- }
346
-
347
- response = admin_api_client .post (url , data = data )
348
- assert response .status_code == 201 , response .data
349
- assert rando .has_obj_perm (inventory , 'change' )
350
-
351
- def test_service_user_assignment_with_valid_created_by (self , admin_api_client , rando , inv_rd , inventory ):
352
- """Test that valid created_by_ansible_id values still work correctly"""
353
- creator = User .objects .create (username = 'creator-user' )
354
- url = get_relative_url ('serviceuserassignment-assign' )
355
- data = {
356
- "role_definition" : inv_rd .name ,
357
- "user_ansible_id" : str (rando .resource .ansible_id ),
358
- "object_id" : inventory .pk ,
359
- "created_by_ansible_id" : str (creator .resource .ansible_id ),
360
- }
361
-
362
- response = admin_api_client .post (url , data = data )
363
- assert response .status_code == 201 , response .data
364
- assert rando .has_obj_perm (inventory , 'change' )
365
-
366
- def test_service_team_assignment_with_null_created_by (self , admin_api_client , team , inv_rd , inventory , member_rd , rando ):
367
- """Test that ServiceRoleTeamAssignmentSerializer accepts null created_by_ansible_id"""
368
- member_rd .give_permission (rando , team )
369
- url = get_relative_url ('serviceteamassignment-assign' )
370
- data = {
371
- "role_definition" : inv_rd .name ,
372
- "team_ansible_id" : str (team .resource .ansible_id ),
373
- "object_id" : inventory .pk ,
374
- "created_by_ansible_id" : "" , # Use empty string instead of None
375
- }
376
-
377
- response = admin_api_client .post (url , data = data )
378
- assert response .status_code == 201 , response .data
379
- assert rando .has_obj_perm (inventory , 'change' )
380
-
381
- def test_service_team_assignment_without_created_by (self , admin_api_client , team , inv_rd , inventory , member_rd , rando ):
382
- """Test that ServiceRoleTeamAssignmentSerializer works when created_by_ansible_id is omitted"""
383
- member_rd .give_permission (rando , team )
384
- url = get_relative_url ('serviceteamassignment-assign' )
323
+ @pytest .mark .parametrize (
324
+ 'actor_type,created_by_value' ,
325
+ [
326
+ ('user' , '' ), # empty string
327
+ ('user' , None ), # omitted (None means field not present)
328
+ ('user' , 'valid' ), # valid creator
329
+ ('team' , '' ), # empty string
330
+ ('team' , None ), # omitted
331
+ ('team' , 'valid' ), # valid creator
332
+ ],
333
+ )
334
+ def test_service_assignment_created_by_handling (self , admin_api_client , rando , inv_rd , inventory , team , member_rd , actor_type , created_by_value ):
335
+ """Test that ServiceRoleAssignmentSerializer handles created_by_ansible_id correctly"""
336
+ # Setup for team assignments
337
+ if actor_type == 'team' :
338
+ member_rd .give_permission (rando , team )
339
+ actor = team
340
+ else :
341
+ actor = rando
342
+
343
+ url = get_relative_url (f'service{ actor_type } assignment-assign' )
385
344
data = {
386
345
"role_definition" : inv_rd .name ,
387
- "team_ansible_id " : str (team .resource .ansible_id ),
346
+ f" { actor_type } _ansible_id " : str (actor .resource .ansible_id ),
388
347
"object_id" : inventory .pk ,
389
348
}
390
349
391
- response = admin_api_client .post (url , data = data )
392
- assert response .status_code == 201 , response .data
393
- assert rando .has_obj_perm (inventory , 'change' )
394
-
395
- def test_service_team_assignment_with_valid_created_by (self , admin_api_client , team , inv_rd , inventory , member_rd , rando ):
396
- """Test that valid created_by_ansible_id values still work correctly for teams"""
397
- member_rd .give_permission (rando , team )
398
- creator = User .objects .create (username = 'team-creator-user' )
399
- url = get_relative_url ('serviceteamassignment-assign' )
400
- data = {
401
- "role_definition" : inv_rd .name ,
402
- "team_ansible_id" : str (team .resource .ansible_id ),
403
- "object_id" : inventory .pk ,
404
- "created_by_ansible_id" : str (creator .resource .ansible_id ),
405
- }
350
+ # Handle different created_by_value scenarios
351
+ if created_by_value == '' :
352
+ data ["created_by_ansible_id" ] = ""
353
+ elif created_by_value == 'valid' :
354
+ creator = User .objects .create (username = f'{ actor_type } -creator-user' )
355
+ data ["created_by_ansible_id" ] = str (creator .resource .ansible_id )
356
+ # None means field is omitted (not added to data)
406
357
407
358
response = admin_api_client .post (url , data = data )
408
359
assert response .status_code == 201 , response .data
@@ -543,54 +494,74 @@ def test_service_assignment_created_timestamp_sync(admin_api_client, rando, inv_
543
494
544
495
545
496
@pytest .mark .django_db
546
- def test_service_assignment_object_created_timestamp_sync (admin_api_client , rando , inv_rd , inventory ):
497
+ @pytest .mark .parametrize (
498
+ 'object_created_source' ,
499
+ [
500
+ 'custom' , # Provide custom timestamp
501
+ 'local_object' , # Use local object's created timestamp
502
+ ],
503
+ )
504
+ def test_service_assignment_object_created_sync (admin_api_client , rando , inv_rd , inventory , org_inv_rd , organization , object_created_source ):
547
505
"""
548
506
Test that the 'object_created' field can be synchronized in both directions:
549
507
1. POST to /assign/ accepts a provided 'object_created' value
550
- 2. Serializing local assignments includes the 'object_created' field from the DB
508
+ 2. When no object_created is provided, it defaults to the local object's created timestamp
509
+ 3. Serializing local assignments includes the 'object_created' field from the DB
551
510
"""
552
511
from datetime import datetime , timezone
553
512
554
513
from django .utils .dateparse import parse_datetime
555
514
556
515
url = get_relative_url ('serviceuserassignment-assign' )
557
516
558
- creator_user = User .objects .create (username = 'object_created_creator' )
559
-
560
- # Set a specific object_created timestamp that's different from the actual object's created time
561
- custom_object_created = datetime (2022 , 6 , 15 , 14 , 30 , 0 , tzinfo = timezone .utc )
562
- custom_object_created_str = custom_object_created .isoformat ()
517
+ if object_created_source == 'custom' :
518
+ # Use inventory with custom timestamp
519
+ target_object = inventory
520
+ role_def = inv_rd
521
+ custom_object_created = datetime (2022 , 6 , 15 , 14 , 30 , 0 , tzinfo = timezone .utc )
522
+ expected_object_created = custom_object_created
563
523
564
- post_data = {
565
- "role_definition" : inv_rd .name ,
566
- "user_ansible_id" : str (rando .resource .ansible_id ),
567
- "object_id" : str (inventory .pk ),
568
- "created_by_ansible_id" : str (creator_user .resource .ansible_id ),
569
- "object_created" : custom_object_created_str ,
570
- "from_service" : "test_service" ,
571
- }
524
+ post_data = {
525
+ "role_definition" : role_def .name ,
526
+ "user_ansible_id" : str (rando .resource .ansible_id ),
527
+ "object_id" : str (target_object .pk ),
528
+ "object_created" : custom_object_created .isoformat (),
529
+ "from_service" : "test_service" ,
530
+ }
531
+ else : # local_object
532
+ # Use organization without providing object_created
533
+ target_object = organization
534
+ role_def = org_inv_rd
535
+ expected_object_created = organization .created
536
+
537
+ post_data = {
538
+ "role_definition" : role_def .name ,
539
+ "user_ansible_id" : str (rando .resource .ansible_id ),
540
+ "object_id" : str (target_object .pk ),
541
+ "from_service" : "test_service" ,
542
+ # Note: no object_created provided - should default to target_object.created
543
+ }
572
544
573
- # Test 1: POST accepts object_created value
545
+ # Test 1: POST accepts object_created value or defaults to local object
574
546
response = admin_api_client .post (url , data = post_data )
575
547
assert response .status_code == 201 , response .data
576
548
577
- assignment = RoleUserAssignment .objects .get (user = rando , role_definition = inv_rd , object_id = inventory .pk )
549
+ assignment = RoleUserAssignment .objects .get (user = rando , role_definition = role_def , object_id = target_object .pk )
578
550
579
- # Verify the custom object_created timestamp was properly set
580
- expected_object_created = custom_object_created
551
+ # Verify the object_created timestamp was properly set
581
552
actual_object_created = assignment .object_created
582
-
553
+ operation_type = 'synchronized' if object_created_source == 'custom' else 'defaulted to local object'
583
554
assert (
584
555
actual_object_created == expected_object_created
585
- ), f"object_created should be synchronized : Expected '{ expected_object_created } ' but got '{ actual_object_created } '"
556
+ ), f"object_created should be { operation_type } : Expected '{ expected_object_created } ' but got '{ actual_object_created } '"
586
557
587
558
# Test 2: Serializing local assignments includes object_created field
588
559
list_url = get_relative_url ('serviceuserassignment-list' )
589
560
response = admin_api_client .get (list_url + '?page_size=200' , format = "json" )
590
561
assert response .status_code == 200 , response .data
591
562
592
563
# Find our assignment in the list
593
- assignments = [a for a in response .data ['results' ] if a ['role_definition' ] == inv_rd .name and str (a ['object_id' ]) == str (inventory .pk )]
564
+ assignments = [a for a in response .data ['results' ] if a ['role_definition' ] == role_def .name and str (a ['object_id' ]) == str (target_object .pk )]
594
565
assert len (assignments ) >= 1 , "Should find at least our assignment"
595
566
596
567
# Check that object_created is properly serialized
@@ -601,53 +572,3 @@ def test_service_assignment_object_created_timestamp_sync(admin_api_client, rand
601
572
assert (
602
573
response_object_created == expected_object_created
603
574
), f"Serialized object_created should match stored value: expected '{ expected_object_created } ' but got '{ response_object_created } '"
604
-
605
-
606
- @pytest .mark .django_db
607
- def test_service_assignment_object_created_from_local_object (admin_api_client , rando , org_inv_rd , organization ):
608
- """
609
- Test that when no object_created is provided in POST, the field is automatically set
610
- from the local object's created timestamp and properly serialized.
611
- """
612
- url = get_relative_url ('serviceuserassignment-assign' )
613
-
614
- post_data = {
615
- "role_definition" : org_inv_rd .name ,
616
- "user_ansible_id" : str (rando .resource .ansible_id ),
617
- "object_id" : str (organization .pk ),
618
- "from_service" : "test_service" ,
619
- # Note: no object_created provided - should default to organization.created
620
- }
621
-
622
- # Create assignment without providing object_created
623
- response = admin_api_client .post (url , data = post_data )
624
- assert response .status_code == 201 , response .data
625
-
626
- assignment = RoleUserAssignment .objects .get (user = rando , role_definition = org_inv_rd , object_id = organization .pk )
627
-
628
- # Verify object_created was set to the organization's created timestamp
629
- expected_object_created = organization .created
630
- actual_object_created = assignment .object_created
631
-
632
- assert (
633
- actual_object_created == expected_object_created
634
- ), f"object_created should default to organization.created: Expected '{ expected_object_created } ' but got '{ actual_object_created } '"
635
-
636
- # Verify serialization includes the correct object_created value
637
- list_url = get_relative_url ('serviceuserassignment-list' )
638
- response = admin_api_client .get (list_url + '?page_size=200' , format = "json" )
639
- assert response .status_code == 200 , response .data
640
-
641
- # Find our assignment
642
- assignments = [a for a in response .data ['results' ] if a ['role_definition' ] == org_inv_rd .name and str (a ['object_id' ]) == str (organization .pk )]
643
- assert len (assignments ) >= 1 , "Should find at least our assignment"
644
-
645
- assignment_data = assignments [0 ]
646
- assert 'object_created' in assignment_data , "object_created field should be present in serialized output"
647
-
648
- from django .utils .dateparse import parse_datetime
649
-
650
- response_object_created = parse_datetime (assignment_data ['object_created' ])
651
- assert (
652
- response_object_created == expected_object_created
653
- ), f"Serialized object_created should match organization.created: expected '{ expected_object_created } ' but got '{ response_object_created } '"
0 commit comments