@@ -1164,6 +1164,131 @@ module PostHog
11641164
11651165 assert_not_requested :post , flags_endpoint
11661166 end
1167+
1168+ context 'mixed targeting' do
1169+ mixed_flag = {
1170+ 'id' => 1 ,
1171+ 'name' => 'Mixed Flag' ,
1172+ 'key' => 'mixed-flag' ,
1173+ 'active' => true ,
1174+ 'filters' => {
1175+ 'aggregation_group_type_index' => nil ,
1176+ 'groups' => [
1177+ {
1178+ 'aggregation_group_type_index' => 0 ,
1179+ 'properties' => [ {
1180+ 'key' => 'plan' , 'operator' => 'exact' , 'value' => [ 'enterprise' ] ,
1181+ 'type' => 'group' , 'group_type_index' => 0
1182+ } ] ,
1183+ 'rollout_percentage' => 100
1184+ } ,
1185+ {
1186+ 'aggregation_group_type_index' => nil ,
1187+ 'properties' => [ {
1188+ 'key' => 'email' , 'operator' => 'exact' , 'value' => [ 'test@example.com' ] ,
1189+ 'type' => 'person'
1190+ } ] ,
1191+ 'rollout_percentage' => 100
1192+ }
1193+ ]
1194+ }
1195+ }
1196+ mixed_flag_response = { 'flags' => [ mixed_flag ] , 'group_type_mapping' => { '0' => 'company' } }
1197+
1198+ only_group_flag = {
1199+ 'id' => 2 ,
1200+ 'name' => 'Only Group Flag' ,
1201+ 'key' => 'only-group-flag' ,
1202+ 'active' => true ,
1203+ 'filters' => {
1204+ 'aggregation_group_type_index' => nil ,
1205+ 'groups' => [
1206+ {
1207+ 'aggregation_group_type_index' => 0 ,
1208+ 'properties' => [ {
1209+ 'key' => 'plan' , 'operator' => 'exact' , 'value' => [ 'enterprise' ] ,
1210+ 'type' => 'group' , 'group_type_index' => 0
1211+ } ] ,
1212+ 'rollout_percentage' => 100
1213+ }
1214+ ]
1215+ }
1216+ }
1217+ only_group_response = { 'flags' => [ only_group_flag ] , 'group_type_mapping' => { '0' => 'company' } }
1218+
1219+ [
1220+ { name : 'person condition matches when no groups passed' ,
1221+ flag_key : 'mixed-flag' , response : mixed_flag_response ,
1222+ opts : { person_properties : { 'email' => 'test@example.com' } } ,
1223+ expected : true } ,
1224+ { name : 'group condition matches when group props match' ,
1225+ flag_key : 'mixed-flag' , response : mixed_flag_response ,
1226+ opts : { groups : { 'company' => 'acme' } ,
1227+ group_properties : { 'company' => { 'plan' => 'enterprise' } } ,
1228+ person_properties : { 'email' => 'nope@example.com' } } ,
1229+ expected : true } ,
1230+ { name : 'no match when both person and group fail' ,
1231+ flag_key : 'mixed-flag' , response : mixed_flag_response ,
1232+ opts : { groups : { 'company' => 'acme' } ,
1233+ group_properties : { 'company' => { 'plan' => 'free' } } ,
1234+ person_properties : { 'email' => 'nope@example.com' } } ,
1235+ expected : false } ,
1236+ { name : 'only group conditions, no groups passed: returns false without /flags fallback' ,
1237+ flag_key : 'only-group-flag' , response : only_group_response ,
1238+ opts : { } ,
1239+ expected : false }
1240+ ] . each do |tc |
1241+ it tc [ :name ] do
1242+ stub_request (
1243+ :get ,
1244+ 'https://us.i.posthog.com/flags/definitions?token=testsecret&send_cohorts=true'
1245+ ) . to_return ( status : 200 , body : tc [ :response ] . to_json )
1246+ # Server fallback would return a different value if we ever reach it.
1247+ stub_request ( :post , flags_endpoint )
1248+ . to_return ( status : 200 , body : { 'featureFlags' => { tc [ :flag_key ] => 'server-fallback' } } . to_json )
1249+
1250+ c = Client . new ( api_key : API_KEY , personal_api_key : API_KEY , test_mode : true )
1251+
1252+ expect ( c . get_feature_flag ( tc [ :flag_key ] , 'test-distinct-id' , **tc [ :opts ] ) ) . to eq ( tc [ :expected ] )
1253+ assert_not_requested :post , flags_endpoint
1254+ end
1255+ end
1256+
1257+ it 'rollout uses group bucketing for group conditions and resolves locally' do
1258+ rollout_flag = {
1259+ 'id' => 3 ,
1260+ 'name' => 'Rollout Flag' ,
1261+ 'key' => 'rollout-flag' ,
1262+ 'active' => true ,
1263+ 'filters' => {
1264+ 'aggregation_group_type_index' => nil ,
1265+ 'groups' => [
1266+ {
1267+ 'aggregation_group_type_index' => 0 ,
1268+ 'properties' => [ ] ,
1269+ 'rollout_percentage' => 100
1270+ }
1271+ ]
1272+ }
1273+ }
1274+ response = { 'flags' => [ rollout_flag ] , 'group_type_mapping' => { '0' => 'company' } }
1275+
1276+ stub_request (
1277+ :get ,
1278+ 'https://us.i.posthog.com/flags/definitions?token=testsecret&send_cohorts=true'
1279+ ) . to_return ( status : 200 , body : response . to_json )
1280+ stub_request ( :post , flags_endpoint ) . to_return ( status : 400 )
1281+
1282+ c = Client . new ( api_key : API_KEY , personal_api_key : API_KEY , test_mode : true )
1283+
1284+ expect ( c . get_feature_flag (
1285+ 'rollout-flag' , 'any-distinct-id' ,
1286+ groups : { 'company' => 'acme' } ,
1287+ group_properties : { 'company' => { } }
1288+ ) ) . to eq ( true )
1289+ assert_not_requested :post , flags_endpoint
1290+ end
1291+ end
11671292 end
11681293
11691294 describe 'property matching' do
0 commit comments