neiam
/diogramos
diogramos
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
Generate import slugs from an alphabet the slug format allows
dde1795 · Gabriel Morell · 2026-09-16 03:59
Message
{commit_body(@commit)}
Files changed
modified
lib/diogramos/diagrams/canvases.ex
+5
−2
@@ -215,8 +215,11 @@ defmodule Diogramos.Diagrams.Canvases do
| 215 | 215 | |
| 216 | 216 | base = if base == "", do: "canvas", else: base |
| 217 | 217 | |
| 218 | − suffix = | |
| 219 | − :crypto.strong_rand_bytes(3) |> Base.url_encode64(padding: false) |> String.downcase() | |
| 218 | + # base32, not url_encode64: the URL-safe alphabet includes "_", which | |
| 219 | + # `Canvas.create_changeset/2` rejects, so roughly one import in | |
| 220 | + # sixteen failed on a slug it had generated itself. base32's | |
| 221 | + # alphabet lowercases into exactly the characters the slug allows. | |
| 222 | + suffix = :crypto.strong_rand_bytes(3) |> Base.encode32(case: :lower, padding: false) | |
| 220 | 223 | |
| 221 | 224 | base <> "-" <> suffix |
| 222 | 225 | end |
modified
test/diogramos/diagrams_test.exs
+35
−0
@@ -276,4 +276,39 @@ defmodule Diogramos.DiagramsTest do
| 276 | 276 | assert Diagrams.authorize(b, :admin, f) == {:error, :forbidden} |
| 277 | 277 | end |
| 278 | 278 | end |
| 279 | + | |
| 280 | + describe "generated slugs" do | |
| 281 | + test "always satisfy the format the changeset enforces" do | |
| 282 | + scope = Diogramos.AccountsFixtures.user_scope_fixture() | |
| 283 | + | |
| 284 | + payload = %{ | |
| 285 | + "format" => "diogramos.canvas.v1", | |
| 286 | + "title" => "Round-trip", | |
| 287 | + "theme" => "afterdark", | |
| 288 | + "document" => %{"elements" => %{}, "order" => [], "connectors" => %{}} | |
| 289 | + } | |
| 290 | + | |
| 291 | + # The suffix is random, and the previous alphabet produced an | |
| 292 | + # invalid slug about one time in sixteen -- often enough to fail a | |
| 293 | + # build, rarely enough to look flaky rather than broken. | |
| 294 | + for _ <- 1..200 do | |
| 295 | + assert {:ok, canvas} = Diagrams.import_canvas(scope, payload) | |
| 296 | + assert canvas.slug =~ ~r/^[a-z0-9][a-z0-9-]{0,63}$/ | |
| 297 | + end | |
| 298 | + end | |
| 299 | + | |
| 300 | + test "a title with nothing usable in it still yields a valid slug" do | |
| 301 | + scope = Diogramos.AccountsFixtures.user_scope_fixture() | |
| 302 | + | |
| 303 | + payload = %{ | |
| 304 | + "format" => "diogramos.canvas.v1", | |
| 305 | + "title" => "!!! ???", | |
| 306 | + "theme" => "afterdark", | |
| 307 | + "document" => %{"elements" => %{}, "order" => [], "connectors" => %{}} | |
| 308 | + } | |
| 309 | + | |
| 310 | + assert {:ok, canvas} = Diagrams.import_canvas(scope, payload) | |
| 311 | + assert canvas.slug =~ ~r/^[a-z0-9][a-z0-9-]{0,63}$/ | |
| 312 | + end | |
| 313 | + end | |
| 279 | 314 | end |
modified
test/diogramos_web/channels/canvas_channel_test.exs
+8
−5
@@ -17,7 +17,10 @@ defmodule DiogramosWeb.CanvasChannelTest do
| 17 | 17 | |
| 18 | 18 | defp connect_as(user) do |
| 19 | 19 | token = Accounts.create_api_token(user) |
| 20 | − {:ok, socket} = Phoenix.ChannelTest.connect(UserSocket, %{"token" => token}, %{}) | |
| 20 | + | |
| 21 | + {:ok, socket} = | |
| 22 | + Phoenix.ChannelTest.connect(UserSocket, %{"token" => token}, connect_info: %{}) | |
| 23 | + | |
| 21 | 24 | socket |
| 22 | 25 | end |
| 23 | 26 |
@@ -33,14 +36,14 @@ defmodule DiogramosWeb.CanvasChannelTest do
| 33 | 36 | describe "socket connect" do |
| 34 | 37 | test "accepts a valid api token", %{user: user} do |
| 35 | 38 | token = Accounts.create_api_token(user) |
| 36 | − assert {:ok, socket} = connect(UserSocket, %{"token" => token}, %{}) | |
| 39 | + assert {:ok, socket} = connect(UserSocket, %{"token" => token}, connect_info: %{}) | |
| 37 | 40 | assert socket.assigns.user_id == user.id |
| 38 | 41 | assert UserSocket.id(socket) == "user_socket:#{user.id}" |
| 39 | 42 | end |
| 40 | 43 | |
| 41 | 44 | test "rejects a bogus or missing token" do |
| 42 | − assert :error = connect(UserSocket, %{"token" => "nope"}, %{}) | |
| 43 | − assert :error = connect(UserSocket, %{}, %{}) | |
| 45 | + assert :error = connect(UserSocket, %{"token" => "nope"}, connect_info: %{}) | |
| 46 | + assert :error = connect(UserSocket, %{}, connect_info: %{}) | |
| 44 | 47 | end |
| 45 | 48 | |
| 46 | 49 | test "rejects a revoked token", %{user: user} do |
@@ -48,7 +51,7 @@ defmodule DiogramosWeb.CanvasChannelTest do
| 48 | 51 | {_user, token_id} = Accounts.fetch_user_by_api_token(token) |
| 49 | 52 | :ok = Accounts.delete_api_token(user, token_id) |
| 50 | 53 | |
| 51 | − assert :error = connect(UserSocket, %{"token" => token}, %{}) | |
| 54 | + assert :error = connect(UserSocket, %{"token" => token}, connect_info: %{}) | |
| 52 | 55 | end |
| 53 | 56 | end |
| 54 | 57 |
Parents: e380962