fix: fixed bds2: protocol handling
This commit is contained in:
@@ -49,6 +49,50 @@ defmodule BDS.BlogmarkTest do
|
||||
assert candidate["categories"] == ["news"]
|
||||
end
|
||||
|
||||
test "strips credentials and the fragment from the url" do
|
||||
url =
|
||||
"bds2://new-post?title=T&url=" <>
|
||||
URI.encode_www_form("https://user:pass@example.com/p?x=1#frag")
|
||||
|
||||
assert {:ok, candidate} = Blogmark.parse_deep_link(url)
|
||||
assert candidate["url"] == "https://example.com/p?x=1"
|
||||
end
|
||||
|
||||
test "drops a non-http(s) url so it cannot reach the post body" do
|
||||
url = "bds2://new-post?title=Unsafe&url=" <> URI.encode_www_form("javascript:alert(1)")
|
||||
|
||||
assert {:ok, candidate} = Blogmark.parse_deep_link(url)
|
||||
assert candidate["url"] == nil
|
||||
end
|
||||
|
||||
test "drops an over-long url" do
|
||||
long = "https://example.com/" <> String.duplicate("a", 2100)
|
||||
url = "bds2://new-post?title=T&url=" <> URI.encode_www_form(long)
|
||||
|
||||
assert {:ok, candidate} = Blogmark.parse_deep_link(url)
|
||||
assert candidate["url"] == nil
|
||||
end
|
||||
|
||||
test "falls back to the url host when the title is blank" do
|
||||
url = "bds2://new-post?title=&url=" <> URI.encode_www_form("https://example.com/page")
|
||||
|
||||
assert {:ok, candidate} = Blogmark.parse_deep_link(url)
|
||||
assert candidate["title"] == "example.com"
|
||||
end
|
||||
|
||||
test "strips control characters and caps the title length" do
|
||||
raw_title = "A\x00B\x07C" <> String.duplicate("x", 250)
|
||||
|
||||
url =
|
||||
"bds2://new-post?title=" <>
|
||||
URI.encode_www_form(raw_title) <> "&url=https://x"
|
||||
|
||||
assert {:ok, candidate} = Blogmark.parse_deep_link(url)
|
||||
refute String.contains?(candidate["title"], "\x00")
|
||||
refute String.contains?(candidate["title"], "\x07")
|
||||
assert String.length(candidate["title"]) == 200
|
||||
end
|
||||
|
||||
test "rejects an unsupported scheme" do
|
||||
assert {:error, :unsupported_scheme} =
|
||||
Blogmark.parse_deep_link("bds://new-post?title=T")
|
||||
@@ -60,6 +104,21 @@ defmodule BDS.BlogmarkTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "deep_link_project_id/1" do
|
||||
test "returns the project_id carried by the link" do
|
||||
assert Blogmark.deep_link_project_id("bds2://new-post?title=T&project_id=proj-7") == "proj-7"
|
||||
end
|
||||
|
||||
test "returns nil when absent or blank" do
|
||||
assert Blogmark.deep_link_project_id("bds2://new-post?title=T") == nil
|
||||
assert Blogmark.deep_link_project_id("bds2://new-post?title=T&project_id=") == nil
|
||||
end
|
||||
|
||||
test "returns nil for an unparseable link" do
|
||||
assert Blogmark.deep_link_project_id("bds://new-post?title=T") == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "receive_deep_link/3" do
|
||||
test "creates a draft post from the deep link", %{project: project} do
|
||||
url =
|
||||
@@ -119,6 +178,32 @@ defmodule BDS.BlogmarkTest do
|
||||
assert post.categories == ["article"]
|
||||
end
|
||||
|
||||
test "defaults the body to a markdown link to the bookmarked page (parity with bDS)",
|
||||
%{project: project} do
|
||||
url =
|
||||
"bds2://new-post?title=" <>
|
||||
URI.encode_www_form("Hello (World)") <>
|
||||
"&url=" <> URI.encode_www_form("https://example.com/a")
|
||||
|
||||
assert {:ok, %{post: post}} = Blogmark.receive_deep_link(project.id, url)
|
||||
assert post.content == "[Hello \\(World\\)](https://example.com/a)"
|
||||
end
|
||||
|
||||
test "keeps explicit content over the default markdown link", %{project: project} do
|
||||
url =
|
||||
"bds2://new-post?title=T&url=https://x&content=" <> URI.encode_www_form("body text")
|
||||
|
||||
assert {:ok, %{post: post}} = Blogmark.receive_deep_link(project.id, url)
|
||||
assert post.content == "body text"
|
||||
end
|
||||
|
||||
test "leaves the body empty when the deep link carries no url", %{project: project} do
|
||||
assert {:ok, %{post: post}} =
|
||||
Blogmark.receive_deep_link(project.id, "bds2://new-post?title=Just%20A%20Title")
|
||||
|
||||
assert post.content in [nil, ""]
|
||||
end
|
||||
|
||||
test "returns an error for an invalid deep link", %{project: project} do
|
||||
assert {:error, :unsupported_scheme} =
|
||||
Blogmark.receive_deep_link(project.id, "bds://new-post?title=T")
|
||||
|
||||
@@ -4,29 +4,74 @@ defmodule BDS.Desktop.DeepLinkTest do
|
||||
alias BDS.Desktop.DeepLink
|
||||
|
||||
setup do
|
||||
topic = "deep-link-test:#{System.unique_integer([:positive])}"
|
||||
Phoenix.PubSub.subscribe(BDS.PubSub, topic)
|
||||
|
||||
{:ok, pid} = DeepLink.start_link(name: nil, pubsub: BDS.PubSub, topic: topic)
|
||||
{:ok, pid} = DeepLink.start_link(name: nil)
|
||||
on_exit(fn -> if Process.alive?(pid), do: GenServer.stop(pid) end)
|
||||
|
||||
%{pid: pid, topic: topic}
|
||||
%{pid: pid}
|
||||
end
|
||||
|
||||
test "broadcasts a bds2:// open_url event to the shell topic", %{pid: pid} do
|
||||
test "delivers a bds2:// link (wx charlist payload) to an attached shell", %{pid: pid} do
|
||||
:ok = DeepLink.attach(self(), pid)
|
||||
|
||||
url = "bds2://new-post?title=Hello&url=https://example.com"
|
||||
send(pid, {:open_url, [url]})
|
||||
# This is the exact shape erlang :wx sends on macOS: {:open_url, charlist}.
|
||||
send(pid, {:open_url, ~c"bds2://new-post?title=Hello&url=https://example.com"})
|
||||
|
||||
assert_receive {:blogmark_deep_link, ^url}, 500
|
||||
end
|
||||
|
||||
test "also accepts a binary payload", %{pid: pid} do
|
||||
:ok = DeepLink.attach(self(), pid)
|
||||
|
||||
url = "bds2://new-post?title=Bin"
|
||||
send(pid, {:open_url, url})
|
||||
|
||||
assert_receive {:blogmark_deep_link, ^url}, 500
|
||||
end
|
||||
|
||||
test "queues a link that arrives before a shell attaches, then replays it", %{pid: pid} do
|
||||
url = "bds2://new-post?title=Cold&url=https://example.com"
|
||||
send(pid, {:open_url, String.to_charlist(url)})
|
||||
|
||||
# Nothing is connected yet, so nothing is delivered.
|
||||
refute_receive {:blogmark_deep_link, _}, 100
|
||||
|
||||
:ok = DeepLink.attach(self(), pid)
|
||||
assert_receive {:blogmark_deep_link, ^url}, 500
|
||||
end
|
||||
|
||||
test "replays multiple cold-start links in arrival order", %{pid: pid} do
|
||||
send(pid, {:open_url, ~c"bds2://new-post?title=One"})
|
||||
send(pid, {:open_url, ~c"bds2://new-post?title=Two"})
|
||||
|
||||
:ok = DeepLink.attach(self(), pid)
|
||||
|
||||
assert_receive {:blogmark_deep_link, "bds2://new-post?title=One"}, 500
|
||||
assert_receive {:blogmark_deep_link, "bds2://new-post?title=Two"}, 500
|
||||
end
|
||||
|
||||
test "queues again after the attached shell dies", %{pid: pid} do
|
||||
task = Task.async(fn -> DeepLink.attach(self(), pid) end)
|
||||
Task.await(task)
|
||||
# task process is now dead; DeepLink should observe the DOWN and re-queue.
|
||||
Process.sleep(50)
|
||||
|
||||
url = "bds2://new-post?title=After&url=https://example.com"
|
||||
send(pid, {:open_url, String.to_charlist(url)})
|
||||
|
||||
:ok = DeepLink.attach(self(), pid)
|
||||
assert_receive {:blogmark_deep_link, ^url}, 500
|
||||
end
|
||||
|
||||
test "ignores non-bds2 open_url events", %{pid: pid} do
|
||||
send(pid, {:open_url, ["https://example.com"]})
|
||||
:ok = DeepLink.attach(self(), pid)
|
||||
send(pid, {:open_url, ~c"https://example.com"})
|
||||
|
||||
refute_receive {:blogmark_deep_link, _url}, 200
|
||||
end
|
||||
|
||||
test "ignores unrelated messages", %{pid: pid} do
|
||||
:ok = DeepLink.attach(self(), pid)
|
||||
send(pid, {:open_file, ["/tmp/x"]})
|
||||
|
||||
refute_receive {:blogmark_deep_link, _url}, 200
|
||||
|
||||
@@ -904,6 +904,59 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
assert html =~ ~s(data-tab-id="#{created_post.id}")
|
||||
end
|
||||
|
||||
test "blogmark deep link with a project_id switches to that project and imports there",
|
||||
%{project: active} do
|
||||
{:ok, other} =
|
||||
Projects.create_project(%{
|
||||
name: "Other Blog",
|
||||
data_path: Path.join(System.tmp_dir!(), "bds-other-#{System.unique_integer([:positive])}")
|
||||
})
|
||||
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
url =
|
||||
"bds2://new-post?title=" <>
|
||||
URI.encode_www_form("Cross Project") <>
|
||||
"&url=" <> URI.encode_www_form("https://example.com/x") <>
|
||||
"&project_id=" <> other.id
|
||||
|
||||
send(view.pid, {:blogmark_deep_link, url})
|
||||
html = render(view)
|
||||
|
||||
created_post = Repo.get_by!(Post, title: "Cross Project")
|
||||
# Imported into the link's project, not the one that was active.
|
||||
assert created_post.project_id == other.id
|
||||
refute created_post.project_id == active.id
|
||||
# And the shell switched the active project to the link's target.
|
||||
assert Projects.get_active_project().id == other.id
|
||||
assert html =~ ~s(data-tab-id="#{created_post.id}")
|
||||
end
|
||||
|
||||
test "blogmark deep link with an unknown project_id fails without importing", %{project: active} do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
post_count_before = Repo.aggregate(Post, :count, :id)
|
||||
|
||||
url =
|
||||
"bds2://new-post?title=" <>
|
||||
URI.encode_www_form("Stale Link") <>
|
||||
"&url=" <> URI.encode_www_form("https://example.com/s") <>
|
||||
"&project_id=does-not-exist-00000000-0000-0000-0000-000000000000"
|
||||
|
||||
send(view.pid, {:blogmark_deep_link, url})
|
||||
_html = render(view)
|
||||
|
||||
# No post is created, and the active project is left untouched.
|
||||
assert Repo.aggregate(Post, :count, :id) == post_count_before
|
||||
assert Projects.get_active_project().id == active.id
|
||||
|
||||
# The user is told the targeted project does not exist (error toast entry).
|
||||
entries = :sys.get_state(view.pid).socket.assigns.output_entries
|
||||
|
||||
assert Enum.any?(entries, fn entry ->
|
||||
entry.level == "error" and entry.message =~ "does not exist here"
|
||||
end)
|
||||
end
|
||||
|
||||
test "settings sidebar selections expose a scroll target for the preferences editor" do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user