test: explicit tag delete-cascade cleanup test (MPS-24)

Operator-facing concern: 'can I experiment with auto-suggested tags
and undo cleanly without DB cruft?' Yes — Phase 1 already wires the
SQLAlchemy cascade via ProductTag.tag's backref
(cascade='all, delete-orphan'), but the existing functional test only
verified the Tag row went away. This adds an end-to-end test that:

1. Applies a tag to 3 products via action=apply_suggestion (creating
   3 ProductTag rows).
2. Deletes the tag via action=delete.
3. Asserts: Tag row gone, all 3 ProductTag rows gone, products survive
   with empty .tags.

Proves reversibility for an operator testing categorizations on the
suggest-then-approve loop.
This commit is contained in:
russell@unturf.com 2026-05-15 11:55:40 -04:00
parent 8b25285647
commit 28ffee6c8c
No known key found for this signature in database

View file

@ -8618,3 +8618,63 @@ class TestHomeLayoutAndTags(_AuthenticatedBase):
if res.status_int == 302:
res = res.follow()
self.assertIn("Could not apply suggestion", res.body.decode())
def test_tag_delete_cascade_cleans_product_associations(self):
"""Deleting a tag cascade-deletes every ProductTag row pointing
at it. The operator can apply experimental tags, dislike them,
and delete them no orphan rows accumulate in the DB."""
shop, products = self._make_shop_with_products(
"cascade-shop",
[
("Alpha Product", "Body one."),
("Beta Product", "Body two."),
("Gamma Product", "Body three."),
],
)
product_ids = [str(p.id) for p in products]
# Apply a tag to all three via the suggest-apply path
self.testapp.post(
f"/s/{shop.id}/tags",
{
"action": "apply_suggestion",
"label": "Experimental",
"product_ids": ",".join(product_ids),
},
)
from ..models.tag import get_tag_by_shop_and_slug
from ..models.product_tag import ProductTag
self.dbsession.expire_all()
tag = get_tag_by_shop_and_slug(self.dbsession, shop, "experimental")
self.assertIsNotNone(tag)
tag_id = tag.id
self.assertEqual(
self.dbsession.query(ProductTag)
.filter(ProductTag.tag_id == tag_id)
.count(),
3,
)
# Delete the tag — cascade must clean all ProductTag rows
self.testapp.post(
f"/s/{shop.id}/tags",
{"action": "delete", "tag_slug": "experimental"},
)
self.dbsession.expire_all()
# Tag itself gone
self.assertIsNone(
get_tag_by_shop_and_slug(self.dbsession, shop, "experimental")
)
# Zero orphan ProductTag rows for the deleted tag
self.assertEqual(
self.dbsession.query(ProductTag)
.filter(ProductTag.tag_id == tag_id)
.count(),
0,
)
# Products themselves survive + carry no tags
from ..models.product import get_product_by_id
for pid in product_ids:
p = get_product_by_id(self.dbsession, pid)
self.assertIsNotNone(p)
self.assertEqual(list(p.tags), [])