"""FC-6.x Phase 2 — pending staging for add-from-post.""" import pytest from sqlalchemy import select from backend.app.models import Artist, ImageRecord, Post, TagKind from backend.app.models.series_page import SeriesPage from backend.app.services.series_service import SeriesService from backend.app.services.tag_service import TagService pytestmark = pytest.mark.integration _SEQ = 0 async def _artist(db, name): a = Artist(name=name, slug=name.lower().replace(" ", "-")) db.add(a) await db.flush() return a async def _post(db, artist, title, ext): p = Post(artist_id=artist.id, external_post_id=ext, post_title=title) db.add(p) await db.flush() return p async def _post_images(db, post, artist, n): global _SEQ ids = [] for _ in range(n): _SEQ += 1 rec = ImageRecord( path=f"/tmp/fc_pend_{_SEQ}.png", sha256=f"{_SEQ:064d}", size_bytes=1, mime="image/png", origin="downloaded", primary_post_id=post.id, artist_id=artist.id, ) db.add(rec) await db.flush() ids.append(rec.id) return ids async def _placed_order(db, sid): rows = ( await db.execute( select(SeriesPage.image_id) .where( SeriesPage.series_tag_id == sid, SeriesPage.status == "placed", ) .order_by(SeriesPage.page_number) ) ).scalars().all() return list(rows) async def _setup(db, name, seed_n=2): svc = SeriesService(db) artist = await _artist(db, name + " Artist") sid = (await TagService(db).find_or_create(name, TagKind.series)).id seed = [] if seed_n: seed_post = await _post(db, artist, name + " seed", name + "-s") seed = await _post_images(db, seed_post, artist, seed_n) await svc.add_images(sid, seed) return svc, artist, sid, seed @pytest.mark.asyncio async def test_place_pending_appends_and_flips_status(db): svc, artist, sid, seed = await _setup(db, "PA") post = await _post(db, artist, "PA pages", "pa-p") imgs = await _post_images(db, post, artist, 3) await svc.add_post(sid, post.id) placed = await svc.place_pending(sid, imgs, start_page=3) assert placed == 3 assert await _placed_order(db, sid) == seed + imgs # nothing left pending; the placed pages are numbered 1,2 (seed) + 3,4,5 data = await svc.list_pages(sid) assert data["pending"] == [] assert [p["page_number"] for p in data["pages"]] == [1, 2, 3, 4, 5] @pytest.mark.asyncio async def test_place_pending_numbers_from_start(db): svc, artist, sid, seed = await _setup(db, "PB") # seed numbered 1, 2 post = await _post(db, artist, "PB pages", "pb-p") x, y = await _post_images(db, post, artist, 2) await svc.add_post(sid, post.id) await svc.place_pending(sid, [x, y], start_page=10) data = await svc.list_pages(sid) nums = {p["image_id"]: p["page_number"] for p in data["pages"]} assert nums[x] == 10 assert nums[y] == 11 # order = seed (1, 2) then x (10), y (11) assert [p["image_id"] for p in data["pages"]] == seed + [x, y] @pytest.mark.asyncio async def test_place_pending_ignores_already_placed(db): svc, artist, sid, seed = await _setup(db, "PC", seed_n=1) # the seed page is already placed — place_pending is a no-op for it. assert await svc.place_pending(sid, [seed[0]]) == 0 assert await _placed_order(db, sid) == seed @pytest.mark.asyncio async def test_remove_drops_a_pending_junk_page(db): svc, artist, sid, _ = await _setup(db, "PD", seed_n=0) post = await _post(db, artist, "PD pages", "pd-p") x, y, z = await _post_images(db, post, artist, 3) await svc.add_post(sid, post.id) # y is junk (a text-free alt) — drop it before placing. await svc.remove_images(sid, [y]) data = await svc.list_pages(sid) assert [pg["image_id"] for pg in data["pending"][0]["pages"]] == [x, z] @pytest.mark.asyncio async def test_place_pending_route_requires_ids(client): r = await client.post( "/api/tags", json={"name": "PReq", "kind": "series"} ) sid = (await r.get_json())["id"] resp = await client.post(f"/api/series/{sid}/pending/place", json={}) assert resp.status_code == 400