defmodule LambentExWeb.ChannelsTest do
use LambentExWeb.ChannelCase
alias LambentExWeb.Channels.Payload
alias LambentExWeb.UserSocket
@pubsub LambentEx.PubSub
defp connect_to(topic, params \\ %{}) do
{:ok, _reply, socket} =
UserSocket
|> socket("test-client", %{})
|> subscribe_and_join(topic, params)
socket
end
describe "payload" do
test "makes the wire-hostile bits of a publish encodable" do
device = %{"ip" => {172, 16, 15, 140}, "sox" => 2, "ind" => 7776, "name" => "S5"}
wired = Payload.device(device)
assert wired["ip"] == "172.16.15.140"
assert {:ok, _json} = Jason.encode(wired)
end
test "keyword options become objects and atoms become strings" do
link = %{
name: "l1",
chan: :all,
opts: [name: "l1", source: "src", target: "mac", chan: :all, persist: true]
}
wired = Payload.wire(link)
assert wired["chan"] == "all"
assert wired["opts"]["persist"] == true
assert {:ok, _json} = Jason.encode(wired)
end
end
# the clamps publish on their own timer, so the push in flight when a command
# lands is one from before it. wait for the one that carries the change.
defp await_clamp(pick, tries \\ 8) do
assert_push "brightness", payload, 1000
case pick.(payload) do
nil when tries > 0 -> await_clamp(pick, tries - 1)
found -> found
end
end
describe "links made over the channel" do
test "a link created with a machine's id still finds the machine" do
{:ok, machine} =
LambentEx.Machine.start_link(nil,
name: "channel-source",
step: LambentEx.Machine.Steps.Solid,
step_opts: %{h: 84},
count: 1,
single: true
)
id = LambentEx.Machine.info("channel-source")[:id]
socket = connect_to("links")
ref =
push(socket, "create", %{
"link" => %{
"name" => "channel-link",
"source_id" => id,
"target_id" => "aa:bb:cc:dd:ee:99",
"chan" => "all"
}
})
assert_reply ref, :ok
assert [{pid, _}] = Registry.lookup(:lambent_links, "link-channel-link")
assert :sys.get_state(pid).source == "channel-source"
LambentEx.LinkSupervisor.abort_child(pid)
Process.exit(machine, :kill)
end
end
describe "re-probing" do
test "a client can ask every device what it runs" do
socket = connect_to("devices")
assert_push "devices", %{devices: _snapshot}
ref = push(socket, "refresh", %{})
assert_reply ref, :ok
end
end
describe "choosing what gets swept" do
setup do
on_exit(fn -> LambentEx.Meta.put_skipped_ifaces([]) end)
:ok
end
test "the devices channel says which interfaces the sweep runs on" do
connect_to("devices")
assert_push "interfaces", %{interfaces: interfaces}
assert is_list(interfaces)
assert {:ok, _json} = Jason.encode(interfaces)
# never the ones filtered out by prefix
refute Enum.any?(interfaces, fn iface -> String.starts_with?(iface.name, "lo") end)
refute Enum.any?(interfaces, fn iface -> String.starts_with?(iface.name, "docker") end)
end
test "turning one off is remembered and pushed back out" do
socket = connect_to("devices")
assert_push "interfaces", %{interfaces: _first}
ref = push(socket, "interface", %{"name" => "test-iface0", "enabled" => false})
assert_reply ref, :ok
assert_push "interfaces", %{interfaces: _updated}
assert "test-iface0" in LambentEx.Meta.get_skipped_ifaces()
ref = push(socket, "interface", %{"name" => "test-iface0", "enabled" => true})
assert_reply ref, :ok
assert_push "interfaces", %{interfaces: _again}
refute "test-iface0" in LambentEx.Meta.get_skipped_ifaces()
end
end
describe "the machine form over the machines channel" do
test "describes what it can build and what each type carries" do
connect_to("machines", %{"previews" => false})
assert_push "types", %{types: types}
assert {:ok, _json} = Jason.encode(types)
chaser = Enum.find(types, fn t -> t.type == "gen_chaser_h" end)
assert chaser.label == "Chaser (HSV)"
hue = Enum.find(chaser.fields, fn f -> f.name == "h" end)
assert hue.kind == "hue"
assert hue.required
spacing = Enum.find(chaser.fields, fn f -> f.name == "spacing" end)
refute spacing.required
assert spacing.default == 30
# nothing that has no form behind it
refute Enum.any?(types, fn t -> t.type == "rcv_gsi" end)
# the fft receiver picks its broker from the ones the server knows
fft = Enum.find(types, fn t -> t.type == "rcv_fft" end)
broker = Enum.find(fft.fields, fn f -> f.name == "broker" end)
assert broker.kind == "broker"
assert is_list(broker.options)
end
test "previews a configuration without starting it" do
socket = connect_to("machines", %{"previews" => false})
ref =
push(socket, "preview", %{
"machine" => %{
"name" => "preview-test",
"type" => "gen_solid_h",
"class" => %{"h" => 84, "s" => 255, "v" => 255}
}
})
assert_reply ref, :ok, %{pixels: pixels}
assert pixels != []
assert [6, 255, 0] in pixels
# and nothing was started
assert Registry.lookup(:lambent_machine, "machine-preview-test") == []
end
test "a half filled configuration previews as nothing" do
socket = connect_to("machines", %{"previews" => false})
ref =
push(socket, "preview", %{
"machine" => %{"name" => "preview-empty", "type" => "gen_solid_h"}
})
assert_reply ref, :ok, %{pixels: []}
end
test "hands over a catalog entry for editing" do
socket = connect_to("machines", %{"previews" => false})
ref = push(socket, "library_edit", %{"cls" => "chase", "entry" => "purple"})
assert_reply ref, :ok, %{machine: machine}
assert machine.name == "purple-edited"
assert machine.type == "gen_chaser_h"
assert machine.class["h"] == 170
assert {:ok, _json} = Jason.encode(machine)
end
test "refuses an entry with no form behind it" do
socket = connect_to("machines", %{"previews" => false})
ref = push(socket, "library_edit", %{"cls" => "solidrgb", "entry" => "ruby"})
assert_reply ref, :error, %{reason: _why}
end
end
describe "brightness channel" do
test "pushes the ladders and the clamps on join" do
connect_to("brightness")
assert_push "ladder", %{steps: steps, fades: fades}
assert %{value: 255, name: "FULL"} in steps
assert %{rate: 120_000, name: "TWOMINS"} in fades
assert {:ok, _json} = Jason.encode(%{steps: steps, fades: fades})
assert_push "brightness", %{"global" => global}
assert global["curr"] != nil
assert global["rate"] != nil
end
test "sets a place clamp and pushes it back" do
socket = connect_to("brightness")
ref = push(socket, "set", %{"place" => "channel-test", "value" => 63})
assert_reply ref, :ok
clamp = await_clamp(fn payload -> payload["places"]["channel-test"] end)
assert clamp["tgt"] == 63
ref = push(socket, "clear", %{"place" => "channel-test"})
assert_reply ref, :ok
end
test "the global clamp is the one with no place" do
socket = connect_to("brightness")
ref = push(socket, "set", %{"value" => 127})
assert_reply ref, :ok
assert await_clamp(fn payload ->
if payload["global"]["tgt"] == 127, do: payload["global"]
end)
ref = push(socket, "set", %{"value" => 255})
assert_reply ref, :ok
end
end
describe "scheduling over the services channel" do
test "schedules a cronos entry" do
socket = connect_to("services")
ref =
push(socket, "create_cronos", %{
"cronos" => %{
"name" => "channel-sched",
"time" => "23:30",
"action" => "global_bright",
"arg" => "63"
}
})
assert_reply ref, :ok, %{name: "channel-sched"}
assert {_name, cfg} =
LambentEx.Meta.get_cronos() |> Enum.find(fn {k, _v} -> k == "channel-sched" end)
assert cfg.action == :global_bright
LambentEx.Meta.del_cronos("channel-sched")
end
test "refuses a schedule with nothing to act on" do
socket = connect_to("services")
ref =
push(socket, "create_cronos", %{
"cronos" => %{"name" => "channel-bad", "time" => "23:30", "action" => "link_on"}
})
assert_reply ref, :error, %{errors: errors}
assert errors[:targets] != nil
end
test "offers the same choices the web form does" do
socket = connect_to("services")
ref = push(socket, "cronos_options", %{})
assert_reply ref, :ok
assert_push "cronos_options", %{actions: actions, args: args}
assert %{label: "Link on", value: "link_on"} in actions
assert args["link_on"] == []
assert {:ok, _json} = Jason.encode(%{actions: actions, args: args})
end
end
describe "devices channel" do
test "pushes a snapshot on join and forwards publishes" do
connect_to("devices")
assert_push "devices", %{devices: _snapshot}
Phoenix.PubSub.broadcast(
@pubsub,
"scan-82667777",
{:devices_pub, %{"aa:bb" => %{"ip" => {10, 0, 0, 9}, "sox" => 2, "type" => "7777-flex"}}}
)
assert_push "devices", %{devices: devices}
assert devices["aa:bb"]["ip"] == "10.0.0.9"
assert {:ok, _json} = Jason.encode(devices)
end
test "forwards manual probe results" do
connect_to("devices")
Phoenix.PubSub.broadcast(@pubsub, "scan-manual", {:manual_result, "10.0.0.9", :not_lambent})
assert_push "probe_result", %{ip: "10.0.0.9", status: "not_lambent"}
end
test "rejects a probe of something that is not an address" do
socket = connect_to("devices")
ref = push(socket, "probe", %{"ip" => "nope"})
assert_reply ref, :error, %{reason: _}
end
test "accepts the device controls" do
socket = connect_to("devices")
for {cmd, params} <- [
{"rename", %{"id" => "aa:bb", "name" => "kitchen"}},
{"replace", %{"id" => "aa:bb", "place" => "cubby"}},
{"reorder", %{"id" => "aa:bb", "ord" => "grb"}},
{"poke", %{"id" => "aa:bb", "mode" => "indicator"}}
] do
ref = push(socket, cmd, params)
assert_reply ref, :ok
end
end
end
describe "machines channel" do
test "forwards publishes and the preview firehose" do
connect_to("machines")
Phoenix.PubSub.broadcast(
@pubsub,
"machines_idx",
{:machines_pub,
%{
id: "SolidxA",
name: "A",
step: "Solid",
speed: 1000,
opts: [single: true],
bgt: 255,
persist: false
}}
)
assert_push "machine", machine
assert machine["opts"]["single"] == true
assert {:ok, _json} = Jason.encode(machine)
Phoenix.PubSub.broadcast(
@pubsub,
"machine:",
{:firehose, {"A", [[255, 0, 0], [0, 255, 0]]}}
)
assert_push "preview", %{name: "A", pixels: [[255, 0, 0], [0, 255, 0]]}
end
test "pushes the library on join, so a client can just listen" do
connect_to("machines")
assert_push "library", %{library: library}
assert {:ok, _json} = Jason.encode(library)
assert map_size(library) > 0
end
test "serves the library again on request" do
socket = connect_to("machines")
assert_push "library", %{library: _}
ref = push(socket, "library", %{})
assert_reply ref, :ok, %{library: library}
assert_push "library", %{library: ^library}
end
test "refuses a library entry that does not exist" do
socket = connect_to("machines")
ref = push(socket, "library_add", %{"cls" => "nope", "entry" => "nope"})
assert_reply ref, :error, %{reason: _}
end
test "skips the firehose when asked to" do
connect_to("machines", %{"previews" => false})
Phoenix.PubSub.broadcast(@pubsub, "machine:", {:firehose, {"A", [[1, 2, 3]]}})
refute_push "preview", %{}
end
end
describe "links channel" do
test "forwards publishes with the channel the link drives" do
connect_to("links")
Phoenix.PubSub.broadcast(
@pubsub,
"links_idx",
{:links_pub,
%{
name: "l1",
source: "SolidxA",
target: "aa:bb",
chan: 1,
enabled: true,
persist: false,
opts: []
}}
)
assert_push "link", link
assert link["chan"] == 1
assert {:ok, _json} = Jason.encode(link)
end
test "reports changeset errors on a bad create" do
socket = connect_to("links")
ref = push(socket, "create", %{"link" => %{"name" => "only-a-name"}})
assert_reply ref, :error, %{errors: errors}
assert errors[:target_id] != nil
end
end
describe "services channel" do
test "pushes each configured kind on join" do
connect_to("services")
assert_push "services", %{kind: "http", services: _}
assert_push "services", %{kind: "mqtt", services: _}
assert_push "services", %{kind: "cronos", services: _}
end
end
end
neiam
/lambent_ex
Lambent Aether 5
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
13.3 KiB · text
History
9c0fc16