Files
bDS2/test/bds/image_import_pipeline_test.exs

217 lines
7.0 KiB
Elixir

defmodule BDS.ImageImportPipelineTest do
use ExUnit.Case, async: false
import Phoenix.ConnTest
import Phoenix.LiveViewTest
alias BDS.Desktop.{FilePicker, Overlay}
alias BDS.{Metadata, AI, Repo}
@endpoint BDS.Desktop.Endpoint
setup do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
Ecto.Adapters.SQL.Sandbox.mode(Repo, {:shared, self()})
temp_dir =
Path.join(System.tmp_dir!(), "bds-image-import-#{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: "Image Import Test", data_path: temp_dir})
%{project: project, temp_dir: temp_dir}
end
# ── FilePicker multi-select parsing ────────────────────────────────────────
describe "FilePicker.choose_files/2" do
test "single selection returns a single-item list" do
# Simulate what osascript returns for regular choose file
result = FilePicker.parse_choose_files_result("/Users/test/image.png", false)
assert result == {:ok, "/Users/test/image.png"}
end
test "multi selection parses newline-separated paths" do
result =
FilePicker.parse_choose_files_result(
"/Users/test/photo1.jpg\n/Users/test/photo2.png\n/Users/test/photo3.heic",
true
)
assert result ==
{:ok, ["/Users/test/photo1.jpg", "/Users/test/photo2.png", "/Users/test/photo3.heic"]}
end
test "multi selection filters out empty lines" do
result =
FilePicker.parse_choose_files_result(
"/Users/test/photo1.jpg\n\n/Users/test/photo2.png\n \n/Users/test/photo3.heic\n",
true
)
assert result ==
{:ok, ["/Users/test/photo1.jpg", "/Users/test/photo2.png", "/Users/test/photo3.heic"]}
end
test "multi selection with single file returns list with one element" do
result = FilePicker.parse_choose_files_result("/Users/test/photo1.jpg", true)
assert result == {:ok, ["/Users/test/photo1.jpg"]}
end
end
# ── Metadata image_import_concurrency ───────────────────────────────────────
describe "Metadata image_import_concurrency" do
test "defaults to 4 for new projects", %{project: project} do
{:ok, metadata} = Metadata.get_project_metadata(project.id)
assert metadata.image_import_concurrency == 4
end
test "clamps to minimum 1", %{project: project} do
{:ok, _metadata} =
Metadata.update_project_metadata(project.id, %{image_import_concurrency: 0})
{:ok, metadata} = Metadata.get_project_metadata(project.id)
assert metadata.image_import_concurrency == 1
end
test "clamps to maximum 8", %{project: project} do
{:ok, _metadata} =
Metadata.update_project_metadata(project.id, %{image_import_concurrency: 100})
{:ok, metadata} = Metadata.get_project_metadata(project.id)
assert metadata.image_import_concurrency == 8
end
test "persists and reads correctly", %{project: project} do
{:ok, _metadata} =
Metadata.update_project_metadata(project.id, %{image_import_concurrency: 3})
{:ok, metadata} = Metadata.get_project_metadata(project.id)
assert metadata.image_import_concurrency == 3
end
test "handles string input", %{project: project} do
{:ok, _metadata} =
Metadata.update_project_metadata(project.id, %{image_import_concurrency: "5"})
{:ok, metadata} = Metadata.get_project_metadata(project.id)
assert metadata.image_import_concurrency == 5
end
test "handles nil as default", %{project: project} do
{:ok, _metadata} =
Metadata.update_project_metadata(project.id, %{image_import_concurrency: nil})
{:ok, metadata} = Metadata.get_project_metadata(project.id)
assert metadata.image_import_concurrency == 4
end
test "reflects in form as string", %{project: project} do
{:ok, metadata} = Metadata.get_project_metadata(project.id)
form =
BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings.project_form(metadata)
assert form["image_import_concurrency"] == "4"
end
end
# ── Overlay struct post_id ────────────────────────────────────────────────
describe "Overlay insert_media" do
test "open(:post, :insert_media, context) includes post_id" do
context = %{
current_tab: %{
type: :post,
id: "post-uuid-123",
title: "Test Post",
subtitle: "draft"
},
media: [],
insert_media_title: "Insert Media"
}
overlay = Overlay.open(:post, :insert_media, context)
assert overlay.post_id == "post-uuid-123"
end
test "post_id is nil when opened from non-post context" do
context = %{
current_tab: %{
type: :media,
id: "media-uuid-456",
title: "Test Media",
subtitle: "image/png"
},
media: [],
insert_media_title: "Insert Media"
}
overlay = Overlay.open(:media, :insert_media, context)
assert overlay == nil
end
test "set_search_query preserves post_id" do
overlay = %{
kind: :insert_media,
title: "Insert Media",
search_query: "",
results: [],
all_media: [],
post_id: "post-uuid-789"
}
result = Overlay.set_search_query(overlay, "search term")
assert result.post_id == "post-uuid-789"
end
end
# ── Airplane mode gating via shell_live event ─────────────────────────────
describe "overlay_add_images airplane mode gating" do
setup do
prev_auto = System.get_env("BDS_DESKTOP_AUTOMATION")
System.put_env("BDS_DESKTOP_AUTOMATION", "1")
on_exit(fn ->
if prev_auto,
do: System.put_env("BDS_DESKTOP_AUTOMATION", prev_auto),
else: System.delete_env("BDS_DESKTOP_AUTOMATION")
end)
end
test "shows toast in airplane mode when Add Gallery Images is clicked", %{project: project} do
{:ok, _project} = BDS.Projects.set_active_project(project.id)
assert :ok = AI.set_airplane_mode(true)
{:ok, post} =
BDS.Posts.create_post(%{
project_id: project.id,
title: "Gallery Post",
content: "Content"
})
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
render_click(view, "pin_sidebar_item", %{
"route" => "post",
"id" => post.id,
"title" => post.title,
"subtitle" => "draft"
})
html =
view
|> element("[phx-click='add_gallery_images']")
|> render_click()
assert html =~ "Automatic AI actions stay gated by airplane mode"
assert :ok = AI.set_airplane_mode(false)
end
end
end