Update Last Changed only when Bindery stores a new chapter or adds a new chapter revision. Manual ePub generation, rebuilds, metadata edits and rule changes leave it unchanged. Save the timestamp in the same transaction as the chapter or revision. This preserves the date if a later build fails and prevents retries from moving it forward without another chapter change. Add regression tests for new chapters, revisions, forced rebuilds, failed builds, retries and transaction rollback. Adjust OPDS test data setup and document what Last Changed records.
67 lines
3.0 KiB
Python
67 lines
3.0 KiB
Python
"""Last Changed records stored chapters and revisions, not changes to the built book."""
|
|
|
|
import sqlite3
|
|
|
|
import pytest
|
|
|
|
from bindery import store as storage
|
|
|
|
|
|
@pytest.fixture
|
|
def state(tmp_path, monkeypatch):
|
|
clock = ["2024-01-01T00:00:00"]
|
|
monkeypatch.setattr(storage, "now", lambda: clock[0])
|
|
store = storage.Store(tmp_path / "stories.db")
|
|
sid = store.create_story("story", "site", "Title", "Author", [])
|
|
return store, sid, clock
|
|
|
|
|
|
@pytest.mark.parametrize("source", ["new", "import"])
|
|
def test_new_chapters_and_revisions_set_the_timestamp_in_the_store(state, source):
|
|
store, sid, clock = state
|
|
other = store.create_story("other", "site", "Other", "Author", [])
|
|
assert store.get_story(sid)["last_changed"] is None
|
|
first = store.add_chapter(sid, "one", "One", "first", "h1", source, 0)
|
|
assert store.get_story(sid)["last_changed"] == clock[0]
|
|
assert store.get_chapter(first)["added_at"] == clock[0]
|
|
clock[0] = "2024-02-01T00:00:00"
|
|
store.add_chapters(sid, [{"url": "two", "title": "Two", "html": "second", "text_hash": "h2"}], source, [])
|
|
assert store.get_story(sid)["last_changed"] == clock[0]
|
|
clock[0] = "2024-03-01T00:00:00"
|
|
store.add_revision(first, "revised", "h3", "recheck")
|
|
assert store.get_story(sid)["last_changed"] == clock[0]
|
|
assert store.get_chapter(first)["changed_at"] == clock[0]
|
|
assert store.list_revisions(first)[-1]["fetched_at"] == clock[0]
|
|
assert storage.Store(store.path).get_story(sid)["last_changed"] == clock[0]
|
|
assert store.get_story(other)["last_changed"] is None
|
|
|
|
|
|
def test_changes_without_new_chapters_or_revisions_keep_the_timestamp(state):
|
|
store, sid, clock = state
|
|
first = store.add_chapter(sid, "one", "One", "first", "h1", "new", 0)
|
|
second = store.add_chapter(sid, "two", "Two", "second", "h2", "new", 1)
|
|
store.add_revision(first, "revised", "h3", "recheck")
|
|
previous = clock[0]
|
|
clock[0] = "2024-02-01T00:00:00"
|
|
store.update_story(sid, title="Edited", pinned={"title": "Edited"})
|
|
store.set_rules(sid, [{"pattern": "first", "replacement": "1st"}])
|
|
store.update_chapter(first, title="Renamed", included=False, on_site=False, current_rev=1)
|
|
store.replace_revision(first, 1, "original site text", "h4")
|
|
store.reorder_chapters(sid, lambda order: list(reversed(order)))
|
|
store.add_chapters(sid, [], "import", ["one", "two"])
|
|
store.delete_revision(first, 2)
|
|
store.delete_chapters(sid, [second])
|
|
assert store.get_story(sid)["last_changed"] == previous
|
|
|
|
|
|
def test_a_failed_chapter_transaction_does_not_change_the_timestamp(state):
|
|
store, sid, clock = state
|
|
store.add_chapter(sid, "one", "One", "first", "h1", "new", 0)
|
|
previous = clock[0]
|
|
clock[0] = "2024-02-01T00:00:00"
|
|
chapters = [{"url": url, "title": url, "html": "text", "text_hash": "h"} for url in ("two", "one")]
|
|
with pytest.raises(sqlite3.IntegrityError):
|
|
store.add_chapters(sid, chapters, "import", [])
|
|
assert store.get_story(sid)["last_changed"] == previous
|
|
assert [chapter["url"] for chapter in store.list_chapters(sid)] == ["one"]
|