Pythiae: a pythiae only takes the owner's visions, foci, plani and ankyra

e70ed4d · Gabe Morell · 2026-11-04 22:43

11 files +106 -1
Message
{commit_body(@commit)}

Files changed

modified apps/room_sanctum/lib/room_sanctum/configuration/pythiae.ex
+10 −0
@@ -39,6 +39,9 @@ defmodule RoomSanctum.Configuration.Pythiae do
39 39 use Ecto.Schema
40 40 import Ecto.Changeset
41 41
42 + alias RoomSanctum.Accounts.RabbitUser
43 + alias RoomSanctum.Configuration.{Foci, Ownership, Plani, Vision}
44 +
42 45 schema "cfg_pythiae" do
43 46 field :name, :string
44 47 field :ankyra, {:array, :integer}
@@ -79,5 +82,12 @@ defmodule RoomSanctum.Configuration.Pythiae do
79 82 |> foreign_key_constraint(:curr_foci)
80 83 |> foreign_key_constraint(:curr_plani)
81 84 |> validate_required([:visions, :ankyra, :name])
85 + # The form only offers the owner's own; a forged id of someone else's
86 + # would show their vision, at their foci, on their Ankyra.
87 + |> Ownership.validate_owned(:visions, Vision)
88 + |> Ownership.validate_owned(:ankyra, RabbitUser)
89 + |> Ownership.validate_owned(:curr_vision, Vision)
90 + |> Ownership.validate_owned(:curr_foci, Foci)
91 + |> Ownership.validate_owned(:curr_plani, Plani)
82 92 end
83 93 end
modified apps/room_sanctum/test/room_sanctum_web/live/ownership_test.exs
+11 −1
@@ -136,7 +136,17 @@ defmodule RoomSanctumWeb.OwnershipTest do
136 136
137 137 test "neither lists nor edits another user's pythiae that holds the vision", ctx do
138 138 mine = vision_for(ctx.me, "My dash")
139 − their_screen = pythiae_for(ctx.them, [mine.id])
139 + # Planted past the changeset, which no longer lets their pythiae take
140 + # someone else's vision -- as one saved before it checked could have.
141 + their_screen =
142 + Repo.insert!(%Configuration.Pythiae{
143 + name: "screen-#{System.unique_integer([:positive])}",
144 + user_id: ctx.them.id,
145 + ankyra: [],
146 + visions: [mine.id],
147 + curr_vision: mine.id
148 + })
149 +
140 150 {:ok, live, _html} = live(ctx.conn, Routes.vision_show_path(ctx.conn, :show, mine))
141 151 send(live.pid, :update_sec)
142 152 render_click(live, "toggle-pythiae-sel", %{})
added apps/room_sanctum/test/room_sanctum_web/live/pythiae_owned_ids_test.exs
+85 −0
@@ -0,0 +1,85 @@
1 +defmodule RoomSanctumWeb.PythiaeOwnedIdsTest do
2 + @moduledoc """
3 + A pythiae shows its owner's visions, at their foci or Plani, on their
4 + Ankyra. The form only offers those; a forged id of anyone else's is
5 + refused with a changeset error and nothing is saved.
6 + """
7 + use RoomSanctumWeb.ConnCase
8 +
9 + import Phoenix.LiveViewTest
10 + import RoomSanctum.DataCase, only: [errors_on: 1]
11 + import RoomSanctum.OwnershipFixtures
12 +
13 + alias RoomSanctum.{Configuration, Repo}
14 + alias RoomSanctum.Configuration.Pythiae
15 +
16 + setup :users_rabbit!
17 +
18 + setup %{conn: conn} do
19 + me = owner()
20 + %{conn: log_in_user(conn, me), me: me, them: owner()}
21 + end
22 +
23 + defp submit(conn, fields) do
24 + {:ok, live, _html} = live(conn, Routes.pythiae_index_path(conn, :new))
25 +
26 + live
27 + |> form("#pythiae-form")
28 + |> render_submit(%{"pythiae" => Map.put(fields, "name", "screen")})
29 + end
30 +
31 + defp ids(records), do: Enum.map(records, &to_string(&1.id))
32 +
33 + test "the form saves the owner's own visions, ankyra and foci", ctx do
34 + vision = vision_for(ctx.me)
35 + ankyra = ankyra_for(ctx.me)
36 + foci = foci_for(ctx.me)
37 +
38 + submit(ctx.conn, %{
39 + "visions" => ids([vision]),
40 + "ankyra" => ids([ankyra]),
41 + "curr_vision" => to_string(vision.id),
42 + "curr_foci" => to_string(foci.id)
43 + })
44 +
45 + assert [%Pythiae{} = saved] = Repo.all(Pythiae)
46 + assert saved.visions == [vision.id]
47 + assert saved.ankyra == [ankyra.id]
48 + assert saved.curr_foci == foci.id
49 + end
50 +
51 + test "the form refuses each of another user's records", ctx do
52 + vision = vision_for(ctx.me)
53 + ankyra = ankyra_for(ctx.me)
54 +
55 + own = %{
56 + "visions" => ids([vision]),
57 + "ankyra" => ids([ankyra]),
58 + "curr_vision" => to_string(vision.id)
59 + }
60 +
61 + forged = [
62 + %{"visions" => ids([vision, vision_for(ctx.them)])},
63 + %{"ankyra" => ids([ankyra, ankyra_for(ctx.them)])},
64 + %{"curr_vision" => to_string(vision_for(ctx.them).id)},
65 + %{"curr_foci" => to_string(foci_for(ctx.them).id)},
66 + %{"curr_plani" => to_string(plani_for(ctx.them).id)}
67 + ]
68 +
69 + for fields <- forged do
70 + submit(ctx.conn, Map.merge(own, fields))
71 + assert Repo.all(Pythiae) == [], "saved with #{inspect(fields)}"
72 + end
73 + end
74 +
75 + test "an existing pythiae cannot be moved onto another user's plani", ctx do
76 + {:ok, screen} =
77 + Configuration.create_pythiae(%{name: "screen", user_id: ctx.me.id, visions: [], ankyra: []})
78 +
79 + assert {:error, changeset} =
80 + Configuration.update_pythiae(screen, %{curr_plani: plani_for(ctx.them).id})
81 +
82 + assert %{curr_plani: [_]} = errors_on(changeset)
83 + assert {:ok, _} = Configuration.update_pythiae(screen, %{curr_plani: plani_for(ctx.me).id})
84 + end
85 +end

Parents: 2d73edf