Removes the "Daybook" name from everything user- and agent-facing, since no agents are configured yet and this is the cheap moment to do it. - UI brand, docs, README, CLAUDE.md -> AgentFeed. - Ingest env vars DAYBOOK_URL/TOKEN/SOURCE/DATA_DIR/BIND_ADDR/HOST_DATA_DIR/ KEY_PROVISIONING_API -> AGENTFEED_* (token *values* unchanged). - Skill: skills/daybook-posting -> skills/agentfeed-posting, daybook_post.py -> agentfeed_post.py (+ scripts/ wrapper and its test). - static/daybook.css -> static/agentfeed.css; localStorage daybook.* -> agentfeed.*; templatetag daybook_extras -> agentfeed_extras; multipart boundaries; json/csv viewer element ids; daybook_exception_handler -> agentfeed_exception_handler; compose service/container -> agentfeed. - Key-provisioning API now OFF by default (independent of DEBUG); enable with AGENTFEED_KEY_PROVISIONING_API=1 on test installs only. - Posting helper now points at Settings -> API Keys. Deferred on purpose (a later, separate step): the Django package `daybook_project`, the `/opt/daybook` install path, and the Forgejo repo name. The sibling AGENT_DAYBOOK__LLM_TEST name is intentionally untouched. Full suite: 129 passing; live server verified serving under the new name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
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())
|