@@ -417,17 +417,6 @@ def setUp(self):
417417 self .api = UserFeatureFlagsApiImpl ()
418418 self .session = MagicMock ()
419419
420- def _setup_flags_query (self , flag_ids ):
421- """Make session.query(FeatureFlagORM.id).filter(...).all() return rows for given IDs."""
422- mock_rows = [MagicMock (id = fid ) for fid in flag_ids ]
423- mock_q = MagicMock ()
424- mock_q .filter .return_value = mock_q
425- mock_q .all .return_value = mock_rows
426- self .session .query .side_effect = lambda model : (
427- mock_q if model is FeatureFlagORM .id else self .session .query (model )
428- )
429- return mock_q
430-
431420 def test_raises_404_when_user_not_found (self ):
432421 _mock_query (self .session , None )
433422
@@ -440,7 +429,7 @@ def test_raises_404_when_user_not_found(self):
440429 def test_raises_404_when_flag_not_found (self ):
441430 user = _make_user (user_feature_flags = [])
442431
443- def query_side_effect (model ):
432+ def query_side_effect (* args ):
444433 mock_q = MagicMock ()
445434 mock_q .options .return_value = mock_q
446435 mock_q .filter_by .return_value = mock_q
@@ -460,6 +449,91 @@ def query_side_effect(model):
460449 self .assertEqual (ctx .exception .status_code , 404 )
461450 self .assertIn ("nonexistent" , ctx .exception .detail )
462451
452+ def _dispatch_put_queries (self , user , flag_meta_rows ):
453+ """Route session.query() calls for the put flow.
454+
455+ `flag_meta_rows` are the (id, value_type) rows returned for the
456+ flag-validation query.
457+ """
458+ user_q = MagicMock ()
459+ user_q .options .return_value = user_q
460+ user_q .filter_by .return_value = user_q
461+ user_q .first .return_value = user
462+
463+ meta_q = MagicMock ()
464+ meta_q .filter .return_value = meta_q
465+ meta_q .all .return_value = flag_meta_rows
466+
467+ delete_q = MagicMock ()
468+ delete_q .filter_by .return_value = delete_q
469+
470+ flags_q = MagicMock ()
471+ flags_q .order_by .return_value = flags_q
472+ flags_q .all .return_value = []
473+
474+ def side_effect (* args ):
475+ first = args [0 ]
476+ if first is AppUser :
477+ return user_q
478+ if first is FeatureFlagORM .id :
479+ return meta_q
480+ if first is UserFeatureFlag :
481+ return delete_q
482+ return flags_q
483+
484+ self .session .query .side_effect = side_effect
485+ return delete_q
486+
487+ def test_assigns_value_matching_flag_type (self ):
488+ user = _make_user (user_feature_flags = [])
489+ rows = [MagicMock (id = "beta_editor" , value_type = "boolean" )]
490+ delete_q = self ._dispatch_put_queries (user , rows )
491+
492+ req = PutUserFeatureFlagsRequest (
493+ assignments = [
494+ FeatureFlagAssignment (feature_flag_id = "beta_editor" , value = True )
495+ ]
496+ )
497+ self .api .put_user_feature_flags ("uid-1" , req , db_session = self .session )
498+
499+ delete_q .delete .assert_called_once ()
500+ added = self .session .add_all .call_args [0 ][0 ]
501+ self .assertEqual (len (added ), 1 )
502+ self .assertEqual (added [0 ].feature_flag_id , "beta_editor" )
503+ self .assertEqual (added [0 ].value , True )
504+
505+ def test_raises_422_when_value_type_mismatch (self ):
506+ user = _make_user (user_feature_flags = [])
507+ rows = [MagicMock (id = "beta_editor" , value_type = "boolean" )]
508+ self ._dispatch_put_queries (user , rows )
509+
510+ req = PutUserFeatureFlagsRequest (
511+ assignments = [
512+ FeatureFlagAssignment (feature_flag_id = "beta_editor" , value = "not-a-bool" )
513+ ]
514+ )
515+ with self .assertRaises (HTTPException ) as ctx :
516+ self .api .put_user_feature_flags ("uid-1" , req , db_session = self .session )
517+
518+ self .assertEqual (ctx .exception .status_code , 422 )
519+ self .session .add_all .assert_not_called ()
520+
521+ def test_skips_type_validation_when_value_is_null (self ):
522+ user = _make_user (user_feature_flags = [])
523+ rows = [MagicMock (id = "beta_editor" , value_type = "boolean" )]
524+ self ._dispatch_put_queries (user , rows )
525+
526+ req = PutUserFeatureFlagsRequest (
527+ assignments = [
528+ FeatureFlagAssignment (feature_flag_id = "beta_editor" , value = None )
529+ ]
530+ )
531+ self .api .put_user_feature_flags ("uid-1" , req , db_session = self .session )
532+
533+ added = self .session .add_all .call_args [0 ][0 ]
534+ self .assertEqual (len (added ), 1 )
535+ self .assertIsNone (added [0 ].value )
536+
463537 def test_clears_flags_when_empty_list_provided (self ):
464538 flag = _make_flag ()
465539 user = _make_user (user_feature_flags = [_make_uff (flag )])
0 commit comments