@@ -320,89 +320,40 @@ def test_role_types_and_permissions_payload_shape(user_api_client):
320320class TestCreatedByAnsibleIdAllowNull :
321321 """Test that created_by_ansible_id field accepts null values and omissions"""
322322
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' )
385344 data = {
386345 "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 ),
388347 "object_id" : inventory .pk ,
389348 }
390349
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)
406357
407358 response = admin_api_client .post (url , data = data )
408359 assert response .status_code == 201 , response .data
@@ -543,54 +494,74 @@ def test_service_assignment_created_timestamp_sync(admin_api_client, rando, inv_
543494
544495
545496@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 ):
547505 """
548506 Test that the 'object_created' field can be synchronized in both directions:
549507 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
551510 """
552511 from datetime import datetime , timezone
553512
554513 from django .utils .dateparse import parse_datetime
555514
556515 url = get_relative_url ('serviceuserassignment-assign' )
557516
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
563523
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+ }
572544
573- # Test 1: POST accepts object_created value
545+ # Test 1: POST accepts object_created value or defaults to local object
574546 response = admin_api_client .post (url , data = post_data )
575547 assert response .status_code == 201 , response .data
576548
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 )
578550
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
581552 actual_object_created = assignment .object_created
582-
553+ operation_type = 'synchronized' if object_created_source == 'custom' else 'defaulted to local object'
583554 assert (
584555 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 } '"
586557
587558 # Test 2: Serializing local assignments includes object_created field
588559 list_url = get_relative_url ('serviceuserassignment-list' )
589560 response = admin_api_client .get (list_url + '?page_size=200' , format = "json" )
590561 assert response .status_code == 200 , response .data
591562
592563 # 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 )]
594565 assert len (assignments ) >= 1 , "Should find at least our assignment"
595566
596567 # Check that object_created is properly serialized
@@ -601,53 +572,3 @@ def test_service_assignment_object_created_timestamp_sync(admin_api_client, rand
601572 assert (
602573 response_object_created == expected_object_created
603574 ), 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