Delete access tokens on user delete (#34036)

Joseph T. Lyons created

Trying to delete a user record from our admin panel throws the following
error:

`update or delete on table "users" violates foreign key constraint
"access_tokens_user_id_fkey" on table "access_tokens"
Detail: Key (id)=(....) is still referenced from table "access_tokens".`

We need to add a cascade delete to the `access_tokens` table.

Release Notes:

- N/A

Change summary

crates/collab/migrations.sqlite/20221109000000_test_schema.sql                       |  2 
crates/collab/migrations/20250707182700_add_access_tokens_cascade_delete_on_user.sql |  3 
crates/collab/src/db/tests/user_tests.rs                                             | 50 
3 files changed, 54 insertions(+), 1 deletion(-)

Detailed changes

crates/collab/migrations.sqlite/20221109000000_test_schema.sql 🔗

@@ -26,7 +26,7 @@ CREATE UNIQUE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id"
 
 CREATE TABLE "access_tokens" (
     "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "user_id" INTEGER REFERENCES users (id),
+    "user_id" INTEGER REFERENCES users (id) ON DELETE CASCADE,
     "impersonated_user_id" INTEGER REFERENCES users (id),
     "hash" VARCHAR(128)
 );

crates/collab/src/db/tests/user_tests.rs 🔗

@@ -44,3 +44,53 @@ async fn test_accepted_tos(db: &Arc<Database>) {
     let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
     assert!(user.accepted_tos_at.is_none());
 }
+
+test_both_dbs!(
+    test_destroy_user_cascade_deletes_access_tokens,
+    test_destroy_user_cascade_deletes_access_tokens_postgres,
+    test_destroy_user_cascade_deletes_access_tokens_sqlite
+);
+
+async fn test_destroy_user_cascade_deletes_access_tokens(db: &Arc<Database>) {
+    let user_id = db
+        .create_user(
+            "user1@example.com",
+            Some("user1"),
+            false,
+            NewUserParams {
+                github_login: "user1".to_string(),
+                github_user_id: 12345,
+            },
+        )
+        .await
+        .unwrap()
+        .user_id;
+
+    let user = db.get_user_by_id(user_id).await.unwrap();
+    assert!(user.is_some());
+
+    let token_1_id = db
+        .create_access_token(user_id, None, "token-1", 10)
+        .await
+        .unwrap();
+
+    let token_2_id = db
+        .create_access_token(user_id, None, "token-2", 10)
+        .await
+        .unwrap();
+
+    let token_1 = db.get_access_token(token_1_id).await;
+    let token_2 = db.get_access_token(token_2_id).await;
+    assert!(token_1.is_ok());
+    assert!(token_2.is_ok());
+
+    db.destroy_user(user_id).await.unwrap();
+
+    let user = db.get_user_by_id(user_id).await.unwrap();
+    assert!(user.is_none());
+
+    let token_1 = db.get_access_token(token_1_id).await;
+    let token_2 = db.get_access_token(token_2_id).await;
+    assert!(token_1.is_err());
+    assert!(token_2.is_err());
+}