refactor(admin): consolidate Tier-A dry-run/apply handlers onto one helper (#753)
DRY pass on the cleanup/admin destructive-ops surface (task #753, hardened process #594). Five Tier-A endpoints repeated the same get_json -> dry_run -> run_sync(service_fn) -> jsonify block verbatim. Extract _run_dry_run_op(service_fn, **kwargs); the five route handlers now delegate. reconcile keeps its source_id validation and passes it through **kwargs. The cleanup_service predicates were already shared between preview and apply (find_*_conditions / find_duplicate_post_groups) — the post-data-loss fix — so no backend-logic change; this is purely the HTTP-handler boilerplate. Consumers (all routed through the helper, verified no copy left behind): prune_unused_tags, prune_bare_posts, reconcile_duplicate_posts (+source_id), purge_legacy_tags, reset_content_tagging. Added route-level tests for prune-bare (apply) and reconcile (apply + source_id passthrough + invalid-source_id 400) — the two helper consumers that previously had only service-level coverage, so every consumer is exercised at the route. Findings B (queued-response helper) and C (store dry-run POST helper) identified but not applied this pass (operator scoped to A). The card preview->commit state machine is deferred to a frontend pattern-consistency sweep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+62
-1
@@ -8,7 +8,7 @@ import hashlib
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.app.models import Artist, ImageRecord, Tag, TagKind
|
||||
from backend.app.models import Artist, ImageRecord, Post, Tag, TagKind
|
||||
from backend.app.models.tag import image_tag
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
@@ -411,6 +411,67 @@ async def test_purge_legacy_commit_deletes_only_legacy(client, db):
|
||||
assert gone == 0
|
||||
|
||||
|
||||
# --- Tier-A: POST /posts/prune-bare + /posts/reconcile-duplicates ---
|
||||
# These two routes share _run_dry_run_op with the tag prunes (DRY pass,
|
||||
# task #753); cover their apply path + reconcile's source_id passthrough so
|
||||
# every helper consumer is exercised at the route level, not just the service.
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prune_bare_commit_deletes_bare_posts(client, db):
|
||||
from sqlalchemy import func, select
|
||||
|
||||
a = Artist(name="BP", slug="bp-api")
|
||||
db.add(a)
|
||||
await db.flush()
|
||||
db.add(Post(artist_id=a.id, external_post_id="bare1")) # no images/attachments
|
||||
await db.commit()
|
||||
|
||||
resp = await client.post(
|
||||
"/api/admin/posts/prune-bare", json={"dry_run": False},
|
||||
)
|
||||
body = await resp.get_json()
|
||||
assert body["deleted"] >= 1
|
||||
remaining = (await db.execute(
|
||||
select(func.count()).select_from(Post).where(Post.external_post_id == "bare1")
|
||||
)).scalar_one()
|
||||
assert remaining == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reconcile_duplicates_commit_merges_via_endpoint(client, db):
|
||||
from sqlalchemy import select
|
||||
|
||||
a = Artist(name="RC", slug="rc-api")
|
||||
db.add(a)
|
||||
await db.flush()
|
||||
db.add_all([
|
||||
Post(artist_id=a.id, external_post_id="711509",
|
||||
raw_metadata={"post_id": 1923726, "category": "subscribestar"},
|
||||
description="body"),
|
||||
Post(artist_id=a.id, external_post_id="1923726",
|
||||
raw_metadata={"post_id": 1923726, "category": "subscribestar"}),
|
||||
])
|
||||
await db.commit()
|
||||
|
||||
resp = await client.post(
|
||||
"/api/admin/posts/reconcile-duplicates", json={"dry_run": False},
|
||||
)
|
||||
body = await resp.get_json()
|
||||
assert body["merged"] == 1
|
||||
rows = (await db.execute(select(Post.external_post_id))).scalars().all()
|
||||
assert rows == ["1923726"] # one post-id-keyed keeper survives
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reconcile_duplicates_rejects_bad_source_id(client):
|
||||
resp = await client.post(
|
||||
"/api/admin/posts/reconcile-duplicates",
|
||||
json={"dry_run": True, "source_id": "abc"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
# --- DB maintenance: bloat readout + manual VACUUM trigger ----------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user