import shutil import tempfile from pathlib import Path from django.contrib.auth import get_user_model from django.test import TestCase, override_settings from artifacts.models import Artifact, UserProfile from artifacts.storage import QuotaExceeded, make_room_for User = get_user_model() @override_settings() class MakeRoomForTestCase(TestCase): @classmethod def setUpClass(cls): super().setUpClass() cls.artifacts_root = Path(tempfile.mkdtemp(prefix="agentfeed-test-storage-")) cls.settings_override = override_settings(ARTIFACTS_ROOT=cls.artifacts_root) cls.settings_override.enable() @classmethod def tearDownClass(cls): cls.settings_override.disable() shutil.rmtree(cls.artifacts_root, ignore_errors=True) super().tearDownClass() def setUp(self): self.user = User.objects.create_user(username="quota-test", password="pw") def make_artifact(self, artifact_id, size_bytes, is_kept=False): return Artifact.objects.create( id=artifact_id, owner=self.user, source="test", title=f"Artifact {artifact_id}", kind=Artifact.MARKDOWN, day="2026-01-01", path=f"test/2026-01-01/{artifact_id}.html", original_kind=Artifact.MARKDOWN, size_bytes=size_bytes, is_kept=is_kept, ) def set_quota(self, quota_bytes): UserProfile.objects.update_or_create(user=self.user, defaults={"quota_bytes": quota_bytes}) def test_unwinnable_eviction_deletes_nothing(self): """Regression test for issue #006: a doomed post must not destroy unkept artifacts on its way to still failing.""" self.set_quota(1000) self.make_artifact("kept0001", size_bytes=900, is_kept=True) self.make_artifact("unkept01", size_bytes=50) self.make_artifact("unkept02", size_bytes=40) with self.assertRaises(QuotaExceeded): make_room_for(self.user, incoming_bytes=200) self.assertTrue(Artifact.objects.filter(id="unkept01").exists()) self.assertTrue(Artifact.objects.filter(id="unkept02").exists()) self.assertTrue(Artifact.objects.filter(id="kept0001").exists()) def test_successful_eviction_frees_only_what_is_needed(self): self.set_quota(1000) self.make_artifact("kept0001", size_bytes=500, is_kept=True) self.make_artifact("oldest01", size_bytes=200) self.make_artifact("newest01", size_bytes=200) Artifact.objects.filter(id="oldest01").update(created_at="2026-01-01T00:00:00Z") Artifact.objects.filter(id="newest01").update(created_at="2026-06-01T00:00:00Z") evicted = make_room_for(self.user, incoming_bytes=300) self.assertEqual(set(evicted), {"oldest01"}) self.assertFalse(Artifact.objects.filter(id="oldest01").exists()) self.assertTrue(Artifact.objects.filter(id="newest01").exists()) self.assertTrue(Artifact.objects.filter(id="kept0001").exists()) def test_incoming_larger_than_quota_deletes_nothing(self): self.set_quota(1000) self.make_artifact("unkept01", size_bytes=50) with self.assertRaises(QuotaExceeded): make_room_for(self.user, incoming_bytes=2000) self.assertTrue(Artifact.objects.filter(id="unkept01").exists())