package integration_test import ( "context" "net/http" "testing" "time" "galaxy/integration/testenv" usermodel "galaxy/model/user" "galaxy/transcoder" ) // TestSoftDelete_Cascade triggers `POST /api/v1/user/account/delete` // with X-User-ID set (mirroring what gateway does after authenticated // verification) and asserts: // - the account fetch through the authenticated gRPC surface // subsequently fails because soft-delete revoked the session; // - the admin geo endpoint reports the user has no remaining // country counter rows. func TestSoftDelete_Cascade(t *testing.T) { plat := testenv.Bootstrap(t, testenv.BootstrapOptions{}) ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() sess := testenv.RegisterSession(t, plat, "pilot+softdelete@example.com") gw, err := sess.DialAuthenticated(ctx, plat) if err != nil { t.Fatalf("dial: %v", err) } defer gw.Close() // Touch the account once so a geo counter row exists. payload, err := transcoder.GetMyAccountRequestToPayload(&usermodel.GetMyAccountRequest{}) if err != nil { t.Fatalf("encode payload: %v", err) } if _, err := gw.Execute(ctx, usermodel.MessageTypeGetMyAccount, payload, testenv.ExecuteOptions{}); err != nil { t.Fatalf("pre-delete fetch failed: %v", err) } userID, err := sess.LookupUserID(ctx, plat) if err != nil { t.Fatalf("resolve user_id: %v", err) } // Trigger soft delete. The user surface is fronted by gateway in // production; here we replicate gateway's forwarding by hitting // backend's HTTP listener directly with X-User-ID, which is the // trusted identity input on the user surface. user := testenv.NewBackendUserClient(plat.Backend.HTTPURL, userID) raw, resp, err := user.Do(ctx, http.MethodPost, "/api/v1/user/account/delete", nil) if err != nil { t.Fatalf("soft delete: %v", err) } if resp.StatusCode != http.StatusNoContent && resp.StatusCode/100 != 2 { t.Fatalf("soft delete: status %d body=%s", resp.StatusCode, string(raw)) } // Authenticated gRPC must now be rejected. deadline := time.Now().Add(2 * time.Second) var lastErr error for time.Now().Before(deadline) { _, lastErr = gw.Execute(ctx, usermodel.MessageTypeGetMyAccount, payload, testenv.ExecuteOptions{}) if lastErr != nil { break } time.Sleep(100 * time.Millisecond) } if lastErr == nil { t.Fatalf("gateway accepted authenticated call after soft delete; expected rejection") } if !testenv.IsUnauthenticated(lastErr) { t.Fatalf("post-delete status: expected Unauthenticated, got %v", lastErr) } // Geo cascade: counters for this user should be gone. admin := testenv.NewBackendAdminClient(plat.Backend.HTTPURL, plat.Backend.AdminUser, plat.Backend.AdminPassword) body, resp, err := admin.Do(ctx, http.MethodGet, "/api/v1/admin/geo/users/"+userID+"/countries", nil) if err != nil { t.Fatalf("admin geo lookup: %v", err) } if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound { t.Fatalf("admin geo lookup: status %d body=%s", resp.StatusCode, string(body)) } }