feat: more complete metadata diff, scp publishing and rendering context
This commit is contained in:
@@ -10,7 +10,10 @@ defmodule BDS.GenerationTest do
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-generation-#{System.unique_integer([:positive])}")
|
||||
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-generation-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
@@ -18,8 +21,13 @@ defmodule BDS.GenerationTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "write_generated_file writes under html output and skips unchanged content by hash", %{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, first_write} = BDS.Generation.write_generated_file(project.id, "index.html", "<html>hello</html>")
|
||||
test "write_generated_file writes under html output and skips unchanged content by hash", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, first_write} =
|
||||
BDS.Generation.write_generated_file(project.id, "index.html", "<html>hello</html>")
|
||||
|
||||
assert first_write.written? == true
|
||||
|
||||
output_path = Path.join([temp_dir, "html", "index.html"])
|
||||
@@ -29,18 +37,30 @@ defmodule BDS.GenerationTest do
|
||||
assert tracked_file.relative_path == "index.html"
|
||||
assert tracked_file.content_hash == first_write.content_hash
|
||||
|
||||
assert {:ok, second_write} = BDS.Generation.write_generated_file(project.id, "index.html", "<html>hello</html>")
|
||||
assert {:ok, second_write} =
|
||||
BDS.Generation.write_generated_file(project.id, "index.html", "<html>hello</html>")
|
||||
|
||||
assert second_write.written? == false
|
||||
assert second_write.content_hash == first_write.content_hash
|
||||
|
||||
assert {:ok, third_write} = BDS.Generation.write_generated_file(project.id, "index.html", "<html>updated</html>")
|
||||
assert {:ok, third_write} =
|
||||
BDS.Generation.write_generated_file(project.id, "index.html", "<html>updated</html>")
|
||||
|
||||
assert third_write.written? == true
|
||||
assert third_write.content_hash != first_write.content_hash
|
||||
assert File.read!(output_path) == "<html>updated</html>"
|
||||
end
|
||||
|
||||
test "delete_generated_file removes tracked output and forgets its hash", %{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, _write} = BDS.Generation.write_generated_file(project.id, "tag/elixir/index.html", "<html>tag</html>")
|
||||
test "delete_generated_file removes tracked output and forgets its hash", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, _write} =
|
||||
BDS.Generation.write_generated_file(
|
||||
project.id,
|
||||
"tag/elixir/index.html",
|
||||
"<html>tag</html>"
|
||||
)
|
||||
|
||||
output_path = Path.join([temp_dir, "html", "tag", "elixir", "index.html"])
|
||||
assert File.exists?(output_path)
|
||||
@@ -52,7 +72,8 @@ defmodule BDS.GenerationTest do
|
||||
assert files == []
|
||||
end
|
||||
|
||||
test "plan_generation derives generation settings from project metadata and core generation writes tracked files", %{project: project, temp_dir: temp_dir} do
|
||||
test "plan_generation derives generation settings from project metadata and core generation writes tracked files",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
public_url: "https://example.com/blog",
|
||||
@@ -88,7 +109,8 @@ defmodule BDS.GenerationTest do
|
||||
"de/atom.xml"
|
||||
]
|
||||
|
||||
assert Enum.sort(Enum.map(result.generated_files, & &1.relative_path)) == Enum.sort(expected_paths)
|
||||
assert Enum.sort(Enum.map(result.generated_files, & &1.relative_path)) ==
|
||||
Enum.sort(expected_paths)
|
||||
|
||||
for relative_path <- expected_paths do
|
||||
assert File.exists?(Path.join([temp_dir, "html", relative_path]))
|
||||
@@ -97,7 +119,10 @@ defmodule BDS.GenerationTest do
|
||||
assert File.read!(Path.join([temp_dir, "html", "sitemap.xml"])) =~ "https://example.com/blog/"
|
||||
end
|
||||
|
||||
test "generation renders published list and post templates for core and single pages", %{project: project, temp_dir: temp_dir} do
|
||||
test "generation renders published list and post templates for core and single pages", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
public_url: "https://example.com/blog",
|
||||
@@ -110,7 +135,8 @@ defmodule BDS.GenerationTest do
|
||||
project_id: project.id,
|
||||
title: "List View",
|
||||
kind: :list,
|
||||
content: "<main class=\"list-template\"><h1>{{ page_title }}</h1>{% for post in posts %}<a href=\"{{ post.href }}\">{{ post.title }}</a>{% endfor %}</main>"
|
||||
content:
|
||||
"<main class=\"list-template\"><h1>{{ page_title }}</h1>{% for post in posts %}<a href=\"{{ post.href }}\">{{ post.title }}</a>{% endfor %}</main>"
|
||||
})
|
||||
|
||||
assert {:ok, _published_list} = BDS.Templates.publish_template(list_template.id)
|
||||
@@ -120,7 +146,8 @@ defmodule BDS.GenerationTest do
|
||||
project_id: project.id,
|
||||
title: "Post View",
|
||||
kind: :post,
|
||||
content: "<article class=\"post-template\"><h1>{{ post.title }}</h1><div class=\"body\">{{ post.content }}</div></article>"
|
||||
content:
|
||||
"<article class=\"post-template\"><h1>{{ post.title }}</h1><div class=\"body\">{{ post.content }}</div></article>"
|
||||
})
|
||||
|
||||
assert {:ok, published_post_template} = BDS.Templates.publish_template(post_template.id)
|
||||
@@ -153,7 +180,10 @@ defmodule BDS.GenerationTest do
|
||||
assert post_html =~ "Rendered body"
|
||||
end
|
||||
|
||||
test "generation renders copied starter templates with partials, i18n, and markdown", %{project: project, temp_dir: temp_dir} do
|
||||
test "generation renders copied starter templates with partials, i18n, and markdown", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, _menu} =
|
||||
BDS.Menu.update_menu(project.id, [
|
||||
%{kind: :page, label: "Notes", slug: "notes"}
|
||||
@@ -199,7 +229,8 @@ defmodule BDS.GenerationTest do
|
||||
assert post_html =~ "Language"
|
||||
end
|
||||
|
||||
test "generation expands starter-template markdown macros, rewrites canonical post links, media links, and emits not-found page", %{project: project, temp_dir: temp_dir} do
|
||||
test "generation expands starter-template markdown macros, rewrites canonical post links, media links, and emits not-found page",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
public_url: "https://example.com/blog",
|
||||
@@ -228,7 +259,10 @@ defmodule BDS.GenerationTest do
|
||||
assert {:ok, published_linked_post} = Posts.publish_post(linked_post.id)
|
||||
|
||||
media_source_reference = "/" <> Path.join(Path.dirname(media.file_path), media.original_name)
|
||||
canonical_post_href = "/" <> String.trim_trailing(BDS.Generation.post_output_path(published_linked_post), "index.html")
|
||||
|
||||
canonical_post_href =
|
||||
"/" <>
|
||||
String.trim_trailing(BDS.Generation.post_output_path(published_linked_post), "index.html")
|
||||
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
@@ -252,7 +286,9 @@ defmodule BDS.GenerationTest do
|
||||
|
||||
assert "404.html" in Enum.map(result.generated_files, & &1.relative_path)
|
||||
|
||||
post_html = File.read!(Path.join([temp_dir, "html", BDS.Generation.post_output_path(published_post)]))
|
||||
post_html =
|
||||
File.read!(Path.join([temp_dir, "html", BDS.Generation.post_output_path(published_post)]))
|
||||
|
||||
assert post_html =~ ~s(src="https://www.youtube.com/embed/dQw4w9WgXcQ?rel=0")
|
||||
assert post_html =~ ~s(href="#{canonical_post_href}")
|
||||
assert post_html =~ ~s(src="/#{media.file_path}")
|
||||
@@ -262,7 +298,10 @@ defmodule BDS.GenerationTest do
|
||||
assert not_found_html =~ "Back to preview home"
|
||||
end
|
||||
|
||||
test "single generation writes canonical post pages and language-prefixed translation pages", %{project: project, temp_dir: temp_dir} do
|
||||
test "single generation writes canonical post pages and language-prefixed translation pages", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
public_url: "https://example.com/blog",
|
||||
@@ -291,14 +330,18 @@ defmodule BDS.GenerationTest do
|
||||
post_path = BDS.Generation.post_output_path(published_post)
|
||||
translation_path = BDS.Generation.post_output_path(published_post, "de")
|
||||
|
||||
assert Enum.map(result.generated_files, & &1.relative_path) |> Enum.sort() == Enum.sort([post_path, translation_path])
|
||||
assert Enum.map(result.generated_files, & &1.relative_path) |> Enum.sort() ==
|
||||
Enum.sort([post_path, translation_path])
|
||||
|
||||
assert File.read!(Path.join([temp_dir, "html", post_path])) =~ "Hello generated world"
|
||||
assert File.read!(Path.join([temp_dir, "html", translation_path])) =~ "Hallo generierte Welt"
|
||||
assert File.read!(Path.join([temp_dir, "html", post_path])) =~ ~s(data-pagefind-body)
|
||||
end
|
||||
|
||||
test "archive generation writes paginated category, tag, and date pages", %{project: project, temp_dir: temp_dir} do
|
||||
test "archive generation writes paginated category, tag, and date pages", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
public_url: "https://example.com/blog",
|
||||
@@ -319,7 +362,11 @@ defmodule BDS.GenerationTest do
|
||||
})
|
||||
|
||||
created_at = DateTime.to_unix(~U[2026-04-15 12:00:00Z]) + index
|
||||
Repo.update_all(from(p in BDS.Posts.Post, where: p.id == ^post.id), set: [created_at: created_at, updated_at: created_at])
|
||||
|
||||
Repo.update_all(from(p in BDS.Posts.Post, where: p.id == ^post.id),
|
||||
set: [created_at: created_at, updated_at: created_at]
|
||||
)
|
||||
|
||||
assert {:ok, _published} = Posts.publish_post(post.id)
|
||||
end
|
||||
|
||||
@@ -341,8 +388,13 @@ defmodule BDS.GenerationTest do
|
||||
|
||||
assert expected_paths -- Enum.map(result.generated_files, & &1.relative_path) == []
|
||||
|
||||
assert File.read!(Path.join([temp_dir, "html", "category", "notes", "index.html"])) =~ "Archive 1"
|
||||
assert File.read!(Path.join([temp_dir, "html", "category", "notes", "page", "2", "index.html"])) =~ "Archive 3"
|
||||
assert File.read!(Path.join([temp_dir, "html", "category", "notes", "index.html"])) =~
|
||||
"Archive 1"
|
||||
|
||||
assert File.read!(
|
||||
Path.join([temp_dir, "html", "category", "notes", "page", "2", "index.html"])
|
||||
) =~ "Archive 3"
|
||||
|
||||
assert File.read!(Path.join([temp_dir, "html", "tag", "elixir", "index.html"])) =~ "Elixir"
|
||||
assert File.read!(Path.join([temp_dir, "html", "2026", "04", "index.html"])) =~ "2026-04"
|
||||
end
|
||||
|
||||
@@ -7,7 +7,10 @@ defmodule BDS.MaintenanceTest do
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-maintenance-#{System.unique_integer([:positive])}")
|
||||
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-maintenance-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
@@ -15,7 +18,10 @@ defmodule BDS.MaintenanceTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "rebuild_from_filesystem dispatches to posts, media, scripts, and templates rebuilders", %{project: project, temp_dir: temp_dir} do
|
||||
test "rebuild_from_filesystem dispatches to posts, media, scripts, and templates rebuilders", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
posts_dir = Path.join([temp_dir, "posts", "2026", "04"])
|
||||
File.mkdir_p!(posts_dir)
|
||||
|
||||
@@ -60,6 +66,7 @@ defmodule BDS.MaintenanceTest do
|
||||
|
||||
template_dir = Path.join(temp_dir, "templates")
|
||||
File.mkdir_p!(template_dir)
|
||||
|
||||
File.write!(
|
||||
Path.join(template_dir, "dispatch-view.liquid"),
|
||||
[
|
||||
@@ -81,6 +88,7 @@ defmodule BDS.MaintenanceTest do
|
||||
|
||||
script_dir = Path.join(temp_dir, "scripts")
|
||||
File.mkdir_p!(script_dir)
|
||||
|
||||
File.write!(
|
||||
Path.join(script_dir, "dispatch.lua"),
|
||||
[
|
||||
@@ -120,10 +128,14 @@ defmodule BDS.MaintenanceTest do
|
||||
end
|
||||
|
||||
test "rebuild_from_filesystem rejects unsupported entity types", %{project: project} do
|
||||
assert {:error, :unsupported_entity_type} = BDS.Maintenance.rebuild_from_filesystem(project.id, "unknown")
|
||||
assert {:error, :unsupported_entity_type} =
|
||||
BDS.Maintenance.rebuild_from_filesystem(project.id, "unknown")
|
||||
end
|
||||
|
||||
test "metadata_diff reports field differences and orphan files across managed entities", %{project: project, temp_dir: temp_dir} do
|
||||
test "metadata_diff reports field differences and orphan files across managed entities", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello media")
|
||||
|
||||
@@ -192,6 +204,7 @@ defmodule BDS.MaintenanceTest do
|
||||
assert {:ok, published_template} = BDS.Templates.publish_template(template.id)
|
||||
|
||||
post_path = Path.join(temp_dir, published_post.file_path)
|
||||
|
||||
File.write!(
|
||||
post_path,
|
||||
[
|
||||
@@ -205,9 +218,9 @@ defmodule BDS.MaintenanceTest do
|
||||
"language: de",
|
||||
"do_not_translate: false",
|
||||
"template_slug: ",
|
||||
"created_at: #{published_post.created_at}",
|
||||
"updated_at: #{published_post.updated_at}",
|
||||
"published_at: #{published_post.published_at}",
|
||||
"created_at: #{published_post.created_at + 10}",
|
||||
"updated_at: #{published_post.updated_at + 20}",
|
||||
"published_at: #{published_post.published_at + 30}",
|
||||
"tags:",
|
||||
" - beta",
|
||||
"categories:",
|
||||
@@ -220,6 +233,7 @@ defmodule BDS.MaintenanceTest do
|
||||
)
|
||||
|
||||
post_translation_path = Path.join(temp_dir, published_post_translation.file_path)
|
||||
|
||||
File.write!(
|
||||
post_translation_path,
|
||||
[
|
||||
@@ -241,6 +255,7 @@ defmodule BDS.MaintenanceTest do
|
||||
)
|
||||
|
||||
media_sidecar_path = Path.join(temp_dir, media.sidecar_path)
|
||||
|
||||
File.write!(
|
||||
media_sidecar_path,
|
||||
[
|
||||
@@ -262,7 +277,9 @@ defmodule BDS.MaintenanceTest do
|
||||
|> Enum.join("\n")
|
||||
)
|
||||
|
||||
media_translation_sidecar_path = Path.join(temp_dir, "#{media.file_path}.#{media_translation.language}.meta")
|
||||
media_translation_sidecar_path =
|
||||
Path.join(temp_dir, "#{media.file_path}.#{media_translation.language}.meta")
|
||||
|
||||
File.write!(
|
||||
media_translation_sidecar_path,
|
||||
[
|
||||
@@ -277,6 +294,7 @@ defmodule BDS.MaintenanceTest do
|
||||
)
|
||||
|
||||
script_path = Path.join(temp_dir, published_script.file_path)
|
||||
|
||||
File.write!(
|
||||
script_path,
|
||||
[
|
||||
@@ -298,6 +316,7 @@ defmodule BDS.MaintenanceTest do
|
||||
)
|
||||
|
||||
template_path = Path.join(temp_dir, published_template.file_path)
|
||||
|
||||
File.write!(
|
||||
template_path,
|
||||
[
|
||||
@@ -317,50 +336,128 @@ defmodule BDS.MaintenanceTest do
|
||||
|> Enum.join("\n")
|
||||
)
|
||||
|
||||
File.write!(Path.join([temp_dir, "posts", "2026", "04", "orphan-post.md"]), "---\nid: orphan\ntitle: Orphan\nslug: orphan\nstatus: published\ncreated_at: 1\nupdated_at: 1\npublished_at: 1\ntags:\ncategories:\n---\nBody\n")
|
||||
File.write!(Path.join([temp_dir, "posts", "2026", "04", "orphan-post.es.md"]), "---\nid: orphan-post-translation\ntranslation_for: orphan\nlanguage: es\ntitle: Huerfano\nstatus: published\ncreated_at: 1\nupdated_at: 1\npublished_at: 1\n---\nCuerpo\n")
|
||||
File.write!(Path.join([temp_dir, "media", "2026", "04", "orphan.txt"]), "orphan")
|
||||
File.write!(Path.join([temp_dir, "media", "2026", "04", "orphan.txt.meta"]), "id: orphan-media\noriginal_name: orphan.txt\nmime_type: text/plain\nsize: 6\ncreated_at: 1\nupdated_at: 1\ntags:\n")
|
||||
File.write!(Path.join([temp_dir, "media", "2026", "04", "orphan.txt.es.meta"]), "translation_for: orphan-media\nlanguage: es\ntitle: Huerfano\nalt: Texto\ncaption: Leyenda\n")
|
||||
File.write!(Path.join([temp_dir, "scripts", "orphan.lua"]), "---\nid: orphan-script\nslug: orphan-script\ntitle: Orphan Script\nkind: utility\nentrypoint: main\nenabled: true\nversion: 1\ncreated_at: 1\nupdated_at: 1\n---\nfunction main() return true end\n")
|
||||
File.write!(Path.join([temp_dir, "templates", "orphan-view.liquid"]), "---\nid: orphan-template\nslug: orphan-view\ntitle: Orphan View\nkind: list\nenabled: true\nversion: 1\ncreated_at: 1\nupdated_at: 1\n---\n<section>Orphan</section>\n")
|
||||
File.write!(
|
||||
Path.join([temp_dir, "posts", "2026", "04", "orphan-post.md"]),
|
||||
"---\nid: orphan\ntitle: Orphan\nslug: orphan\nstatus: published\ncreated_at: 1\nupdated_at: 1\npublished_at: 1\ntags:\ncategories:\n---\nBody\n"
|
||||
)
|
||||
|
||||
assert {:ok, %{diff_reports: diff_reports, orphan_reports: orphan_reports}} = BDS.Maintenance.metadata_diff(project.id)
|
||||
File.write!(
|
||||
Path.join([temp_dir, "posts", "2026", "04", "orphan-post.es.md"]),
|
||||
"---\nid: orphan-post-translation\ntranslation_for: orphan\nlanguage: es\ntitle: Huerfano\nstatus: published\ncreated_at: 1\nupdated_at: 1\npublished_at: 1\n---\nCuerpo\n"
|
||||
)
|
||||
|
||||
File.write!(Path.join([temp_dir, "media", "2026", "04", "orphan.txt"]), "orphan")
|
||||
|
||||
File.write!(
|
||||
Path.join([temp_dir, "media", "2026", "04", "orphan.txt.meta"]),
|
||||
"id: orphan-media\noriginal_name: orphan.txt\nmime_type: text/plain\nsize: 6\ncreated_at: 1\nupdated_at: 1\ntags:\n"
|
||||
)
|
||||
|
||||
File.write!(
|
||||
Path.join([temp_dir, "media", "2026", "04", "orphan.txt.es.meta"]),
|
||||
"translation_for: orphan-media\nlanguage: es\ntitle: Huerfano\nalt: Texto\ncaption: Leyenda\n"
|
||||
)
|
||||
|
||||
File.write!(
|
||||
Path.join([temp_dir, "scripts", "orphan.lua"]),
|
||||
"---\nid: orphan-script\nslug: orphan-script\ntitle: Orphan Script\nkind: utility\nentrypoint: main\nenabled: true\nversion: 1\ncreated_at: 1\nupdated_at: 1\n---\nfunction main() return true end\n"
|
||||
)
|
||||
|
||||
File.write!(
|
||||
Path.join([temp_dir, "templates", "orphan-view.liquid"]),
|
||||
"---\nid: orphan-template\nslug: orphan-view\ntitle: Orphan View\nkind: list\nenabled: true\nversion: 1\ncreated_at: 1\nupdated_at: 1\n---\n<section>Orphan</section>\n"
|
||||
)
|
||||
|
||||
assert {:ok, %{diff_reports: diff_reports, orphan_reports: orphan_reports}} =
|
||||
BDS.Maintenance.metadata_diff(project.id)
|
||||
|
||||
assert Enum.any?(diff_reports, fn report ->
|
||||
report.entity_type == "post" and report.entity_id == published_post.id and
|
||||
Enum.any?(report.differences, &(&1.name == "title" and &1.db_value == "Original Post" and &1.file_value == "Edited Post")) and
|
||||
Enum.any?(report.differences, &(&1.name == "excerpt" and &1.db_value == "Original summary" and &1.file_value == "Edited summary"))
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "title" and &1.db_value == "Original Post" and
|
||||
&1.file_value == "Edited Post")
|
||||
) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "excerpt" and &1.db_value == "Original summary" and
|
||||
&1.file_value == "Edited summary")
|
||||
) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "created_at" and
|
||||
&1.file_value == Integer.to_string(published_post.created_at + 10))
|
||||
) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "updated_at" and
|
||||
&1.file_value == Integer.to_string(published_post.updated_at + 20))
|
||||
) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "published_at" and
|
||||
&1.file_value == Integer.to_string(published_post.published_at + 30))
|
||||
)
|
||||
end)
|
||||
|
||||
assert Enum.any?(diff_reports, fn report ->
|
||||
report.entity_type == "media" and report.entity_id == media.id and
|
||||
Enum.any?(report.differences, &(&1.name == "title" and &1.file_value == "Edited media title")) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "title" and &1.file_value == "Edited media title")
|
||||
) and
|
||||
Enum.any?(report.differences, &(&1.name == "language" and &1.file_value == "de"))
|
||||
end)
|
||||
|
||||
assert Enum.any?(diff_reports, fn report ->
|
||||
report.entity_type == "script" and report.entity_id == published_script.id and
|
||||
Enum.any?(report.differences, &(&1.name == "title" and &1.file_value == "Edited Script")) and
|
||||
Enum.any?(report.differences, &(&1.name == "entrypoint" and &1.file_value == "run"))
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "title" and &1.file_value == "Edited Script")
|
||||
) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "entrypoint" and &1.file_value == "run")
|
||||
)
|
||||
end)
|
||||
|
||||
assert Enum.any?(diff_reports, fn report ->
|
||||
report.entity_type == "template" and report.entity_id == published_template.id and
|
||||
Enum.any?(report.differences, &(&1.name == "title" and &1.file_value == "Edited Template")) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "title" and &1.file_value == "Edited Template")
|
||||
) and
|
||||
Enum.any?(report.differences, &(&1.name == "enabled" and &1.file_value == "false"))
|
||||
end)
|
||||
|
||||
assert Enum.any?(diff_reports, fn report ->
|
||||
report.entity_type == "post_translation" and report.entity_id == published_post_translation.id and
|
||||
Enum.any?(report.differences, &(&1.name == "title" and &1.db_value == "Ursprunglicher Beitrag" and &1.file_value == "Bearbeiteter Beitrag")) and
|
||||
Enum.any?(report.differences, &(&1.name == "excerpt" and &1.db_value == "Zusammenfassung" and &1.file_value == "Bearbeitete Zusammenfassung"))
|
||||
report.entity_type == "post_translation" and
|
||||
report.entity_id == published_post_translation.id and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "title" and &1.db_value == "Ursprunglicher Beitrag" and
|
||||
&1.file_value == "Bearbeiteter Beitrag")
|
||||
) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "excerpt" and &1.db_value == "Zusammenfassung" and
|
||||
&1.file_value == "Bearbeitete Zusammenfassung")
|
||||
)
|
||||
end)
|
||||
|
||||
assert Enum.any?(diff_reports, fn report ->
|
||||
report.entity_type == "media_translation" and report.entity_id == media_translation.id and
|
||||
Enum.any?(report.differences, &(&1.name == "title" and &1.db_value == "Ubersetzter Medientitel" and &1.file_value == "Bearbeiteter Medientitel")) and
|
||||
Enum.any?(report.differences, &(&1.name == "alt" and &1.db_value == "Ubersetzter Alt-Text" and &1.file_value == "Bearbeiteter Alt-Text"))
|
||||
report.entity_type == "media_translation" and
|
||||
report.entity_id == media_translation.id and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "title" and &1.db_value == "Ubersetzter Medientitel" and
|
||||
&1.file_value == "Bearbeiteter Medientitel")
|
||||
) and
|
||||
Enum.any?(
|
||||
report.differences,
|
||||
&(&1.name == "alt" and &1.db_value == "Ubersetzter Alt-Text" and
|
||||
&1.file_value == "Bearbeiteter Alt-Text")
|
||||
)
|
||||
end)
|
||||
|
||||
orphan_paths = Enum.map(orphan_reports, & &1.file_path)
|
||||
|
||||
@@ -13,7 +13,10 @@ defmodule BDS.MediaTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "import_media copies the binary, creates a sidecar, and persists the row", %{project: project, temp_dir: temp_dir} do
|
||||
test "import_media copies the binary, creates a sidecar, and persists the row", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello media")
|
||||
|
||||
@@ -54,7 +57,8 @@ defmodule BDS.MediaTest do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello media")
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
assert {:ok, updated} =
|
||||
BDS.Media.update_media(media.id, %{
|
||||
@@ -76,11 +80,15 @@ defmodule BDS.MediaTest do
|
||||
assert sidecar =~ "tags:\n - beta\n"
|
||||
end
|
||||
|
||||
test "delete_media removes the binary, sidecar, and database row", %{project: project, temp_dir: temp_dir} do
|
||||
test "delete_media removes the binary, sidecar, and database row", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello media")
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
assert {:ok, _translation} =
|
||||
BDS.Media.upsert_media_translation(media.id, "de", %{
|
||||
@@ -103,7 +111,10 @@ defmodule BDS.MediaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "rebuild_media_from_files recreates media rows from sidecars", %{project: project, temp_dir: temp_dir} do
|
||||
test "rebuild_media_from_files recreates media rows from sidecars", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
media_dir = Path.join([temp_dir, "media", "2026", "04"])
|
||||
File.mkdir_p!(media_dir)
|
||||
|
||||
@@ -181,16 +192,27 @@ defmodule BDS.MediaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "import_media generates the four thumbnail files in bucketed thumbnail paths", %{project: project, temp_dir: temp_dir} do
|
||||
test "import_media generates the four thumbnail files in bucketed thumbnail paths", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
source_path = Path.join(temp_dir, "sample.jpg")
|
||||
File.write!(source_path, tiny_jpeg_binary())
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
thumbnail_paths = BDS.Media.thumbnail_paths(media)
|
||||
assert thumbnail_paths.small == "thumbnails/#{String.slice(media.id, 0, 2)}/#{media.id}-small.webp"
|
||||
assert thumbnail_paths.medium == "thumbnails/#{String.slice(media.id, 0, 2)}/#{media.id}-medium.webp"
|
||||
assert thumbnail_paths.large == "thumbnails/#{String.slice(media.id, 0, 2)}/#{media.id}-large.webp"
|
||||
|
||||
assert thumbnail_paths.small ==
|
||||
"thumbnails/#{String.slice(media.id, 0, 2)}/#{media.id}-small.webp"
|
||||
|
||||
assert thumbnail_paths.medium ==
|
||||
"thumbnails/#{String.slice(media.id, 0, 2)}/#{media.id}-medium.webp"
|
||||
|
||||
assert thumbnail_paths.large ==
|
||||
"thumbnails/#{String.slice(media.id, 0, 2)}/#{media.id}-large.webp"
|
||||
|
||||
assert thumbnail_paths.ai == "thumbnails/#{String.slice(media.id, 0, 2)}/#{media.id}-ai.jpg"
|
||||
|
||||
Enum.each(Map.values(thumbnail_paths), fn path ->
|
||||
@@ -198,11 +220,15 @@ defmodule BDS.MediaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "import_media extracts image dimensions and writes real encoded thumbnails", %{project: project, temp_dir: temp_dir} do
|
||||
test "import_media extracts image dimensions and writes real encoded thumbnails", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
source_path = Path.join(temp_dir, "sample.jpg")
|
||||
File.write!(source_path, tiny_jpeg_binary())
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
assert media.mime_type == "image/jpeg"
|
||||
assert media.width == 3
|
||||
@@ -230,11 +256,13 @@ defmodule BDS.MediaTest do
|
||||
assert Path.extname(thumbnail_paths.ai) == ".jpg"
|
||||
end
|
||||
|
||||
test "import_media keeps raw header dimensions but autorotates thumbnails from EXIF orientation", %{project: project, temp_dir: temp_dir} do
|
||||
test "import_media keeps raw header dimensions but autorotates thumbnails from EXIF orientation",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
source_path = Path.join(temp_dir, "rotated.jpg")
|
||||
write_oriented_jpeg!(source_path, 6)
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
assert media.width == 2
|
||||
assert media.height == 3
|
||||
@@ -248,11 +276,15 @@ defmodule BDS.MediaTest do
|
||||
assert_images_match!(actual_thumbnail, expected_thumbnail)
|
||||
end
|
||||
|
||||
test "regenerate_thumbnails recreates thumbnail files for an existing image media item", %{project: project, temp_dir: temp_dir} do
|
||||
test "regenerate_thumbnails recreates thumbnail files for an existing image media item", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
source_path = Path.join(temp_dir, "sample.jpg")
|
||||
File.write!(source_path, tiny_jpeg_binary())
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
thumbnail_paths = BDS.Media.thumbnail_paths(media)
|
||||
File.rm!(Path.join(temp_dir, thumbnail_paths.small))
|
||||
@@ -266,12 +298,17 @@ defmodule BDS.MediaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "import_media generates thumbnails for png and webp sources", %{project: project, temp_dir: temp_dir} do
|
||||
Enum.each([{ ".png", "image/png"}, {".webp", "image/webp"}], fn {extension, mime_type} ->
|
||||
test "import_media generates thumbnails for png and webp sources", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
Enum.each([{".png", "image/png"}, {".webp", "image/webp"}], fn {extension, mime_type} ->
|
||||
source_path = Path.join(temp_dir, "sample#{extension}")
|
||||
File.write!(source_path, sample_image_binary(extension))
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
assert media.mime_type == mime_type
|
||||
assert media.width == 2
|
||||
assert media.height == 3
|
||||
@@ -282,29 +319,39 @@ defmodule BDS.MediaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "import_media detects supported TIFF, BMP, HEIC, and HEIF extensions", %{project: project, temp_dir: temp_dir} do
|
||||
Enum.each([
|
||||
{"asset.tif", "image/tiff"},
|
||||
{"asset.tiff", "image/tiff"},
|
||||
{"asset.bmp", "image/bmp"},
|
||||
{"asset.heic", "image/heic"},
|
||||
{"asset.heif", "image/heif"}
|
||||
], fn {file_name, mime_type} ->
|
||||
source_path = Path.join(temp_dir, file_name)
|
||||
File.write!(source_path, "placeholder")
|
||||
test "import_media detects supported TIFF, BMP, HEIC, and HEIF extensions", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
Enum.each(
|
||||
[
|
||||
{"asset.tif", "image/tiff"},
|
||||
{"asset.tiff", "image/tiff"},
|
||||
{"asset.bmp", "image/bmp"},
|
||||
{"asset.heic", "image/heic"},
|
||||
{"asset.heif", "image/heif"}
|
||||
],
|
||||
fn {file_name, mime_type} ->
|
||||
source_path = Path.join(temp_dir, file_name)
|
||||
File.write!(source_path, "placeholder")
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert media.mime_type == mime_type
|
||||
assert media.width == nil
|
||||
assert media.height == nil
|
||||
end)
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
assert media.mime_type == mime_type
|
||||
assert media.width == nil
|
||||
assert media.height == nil
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
test "upsert_media_translation persists the row and writes a translated sidecar next to the binary", %{project: project, temp_dir: temp_dir} do
|
||||
test "upsert_media_translation persists the row and writes a translated sidecar next to the binary",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello media")
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
assert {:ok, media} =
|
||||
BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
assert {:ok, translation} =
|
||||
BDS.Media.upsert_media_translation(media.id, "de", %{
|
||||
|
||||
@@ -11,7 +11,8 @@ defmodule BDS.MenuTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "update_menu normalizes Home first, writes meta/menu.opml, and load returns nested items", %{project: project, temp_dir: temp_dir} do
|
||||
test "update_menu normalizes Home first, writes meta/menu.opml, and load returns nested items",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, menu} =
|
||||
BDS.Menu.update_menu(project.id, [
|
||||
%{kind: :page, label: "About", slug: "about"},
|
||||
@@ -52,7 +53,10 @@ defmodule BDS.MenuTest do
|
||||
assert loaded == menu
|
||||
end
|
||||
|
||||
test "sync_menu_from_filesystem loads canonical OPML and preserves a prepended Home entry", %{project: project, temp_dir: temp_dir} do
|
||||
test "sync_menu_from_filesystem loads canonical OPML and preserves a prepended Home entry", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
meta_dir = Path.join(temp_dir, "meta")
|
||||
File.mkdir_p!(meta_dir)
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@ defmodule BDS.MetadataTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "update_project_metadata writes meta/project.json and load returns the saved values", %{project: project, temp_dir: temp_dir} do
|
||||
test "update_project_metadata writes meta/project.json and load returns the saved values", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, metadata} =
|
||||
BDS.Metadata.update_project_metadata(project.id, %{
|
||||
name: "Renamed Blog",
|
||||
@@ -51,7 +54,8 @@ defmodule BDS.MetadataTest do
|
||||
assert loaded.blog_languages == ["de", "fr"]
|
||||
end
|
||||
|
||||
test "category and publishing updates write their meta files and sync_project_metadata_from_filesystem loads them", %{project: project, temp_dir: temp_dir} do
|
||||
test "category and publishing updates write their meta files and sync_project_metadata_from_filesystem loads them",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, _metadata} = BDS.Metadata.add_category(project.id, "news")
|
||||
|
||||
assert {:ok, _metadata} =
|
||||
|
||||
@@ -6,7 +6,10 @@ defmodule BDS.PostTranslationsTest do
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-post-translations-#{System.unique_integer([:positive])}")
|
||||
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-post-translations-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
@@ -14,7 +17,8 @@ defmodule BDS.PostTranslationsTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "upserted post translations publish with the canonical post, reopen on edit, and delete their file", %{project: project, temp_dir: temp_dir} do
|
||||
test "upserted post translations publish with the canonical post, reopen on edit, and delete their file",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
@@ -75,7 +79,8 @@ defmodule BDS.PostTranslationsTest do
|
||||
assert {:ok, []} = Posts.list_post_translations(post.id)
|
||||
end
|
||||
|
||||
test "validate_translations reports missing languages, orphan translation files, and do-not-translate posts", %{project: project, temp_dir: temp_dir} do
|
||||
test "validate_translations reports missing languages, orphan translation files, and do-not-translate posts",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
main_language: "en",
|
||||
|
||||
@@ -13,7 +13,9 @@ defmodule BDS.PostsTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "create_post slugifies titles, stores list fields, and defaults draft fields", %{project: project} do
|
||||
test "create_post slugifies titles, stores list fields, and defaults draft fields", %{
|
||||
project: project
|
||||
} do
|
||||
assert {:ok, post} =
|
||||
BDS.Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
@@ -48,7 +50,10 @@ defmodule BDS.PostsTest do
|
||||
assert duplicate_slug_post.categories == []
|
||||
end
|
||||
|
||||
test "create_post falls back to untitled and keeps slug uniqueness scoped to a project", %{project: project, temp_dir: temp_dir} do
|
||||
test "create_post falls back to untitled and keeps slug uniqueness scoped to a project", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, first} = BDS.Posts.create_post(%{project_id: project.id, title: nil})
|
||||
assert first.title == ""
|
||||
assert first.slug == "untitled"
|
||||
@@ -59,12 +64,15 @@ defmodule BDS.PostsTest do
|
||||
other_temp_dir = Path.join(temp_dir, "elsewhere")
|
||||
File.mkdir_p!(other_temp_dir)
|
||||
|
||||
assert {:ok, other_project} = BDS.Projects.create_project(%{name: "Elsewhere", data_path: other_temp_dir})
|
||||
assert {:ok, other_project} =
|
||||
BDS.Projects.create_project(%{name: "Elsewhere", data_path: other_temp_dir})
|
||||
|
||||
assert {:ok, other_post} = BDS.Posts.create_post(%{project_id: other_project.id, title: nil})
|
||||
assert other_post.slug == "untitled"
|
||||
end
|
||||
|
||||
test "update_post rejects slug changes after first publish and reopens published posts when content changes", %{project: project} do
|
||||
test "update_post rejects slug changes after first publish and reopens published posts when content changes",
|
||||
%{project: project} do
|
||||
assert {:ok, post} =
|
||||
BDS.Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
@@ -97,11 +105,14 @@ defmodule BDS.PostsTest do
|
||||
end
|
||||
|
||||
test "publish_post writes frontmatter to the project data directory and clears draft content" do
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-post-publish-#{System.unique_integer([:positive])}")
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-post-publish-#{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: "Filesystem", data_path: temp_dir})
|
||||
assert {:ok, project} =
|
||||
BDS.Projects.create_project(%{name: "Filesystem", data_path: temp_dir})
|
||||
|
||||
assert {:ok, post} =
|
||||
BDS.Posts.create_post(%{
|
||||
@@ -144,7 +155,9 @@ defmodule BDS.PostsTest do
|
||||
end
|
||||
|
||||
test "delete_post removes the database row and published markdown file when present" do
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-post-delete-#{System.unique_integer([:positive])}")
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-post-delete-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
@@ -178,11 +191,14 @@ defmodule BDS.PostsTest do
|
||||
assert {:ok, archived_draft} = BDS.Posts.archive_post(draft_post.id)
|
||||
assert archived_draft.status == :archived
|
||||
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-post-archive-#{System.unique_integer([:positive])}")
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-post-archive-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
assert {:ok, publish_project} = BDS.Projects.create_project(%{name: "Archive Published", data_path: temp_dir})
|
||||
assert {:ok, publish_project} =
|
||||
BDS.Projects.create_project(%{name: "Archive Published", data_path: temp_dir})
|
||||
|
||||
assert {:ok, published_post} =
|
||||
BDS.Posts.create_post(%{
|
||||
@@ -200,7 +216,9 @@ defmodule BDS.PostsTest do
|
||||
end
|
||||
|
||||
test "publish_post republishes archived posts without losing the existing body or original published_at" do
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-post-republish-#{System.unique_integer([:positive])}")
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-post-republish-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
@@ -227,7 +245,9 @@ defmodule BDS.PostsTest do
|
||||
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])}")
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-post-rebuild-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
@@ -279,9 +299,9 @@ defmodule BDS.PostsTest do
|
||||
assert post.language == "en"
|
||||
assert post.do_not_translate == true
|
||||
assert post.template_slug == "article"
|
||||
assert post.created_at == 1711843200
|
||||
assert post.updated_at == 1711929600
|
||||
assert post.published_at == 1712016000
|
||||
assert post.created_at == 1_711_843_200
|
||||
assert post.updated_at == 1_711_929_600
|
||||
assert post.published_at == 1_712_016_000
|
||||
assert post.tags == ["alpha"]
|
||||
assert post.categories == ["notes"]
|
||||
assert post.file_path == "posts/2026/04/recovered-post.md"
|
||||
|
||||
@@ -16,7 +16,8 @@ defmodule BDS.PreviewTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "start_preview binds localhost and request resolves generated routes, assets, media, and draft previews", %{project: project, temp_dir: temp_dir} do
|
||||
test "start_preview binds localhost and request resolves generated routes, assets, media, and draft previews",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
public_url: "https://example.com/blog",
|
||||
@@ -24,10 +25,29 @@ defmodule BDS.PreviewTest do
|
||||
blog_languages: ["en", "de"]
|
||||
})
|
||||
|
||||
assert {:ok, _} = Generation.write_generated_file(project.id, "index.html", "<html>home</html>")
|
||||
assert {:ok, _} = Generation.write_generated_file(project.id, "de/index.html", "<html>startseite</html>")
|
||||
assert {:ok, _} = Generation.write_generated_file(project.id, "tag/elixir/index.html", "<html>tag archive</html>")
|
||||
assert {:ok, _} = Generation.write_generated_file(project.id, "pagefind/pagefind-ui.js", "console.log('pagefind')")
|
||||
assert {:ok, _} =
|
||||
Generation.write_generated_file(project.id, "index.html", "<html>home</html>")
|
||||
|
||||
assert {:ok, _} =
|
||||
Generation.write_generated_file(
|
||||
project.id,
|
||||
"de/index.html",
|
||||
"<html>startseite</html>"
|
||||
)
|
||||
|
||||
assert {:ok, _} =
|
||||
Generation.write_generated_file(
|
||||
project.id,
|
||||
"tag/elixir/index.html",
|
||||
"<html>tag archive</html>"
|
||||
)
|
||||
|
||||
assert {:ok, _} =
|
||||
Generation.write_generated_file(
|
||||
project.id,
|
||||
"pagefind/pagefind-ui.js",
|
||||
"console.log('pagefind')"
|
||||
)
|
||||
|
||||
media_dir = Path.join([temp_dir, "media", "2026", "04"])
|
||||
File.mkdir_p!(media_dir)
|
||||
@@ -46,11 +66,20 @@ defmodule BDS.PreviewTest do
|
||||
assert server.port == 4123
|
||||
assert server.is_running == true
|
||||
|
||||
assert {:ok, %{body: "<html>home</html>", content_type: "text/html"}} = BDS.Preview.request(project.id, "/")
|
||||
assert {:ok, %{body: "<html>startseite</html>", content_type: "text/html"}} = BDS.Preview.request(project.id, "/de/")
|
||||
assert {:ok, %{body: "<html>tag archive</html>", content_type: "text/html"}} = BDS.Preview.request(project.id, "/tag/elixir")
|
||||
assert {:ok, %{body: "console.log('pagefind')", content_type: "application/javascript"}} = BDS.Preview.request(project.id, "/pagefind/pagefind-ui.js")
|
||||
assert {:ok, %{body: "media body", content_type: "text/plain"}} = BDS.Preview.request(project.id, "/media/2026/04/image.txt")
|
||||
assert {:ok, %{body: "<html>home</html>", content_type: "text/html"}} =
|
||||
BDS.Preview.request(project.id, "/")
|
||||
|
||||
assert {:ok, %{body: "<html>startseite</html>", content_type: "text/html"}} =
|
||||
BDS.Preview.request(project.id, "/de/")
|
||||
|
||||
assert {:ok, %{body: "<html>tag archive</html>", content_type: "text/html"}} =
|
||||
BDS.Preview.request(project.id, "/tag/elixir")
|
||||
|
||||
assert {:ok, %{body: "console.log('pagefind')", content_type: "application/javascript"}} =
|
||||
BDS.Preview.request(project.id, "/pagefind/pagefind-ui.js")
|
||||
|
||||
assert {:ok, %{body: "media body", content_type: "text/plain"}} =
|
||||
BDS.Preview.request(project.id, "/media/2026/04/image.txt")
|
||||
|
||||
assert {:ok, %{body: draft_html, content_type: "text/html"}} =
|
||||
BDS.Preview.preview_draft(project.id, "/draft/draft-post", post.id)
|
||||
@@ -67,7 +96,8 @@ defmodule BDS.PreviewTest do
|
||||
project_id: project.id,
|
||||
title: "Preview Post",
|
||||
kind: :post,
|
||||
content: "<article class=\"preview-template\"><h1>{{ post.title }}</h1><div>{{ post.content }}</div></article>"
|
||||
content:
|
||||
"<article class=\"preview-template\"><h1>{{ post.title }}</h1><div>{{ post.content }}</div></article>"
|
||||
})
|
||||
|
||||
assert {:ok, published_template} = BDS.Templates.publish_template(template.id)
|
||||
@@ -93,7 +123,9 @@ defmodule BDS.PreviewTest do
|
||||
assert :ok = BDS.Preview.stop_preview(project.id)
|
||||
end
|
||||
|
||||
test "draft preview renders through copied starter templates with markdown and i18n", %{project: project} do
|
||||
test "draft preview renders through copied starter templates with markdown and i18n", %{
|
||||
project: project
|
||||
} do
|
||||
assert {:ok, _menu} =
|
||||
BDS.Menu.update_menu(project.id, [
|
||||
%{kind: :page, label: "Notes", slug: "notes"}
|
||||
@@ -126,7 +158,8 @@ defmodule BDS.PreviewTest do
|
||||
assert :ok = BDS.Preview.stop_preview(project.id)
|
||||
end
|
||||
|
||||
test "preview renders not-found template for missing routes and rewrites markdown macros and canonical URLs", %{project: project, temp_dir: temp_dir} do
|
||||
test "preview renders not-found template for missing routes and rewrites markdown macros and canonical URLs",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
:inets.start()
|
||||
|
||||
assert {:ok, _metadata} =
|
||||
@@ -157,7 +190,10 @@ defmodule BDS.PreviewTest do
|
||||
assert {:ok, published_linked_post} = Posts.publish_post(linked_post.id)
|
||||
|
||||
media_source_reference = "/" <> Path.join(Path.dirname(media.file_path), media.original_name)
|
||||
canonical_post_href = "/" <> String.trim_trailing(BDS.Generation.post_output_path(published_linked_post), "index.html")
|
||||
|
||||
canonical_post_href =
|
||||
"/" <>
|
||||
String.trim_trailing(BDS.Generation.post_output_path(published_linked_post), "index.html")
|
||||
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
@@ -190,14 +226,21 @@ defmodule BDS.PreviewTest do
|
||||
assert missing_body =~ ~s(data-template="not-found")
|
||||
|
||||
assert {:ok, {{_version, 404, _reason}, _headers, body}} =
|
||||
:httpc.request(:get, {to_charlist("http://#{server.host}:#{server.port}/missing-page"), []}, [], body_format: :binary)
|
||||
:httpc.request(
|
||||
:get,
|
||||
{to_charlist("http://#{server.host}:#{server.port}/missing-page"), []},
|
||||
[],
|
||||
body_format: :binary
|
||||
)
|
||||
|
||||
assert body =~ ~s(data-template="not-found")
|
||||
|
||||
assert :ok = BDS.Preview.stop_preview(project.id)
|
||||
end
|
||||
|
||||
test "start_preview serves generated and draft routes over real HTTP on localhost", %{project: project} do
|
||||
test "start_preview serves generated and draft routes over real HTTP on localhost", %{
|
||||
project: project
|
||||
} do
|
||||
:inets.start()
|
||||
|
||||
assert {:ok, _metadata} =
|
||||
@@ -207,7 +250,8 @@ defmodule BDS.PreviewTest do
|
||||
blog_languages: ["en"]
|
||||
})
|
||||
|
||||
assert {:ok, _} = Generation.write_generated_file(project.id, "index.html", "<html>http home</html>")
|
||||
assert {:ok, _} =
|
||||
Generation.write_generated_file(project.id, "index.html", "<html>http home</html>")
|
||||
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
@@ -220,13 +264,26 @@ defmodule BDS.PreviewTest do
|
||||
assert {:ok, server} = BDS.Preview.start_preview(project.id)
|
||||
|
||||
assert {:ok, {{_version, 200, _reason}, headers, body}} =
|
||||
:httpc.request(:get, {to_charlist("http://#{server.host}:#{server.port}/"), []}, [], body_format: :binary)
|
||||
:httpc.request(:get, {to_charlist("http://#{server.host}:#{server.port}/"), []}, [],
|
||||
body_format: :binary
|
||||
)
|
||||
|
||||
assert body == "<html>http home</html>"
|
||||
assert Enum.any?(headers, fn {name, value} -> String.downcase(to_string(name)) == "content-type" and to_string(value) =~ "text/html" end)
|
||||
|
||||
assert Enum.any?(headers, fn {name, value} ->
|
||||
String.downcase(to_string(name)) == "content-type" and
|
||||
to_string(value) =~ "text/html"
|
||||
end)
|
||||
|
||||
assert {:ok, {{_version, 200, _reason}, _headers, draft_body}} =
|
||||
:httpc.request(:get, {to_charlist("http://#{server.host}:#{server.port}/draft/http-draft?post_id=#{post.id}"), []}, [], body_format: :binary)
|
||||
:httpc.request(
|
||||
:get,
|
||||
{to_charlist(
|
||||
"http://#{server.host}:#{server.port}/draft/http-draft?post_id=#{post.id}"
|
||||
), []},
|
||||
[],
|
||||
body_format: :binary
|
||||
)
|
||||
|
||||
assert draft_body =~ "Draft over HTTP"
|
||||
|
||||
|
||||
@@ -18,13 +18,16 @@ defmodule BDS.ProjectsTest do
|
||||
%{temp_root: temp_root}
|
||||
end
|
||||
|
||||
test "create_project slugifies names, keeps new projects inactive, and deduplicates slugs", %{temp_root: temp_root} do
|
||||
test "create_project slugifies names, keeps new projects inactive, and deduplicates slugs", %{
|
||||
temp_root: temp_root
|
||||
} do
|
||||
first_dir = Path.join(temp_root, "first")
|
||||
second_dir = Path.join(temp_root, "second")
|
||||
File.mkdir_p!(first_dir)
|
||||
File.mkdir_p!(second_dir)
|
||||
|
||||
assert {:ok, first} = BDS.Projects.create_project(%{name: "Föö Bär Blog", data_path: first_dir})
|
||||
assert {:ok, first} =
|
||||
BDS.Projects.create_project(%{name: "Föö Bär Blog", data_path: first_dir})
|
||||
|
||||
assert first.name == "Föö Bär Blog"
|
||||
assert first.slug == "foo-bar-blog"
|
||||
@@ -33,16 +36,21 @@ defmodule BDS.ProjectsTest do
|
||||
assert is_integer(first.created_at)
|
||||
assert is_integer(first.updated_at)
|
||||
|
||||
assert {:ok, second} = BDS.Projects.create_project(%{name: "Föö Bär Blog", data_path: second_dir})
|
||||
assert {:ok, second} =
|
||||
BDS.Projects.create_project(%{name: "Föö Bär Blog", data_path: second_dir})
|
||||
|
||||
assert second.slug == "foo-bar-blog-2"
|
||||
assert second.is_active == false
|
||||
end
|
||||
|
||||
test "create_project installs starter templates into the project data directory", %{temp_root: temp_root} do
|
||||
test "create_project installs starter templates into the project data directory", %{
|
||||
temp_root: temp_root
|
||||
} do
|
||||
temp_dir = Path.join(temp_root, "starter")
|
||||
File.mkdir_p!(temp_dir)
|
||||
|
||||
assert {:ok, project} = BDS.Projects.create_project(%{name: "Starter Blog", data_path: temp_dir})
|
||||
assert {:ok, project} =
|
||||
BDS.Projects.create_project(%{name: "Starter Blog", data_path: temp_dir})
|
||||
|
||||
assert File.exists?(Path.join([temp_dir, "templates", "single-post.liquid"]))
|
||||
assert File.exists?(Path.join([temp_dir, "templates", "post-list.liquid"]))
|
||||
@@ -52,18 +60,25 @@ defmodule BDS.ProjectsTest do
|
||||
assert File.exists?(Path.join([temp_dir, "templates", "macros", "gallery.liquid"]))
|
||||
|
||||
starter_slugs =
|
||||
Repo.all(from template in Template, where: template.project_id == ^project.id, select: {template.slug, template.kind})
|
||||
Repo.all(
|
||||
from template in Template,
|
||||
where: template.project_id == ^project.id,
|
||||
select: {template.slug, template.kind}
|
||||
)
|
||||
|
||||
assert {"single-post", :post} in starter_slugs
|
||||
assert {"post-list", :list} in starter_slugs
|
||||
assert {"not-found", :not_found} in starter_slugs
|
||||
end
|
||||
|
||||
test "starter template installation is idempotent for existing top-level templates", %{temp_root: temp_root} do
|
||||
test "starter template installation is idempotent for existing top-level templates", %{
|
||||
temp_root: temp_root
|
||||
} do
|
||||
temp_dir = Path.join(temp_root, "idempotent-starter")
|
||||
File.mkdir_p!(temp_dir)
|
||||
|
||||
assert {:ok, project} = BDS.Projects.create_project(%{name: "Starter Blog", data_path: temp_dir})
|
||||
assert {:ok, project} =
|
||||
BDS.Projects.create_project(%{name: "Starter Blog", data_path: temp_dir})
|
||||
|
||||
template_path = Path.join([temp_dir, "templates", "single-post.liquid"])
|
||||
original_contents = File.read!(template_path)
|
||||
@@ -75,11 +90,15 @@ defmodule BDS.ProjectsTest do
|
||||
reinstalled_contents = File.read!(template_path)
|
||||
assert reinstalled_contents == original_contents
|
||||
|
||||
assert {:ok, %{fields: reinstalled_fields}} = BDS.Frontmatter.parse_document(reinstalled_contents)
|
||||
assert {:ok, %{fields: reinstalled_fields}} =
|
||||
BDS.Frontmatter.parse_document(reinstalled_contents)
|
||||
|
||||
assert reinstalled_fields["id"] == original_fields["id"]
|
||||
end
|
||||
|
||||
test "set_active_project clears the previous active project and activates the target", %{temp_root: temp_root} do
|
||||
test "set_active_project clears the previous active project and activates the target", %{
|
||||
temp_root: temp_root
|
||||
} do
|
||||
first_dir = Path.join(temp_root, "active-first")
|
||||
second_dir = Path.join(temp_root, "active-second")
|
||||
File.mkdir_p!(first_dir)
|
||||
|
||||
@@ -3,7 +3,10 @@ defmodule BDS.PublishingTest do
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-publishing-#{System.unique_integer([:positive])}")
|
||||
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-publishing-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
@@ -11,7 +14,10 @@ defmodule BDS.PublishingTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "upload_site creates a publish job, uploads all targets, and excludes media sidecars", %{project: project, temp_dir: temp_dir} do
|
||||
test "upload_site creates a publish job, uploads all targets, and excludes media sidecars", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
test_pid = self()
|
||||
|
||||
File.mkdir_p!(Path.join([temp_dir, "html"]))
|
||||
@@ -25,7 +31,11 @@ defmodule BDS.PublishingTest do
|
||||
File.write!(Path.join([temp_dir, "media", "asset.jpg.meta"]), "meta")
|
||||
|
||||
uploader = fn target, files, credentials ->
|
||||
send(test_pid, {:uploaded, target.kind, target.remote_dir, Enum.sort(files), credentials.ssh_mode})
|
||||
send(
|
||||
test_pid,
|
||||
{:uploaded, target.kind, target.remote_dir, Enum.sort(files), credentials.ssh_mode}
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -46,7 +56,10 @@ defmodule BDS.PublishingTest do
|
||||
assert_receive {:uploaded, :media, "/srv/blog/media", ["asset.jpg"], :rsync}
|
||||
end
|
||||
|
||||
test "upload_site runs rsync commands with SSH agent env and media exclude filters", %{project: project, temp_dir: temp_dir} do
|
||||
test "upload_site runs rsync commands with SSH agent env and media exclude filters", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
test_pid = self()
|
||||
|
||||
File.mkdir_p!(Path.join([temp_dir, "html"]))
|
||||
@@ -78,17 +91,47 @@ defmodule BDS.PublishingTest do
|
||||
assert wait_for_publish_job(job.id, &(&1.status == :completed)).status == :completed
|
||||
|
||||
assert_receive {:command_run, "rsync", html_args, html_opts}
|
||||
assert html_args == ["--update", "--compress", "--verbose", "-e", "ssh", Path.join([temp_dir, "html"]) <> "/", "deploy@example.com:/srv/blog/"]
|
||||
|
||||
assert html_args == [
|
||||
"--update",
|
||||
"--compress",
|
||||
"--verbose",
|
||||
"-e",
|
||||
"ssh",
|
||||
Path.join([temp_dir, "html"]) <> "/",
|
||||
"deploy@example.com:/srv/blog/"
|
||||
]
|
||||
|
||||
assert html_opts[:env] == [{"SSH_AUTH_SOCK", "/tmp/test-agent.sock"}]
|
||||
|
||||
assert_receive {:command_run, "rsync", thumb_args, _thumb_opts}
|
||||
assert thumb_args == ["--update", "--compress", "--verbose", "-e", "ssh", Path.join([temp_dir, "thumbnails"]) <> "/", "deploy@example.com:/srv/blog/thumbnails/"]
|
||||
|
||||
assert thumb_args == [
|
||||
"--update",
|
||||
"--compress",
|
||||
"--verbose",
|
||||
"-e",
|
||||
"ssh",
|
||||
Path.join([temp_dir, "thumbnails"]) <> "/",
|
||||
"deploy@example.com:/srv/blog/thumbnails/"
|
||||
]
|
||||
|
||||
assert_receive {:command_run, "rsync", media_args, _media_opts}
|
||||
assert media_args == ["--update", "--compress", "--verbose", "--exclude=*.meta", "-e", "ssh", Path.join([temp_dir, "media"]) <> "/", "deploy@example.com:/srv/blog/media/"]
|
||||
|
||||
assert media_args == [
|
||||
"--update",
|
||||
"--compress",
|
||||
"--verbose",
|
||||
"--exclude=*.meta",
|
||||
"-e",
|
||||
"ssh",
|
||||
Path.join([temp_dir, "media"]) <> "/",
|
||||
"deploy@example.com:/srv/blog/media/"
|
||||
]
|
||||
end
|
||||
|
||||
test "upload_site runs scp commands for each eligible file and fails when a command exits non-zero", %{project: project, temp_dir: temp_dir} do
|
||||
test "upload_site runs scp commands for each eligible file and fails when a command exits non-zero",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
test_pid = self()
|
||||
html_index = Path.join([temp_dir, "html", "index.html"])
|
||||
html_entry = Path.join([temp_dir, "html", "posts", "entry.html"])
|
||||
@@ -129,14 +172,26 @@ defmodule BDS.PublishingTest do
|
||||
failed_job = wait_for_publish_job(job.id, &(&1.status == :failed))
|
||||
assert failed_job.error =~ "thumbnail failure"
|
||||
|
||||
assert_receive {:command_run, "scp", ["-q", ^html_index, "deploy@example.com:/srv/blog/index.html"], opts_a}
|
||||
assert_receive {:command_run, "scp",
|
||||
["-q", ^html_index, "deploy@example.com:/srv/blog/index.html"], opts_a}
|
||||
|
||||
assert opts_a[:env] == [{"SSH_AUTH_SOCK", "/tmp/test-agent.sock"}]
|
||||
assert_receive {:command_run, "scp", ["-q", ^html_entry, "deploy@example.com:/srv/blog/posts/entry.html"], _opts_b}
|
||||
assert_receive {:command_run, "scp", ["-q", ^thumb_path, "deploy@example.com:/srv/blog/thumbnails/thumb.jpg"], _opts_c}
|
||||
refute_receive {:command_run, "scp", ["-q", _, "deploy@example.com:/srv/blog/media/asset.jpg"], _opts_d}
|
||||
|
||||
assert_receive {:command_run, "scp",
|
||||
["-q", ^html_entry, "deploy@example.com:/srv/blog/posts/entry.html"], _opts_b}
|
||||
|
||||
assert_receive {:command_run, "scp",
|
||||
["-q", ^thumb_path, "deploy@example.com:/srv/blog/thumbnails/thumb.jpg"],
|
||||
_opts_c}
|
||||
|
||||
refute_receive {:command_run, "scp",
|
||||
["-q", _, "deploy@example.com:/srv/blog/media/asset.jpg"], _opts_d}
|
||||
end
|
||||
|
||||
test "upload_site marks the publish job failed when a target upload fails", %{project: project, temp_dir: temp_dir} do
|
||||
test "upload_site marks the publish job failed when a target upload fails", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
File.mkdir_p!(Path.join([temp_dir, "html"]))
|
||||
File.write!(Path.join([temp_dir, "html", "index.html"]), "<html />")
|
||||
File.mkdir_p!(Path.join([temp_dir, "thumbnails"]))
|
||||
@@ -161,6 +216,75 @@ defmodule BDS.PublishingTest do
|
||||
assert failed_job.error == "thumbnail failure"
|
||||
end
|
||||
|
||||
test "upload_site skips unchanged files for scp and only re-uploads files with newer mtimes", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
test_pid = self()
|
||||
html_index = Path.join([temp_dir, "html", "index.html"])
|
||||
media_asset = Path.join([temp_dir, "media", "asset.jpg"])
|
||||
|
||||
File.mkdir_p!(Path.dirname(html_index))
|
||||
File.write!(html_index, "<html />")
|
||||
File.mkdir_p!(Path.join([temp_dir, "thumbnails"]))
|
||||
File.write!(Path.join([temp_dir, "thumbnails", "thumb.jpg"]), "thumb")
|
||||
File.mkdir_p!(Path.dirname(media_asset))
|
||||
File.write!(media_asset, "asset")
|
||||
|
||||
runner = fn command, args, opts ->
|
||||
send(test_pid, {:command_run, command, args, opts})
|
||||
{"", 0}
|
||||
end
|
||||
|
||||
credentials = %{
|
||||
ssh_host: "example.com",
|
||||
ssh_user: "deploy",
|
||||
ssh_remote_path: "/srv/blog",
|
||||
ssh_mode: :scp
|
||||
}
|
||||
|
||||
assert {:ok, first_job} =
|
||||
BDS.Publishing.upload_site(project.id, credentials,
|
||||
command_runner: runner,
|
||||
ssh_auth_sock: "/tmp/test-agent.sock"
|
||||
)
|
||||
|
||||
assert wait_for_publish_job(first_job.id, &(&1.status == :completed)).status == :completed
|
||||
first_uploads = collect_command_runs()
|
||||
assert length(first_uploads) == 3
|
||||
|
||||
assert {:ok, second_job} =
|
||||
BDS.Publishing.upload_site(project.id, credentials,
|
||||
command_runner: runner,
|
||||
ssh_auth_sock: "/tmp/test-agent.sock"
|
||||
)
|
||||
|
||||
assert wait_for_publish_job(second_job.id, &(&1.status == :completed)).status == :completed
|
||||
assert collect_command_runs() == []
|
||||
|
||||
:ok = File.touch(html_index, {{2099, 1, 1}, {0, 0, 0}})
|
||||
|
||||
assert {:ok, third_job} =
|
||||
BDS.Publishing.upload_site(project.id, credentials,
|
||||
command_runner: runner,
|
||||
ssh_auth_sock: "/tmp/test-agent.sock"
|
||||
)
|
||||
|
||||
assert wait_for_publish_job(third_job.id, &(&1.status == :completed)).status == :completed
|
||||
|
||||
assert [html_upload] = collect_command_runs()
|
||||
assert elem(html_upload, 0) == "scp"
|
||||
assert elem(html_upload, 1) == ["-q", html_index, "deploy@example.com:/srv/blog/index.html"]
|
||||
end
|
||||
|
||||
defp collect_command_runs(acc \\ []) do
|
||||
receive do
|
||||
{:command_run, command, args, _opts} -> collect_command_runs([{command, args} | acc])
|
||||
after
|
||||
50 -> Enum.reverse(acc)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_publish_job(job_id, predicate, attempts \\ 100)
|
||||
|
||||
defp wait_for_publish_job(job_id, predicate, attempts) when attempts > 0 do
|
||||
|
||||
152
test/bds/rendering_test.exs
Normal file
152
test/bds/rendering_test.exs
Normal file
@@ -0,0 +1,152 @@
|
||||
defmodule BDS.RenderingTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias BDS.Rendering
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-rendering-#{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: "Rendering", data_path: temp_dir})
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "render_post_page exposes the spec post context and blog language links", %{
|
||||
project: project
|
||||
} do
|
||||
assert {:ok, _metadata} =
|
||||
BDS.Metadata.update_project_metadata(project.id, %{
|
||||
main_language: "en",
|
||||
blog_languages: ["en", "de"]
|
||||
})
|
||||
|
||||
assert {:ok, template} =
|
||||
BDS.Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
title: "Render Post Context",
|
||||
kind: :post,
|
||||
content:
|
||||
"{{ pico_stylesheet_href }}|{% for lang in blog_languages %}[{{ lang.code }}={{ lang.href }}:{{ lang.href_prefix }}]{% endfor %}|{{ post.author }}|{{ post.published_at }}|{{ post.created_at }}|{{ post.updated_at }}|{{ post.tags.size }}|{{ post.categories.size }}|{{ post.template_slug }}|{{ post.do_not_translate }}"
|
||||
})
|
||||
|
||||
assert {:ok, published_template} = BDS.Templates.publish_template(template.id)
|
||||
|
||||
assert {:ok, post} =
|
||||
BDS.Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
title: "Render Me",
|
||||
content: "Body",
|
||||
author: "Writer",
|
||||
tags: ["alpha", "beta"],
|
||||
categories: ["notes"],
|
||||
language: "en",
|
||||
template_slug: published_template.slug,
|
||||
do_not_translate: true
|
||||
})
|
||||
|
||||
assert {:ok, published_post} = BDS.Posts.publish_post(post.id)
|
||||
|
||||
assert {:ok, rendered} =
|
||||
Rendering.render_post_page(project.id, published_template.slug, %{
|
||||
id: published_post.id,
|
||||
title: published_post.title,
|
||||
content: published_post.content || "",
|
||||
slug: published_post.slug,
|
||||
language: "de",
|
||||
excerpt: published_post.excerpt,
|
||||
template_slug: published_post.template_slug
|
||||
})
|
||||
|
||||
assert rendered =~ "/assets/pico.min.css"
|
||||
assert rendered =~ "[en=/:]"
|
||||
assert rendered =~ "[de=/de/:/de]"
|
||||
assert rendered =~ "|Writer|"
|
||||
|
||||
assert rendered =~
|
||||
"|#{published_post.published_at}|#{published_post.created_at}|#{published_post.updated_at}|"
|
||||
|
||||
assert rendered =~ "|2|1|#{published_template.slug}|true"
|
||||
end
|
||||
|
||||
test "render_list_page exposes pagination and render_not_found_page localizes default copy", %{
|
||||
project: project
|
||||
} do
|
||||
assert {:ok, _metadata} =
|
||||
BDS.Metadata.update_project_metadata(project.id, %{
|
||||
main_language: "en",
|
||||
blog_languages: ["en", "de"]
|
||||
})
|
||||
|
||||
assert {:ok, list_template} =
|
||||
BDS.Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
title: "Render List Context",
|
||||
kind: :list,
|
||||
content:
|
||||
"{{ current_page }}|{{ total_pages }}|{{ total_items }}|{{ items_per_page }}|{{ has_prev_page }}|{{ prev_page_href }}|{{ has_next_page }}|{{ next_page_href }}"
|
||||
})
|
||||
|
||||
assert {:ok, not_found_template} =
|
||||
BDS.Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
title: "Render Not Found Context",
|
||||
kind: :not_found,
|
||||
content: "{{ not_found_message }}|{{ not_found_back_label }}"
|
||||
})
|
||||
|
||||
assert {:ok, published_list_template} = BDS.Templates.publish_template(list_template.id)
|
||||
|
||||
assert {:ok, _published_not_found_template} =
|
||||
BDS.Templates.publish_template(not_found_template.id)
|
||||
|
||||
BDS.Repo.update_all(
|
||||
from(template in BDS.Templates.Template,
|
||||
where:
|
||||
template.project_id == ^project.id and template.kind == :list and
|
||||
template.id != ^published_list_template.id
|
||||
),
|
||||
set: [enabled: false]
|
||||
)
|
||||
|
||||
BDS.Repo.update_all(
|
||||
from(template in BDS.Templates.Template,
|
||||
where:
|
||||
template.project_id == ^project.id and template.kind == :not_found and
|
||||
template.slug != ^not_found_template.slug
|
||||
),
|
||||
set: [enabled: false]
|
||||
)
|
||||
|
||||
assert {:ok, rendered_list} =
|
||||
Rendering.render_list_page(project.id, %{
|
||||
language: "en",
|
||||
page_title: "Archive",
|
||||
posts: [],
|
||||
archive_context: %{kind: "tag", name: "elixir"},
|
||||
pagination: %{
|
||||
current_page: 2,
|
||||
total_pages: 5,
|
||||
total_items: 12,
|
||||
items_per_page: 3,
|
||||
has_prev_page: true,
|
||||
prev_page_href: "/page/1/",
|
||||
has_next_page: true,
|
||||
next_page_href: "/page/3/"
|
||||
}
|
||||
})
|
||||
|
||||
assert rendered_list == "2|5|12|3|true|/page/1/|true|/page/3/"
|
||||
|
||||
assert {:ok, rendered_not_found} =
|
||||
Rendering.render_not_found_page(project.id, %{language: "de"})
|
||||
|
||||
assert rendered_not_found ==
|
||||
"Die angeforderte Vorschauseite konnte nicht gefunden werden.|Zurück zur Vorschau-Startseite"
|
||||
|
||||
assert published_list_template.kind == :list
|
||||
end
|
||||
end
|
||||
@@ -180,7 +180,13 @@ defmodule BDS.Repo.SchemaMigrationTest do
|
||||
"ai_model_modalities" => ["provider", "model_id", "direction", "modality"],
|
||||
"ai_catalog_meta" => ["key", "value"],
|
||||
"embedding_keys" => ["label", "post_id", "project_id", "content_hash", "vector"],
|
||||
"dismissed_duplicate_pairs" => ["id", "project_id", "post_id_a", "post_id_b", "dismissed_at"],
|
||||
"dismissed_duplicate_pairs" => [
|
||||
"id",
|
||||
"project_id",
|
||||
"post_id_a",
|
||||
"post_id_b",
|
||||
"dismissed_at"
|
||||
],
|
||||
"import_definitions" => [
|
||||
"id",
|
||||
"project_id",
|
||||
@@ -229,8 +235,16 @@ defmodule BDS.Repo.SchemaMigrationTest do
|
||||
|
||||
assert unique_index_columns("tags", "tags_project_name_idx") == ["project_id", "name"]
|
||||
assert unique_index_columns("scripts", "scripts_project_slug_idx") == ["project_id", "slug"]
|
||||
assert unique_index_columns("templates", "templates_project_slug_idx") == ["project_id", "slug"]
|
||||
assert unique_index_columns("post_media", "post_media_post_media_idx") == ["post_id", "media_id"]
|
||||
|
||||
assert unique_index_columns("templates", "templates_project_slug_idx") == [
|
||||
"project_id",
|
||||
"slug"
|
||||
]
|
||||
|
||||
assert unique_index_columns("post_media", "post_media_post_media_idx") == [
|
||||
"post_id",
|
||||
"media_id"
|
||||
]
|
||||
|
||||
assert unique_index_columns(
|
||||
"generated_file_hashes",
|
||||
@@ -345,7 +359,9 @@ defmodule BDS.Repo.SchemaMigrationTest do
|
||||
defp unique_index_columns(table, index_name) do
|
||||
indexes = query_rows("PRAGMA index_list(#{table})")
|
||||
|
||||
assert Enum.any?(indexes, fn [_seq, name, unique | _rest] -> name == index_name and unique == 1 end),
|
||||
assert Enum.any?(indexes, fn [_seq, name, unique | _rest] ->
|
||||
name == index_name and unique == 1
|
||||
end),
|
||||
"expected unique index #{index_name} on #{table}"
|
||||
|
||||
query_rows("PRAGMA index_info(#{index_name})")
|
||||
|
||||
@@ -58,7 +58,13 @@ defmodule BDS.Scripting.JobTest do
|
||||
assert {:ok, job} = BDS.Scripting.start_job("irrelevant", "main")
|
||||
assert job.status in [:queued, :running]
|
||||
|
||||
running_job = wait_for_job(job.id, &(&1.status == :running and &1.progress == %{"phase" => "started", "current" => 1, "total" => 2}))
|
||||
running_job =
|
||||
wait_for_job(
|
||||
job.id,
|
||||
&(&1.status == :running and
|
||||
&1.progress == %{"phase" => "started", "current" => 1, "total" => 2})
|
||||
)
|
||||
|
||||
assert running_job.started_at != nil
|
||||
|
||||
completed_job = wait_for_job(job.id, &(&1.status == :completed))
|
||||
|
||||
@@ -42,7 +42,10 @@ defmodule BDS.ScriptsTest do
|
||||
assert macro_script.slug == "render-card"
|
||||
end
|
||||
|
||||
test "publish_script writes a lua file with frontmatter and clears draft content", %{project: project, temp_dir: temp_dir} do
|
||||
test "publish_script writes a lua file with frontmatter and clears draft content", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, script} =
|
||||
BDS.Scripts.create_script(%{
|
||||
project_id: project.id,
|
||||
@@ -73,7 +76,9 @@ defmodule BDS.ScriptsTest do
|
||||
assert contents =~ "\n---\nfunction main() return 'ok' end\n"
|
||||
end
|
||||
|
||||
test "update_script bumps version and reopens a published script when content changes", %{project: project} do
|
||||
test "update_script bumps version and reopens a published script when content changes", %{
|
||||
project: project
|
||||
} do
|
||||
assert {:ok, script} =
|
||||
BDS.Scripts.create_script(%{
|
||||
project_id: project.id,
|
||||
@@ -99,7 +104,10 @@ defmodule BDS.ScriptsTest do
|
||||
assert updated.updated_at >= published.updated_at
|
||||
end
|
||||
|
||||
test "delete_script removes the published file and database row", %{project: project, temp_dir: temp_dir} do
|
||||
test "delete_script removes the published file and database row", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, script} =
|
||||
BDS.Scripts.create_script(%{
|
||||
project_id: project.id,
|
||||
@@ -116,7 +124,10 @@ defmodule BDS.ScriptsTest do
|
||||
refute File.exists?(Path.join(temp_dir, published.file_path))
|
||||
end
|
||||
|
||||
test "rebuild_scripts_from_files recreates published scripts from disk", %{project: project, temp_dir: temp_dir} do
|
||||
test "rebuild_scripts_from_files recreates published scripts from disk", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
script_dir = Path.join(temp_dir, "scripts")
|
||||
File.mkdir_p!(script_dir)
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ defmodule BDS.SearchTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "search_posts indexes writes, supports filters and pagination, and removes deleted posts", %{project: project} do
|
||||
test "search_posts indexes writes, supports filters and pagination, and removes deleted posts",
|
||||
%{project: project} do
|
||||
assert {:ok, draft_post} =
|
||||
BDS.Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
@@ -54,14 +55,25 @@ defmodule BDS.SearchTest do
|
||||
assert results.limit == 50
|
||||
assert Enum.map(results.posts, & &1.id) == [draft_post.id]
|
||||
|
||||
assert {:ok, tag_results} = BDS.Search.search_posts(project.id, "galaxy", %{tags: ["space"], categories: ["astronomy"]})
|
||||
assert tag_results.total == 2
|
||||
assert Enum.sort(Enum.map(tag_results.posts, & &1.id)) == Enum.sort([draft_post.id, published_post.id])
|
||||
assert {:ok, tag_results} =
|
||||
BDS.Search.search_posts(project.id, "galaxy", %{
|
||||
tags: ["space"],
|
||||
categories: ["astronomy"]
|
||||
})
|
||||
|
||||
assert tag_results.total == 2
|
||||
|
||||
assert Enum.sort(Enum.map(tag_results.posts, & &1.id)) ==
|
||||
Enum.sort([draft_post.id, published_post.id])
|
||||
|
||||
assert {:ok, language_results} =
|
||||
BDS.Search.search_posts(project.id, "galaxy", %{language: "de"})
|
||||
|
||||
assert {:ok, language_results} = BDS.Search.search_posts(project.id, "galaxy", %{language: "de"})
|
||||
assert Enum.map(language_results.posts, & &1.id) == [published_post.id]
|
||||
|
||||
assert {:ok, paged_results} = BDS.Search.search_posts(project.id, "galaxy", %{limit: 1, offset: 1})
|
||||
assert {:ok, paged_results} =
|
||||
BDS.Search.search_posts(project.id, "galaxy", %{limit: 1, offset: 1})
|
||||
|
||||
assert paged_results.total == 3
|
||||
assert paged_results.offset == 1
|
||||
assert paged_results.limit == 1
|
||||
@@ -120,12 +132,17 @@ defmodule BDS.SearchTest do
|
||||
assert Enum.map(results.posts, & &1.id) == [post.id]
|
||||
|
||||
assert {:ok, missing_translation_results} =
|
||||
BDS.Search.search_posts(project.id, "Canonical", %{missing_translation_language: "de"})
|
||||
BDS.Search.search_posts(project.id, "Canonical", %{
|
||||
missing_translation_language: "de"
|
||||
})
|
||||
|
||||
assert Enum.map(missing_translation_results.posts, & &1.id) == [post.id]
|
||||
end
|
||||
|
||||
test "search_media indexes metadata, includes translation text, and removes deleted media", %{project: project, temp_dir: temp_dir} do
|
||||
test "search_media indexes metadata, includes translation text, and removes deleted media", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
source_path = Path.join(temp_dir, "hero.txt")
|
||||
File.write!(source_path, "hero")
|
||||
|
||||
@@ -164,7 +181,10 @@ defmodule BDS.SearchTest do
|
||||
assert deleted_results.total == 0
|
||||
end
|
||||
|
||||
test "rebuild operations repopulate the search index from filesystem truth", %{project: project, temp_dir: temp_dir} do
|
||||
test "rebuild operations repopulate the search index from filesystem truth", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
posts_dir = Path.join([temp_dir, "posts", "2026", "04"])
|
||||
File.mkdir_p!(posts_dir)
|
||||
|
||||
@@ -225,7 +245,9 @@ defmodule BDS.SearchTest do
|
||||
assert Enum.map(media_results.media, & &1.id) == ["search-media-from-file"]
|
||||
end
|
||||
|
||||
test "search_posts applies language-aware stemming to indexed and query text", %{project: project} do
|
||||
test "search_posts applies language-aware stemming to indexed and query text", %{
|
||||
project: project
|
||||
} do
|
||||
assert {:ok, german_post} =
|
||||
BDS.Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
|
||||
@@ -14,8 +14,13 @@ defmodule BDS.TagsTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "create_tag persists the row and rewrites meta/tags.json sorted by name", %{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, zebra} = BDS.Tags.create_tag(%{project_id: project.id, name: "Zebra", color: "#000000"})
|
||||
test "create_tag persists the row and rewrites meta/tags.json sorted by name", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, zebra} =
|
||||
BDS.Tags.create_tag(%{project_id: project.id, name: "Zebra", color: "#000000"})
|
||||
|
||||
assert {:ok, alpha} = BDS.Tags.create_tag(%{project_id: project.id, name: "Alpha"})
|
||||
|
||||
assert zebra.name == "Zebra"
|
||||
@@ -35,7 +40,10 @@ defmodule BDS.TagsTest do
|
||||
assert "has already been taken" in errors_on(changeset).name
|
||||
end
|
||||
|
||||
test "update_tag rewrites the tag row and meta/tags.json", %{project: project, temp_dir: temp_dir} do
|
||||
test "update_tag rewrites the tag row and meta/tags.json", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, tag} = BDS.Tags.create_tag(%{project_id: project.id, name: "Alpha"})
|
||||
|
||||
assert {:ok, updated} =
|
||||
@@ -51,11 +59,16 @@ defmodule BDS.TagsTest do
|
||||
|
||||
tags_path = Path.join([temp_dir, "meta", "tags.json"])
|
||||
|
||||
assert %{"tags" => [%{"name" => "Alpha", "color" => "#112233", "post_template_slug" => "article"}]} =
|
||||
assert %{
|
||||
"tags" => [
|
||||
%{"name" => "Alpha", "color" => "#112233", "post_template_slug" => "article"}
|
||||
]
|
||||
} =
|
||||
Jason.decode!(File.read!(tags_path))
|
||||
end
|
||||
|
||||
test "rename_tag updates post tag arrays, rewrites published post files, and refreshes tags.json", %{project: project, temp_dir: temp_dir} do
|
||||
test "rename_tag updates post tag arrays, rewrites published post files, and refreshes tags.json",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, tag} = BDS.Tags.create_tag(%{project_id: project.id, name: "Alpha"})
|
||||
|
||||
assert {:ok, post} =
|
||||
@@ -83,7 +96,8 @@ defmodule BDS.TagsTest do
|
||||
assert %{"tags" => [%{"name" => "Beta"}]} = Jason.decode!(File.read!(tags_path))
|
||||
end
|
||||
|
||||
test "merge_tags moves source tags onto the target, deduplicates post tags, deletes sources, and refreshes tags.json", %{project: project, temp_dir: temp_dir} do
|
||||
test "merge_tags moves source tags onto the target, deduplicates post tags, deletes sources, and refreshes tags.json",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, source_a} = BDS.Tags.create_tag(%{project_id: project.id, name: "Alpha"})
|
||||
assert {:ok, source_b} = BDS.Tags.create_tag(%{project_id: project.id, name: "Beta"})
|
||||
assert {:ok, target} = BDS.Tags.create_tag(%{project_id: project.id, name: "Gamma"})
|
||||
@@ -115,7 +129,8 @@ defmodule BDS.TagsTest do
|
||||
assert %{"tags" => [%{"name" => "Gamma"}]} = Jason.decode!(File.read!(tags_path))
|
||||
end
|
||||
|
||||
test "delete_tag removes the tag from posts, rewrites published files, deletes the row, and refreshes tags.json", %{project: project, temp_dir: temp_dir} do
|
||||
test "delete_tag removes the tag from posts, rewrites published files, deletes the row, and refreshes tags.json",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, doomed} = BDS.Tags.create_tag(%{project_id: project.id, name: "Alpha"})
|
||||
assert {:ok, _other} = BDS.Tags.create_tag(%{project_id: project.id, name: "Beta"})
|
||||
|
||||
@@ -145,7 +160,8 @@ defmodule BDS.TagsTest do
|
||||
assert %{"tags" => [%{"name" => "Beta"}]} = Jason.decode!(File.read!(tags_path))
|
||||
end
|
||||
|
||||
test "sync_tags_from_posts creates missing tags from post tag arrays and refreshes tags.json", %{project: project, temp_dir: temp_dir} do
|
||||
test "sync_tags_from_posts creates missing tags from post tag arrays and refreshes tags.json",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, existing} =
|
||||
BDS.Tags.create_tag(%{
|
||||
project_id: project.id,
|
||||
@@ -183,7 +199,11 @@ defmodule BDS.TagsTest do
|
||||
assert %{
|
||||
"tags" => [
|
||||
%{"name" => "Another"},
|
||||
%{"name" => "Existing", "color" => "#112233", "post_template_slug" => "feature-view"},
|
||||
%{
|
||||
"name" => "Existing",
|
||||
"color" => "#112233",
|
||||
"post_template_slug" => "feature-view"
|
||||
},
|
||||
%{"name" => "Missing"}
|
||||
]
|
||||
} = Jason.decode!(File.read!(tags_path))
|
||||
|
||||
@@ -94,7 +94,11 @@ defmodule BDS.TasksTest do
|
||||
end
|
||||
|
||||
test "external tasks are registered as running and can report progress and complete" do
|
||||
assert {:ok, task} = BDS.Tasks.register_external_task("preview build", %{group_id: "generation", group_name: "Generation"})
|
||||
assert {:ok, task} =
|
||||
BDS.Tasks.register_external_task("preview build", %{
|
||||
group_id: "generation",
|
||||
group_name: "Generation"
|
||||
})
|
||||
|
||||
assert task.status == :running
|
||||
assert task.group_id == "generation"
|
||||
@@ -106,7 +110,9 @@ defmodule BDS.TasksTest do
|
||||
assert progressed.status == :running
|
||||
|
||||
assert :ok = BDS.Tasks.complete_task(task.id)
|
||||
assert wait_for_task(task.id, &(&1.status == :completed and &1.progress == 1.0)).status == :completed
|
||||
|
||||
assert wait_for_task(task.id, &(&1.status == :completed and &1.progress == 1.0)).status ==
|
||||
:completed
|
||||
end
|
||||
|
||||
defp receive_started do
|
||||
|
||||
@@ -33,12 +33,20 @@ defmodule BDS.TemplatesTest do
|
||||
assert template.content == "<article>{{ content }}</article>"
|
||||
|
||||
assert {:ok, duplicate} =
|
||||
BDS.Templates.create_template(%{project_id: project.id, title: "Article View", kind: :post, content: "x"})
|
||||
BDS.Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
title: "Article View",
|
||||
kind: :post,
|
||||
content: "x"
|
||||
})
|
||||
|
||||
assert duplicate.slug == "article-view-2"
|
||||
end
|
||||
|
||||
test "publish_template writes a liquid file with frontmatter and clears draft content", %{project: project, temp_dir: temp_dir} do
|
||||
test "publish_template writes a liquid file with frontmatter and clears draft content", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, template} =
|
||||
BDS.Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
@@ -68,7 +76,9 @@ defmodule BDS.TemplatesTest do
|
||||
assert contents =~ "\n---\n<section>{{ page_title }}</section>\n"
|
||||
end
|
||||
|
||||
test "update_template bumps version and reopens a published template when content changes", %{project: project} do
|
||||
test "update_template bumps version and reopens a published template when content changes", %{
|
||||
project: project
|
||||
} do
|
||||
assert {:ok, template} =
|
||||
BDS.Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
@@ -94,7 +104,8 @@ defmodule BDS.TemplatesTest do
|
||||
assert updated.updated_at >= published.updated_at
|
||||
end
|
||||
|
||||
test "delete_template refuses referenced templates unless forced, then clears references and deletes the file", %{project: project, temp_dir: temp_dir} do
|
||||
test "delete_template refuses referenced templates unless forced, then clears references and deletes the file",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, template} =
|
||||
BDS.Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
@@ -122,7 +133,8 @@ defmodule BDS.TemplatesTest do
|
||||
post_template_slug: published.slug
|
||||
})
|
||||
|
||||
assert {:error, {:has_references, %{posts: 1, tags: 1}}} = BDS.Templates.delete_template(published.id)
|
||||
assert {:error, {:has_references, %{posts: 1, tags: 1}}} =
|
||||
BDS.Templates.delete_template(published.id)
|
||||
|
||||
assert {:ok, :deleted} = BDS.Templates.delete_template(published.id, force: true)
|
||||
|
||||
@@ -143,7 +155,8 @@ defmodule BDS.TemplatesTest do
|
||||
assert %{"tags" => [%{"name" => "Feature"}]} = Jason.decode!(File.read!(tags_path))
|
||||
end
|
||||
|
||||
test "update_template cascades slug changes to posts and tags and renames the published file", %{project: project, temp_dir: temp_dir} do
|
||||
test "update_template cascades slug changes to posts and tags and renames the published file",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, template} =
|
||||
BDS.Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
@@ -198,11 +211,15 @@ defmodule BDS.TemplatesTest do
|
||||
assert post_contents =~ "\n---\nBody\n"
|
||||
|
||||
tags_path = Path.join([temp_dir, "meta", "tags.json"])
|
||||
|
||||
assert %{"tags" => [%{"name" => "Feature", "post_template_slug" => "feature-view"}]} =
|
||||
Jason.decode!(File.read!(tags_path))
|
||||
end
|
||||
|
||||
test "rebuild_templates_from_files recreates published templates from disk", %{project: project, temp_dir: temp_dir} do
|
||||
test "rebuild_templates_from_files recreates published templates from disk", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
template_dir = Path.join(temp_dir, "templates")
|
||||
File.mkdir_p!(template_dir)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user