test: D1-13 cover DiscardPostChangesSideEffects FTS re-sync after discard

This commit is contained in:
2026-05-30 09:15:15 +02:00
parent 925fe97007
commit 56caa653bb
2 changed files with 47 additions and 1 deletions

View File

@@ -127,7 +127,7 @@ All reconciled to follow code. Specs must be self-consistent and match code.
| D1-10 | ~~TransformPipelineContinuation~~ | script.allium:247-249 | **Resolved:** added focused test in `transforms_test.exs` — a failing *first* transform (no prior valid state) does not halt the pipeline: the original input survives, a later enabled transform still runs against it, and every failure is captured per-script in pipeline order tagged with its slug |
| D1-11 | ~~ChatContextTruncation invariant~~ | ai.allium:375-379 | **Resolved:** test added in `ai_test.exs` — a catalog model with a 2,000-token context window plus 40 large seeded turns forces truncation; the captured chat request keeps the system prompt as the first message, drops the oldest pairs first (surviving markers form a contiguous newest suffix, oldest absent), and always retains the newest user turn |
| D1-12 | ~~BoundedToolLoop enforcement~~ | ai.allium:381-385 | **Resolved:** the round cap is now read from `config.chat_max_tool_rounds` (`config :bds, :chat, max_tool_rounds: 10`) via `chat_max_tool_rounds/0` in chat.ex instead of a hardcoded attribute, matching the spec wording; test added in `ai_test.exs` — a `LoopingToolRuntime` that always returns another tool call (never a final answer) with `max_tool_rounds: 3` ends with `{:error, %{kind: :tool_loop_exhausted}}` after exactly 3 runtime calls (the `rounds_left == 0` round short-circuits before contacting the runtime) |
| D1-13 | DiscardPostChangesSideEffects | engine_side_effects.allium:99-104 | Write test: FTS updated after discard |
| D1-13 | ~~DiscardPostChangesSideEffects~~ | engine_side_effects.allium:99-104 | **Resolved:** test added in `posts_test.exs` — a published post is dirtied with unsaved title/content edits (re-indexing the dirty text in FTS), then `discard_post_changes/1` restores the published file version (status=published, content=nil, original title) and re-syncs the FTS index so the published terms are searchable again and the discarded edits are gone |
| D1-14 | ReplaceMediaFileSideEffects | engine_side_effects.allium:128-134 | Write test: file replaced, thumbnails regenerated |
| D1-15 | Drag-and-drop image chain | action_patterns.allium:84-103 | Write integration test |
| D1-16 | DebouncedPersistence (5s) | embedding.allium:204-208 | Write test: index persistence debounced |

View File

@@ -446,6 +446,52 @@ defmodule BDS.PostsTest do
assert {:error, :not_found} = BDS.Posts.unarchive_post(Ecto.UUID.generate())
end
test "discard_post_changes restores the published version from file and updates the FTS index" do
temp_dir =
Path.join(System.tmp_dir!(), "bds-post-discard-#{System.unique_integer([:positive])}")
File.mkdir_p!(temp_dir)
on_exit(fn -> File.rm_rf(temp_dir) end)
assert {:ok, project} =
BDS.Projects.create_project(%{name: "Discard FTS", data_path: temp_dir})
assert {:ok, post} =
BDS.Posts.create_post(%{
project_id: project.id,
title: "Pristine Headline",
content: "pristine meadow body"
})
assert {:ok, published} = BDS.Posts.publish_post(post.id)
assert published.status == :published
assert published.content == nil
# The published file is the source of truth; the FTS index reflects it.
assert {:ok, %{posts: [%{id: post_id}]}} = BDS.Search.search_posts(project.id, "pristine")
assert post_id == post.id
# Make the DB diverge from the file with unsaved edits, re-indexing the dirty text.
assert {:ok, dirty} =
BDS.Posts.update_post(post.id, %{
title: "Tampered Headline",
content: "tampered swamp body"
})
assert dirty.content == "tampered swamp body"
assert {:ok, %{posts: [%{id: ^post_id}]}} = BDS.Search.search_posts(project.id, "tampered")
assert {:ok, %{posts: []}} = BDS.Search.search_posts(project.id, "pristine")
# Discarding restores the published file version and re-syncs the FTS index.
assert {:ok, restored} = BDS.Posts.discard_post_changes(post.id)
assert restored.status == :published
assert restored.content == nil
assert restored.title == "Pristine Headline"
assert {:ok, %{posts: [%{id: ^post_id}]}} = BDS.Search.search_posts(project.id, "pristine")
assert {:ok, %{posts: []}} = BDS.Search.search_posts(project.id, "tampered")
end
test "rebuild_posts_from_files recreates published posts from disk" do
temp_dir =
Path.join(System.tmp_dir!(), "bds-post-rebuild-#{System.unique_integer([:positive])}")