D2-10/D2-12/D2-15/D2-16: close out remaining D2 spec gaps with tests + validate_media implementation
This commit is contained in:
@@ -13,6 +13,37 @@ defmodule BDS.CliSyncTest do
|
||||
:ok
|
||||
end
|
||||
|
||||
test "app-side writes do not produce notification rows (AppNoopNotifier)", %{} do
|
||||
existing_before_create = Repo.aggregate(BDS.CliSync.Notification, :count)
|
||||
|
||||
# Perform a few app-side operations — post create, media import, metadata
|
||||
# update — none should leave a notification row behind.
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-app-noop-#{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: "AppNoop", data_path: temp_dir})
|
||||
|
||||
{:ok, _post} =
|
||||
BDS.Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
title: "App-Created",
|
||||
content: "body"
|
||||
})
|
||||
|
||||
source_path = Path.join(temp_dir, "image.png")
|
||||
File.write!(source_path, "fake png")
|
||||
{:ok, _media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
{:ok, _metadata} =
|
||||
BDS.Metadata.update_project_metadata(project.id, %{name: "Renamed"})
|
||||
|
||||
existing_after = Repo.aggregate(BDS.CliSync.Notification, :count)
|
||||
assert existing_after == existing_before_create
|
||||
end
|
||||
|
||||
test "cli mutations are written to db_notifications, processed on file change, and marked seen" do
|
||||
assert {:ok, notification} = CliSync.cli_mutation_performed("post", "post-1", :updated)
|
||||
assert notification.from_cli == true
|
||||
|
||||
@@ -773,6 +773,65 @@ defmodule BDS.MediaTest do
|
||||
|> Image.open!()
|
||||
end
|
||||
|
||||
describe "validate_media (D2-16)" do
|
||||
test "reports no issues for healthy media with a post link", %{project: project, temp_dir: temp_dir} do
|
||||
source_path = Path.join(temp_dir, "sample.png")
|
||||
File.write!(source_path, sample_image_binary(".png"))
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
{:ok, post} = BDS.Posts.create_post(%{project_id: project.id, title: "Linked", content: "body"})
|
||||
{:ok, :linked} = BDS.Media.Linking.link_media_to_post(media.id, post.id)
|
||||
assert [] == BDS.Media.validate_media(project.id)
|
||||
end
|
||||
|
||||
test "reports missing binary file", %{project: project, temp_dir: temp_dir} do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello")
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
media_id = media.id
|
||||
|
||||
data_dir = BDS.Projects.project_data_dir(project)
|
||||
File.rm!(Path.join(data_dir, media.file_path))
|
||||
|
||||
issues = BDS.Media.validate_media(project.id)
|
||||
assert Enum.any?(issues, &(&1.media_id == media_id and &1.issue == "missing_binary"))
|
||||
end
|
||||
|
||||
test "reports missing sidecar", %{project: project, temp_dir: temp_dir} do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello")
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
media_id = media.id
|
||||
|
||||
data_dir = BDS.Projects.project_data_dir(project)
|
||||
File.rm!(Path.join(data_dir, media.sidecar_path))
|
||||
|
||||
issues = BDS.Media.validate_media(project.id)
|
||||
assert Enum.any?(issues, &(&1.media_id == media_id and &1.issue == "missing_sidecar"))
|
||||
end
|
||||
|
||||
test "reports orphan media (not linked to any post)", %{project: project, temp_dir: temp_dir} do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello")
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
media_id = media.id
|
||||
|
||||
issues = BDS.Media.validate_media(project.id)
|
||||
assert Enum.any?(issues, &(&1.media_id == media_id and &1.issue == "orphan"))
|
||||
end
|
||||
|
||||
test "does not report orphan when linked to a post", %{project: project, temp_dir: temp_dir} do
|
||||
source_path = Path.join(temp_dir, "sample.txt")
|
||||
File.write!(source_path, "hello")
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
{:ok, post} = BDS.Posts.create_post(%{project_id: project.id, title: "Linked", content: "body"})
|
||||
{:ok, :linked} = BDS.Media.Linking.link_media_to_post(media.id, post.id)
|
||||
|
||||
issues = BDS.Media.validate_media(project.id)
|
||||
refute Enum.any?(issues, &(&1.issue == "orphan"))
|
||||
end
|
||||
end
|
||||
|
||||
defp assert_images_match!(left, right) do
|
||||
assert Image.shape(left) == Image.shape(right)
|
||||
|
||||
|
||||
@@ -34,6 +34,42 @@ defmodule BDS.Scripting.LuaTest do
|
||||
assert_receive {:progress, %{"phase" => "write", "current" => 2, "total" => 2}}
|
||||
end
|
||||
|
||||
test "sandbox blocks os.execute" do
|
||||
source = "function main() os.execute('echo hacked') end"
|
||||
|
||||
assert {:error, _reason} = BDS.Scripting.execute(source, "main", [])
|
||||
end
|
||||
|
||||
test "sandbox blocks os.rename" do
|
||||
source = "function main() os.rename('/etc/passwd', '/tmp/hacked') end"
|
||||
|
||||
assert {:error, _reason} = BDS.Scripting.execute(source, "main", [])
|
||||
end
|
||||
|
||||
test "sandbox blocks io.open for writing" do
|
||||
source = "function main() io.open('/tmp/hacked', 'w') end"
|
||||
|
||||
assert {:error, _reason} = BDS.Scripting.execute(source, "main", [])
|
||||
end
|
||||
|
||||
test "sandbox blocks require" do
|
||||
source = "function main() require('socket') end"
|
||||
|
||||
assert {:error, _reason} = BDS.Scripting.execute(source, "main", [])
|
||||
end
|
||||
|
||||
test "sandbox blocks dofile" do
|
||||
source = "function main() dofile('/etc/hosts') end"
|
||||
|
||||
assert {:error, _reason} = BDS.Scripting.execute(source, "main", [])
|
||||
end
|
||||
|
||||
test "sandbox blocks loadlib" do
|
||||
source = "function main() package.loadlib('libc.so', 'system') end"
|
||||
|
||||
assert {:error, _reason} = BDS.Scripting.execute(source, "main", [])
|
||||
end
|
||||
|
||||
test "enforces reduction limits" do
|
||||
source = "function main() while true do end end"
|
||||
|
||||
|
||||
@@ -95,6 +95,34 @@ defmodule BDS.TasksTest do
|
||||
assert wait_for_task(third.id, &(&1.status == :completed)).status == :completed
|
||||
end
|
||||
|
||||
test "progress reports within 250ms throttle window are silently dropped" do
|
||||
assert {:ok, task} = BDS.Tasks.register_external_task("fast progress")
|
||||
|
||||
assert :ok = BDS.Tasks.report_progress(task.id, 0.25, "quarter")
|
||||
assert wait_for_task(task.id, &(&1.progress == 0.25)).progress == 0.25
|
||||
|
||||
assert :ok = BDS.Tasks.report_progress(task.id, 0.5, "half")
|
||||
assert task_id = task.id
|
||||
# The 250ms throttle has not elapsed, so progress stays at 0.25.
|
||||
assert wait_for_task(task_id, & &1.progress == 0.25).progress == 0.25
|
||||
|
||||
on_exit(fn -> BDS.Tasks.complete_task(task.id) end)
|
||||
end
|
||||
|
||||
test "progress report with value 1.0 bypasses the throttle" do
|
||||
assert {:ok, task} = BDS.Tasks.register_external_task("completion progress")
|
||||
|
||||
assert :ok = BDS.Tasks.report_progress(task.id, 0.25, "quarter")
|
||||
|
||||
# A completion report (1.0) must go through even if throttled.
|
||||
assert :ok = BDS.Tasks.report_progress(task.id, 1.0, "done")
|
||||
|
||||
assert wait_for_task(task.id, &(&1.progress == 1.0)).progress == 1.0
|
||||
assert wait_for_task(task.id, &(&1.message == "done")).message == "done"
|
||||
|
||||
on_exit(fn -> BDS.Tasks.complete_task(task.id) end)
|
||||
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", %{
|
||||
|
||||
Reference in New Issue
Block a user