1414)
1515from metadata .views import SUPPORTED_REQUIREMENTS_MAPPING # type: ignore[attr-defined]
1616from organisations .models import Organisation
17- from projects .models import Project
17+ from projects .models import Project , UserProjectPermission
1818from users .models import FFAdminUser
1919
2020
@@ -511,8 +511,6 @@ def test_create_metadata_field__project_admin__returns_201(
511511 project : Project ,
512512) -> None :
513513 # Given
514- from projects .models import UserProjectPermission
515-
516514 UserProjectPermission .objects .create (user = staff_user , project = project , admin = True )
517515
518516 url = reverse ("api-v1:metadata:metadata-fields-list" )
@@ -942,8 +940,6 @@ def test_modify_metadata_field__project_admin__permission_check(
942940 request : pytest .FixtureRequest ,
943941) -> None :
944942 # Given
945- from projects .models import UserProjectPermission
946-
947943 UserProjectPermission .objects .create (user = staff_user , project = project , admin = True )
948944 field_project = (
949945 request .getfixturevalue (field_project_attr ) if field_project_attr else None
@@ -969,3 +965,172 @@ def test_modify_metadata_field__project_admin__permission_check(
969965
970966 # Then
971967 assert response .status_code == expected_status
968+
969+
970+ def test_create_model_metadata_field__project_admin__returns_201 (
971+ staff_user : FFAdminUser ,
972+ staff_client : APIClient ,
973+ organisation : Organisation ,
974+ project : Project ,
975+ feature_content_type : ContentType ,
976+ ) -> None :
977+ # Given - a project-scoped MetadataField and a user who is project admin (not org admin)
978+ UserProjectPermission .objects .create (user = staff_user , project = project , admin = True )
979+ project_field = MetadataField .objects .create (
980+ name = "proj_field" ,
981+ type = "int" ,
982+ organisation = organisation ,
983+ project = project ,
984+ )
985+ url = reverse (
986+ "api-v1:organisations:metadata-model-fields-list" , args = [organisation .id ]
987+ )
988+ data = {
989+ "field" : project_field .id ,
990+ "content_type" : feature_content_type .id ,
991+ "is_required_for" : [],
992+ }
993+
994+ # When
995+ response = staff_client .post (
996+ url , data = json .dumps (data ), content_type = "application/json"
997+ )
998+
999+ # Then - project admin should be allowed to bind a project-scoped field
1000+ assert response .status_code == status .HTTP_201_CREATED
1001+ assert response .json ()["field" ] == project_field .id
1002+
1003+
1004+ def test_create_model_metadata_field__project_admin_org_scoped_field__returns_403 (
1005+ staff_user : FFAdminUser ,
1006+ staff_client : APIClient ,
1007+ organisation : Organisation ,
1008+ project : Project ,
1009+ feature_content_type : ContentType ,
1010+ ) -> None :
1011+ # Given - an org-scoped MetadataField (project=None) and a user who is project admin only
1012+ UserProjectPermission .objects .create (user = staff_user , project = project , admin = True )
1013+ org_field = MetadataField .objects .create (
1014+ name = "org_field" ,
1015+ type = "int" ,
1016+ organisation = organisation ,
1017+ )
1018+ url = reverse (
1019+ "api-v1:organisations:metadata-model-fields-list" , args = [organisation .id ]
1020+ )
1021+ data = {
1022+ "field" : org_field .id ,
1023+ "content_type" : feature_content_type .id ,
1024+ "is_required_for" : [],
1025+ }
1026+
1027+ # When
1028+ response = staff_client .post (
1029+ url , data = json .dumps (data ), content_type = "application/json"
1030+ )
1031+
1032+ # Then - project admin must not be able to bind org-scoped fields
1033+ assert response .status_code == status .HTTP_403_FORBIDDEN
1034+
1035+
1036+ @pytest .mark .parametrize (
1037+ "field_project_attr, expected_status" ,
1038+ [
1039+ ("project" , status .HTTP_200_OK ),
1040+ (None , status .HTTP_403_FORBIDDEN ),
1041+ ("project_b" , status .HTTP_403_FORBIDDEN ),
1042+ ],
1043+ ids = [
1044+ "own_project_field_allowed" ,
1045+ "org_scoped_field_denied" ,
1046+ "other_project_field_denied" ,
1047+ ],
1048+ )
1049+ def test_update_model_metadata_field__project_admin__returns_expected_status (
1050+ staff_user : FFAdminUser ,
1051+ staff_client : APIClient ,
1052+ organisation : Organisation ,
1053+ project : Project ,
1054+ project_b : Project ,
1055+ feature_content_type : ContentType ,
1056+ field_project_attr : str | None ,
1057+ expected_status : int ,
1058+ request : pytest .FixtureRequest ,
1059+ ) -> None :
1060+ # Given - a project admin for `project`, and a MetadataModelField whose parent
1061+ # MetadataField belongs to one of: `project`, `project_b`, or the org (None)
1062+ UserProjectPermission .objects .create (user = staff_user , project = project , admin = True )
1063+ field_project = (
1064+ request .getfixturevalue (field_project_attr ) if field_project_attr else None
1065+ )
1066+ field = MetadataField .objects .create (
1067+ name = "model_field" , type = "int" , organisation = organisation , project = field_project
1068+ )
1069+ model_field = MetadataModelField .objects .create (
1070+ field = field , content_type = feature_content_type
1071+ )
1072+ url = reverse (
1073+ "api-v1:organisations:metadata-model-fields-detail" ,
1074+ args = [organisation .id , model_field .id ],
1075+ )
1076+ data = {
1077+ "field" : field .id ,
1078+ "content_type" : feature_content_type .id ,
1079+ "is_required_for" : [],
1080+ }
1081+
1082+ # When
1083+ response = staff_client .put (
1084+ url , data = json .dumps (data ), content_type = "application/json"
1085+ )
1086+
1087+ # Then
1088+ assert response .status_code == expected_status
1089+
1090+
1091+ @pytest .mark .parametrize (
1092+ "field_project_attr, expected_status" ,
1093+ [
1094+ ("project" , status .HTTP_204_NO_CONTENT ),
1095+ (None , status .HTTP_403_FORBIDDEN ),
1096+ ("project_b" , status .HTTP_403_FORBIDDEN ),
1097+ ],
1098+ ids = [
1099+ "own_project_field_allowed" ,
1100+ "org_scoped_field_denied" ,
1101+ "other_project_field_denied" ,
1102+ ],
1103+ )
1104+ def test_delete_model_metadata_field__project_admin__returns_expected_status (
1105+ staff_user : FFAdminUser ,
1106+ staff_client : APIClient ,
1107+ organisation : Organisation ,
1108+ project : Project ,
1109+ project_b : Project ,
1110+ feature_content_type : ContentType ,
1111+ field_project_attr : str | None ,
1112+ expected_status : int ,
1113+ request : pytest .FixtureRequest ,
1114+ ) -> None :
1115+ # Given - a project admin for `project`, and a MetadataModelField whose parent
1116+ # MetadataField belongs to one of: `project`, `project_b`, or the org (None)
1117+ UserProjectPermission .objects .create (user = staff_user , project = project , admin = True )
1118+ field_project = (
1119+ request .getfixturevalue (field_project_attr ) if field_project_attr else None
1120+ )
1121+ field = MetadataField .objects .create (
1122+ name = "model_field" , type = "int" , organisation = organisation , project = field_project
1123+ )
1124+ model_field = MetadataModelField .objects .create (
1125+ field = field , content_type = feature_content_type
1126+ )
1127+ url = reverse (
1128+ "api-v1:organisations:metadata-model-fields-detail" ,
1129+ args = [organisation .id , model_field .id ],
1130+ )
1131+
1132+ # When
1133+ response = staff_client .delete (url )
1134+
1135+ # Then
1136+ assert response .status_code == expected_status
0 commit comments