feat: more work on backend content and generation
This commit is contained in:
47
test/bds/generation_test.exs
Normal file
47
test/bds/generation_test.exs
Normal file
@@ -0,0 +1,47 @@
|
||||
defmodule BDS.GenerationTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
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)
|
||||
|
||||
{:ok, project} = BDS.Projects.create_project(%{name: "Generation", data_path: temp_dir})
|
||||
%{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>")
|
||||
assert first_write.written? == true
|
||||
|
||||
output_path = Path.join([temp_dir, "html", "index.html"])
|
||||
assert File.read!(output_path) == "<html>hello</html>"
|
||||
|
||||
assert {:ok, [tracked_file]} = BDS.Generation.list_generated_files(project.id)
|
||||
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 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 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>")
|
||||
|
||||
output_path = Path.join([temp_dir, "html", "tag", "elixir", "index.html"])
|
||||
assert File.exists?(output_path)
|
||||
|
||||
assert :ok = BDS.Generation.delete_generated_file(project.id, "tag/elixir/index.html")
|
||||
refute File.exists?(output_path)
|
||||
|
||||
assert {:ok, files} = BDS.Generation.list_generated_files(project.id)
|
||||
assert files == []
|
||||
end
|
||||
end
|
||||
90
test/bds/menu_test.exs
Normal file
90
test/bds/menu_test.exs
Normal file
@@ -0,0 +1,90 @@
|
||||
defmodule BDS.MenuTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-menu-#{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: "Menu", data_path: temp_dir})
|
||||
%{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
|
||||
assert {:ok, menu} =
|
||||
BDS.Menu.update_menu(project.id, [
|
||||
%{kind: :page, label: "About", slug: "about"},
|
||||
%{
|
||||
kind: :submenu,
|
||||
label: "Sections",
|
||||
children: [
|
||||
%{kind: :category_archive, label: "Notes", slug: "notes"},
|
||||
%{kind: :page, label: "Contact", slug: "contact"}
|
||||
]
|
||||
},
|
||||
%{kind: :home, label: "Ignored Home"}
|
||||
])
|
||||
|
||||
assert hd(menu.items) == %{kind: :home, label: "Home", slug: nil}
|
||||
assert Enum.at(menu.items, 1) == %{kind: :page, label: "About", slug: "about"}
|
||||
|
||||
assert Enum.at(menu.items, 2) == %{
|
||||
kind: :submenu,
|
||||
label: "Sections",
|
||||
slug: nil,
|
||||
children: [
|
||||
%{kind: :category_archive, label: "Notes", slug: "notes"},
|
||||
%{kind: :page, label: "Contact", slug: "contact"}
|
||||
]
|
||||
}
|
||||
|
||||
opml_path = Path.join([temp_dir, "meta", "menu.opml"])
|
||||
assert File.exists?(opml_path)
|
||||
|
||||
contents = File.read!(opml_path)
|
||||
assert contents =~ ~s(<outline kind="home" text="Home")
|
||||
assert contents =~ ~s(<outline kind="page" text="About" slug="about")
|
||||
assert contents =~ ~s(<outline kind="submenu" text="Sections")
|
||||
assert contents =~ ~s(<outline kind="category_archive" text="Notes" slug="notes")
|
||||
|
||||
assert {:ok, loaded} = BDS.Menu.get_menu(project.id)
|
||||
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
|
||||
meta_dir = Path.join(temp_dir, "meta")
|
||||
File.mkdir_p!(meta_dir)
|
||||
|
||||
File.write!(
|
||||
Path.join(meta_dir, "menu.opml"),
|
||||
[
|
||||
~s(<?xml version="1.0" encoding="UTF-8"?>),
|
||||
~s(<opml version="2.0">),
|
||||
~s( <body>),
|
||||
~s( <outline kind="page" text="Blog" slug="blog" />),
|
||||
~s( <outline kind="submenu" text="Topics">),
|
||||
~s( <outline kind="category_archive" text="Elixir" slug="elixir" />),
|
||||
~s( </outline>),
|
||||
~s( </body>),
|
||||
~s(</opml>)
|
||||
]
|
||||
|> Enum.join("\n")
|
||||
)
|
||||
|
||||
assert {:ok, menu} = BDS.Menu.sync_menu_from_filesystem(project.id)
|
||||
|
||||
assert menu.items == [
|
||||
%{kind: :home, label: "Home", slug: nil},
|
||||
%{kind: :page, label: "Blog", slug: "blog"},
|
||||
%{
|
||||
kind: :submenu,
|
||||
label: "Topics",
|
||||
slug: nil,
|
||||
children: [
|
||||
%{kind: :category_archive, label: "Elixir", slug: "elixir"}
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
136
test/bds/tasks_test.exs
Normal file
136
test/bds/tasks_test.exs
Normal file
@@ -0,0 +1,136 @@
|
||||
defmodule BDS.TasksTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
setup do
|
||||
original = Application.get_env(:bds, :tasks, [])
|
||||
Application.put_env(:bds, :tasks, max_concurrent: 3, progress_throttle_ms: 250)
|
||||
|
||||
on_exit(fn ->
|
||||
Application.put_env(:bds, :tasks, original)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "submitted tasks respect max concurrency and FIFO queue order" do
|
||||
runner = self()
|
||||
|
||||
work = fn name ->
|
||||
fn _report ->
|
||||
send(runner, {:started, name, self()})
|
||||
|
||||
receive do
|
||||
{:release, ^name} -> :ok
|
||||
end
|
||||
|
||||
{:ok, name}
|
||||
end
|
||||
end
|
||||
|
||||
assert {:ok, first} = BDS.Tasks.submit_task("first", work.("first"))
|
||||
assert {:ok, second} = BDS.Tasks.submit_task("second", work.("second"))
|
||||
assert {:ok, third} = BDS.Tasks.submit_task("third", work.("third"))
|
||||
assert {:ok, fourth} = BDS.Tasks.submit_task("fourth", work.("fourth"))
|
||||
|
||||
started = for _ <- 1..3, do: receive_started()
|
||||
assert Enum.sort(Enum.map(started, &elem(&1, 0))) == ["first", "second", "third"]
|
||||
|
||||
started_by_name = Map.new(started, fn {name, pid} -> {name, pid} end)
|
||||
|
||||
assert BDS.Tasks.get_task(first.id).status == :running
|
||||
assert BDS.Tasks.get_task(second.id).status == :running
|
||||
assert BDS.Tasks.get_task(third.id).status == :running
|
||||
assert BDS.Tasks.get_task(fourth.id).status == :pending
|
||||
|
||||
send(started_by_name["first"], {:release, "first"})
|
||||
|
||||
assert wait_for_task(first.id, &(&1.status == :completed)).result == "first"
|
||||
{"fourth", fourth_pid} = receive_started()
|
||||
assert wait_for_task(fourth.id, &(&1.status == :running)).status == :running
|
||||
|
||||
send(started_by_name["second"], {:release, "second"})
|
||||
send(started_by_name["third"], {:release, "third"})
|
||||
send(fourth_pid, {:release, "fourth"})
|
||||
|
||||
assert wait_for_task(second.id, &(&1.status == :completed)).result == "second"
|
||||
assert wait_for_task(third.id, &(&1.status == :completed)).result == "third"
|
||||
assert wait_for_task(fourth.id, &(&1.status == :completed)).result == "fourth"
|
||||
end
|
||||
|
||||
test "cancel_task cancels pending and running tasks" do
|
||||
runner = self()
|
||||
|
||||
blocking = fn name ->
|
||||
fn _report ->
|
||||
send(runner, {:started, name, self()})
|
||||
|
||||
receive do
|
||||
{:release, ^name} -> :ok
|
||||
end
|
||||
|
||||
{:ok, name}
|
||||
end
|
||||
end
|
||||
|
||||
assert {:ok, first} = BDS.Tasks.submit_task("one", blocking.("one"))
|
||||
assert {:ok, second} = BDS.Tasks.submit_task("two", blocking.("two"))
|
||||
assert {:ok, third} = BDS.Tasks.submit_task("three", blocking.("three"))
|
||||
assert {:ok, pending} = BDS.Tasks.submit_task("four", blocking.("four"))
|
||||
|
||||
started = for _ <- 1..3, do: receive_started()
|
||||
started_by_name = Map.new(started, fn {name, pid} -> {name, pid} end)
|
||||
|
||||
assert :ok = BDS.Tasks.cancel_task(pending.id)
|
||||
assert wait_for_task(pending.id, &(&1.status == :cancelled)).status == :cancelled
|
||||
|
||||
assert :ok = BDS.Tasks.cancel_task(first.id)
|
||||
assert wait_for_task(first.id, &(&1.status == :cancelled)).status == :cancelled
|
||||
|
||||
send(started_by_name["two"], {:release, "two"})
|
||||
send(started_by_name["three"], {:release, "three"})
|
||||
|
||||
assert wait_for_task(second.id, &(&1.status == :completed)).status == :completed
|
||||
assert wait_for_task(third.id, &(&1.status == :completed)).status == :completed
|
||||
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 task.status == :running
|
||||
assert task.group_id == "generation"
|
||||
assert task.group_name == "Generation"
|
||||
|
||||
assert :ok = BDS.Tasks.report_progress(task.id, 0.5, "halfway")
|
||||
|
||||
progressed = wait_for_task(task.id, &(&1.progress == 0.5 and &1.message == "halfway"))
|
||||
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
|
||||
end
|
||||
|
||||
defp receive_started do
|
||||
receive do
|
||||
{:started, name, pid} -> {name, pid}
|
||||
after
|
||||
1_000 -> flunk("task did not start")
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_task(task_id, predicate, attempts \\ 100)
|
||||
|
||||
defp wait_for_task(task_id, predicate, attempts) when attempts > 0 do
|
||||
task = BDS.Tasks.get_task(task_id)
|
||||
|
||||
if predicate.(task) do
|
||||
task
|
||||
else
|
||||
Process.sleep(20)
|
||||
wait_for_task(task_id, predicate, attempts - 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_task(_task_id, _predicate, 0) do
|
||||
flunk("task did not reach expected state")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user