From 1ad21cc8f185bf8282e098c8cf835ed37e17180d Mon Sep 17 00:00:00 2001 From: Macky Date: Wed, 26 Aug 2026 14:04:47 +0700 Subject: [PATCH] test: cover product delete cascade (group + own sessions) keeping other-org sessions Proves DELETE /api/groups/ removes the product, its personas (stored on the group), its own sessions, and uploads, while sessions of an unrelated product/ org survive. Guards the cascade against future regressions. --- .../tests/test_security_review_blockers.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/backend/tests/test_security_review_blockers.py b/backend/tests/test_security_review_blockers.py index 7cb4017..8b5b90c 100644 --- a/backend/tests/test_security_review_blockers.py +++ b/backend/tests/test_security_review_blockers.py @@ -1285,3 +1285,82 @@ def test_malformed_visibility_legacy_syntax_not_migrated(client, user_store, log from app.services.groups import resolved_visibility assert resolved_visibility({"visibility": "evil", "owner_user_id": "x"}) is None + + +def test_delete_group_cascades_sessions_and_keeps_other_org_sessions( + client, user_store, login +): + """Deleting a product removes the group, its personas (stored in the group), + its own sessions, and uploaded filenames — but not sessions of another group.""" + # Admin in org-default + admin_token = _setup_admin(user_store, login) + _create_user(user_store, login, username="trainee-delete-cascade", org_id="org-default") + + groups = client.application.extensions["group_store"] + sess = client.application.extensions["session_store"] + + # Build a ready product (group) directly with one persona. + g = groups.create( + org_id="org-default", + creator_id="admin", + title="Cascade test product", + status="ready", + input_data={"files": ["upload-a.pdf"]}, + sales_kit={"productName": "Cascade product"}, + report={"summary": "Cascade report"}, + personas=[{"id": "p-1", "name": "Persona One", "tier": "A"}], + ) + gid = g["id"] + + # A real session (chat) bound to this product + same org. + sess.sessions.create({ + "id": "sess-p1", + "org_id": "org-default", + "user_id": "trainee-delete-cascade", + "group_id": gid, + "persona_id": "p-1", + "persona_name": "Persona One", + "mode": "trainee", + "status": "finished", + "outcome": "won", + "debrief": {"score": 80}, + }, key="sess-p1") + + # A session of ANOTHER (different) group that must survive. + g2 = groups.create( + org_id="org-default", + creator_id="admin", + title="Other product", + status="ready", + sales_kit={"productName": "Other"}, + report={"summary": "R"}, + personas=[{"id": "p-x", "name": "Persona X", "tier": "B"}], + ) + sess.sessions.create({ + "id": "sess-x", + "org_id": "org-default", + "user_id": "trainee-delete-cascade", + "group_id": g2["id"], + "persona_id": "p-x", + "persona_name": "Persona X", + "mode": "trainee", + "status": "finished", + "outcome": "lost", + }, key="sess-x") + + # Sanity: product + its session + other session exist before delete. + assert groups.get_or_none(gid) is not None + assert sess.sessions.get_or_none("sess-p1") is not None + assert sess.sessions.get_or_none("sess-x") is not None + + # Delete the product via the API (admin). + resp = client.delete(f"/api/groups/{gid}", headers=_headers(admin_token)) + assert resp.status_code == 200, resp.get_json() + + # Product + its personas (group) and its own session are gone. + assert groups.get_or_none(gid) is None + assert sess.sessions.get_or_none("sess-p1") is None + # An unrelated session survives. + assert sess.sessions.get_or_none("sess-x") is not None + # Other product survives. + assert groups.get_or_none(g2["id"]) is not None