mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 17:25:26 -04:00
d792b61722
The two delete-ordering tests did monkeypatch.chdir(tmp_path) and wrote the image under tmp_path/data/generated_images, but DATA_DIR (and therefore gallery_routes.GALLERY_IMAGE_DIR) is always an absolute path, so the delete resolver pointed at the repo's real data dir and ignored the chdir. test_file_removed_on_successful_delete therefore failed on dev (the file at the tmp path was never the one being removed), and test_file_kept_when_commit_fails passed only by accident. Set GALLERY_IMAGE_DIR to the seeded tmp dir via monkeypatch so both tests exercise the real path and pass deterministically.
86 lines
3.4 KiB
Python
86 lines
3.4 KiB
Python
"""Regression: deleting a gallery image must not remove the file before the DB
|
|
commit succeeds.
|
|
|
|
delete_gallery_image() removed the on-disk file first and only then set
|
|
is_active=False and committed. If that commit failed and rolled back, the record
|
|
stayed active but its file was already gone — a broken, unviewable image (data
|
|
loss). The file is now removed only after the soft-delete commit succeeds, and
|
|
best-effort so a missing/locked file can't fail an otherwise-successful delete.
|
|
"""
|
|
import asyncio
|
|
|
|
import pytest
|
|
from fastapi import HTTPException, Request
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from core.database import Base, GalleryImage
|
|
import routes.gallery_routes as gallery_routes
|
|
|
|
|
|
def _delete_endpoint():
|
|
router = gallery_routes.setup_gallery_routes()
|
|
for route in router.routes:
|
|
if getattr(route, "path", "") == "/api/gallery/{image_id}" and "DELETE" in getattr(route, "methods", set()):
|
|
return route.endpoint
|
|
raise AssertionError("DELETE /api/gallery/{image_id} endpoint not found")
|
|
|
|
|
|
def _seed(tmp_path):
|
|
engine = create_engine("sqlite:///:memory:")
|
|
Base.metadata.create_all(bind=engine)
|
|
SessionLocal = sessionmaker(bind=engine)
|
|
db = SessionLocal()
|
|
db.add(GalleryImage(id="img-1", filename="x.png", owner="alice", is_active=True))
|
|
db.commit()
|
|
db.close()
|
|
img_dir = tmp_path / "data" / "generated_images"
|
|
img_dir.mkdir(parents=True)
|
|
(img_dir / "x.png").write_bytes(b"image-bytes")
|
|
return SessionLocal
|
|
|
|
|
|
def test_file_kept_when_commit_fails(tmp_path, monkeypatch):
|
|
SessionLocal = _seed(tmp_path)
|
|
# GALLERY_IMAGE_DIR is an absolute path fixed at import, so a chdir can't
|
|
# redirect the delete; point the resolver at the seeded tmp dir directly.
|
|
monkeypatch.setattr(gallery_routes, "GALLERY_IMAGE_DIR", tmp_path / "data" / "generated_images")
|
|
monkeypatch.setattr(gallery_routes, "get_current_user", lambda r: "alice")
|
|
|
|
# A session whose commit always fails, to simulate a DB error mid-delete.
|
|
sess = SessionLocal()
|
|
|
|
def _boom():
|
|
raise RuntimeError("commit failed")
|
|
|
|
monkeypatch.setattr(sess, "commit", _boom)
|
|
monkeypatch.setattr(gallery_routes, "SessionLocal", lambda: sess)
|
|
|
|
delete = _delete_endpoint()
|
|
with pytest.raises(HTTPException):
|
|
asyncio.run(delete(Request(scope={"type": "http"}), "img-1"))
|
|
|
|
# File must survive a failed commit — the record is still active after rollback.
|
|
assert (tmp_path / "data" / "generated_images" / "x.png").exists()
|
|
check = SessionLocal()
|
|
row = check.query(GalleryImage).filter(GalleryImage.id == "img-1").first()
|
|
assert row.is_active is True
|
|
check.close()
|
|
|
|
|
|
def test_file_removed_on_successful_delete(tmp_path, monkeypatch):
|
|
SessionLocal = _seed(tmp_path)
|
|
monkeypatch.setattr(gallery_routes, "GALLERY_IMAGE_DIR", tmp_path / "data" / "generated_images")
|
|
monkeypatch.setattr(gallery_routes, "get_current_user", lambda r: "alice")
|
|
monkeypatch.setattr(gallery_routes, "SessionLocal", SessionLocal)
|
|
|
|
delete = _delete_endpoint()
|
|
result = asyncio.run(delete(Request(scope={"type": "http"}), "img-1"))
|
|
|
|
assert result["status"] == "deleted"
|
|
assert not (tmp_path / "data" / "generated_images" / "x.png").exists()
|
|
check = SessionLocal()
|
|
row = check.query(GalleryImage).filter(GalleryImage.id == "img-1").first()
|
|
assert row.is_active is False
|
|
check.close()
|