towerops/test/towerops/agents/agent_token_test.exs
2026-02-03 15:27:05 -06:00

228 lines
7.4 KiB
Elixir

defmodule Towerops.Agents.AgentTokenTest do
use Towerops.DataCase, async: true
alias Towerops.Agents.AgentToken
describe "build_token/3" do
test "generates a token with 48 bytes of entropy" do
org_id = Ecto.UUID.generate()
{token, _changeset} = AgentToken.build_token(org_id, "Test Agent")
# Decode the base64-encoded token
decoded = Base.url_decode64!(token, padding: false)
# Should be exactly 48 bytes
assert byte_size(decoded) == 48
end
test "returns token string and valid changeset for organization agent" do
org_id = Ecto.UUID.generate()
{token, changeset} = AgentToken.build_token(org_id, "My Agent")
assert is_binary(token)
assert changeset.valid?
assert changeset.changes.token == token
assert changeset.changes.name == "My Agent"
assert changeset.changes.organization_id == org_id
assert Ecto.Changeset.get_field(changeset, :is_cloud_poller) == false
end
test "sets is_cloud_poller to false by default" do
org_id = Ecto.UUID.generate()
{_token, changeset} = AgentToken.build_token(org_id, "Test Agent")
assert Ecto.Changeset.get_field(changeset, :is_cloud_poller) == false
end
test "generates unique tokens on each call" do
org_id = Ecto.UUID.generate()
{token1, _} = AgentToken.build_token(org_id, "Agent 1")
{token2, _} = AgentToken.build_token(org_id, "Agent 2")
{token3, _} = AgentToken.build_token(org_id, "Agent 3")
# All tokens should be different
assert token1 != token2
assert token2 != token3
assert token1 != token3
end
test "creates cloud poller token when is_cloud_poller is true" do
{token, changeset} = AgentToken.build_token(nil, "Cloud Poller", is_cloud_poller: true)
assert is_binary(token)
assert changeset.valid?
assert changeset.changes.is_cloud_poller == true
refute Map.has_key?(changeset.changes, :organization_id)
end
test "requires name" do
org_id = Ecto.UUID.generate()
{_token, changeset} = AgentToken.build_token(org_id, nil)
refute changeset.valid?
assert %{name: ["can't be blank"]} = errors_on(changeset)
end
test "adds error if cloud poller has organization_id" do
org_id = Ecto.UUID.generate()
{_token, changeset} = AgentToken.build_token(org_id, "Test", is_cloud_poller: true)
refute changeset.valid?
assert %{organization_id: ["must be nil for cloud pollers"]} = errors_on(changeset)
end
test "adds error if non-cloud poller has nil organization_id" do
{_token, changeset} = AgentToken.build_token(nil, "Test", is_cloud_poller: false)
refute changeset.valid?
assert %{organization_id: ["can't be blank for non-cloud pollers"]} = errors_on(changeset)
end
test "token is URL-safe base64 encoded without padding" do
org_id = Ecto.UUID.generate()
{token, _changeset} = AgentToken.build_token(org_id, "Test Agent")
# Should not contain padding characters
refute String.contains?(token, "=")
# Should only contain URL-safe base64 characters
assert token =~ ~r/^[A-Za-z0-9_-]+$/
end
end
describe "update_changeset/2" do
test "allows updating name" do
agent_token = %AgentToken{
id: Ecto.UUID.generate(),
token: "existing-token",
name: "Old Name",
organization_id: Ecto.UUID.generate()
}
attrs = %{name: "New Name"}
changeset = AgentToken.update_changeset(agent_token, attrs)
assert changeset.valid?
assert changeset.changes.name == "New Name"
end
test "allows updating allow_remote_debug" do
agent_token = %AgentToken{
id: Ecto.UUID.generate(),
token: "existing-token",
name: "Test Agent",
organization_id: Ecto.UUID.generate(),
allow_remote_debug: false
}
attrs = %{allow_remote_debug: true}
changeset = AgentToken.update_changeset(agent_token, attrs)
assert changeset.valid?
assert changeset.changes.allow_remote_debug == true
end
test "allows updating both name and allow_remote_debug" do
agent_token = %AgentToken{
id: Ecto.UUID.generate(),
token: "existing-token",
name: "Old Name",
organization_id: Ecto.UUID.generate(),
allow_remote_debug: false
}
attrs = %{name: "New Name", allow_remote_debug: true}
changeset = AgentToken.update_changeset(agent_token, attrs)
assert changeset.valid?
assert changeset.changes.name == "New Name"
assert changeset.changes.allow_remote_debug == true
end
test "requires name" do
agent_token = %AgentToken{
id: Ecto.UUID.generate(),
token: "existing-token",
name: "Old Name",
organization_id: Ecto.UUID.generate()
}
attrs = %{name: ""}
changeset = AgentToken.update_changeset(agent_token, attrs)
refute changeset.valid?
assert %{name: ["can't be blank"]} = errors_on(changeset)
end
test "ignores attempts to update token" do
agent_token = %AgentToken{
id: Ecto.UUID.generate(),
token: "original-token",
name: "Test Agent",
organization_id: Ecto.UUID.generate()
}
attrs = %{name: "New Name", token: "hacker-token"}
changeset = AgentToken.update_changeset(agent_token, attrs)
assert changeset.valid?
assert changeset.changes.name == "New Name"
refute Map.has_key?(changeset.changes, :token)
end
test "ignores attempts to update organization_id" do
agent_token = %AgentToken{
id: Ecto.UUID.generate(),
token: "existing-token",
name: "Test Agent",
organization_id: Ecto.UUID.generate()
}
new_org_id = Ecto.UUID.generate()
attrs = %{name: "New Name", organization_id: new_org_id}
changeset = AgentToken.update_changeset(agent_token, attrs)
assert changeset.valid?
assert changeset.changes.name == "New Name"
refute Map.has_key?(changeset.changes, :organization_id)
end
test "ignores attempts to update enabled" do
agent_token = %AgentToken{
id: Ecto.UUID.generate(),
token: "existing-token",
name: "Test Agent",
organization_id: Ecto.UUID.generate(),
enabled: true
}
attrs = %{name: "New Name", enabled: false}
changeset = AgentToken.update_changeset(agent_token, attrs)
assert changeset.valid?
assert changeset.changes.name == "New Name"
refute Map.has_key?(changeset.changes, :enabled)
end
end
describe "verify_token/1" do
test "returns {:ok, token} for valid binary token" do
token = "valid-token-string"
assert AgentToken.verify_token(token) == {:ok, token}
end
test "returns {:ok, token} for any binary string" do
assert AgentToken.verify_token("abc123") == {:ok, "abc123"}
assert AgentToken.verify_token("") == {:ok, ""}
assert AgentToken.verify_token("with-dashes") == {:ok, "with-dashes"}
end
test "returns {:error, :invalid_token} for non-binary values" do
assert AgentToken.verify_token(nil) == {:error, :invalid_token}
assert AgentToken.verify_token(12_345) == {:error, :invalid_token}
assert AgentToken.verify_token([]) == {:error, :invalid_token}
assert AgentToken.verify_token(%{}) == {:error, :invalid_token}
assert AgentToken.verify_token(:atom) == {:error, :invalid_token}
end
end
end