Files
bDS2/test/bds/scripts/transforms_test.exs

243 lines
5.7 KiB
Elixir

defmodule BDS.Scripts.TransformsTest do
use ExUnit.Case, async: false
alias BDS.Scripts
alias BDS.Scripts.Transforms
setup do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()})
temp_dir =
Path.join(System.tmp_dir!(), "bds-transforms-#{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: "Transforms", data_path: temp_dir})
%{project: project}
end
defp transform(project_id, title, content, opts \\ []) do
{:ok, script} =
Scripts.create_script(%{
project_id: project_id,
title: title,
kind: :transform,
content: content,
entrypoint: Keyword.get(opts, :entrypoint, "main")
})
script =
case Keyword.get(opts, :enabled, true) do
true ->
script
false ->
{:ok, s} = Scripts.update_script(script.id, %{enabled: false})
s
end
script
end
test "runs enabled transforms in deterministic order (updated_at, slug, id)", %{
project: project
} do
# Each transform appends its marker to content so we can read execution order.
transform(project.id, "Bravo", """
function main(data, _ctx)
data.content = data.content .. "B"
return data
end
""")
# Ensure distinct updated_at ordering by spacing out creation.
Process.sleep(5)
transform(project.id, "Alpha", """
function main(data, _ctx)
data.content = data.content .. "A"
return data
end
""")
data = %{
"title" => "t",
"content" => "",
"tags" => [],
"categories" => [],
"url" => "http://x"
}
assert {:ok, result} = Transforms.run(project.id, data)
# Bravo created first (earlier updated_at) so runs before Alpha.
assert result.data["content"] == "BA"
assert result.errors == []
end
test "disabled transforms and transforms from other projects are skipped", %{project: project} do
{:ok, other} =
BDS.Projects.create_project(%{
name: "Other",
data_path:
Path.join(
System.tmp_dir!(),
"bds-transforms-other-#{System.unique_integer([:positive])}"
)
})
transform(
project.id,
"Disabled",
"""
function main(data, _ctx)
data.content = data.content .. "D"
return data
end
""",
enabled: false
)
transform(other.id, "Foreign", """
function main(data, _ctx)
data.content = data.content .. "F"
return data
end
""")
transform(project.id, "Enabled", """
function main(data, _ctx)
data.content = data.content .. "E"
return data
end
""")
data = %{
"title" => "t",
"content" => "",
"tags" => [],
"categories" => [],
"url" => "http://x"
}
assert {:ok, result} = Transforms.run(project.id, data)
assert result.data["content"] == "E"
end
test "pipeline continues after a failing transform, keeping last valid state", %{
project: project
} do
transform(project.id, "First", """
function main(data, _ctx)
data.content = data.content .. "1"
return data
end
""")
Process.sleep(5)
transform(project.id, "Boom", """
function main(_data, _ctx)
error("boom")
end
""")
Process.sleep(5)
transform(project.id, "Third", """
function main(data, _ctx)
data.content = data.content .. "3"
return data
end
""")
data = %{
"title" => "t",
"content" => "",
"tags" => [],
"categories" => [],
"url" => "http://x"
}
assert {:ok, result} = Transforms.run(project.id, data)
# Boom's failure does not roll back "1" and does not stop "3".
assert result.data["content"] == "13"
assert [%{reason: _}] = result.errors
end
test "receives blogmark context with source and originating url", %{project: project} do
transform(project.id, "Ctx", """
function main(data, ctx)
data.content = ctx.source .. "|" .. ctx.url
return data
end
""")
data = %{
"title" => "t",
"content" => "",
"tags" => [],
"categories" => [],
"url" => "http://example.com/a"
}
assert {:ok, result} = Transforms.run(project.id, data)
assert result.data["content"] == "blogmark|http://example.com/a"
end
test "per-script toast budget caps and truncates toasts", %{project: project} do
long = String.duplicate("x", 500)
transform(project.id, "Noisy", """
function main(data, _ctx)
local toasts = {}
for i = 1, 10 do toasts[i] = "#{long}" end
return { data = data, toasts = toasts }
end
""")
data = %{
"title" => "t",
"content" => "",
"tags" => [],
"categories" => [],
"url" => "http://x"
}
assert {:ok, result} = Transforms.run(project.id, data)
# max 5 per script
assert length(result.toasts) == 5
# truncated to 300 chars
assert Enum.all?(result.toasts, &(String.length(&1) == 300))
end
test "total toast budget caps across the whole pipeline", %{project: project} do
body = """
function main(data, _ctx)
local toasts = {}
for i = 1, 5 do toasts[i] = "msg" end
return { data = data, toasts = toasts }
end
"""
# 5 transforms x 5 toasts each = 25 emitted, total budget is 20.
for i <- 1..5 do
transform(project.id, "T#{i}", body)
Process.sleep(3)
end
data = %{
"title" => "t",
"content" => "",
"tags" => [],
"categories" => [],
"url" => "http://x"
}
assert {:ok, result} = Transforms.run(project.id, data)
assert length(result.toasts) == 20
end
end