fix: issue #6 post bodies missing for asides

This commit is contained in:
2026-07-05 12:24:30 +02:00
parent fcc574428c
commit 7fa6e6232c
19 changed files with 985 additions and 788 deletions

View File

@@ -438,6 +438,63 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert validation_task.result.payload.updated_post_url_paths == []
end
test "force_render_site rewrites drifted outputs that a normal render would skip", %{
project: project,
temp_dir: temp_dir
} do
assert {:ok, _metadata} =
Metadata.update_project_metadata(project.id, %{
public_url: "https://example.com/blog",
main_language: "en",
blog_languages: ["en"]
})
assert {:ok, post} =
Posts.create_post(%{
project_id: project.id,
title: "Force Render Post",
content: "Force render body",
language: "en",
categories: ["notes"],
tags: ["elixir"]
})
assert {:ok, published} = Posts.publish_post(post.id)
# Populate outputs and hashes, then tamper with a file on disk so its
# stored hash still matches the expected content.
assert {:ok, _} = BDS.Generation.render_site_section(project.id, :single)
post_file = Path.join([temp_dir, "html", BDS.Generation.post_output_path(published)])
original_html = File.read!(post_file)
File.write!(post_file, "TAMPERED")
assert {:ok, result} = ShellCommands.execute("force_render_site")
assert result.kind == "task_queued"
assert result.action == "force_render_site"
assert result.title == "Force Render Site"
assert is_binary(result.task_group_id)
tasks =
wait_for_tasks_by_name(
[
"Render Site Core",
"Render Single Posts",
"Render Category Archives",
"Render Tag Archives",
"Render Date Archives",
"Build Search Index"
],
&(&1.status == :completed),
5_000
)
assert Enum.all?(tasks, &(&1.group_id == result.task_group_id))
assert Enum.all?(tasks, &(&1.group_name == "Force Render Site"))
assert File.read!(post_file) == original_html
end
test "generate_sitemap renders the site as per-section tasks under a Render Site group", %{
project: project,
temp_dir: temp_dir

View File

@@ -130,11 +130,23 @@ defmodule BDS.DesktopTest do
assert menu_item(groups, :publish_selected).native_label == "Publish Selected\tCTRL+SHIFT+P"
assert menu_item(groups, :preview_post).native_label == "Preview Post\tCTRL+SHIFT+V"
assert menu_item(groups, :generate_sitemap).native_label == "Generate Site\tCTRL+R"
assert menu_item(groups, :force_render_site).native_label ==
"Force Render Site\tCTRL+SHIFT+R"
assert menu_item(groups, :validate_site).native_label == "Validate Site\tCTRL+SHIFT+L"
assert menu_item(groups, :upload_site).native_label == "Upload Site\tCTRL+SHIFT+U"
assert menu_item(groups, :metadata_diff).shortcut == nil
end
test "force render sits directly below generate site in the blog menu" do
blog_group = Enum.find(BDS.UI.MenuBar.default_groups(), &(&1.id == :blog))
item_ids = Enum.map(blog_group.items, &Map.get(&1, :id))
generate_index = Enum.find_index(item_ids, &(&1 == :generate_sitemap))
assert Enum.at(item_ids, generate_index + 1) == :force_render_site
end
test "prod forwarded menu surface is covered by the shell dispatcher" do
forwarded_actions =
BDS.Desktop.MenuBar.groups(dev_mode?: false)

View File

@@ -0,0 +1,53 @@
defmodule BDS.Generation.AsideArchiveRenderingTest do
use ExUnit.Case, async: false
alias BDS.Generation
setup do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
temp_dir = Path.join(System.tmp_dir!(), "bds-aside-#{System.unique_integer([:positive])}")
File.mkdir_p!(temp_dir)
on_exit(fn -> File.rm_rf(temp_dir) end)
{:ok, project} = BDS.Projects.create_project(%{name: "AsideArchives", data_path: temp_dir})
{:ok, _} = BDS.Metadata.update_project_metadata(project.id, %{main_language: "en"})
{:ok, post} =
BDS.Posts.create_post(%{
project_id: project.id,
title: "Aside Title Marker",
content: "ASIDE BODY MARKER text of the aside",
categories: ["aside"],
tags: ["asides-tag"],
language: "en"
})
{:ok, _} = BDS.Posts.publish_post(post.id)
%{project: project, temp_dir: temp_dir}
end
defp read_archive(temp_dir, segments) do
path = Path.join([temp_dir, "html"] ++ segments ++ ["index.html"])
assert File.exists?(path), "expected archive page at #{Enum.join(segments, "/")}"
File.read!(path)
end
test "aside bodies render in category, tag, and date archives alike", %{
project: project,
temp_dir: temp_dir
} do
{:ok, _} = Generation.render_site_section(project.id, :category)
{:ok, _} = Generation.render_site_section(project.id, :tag)
{:ok, _} = Generation.render_site_section(project.id, :date)
year = Integer.to_string(DateTime.utc_now().year)
date_html = read_archive(temp_dir, [year])
category_html = read_archive(temp_dir, ["category", "aside"])
tag_html = read_archive(temp_dir, ["tag", "asides-tag"])
assert date_html =~ "ASIDE BODY MARKER"
assert category_html =~ "ASIDE BODY MARKER"
assert tag_html =~ "ASIDE BODY MARKER"
end
end

View File

@@ -74,6 +74,29 @@ defmodule BDS.Rendering.RenderContextTest do
assert Agent.get(counter, & &1) == 1
end
test "memoize/3 computes once even under concurrent first calls", %{project: project} do
ctx = RenderContext.build(project.id)
{:ok, counter} = Agent.start_link(fn -> 0 end)
slow_compute = fn ->
Agent.update(counter, &(&1 + 1))
Process.sleep(30)
:computed
end
results =
1..8
|> Task.async_stream(
fn _index -> RenderContext.memoize(ctx, {:test, "concurrent"}, slow_compute) end,
max_concurrency: 8,
timeout: :infinity
)
|> Enum.map(fn {:ok, value} -> value end)
assert Enum.all?(results, &(&1 == :computed))
assert Agent.get(counter, & &1) == 1
end
test "render_post_page with a context matches the project_id render", %{project: project} do
{:ok, template} =
BDS.Templates.create_template(%{
@@ -227,6 +250,27 @@ defmodule BDS.Rendering.RenderContextTest do
end)
end
test "force render rewrites outputs whose stored hash still matches", %{
project: project,
temp_dir: temp_dir
} do
post = create_published_post(project, %{title: "Force Post", content: "Force body"})
assert {:ok, _} = Generation.render_site_section(project.id, :single)
post_file = Path.join([temp_dir, "html", Generation.post_output_path(post)])
original_html = File.read!(post_file)
File.write!(post_file, "TAMPERED")
# A normal render trusts the stored hash and leaves the drifted file alone.
assert {:ok, _} = Generation.render_site_section(project.id, :single)
assert File.read!(post_file) == "TAMPERED"
# A forced render ignores the stored hashes and rewrites everything.
assert {:ok, _} = Generation.render_site_section(project.id, :single, force: true)
assert File.read!(post_file) == original_html
end
test "full section render issues a bounded number of queries regardless of post count", %{
project: project
} do