TestDeleteUserData verifies the right-of-deletion flow does the full cascade and reports per-table counts. After deletion, the user record must be gone and orphan rows must not exist for any of: domains, agents, messages, api_keys, sessions, usage events/summaries.
(t *testing.T)
| 172 | // must be gone and orphan rows must not exist for any of: domains, |
| 173 | // agents, messages, api_keys, sessions, usage events/summaries. |
| 174 | func TestDeleteUserData(t *testing.T) { |
| 175 | pool := testutil.TestDB(t) |
| 176 | store := identity.NewStore(pool) |
| 177 | ctx := context.Background() |
| 178 | |
| 179 | user := seedUserData(t, store, ctx, "deleter") |
| 180 | |
| 181 | res, err := store.DeleteUserData(ctx, user.ID) |
| 182 | if err != nil { |
| 183 | t.Fatalf("DeleteUserData: %v", err) |
| 184 | } |
| 185 | |
| 186 | if !res.UserDeleted { |
| 187 | t.Error("UserDeleted should be true") |
| 188 | } |
| 189 | if res.DomainsDeleted != 1 { |
| 190 | t.Errorf("DomainsDeleted = %d, want 1", res.DomainsDeleted) |
| 191 | } |
| 192 | if res.AgentsDeleted != 1 { |
| 193 | t.Errorf("AgentsDeleted = %d, want 1", res.AgentsDeleted) |
| 194 | } |
| 195 | if res.MessagesDeleted != 2 { |
| 196 | t.Errorf("MessagesDeleted = %d, want 2", res.MessagesDeleted) |
| 197 | } |
| 198 | if res.APIKeysDeleted != 2 { |
| 199 | t.Errorf("APIKeysDeleted = %d, want 2", res.APIKeysDeleted) |
| 200 | } |
| 201 | if res.SessionsDeleted != 1 { |
| 202 | t.Errorf("SessionsDeleted = %d, want 1", res.SessionsDeleted) |
| 203 | } |
| 204 | |
| 205 | // Verify the user row itself is gone. |
| 206 | var exists bool |
| 207 | if err := pool.QueryRow(ctx, |
| 208 | `SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)`, user.ID, |
| 209 | ).Scan(&exists); err != nil { |
| 210 | t.Fatalf("post-delete user exists: %v", err) |
| 211 | } |
| 212 | if exists { |
| 213 | t.Error("user row still exists after DeleteUserData") |
| 214 | } |
| 215 | |
| 216 | // Verify no orphan rows in any user-scoped table. The cascade is in |
| 217 | // the schema (ON DELETE CASCADE), but a regression that drops a |
| 218 | // cascade clause would leak rows — checking explicitly catches it. |
| 219 | checks := []struct { |
| 220 | name string |
| 221 | query string |
| 222 | }{ |
| 223 | {"domains", `SELECT count(*) FROM domains WHERE user_id = $1`}, |
| 224 | {"agents", `SELECT count(*) FROM agent_identities WHERE user_id = $1`}, |
| 225 | {"api_keys", `SELECT count(*) FROM api_keys WHERE user_id = $1`}, |
| 226 | {"sessions", `SELECT count(*) FROM user_sessions WHERE user_id = $1`}, |
| 227 | {"usage_events", `SELECT count(*) FROM usage_events WHERE user_id = $1`}, |
| 228 | {"usage_summaries", `SELECT count(*) FROM usage_summaries WHERE user_id = $1`}, |
| 229 | } |
| 230 | for _, c := range checks { |
| 231 | var n int |
nothing calls this directly
no test coverage detected