2121import io .supertokens .emailpassword .EmailPassword ;
2222import io .supertokens .featureflag .EE_FEATURES ;
2323import io .supertokens .featureflag .FeatureFlagTestContent ;
24+ import io .supertokens .pluginInterface .MigrationMode ;
2425import io .supertokens .pluginInterface .STORAGE_TYPE ;
2526import io .supertokens .pluginInterface .authRecipe .AuthRecipeUserInfo ;
2627import io .supertokens .pluginInterface .authRecipe .exceptions .AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException ;
3435import org .junit .Test ;
3536import org .junit .rules .TestRule ;
3637
38+ import static io .supertokens .storage .postgresql .QueryExecutorTemplate .execute ;
3739import static io .supertokens .storage .postgresql .QueryExecutorTemplate .update ;
3840import static org .junit .Assert .*;
3941
5557 * before they reach the ON CONFLICT clause.
5658 *
5759 * Test strategy:
60+ * 0. Force MIGRATED mode so that makePrimaryUser_Transaction routes through
61+ * addPrimaryUserAccountInfo_Transaction. Without this the process runs in
62+ * LEGACY mode and the buggy method is never called.
5863 * 1. Make an EmailPassword user primary → seeds primary_user_tenants with the
5964 * conflict target (public, email, test@example.com, EP_USER).
6065 * 2. ThirdParty sign-up with the same email → adds the normal EMAIL row:
@@ -98,6 +103,13 @@ public void createPrimaryUser_fanOutBug_spuriousEmailRowCausesConflictError() th
98103 return ;
99104 }
100105
106+ // Force MIGRATED mode so that makePrimaryUser_Transaction routes through
107+ // addPrimaryUserAccountInfo_Transaction (the method containing the fan-out
108+ // bug) rather than the legacy path. Without this the test always passes
109+ // because LEGACY mode never calls the buggy/fixed method.
110+ Start start = (Start ) StorageLayer .getStorage (process .getProcess ());
111+ Config .getConfig (start ).setMigrationModeForTesting (MigrationMode .MIGRATED );
112+
101113 // Step 1 — create EP primary user, seeding primary_user_tenants with
102114 // (public, email, test@example.com, EP_USER).
103115 AuthRecipeUserInfo epUser = EmailPassword .signUp (
@@ -123,8 +135,12 @@ public void createPrimaryUser_fanOutBug_spuriousEmailRowCausesConflictError() th
123135 // Now recipe_user_account_infos has TWO rows matching
124136 // (recipe_user_id=TP_USER, recipe_id=thirdparty, account_info_type=email,
125137 // account_info_value=test@example.com) — differing only in third_party_id.
126- Start start = (Start ) StorageLayer .getStorage (process .getProcess ());
127138 String accountInfoTable = Config .getConfig (start ).getRecipeUserAccountInfosTable ();
139+ // Confirm migration mode is MIGRATED before injection — if this fails the
140+ // test would pass for the wrong reason (legacy path, no fan-out possible).
141+ assertEquals ("MIGRATED mode must be active for this test to exercise the fan-out bug" ,
142+ MigrationMode .MIGRATED , Config .getConfig (start ).getMigrationMode ());
143+
128144 update (start ,
129145 "INSERT INTO " + accountInfoTable
130146 + " (app_id, recipe_user_id, recipe_id, account_info_type,"
@@ -136,6 +152,18 @@ public void createPrimaryUser_fanOutBug_spuriousEmailRowCausesConflictError() th
136152 pst .setString (3 , "test@example.com" );
137153 });
138154
155+ // Confirm both email rows are present — if this fails the injection silently
156+ // did not create the second row and the test cannot demonstrate the fan-out.
157+ int emailRowCount = execute (start ,
158+ "SELECT COUNT(*) FROM " + accountInfoTable
159+ + " WHERE app_id = 'public' AND recipe_user_id = ? AND account_info_type = 'email'" ,
160+ pst -> pst .setString (1 , tpUserId ),
161+ rs -> {
162+ rs .next ();
163+ return rs .getInt (1 );
164+ });
165+ assertEquals ("Spurious injection must produce exactly 2 email rows to trigger fan-out" , 2 , emailRowCount );
166+
139167 // Step 4 — attempt to make the ThirdParty user primary.
140168 //
141169 // Inside addPrimaryUserAccountInfo_Transaction the INSERT…SELECT joins
0 commit comments