Merge remote-tracking branch 'origin/main' into gaiia
This commit is contained in:
commit
f7dc546bcf
6 changed files with 651 additions and 4 deletions
|
|
@ -32,7 +32,7 @@ spec:
|
|||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: towerops
|
||||
image: git.mcintire.me/graham/towerops-web:main-1771190642-e6665ec # {"$imagepolicy": "flux-system:towerops-web"}
|
||||
image: git.mcintire.me/graham/towerops-web:main-1771194965-074c55c # {"$imagepolicy": "flux-system:towerops-web"}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
|
|
|
|||
|
|
@ -199,8 +199,11 @@ defmodule ToweropsWeb.Org.GaiiaMappingLive do
|
|||
if matches == [], do: acc, else: Map.put(acc, site.gaiia_id, matches)
|
||||
end)
|
||||
|
||||
# Sort items with suggestions first
|
||||
sorted_sites = Enum.sort_by(sites, fn site -> if Map.has_key?(suggestions, site.gaiia_id), do: 0, else: 1 end)
|
||||
|
||||
socket
|
||||
|> assign(:items, sites)
|
||||
|> assign(:items, sorted_sites)
|
||||
|> assign(:suggestions, suggestions)
|
||||
end
|
||||
|
||||
|
|
@ -217,8 +220,11 @@ defmodule ToweropsWeb.Org.GaiiaMappingLive do
|
|||
if matches == [], do: acc, else: Map.put(acc, item.gaiia_id, matches)
|
||||
end)
|
||||
|
||||
# Sort items with suggestions first
|
||||
sorted_items = Enum.sort_by(items, fn item -> if Map.has_key?(suggestions, item.gaiia_id), do: 0, else: 1 end)
|
||||
|
||||
socket
|
||||
|> assign(:items, items)
|
||||
|> assign(:items, sorted_items)
|
||||
|> assign(:suggestions, suggestions)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -193,5 +193,70 @@ defmodule Towerops.Gaiia.ClientTest do
|
|||
|
||||
assert {:error, {:rate_limited, 30}} = Client.query("test-key", "{ viewer { id } }")
|
||||
end
|
||||
|
||||
test "handles unexpected status codes" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(502)
|
||||
|> Req.Test.json(%{"error" => "bad gateway"})
|
||||
end)
|
||||
|
||||
assert {:error, {:unexpected_status, 502}} = Client.query("test-key", "{ viewer { id } }")
|
||||
end
|
||||
|
||||
test "sends correct headers" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
api_key = Plug.Conn.get_req_header(conn, "x-gaiia-api-key")
|
||||
assert api_key == ["my-api-key"]
|
||||
|
||||
content_type = Plug.Conn.get_req_header(conn, "content-type")
|
||||
assert content_type == ["application/json"]
|
||||
|
||||
Req.Test.json(conn, %{"data" => %{}})
|
||||
end)
|
||||
|
||||
Client.query("my-api-key", "{ viewer { id } }")
|
||||
end
|
||||
end
|
||||
|
||||
describe "create_ticket/3" do
|
||||
test "creates a ticket via mutation" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
{:ok, body, _conn} = Plug.Conn.read_body(conn)
|
||||
decoded = Jason.decode!(body)
|
||||
assert String.contains?(decoded["query"], "createExternalTicket")
|
||||
assert decoded["variables"]["subject"] == "Test ticket"
|
||||
|
||||
Req.Test.json(conn, %{
|
||||
"data" => %{
|
||||
"createExternalTicket" => %{
|
||||
"id" => "ticket-1",
|
||||
"subject" => "Test ticket"
|
||||
}
|
||||
}
|
||||
})
|
||||
end)
|
||||
|
||||
assert {:ok, data} = Client.create_ticket("test-key", %{"subject" => "Test ticket", "description" => "Details"})
|
||||
assert data["createExternalTicket"]["id"] == "ticket-1"
|
||||
end
|
||||
end
|
||||
|
||||
describe "create_note/5" do
|
||||
test "creates a note on an entity" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
{:ok, body, _conn} = Plug.Conn.read_body(conn)
|
||||
decoded = Jason.decode!(body)
|
||||
assert decoded["variables"]["entityId"] == "acct-1"
|
||||
assert decoded["variables"]["entityType"] == "Account"
|
||||
assert decoded["variables"]["body"] == "Test note"
|
||||
|
||||
Req.Test.json(conn, %{
|
||||
"data" => %{"createNote" => %{"id" => "note-1"}}
|
||||
})
|
||||
end)
|
||||
|
||||
assert {:ok, _} = Client.create_note("test-key", "Account", "acct-1", "Test note")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ defmodule Towerops.Gaiia.SyncTest do
|
|||
end
|
||||
|
||||
test "handles API that returns null when after variable is null", %{
|
||||
org: org,
|
||||
org: _org,
|
||||
integration: integration
|
||||
} do
|
||||
# Some Gaiia endpoints return null for the root key when $after is null
|
||||
|
|
|
|||
366
test/towerops/splynx/client_test.exs
Normal file
366
test/towerops/splynx/client_test.exs
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
defmodule Towerops.Splynx.ClientTest do
|
||||
use Towerops.DataCase, async: true
|
||||
|
||||
alias Towerops.Splynx.Client
|
||||
|
||||
@instance_url "https://demo.splynx.com"
|
||||
@api_key "test_api_key"
|
||||
@api_secret "test_api_secret"
|
||||
|
||||
describe "authenticate/3" do
|
||||
test "succeeds with api_key auth type" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
||||
decoded = Jason.decode!(body)
|
||||
|
||||
assert decoded["auth_type"] == "api_key"
|
||||
assert decoded["key"] == @api_key
|
||||
assert decoded["secret"] == @api_secret
|
||||
|
||||
conn
|
||||
|> Plug.Conn.put_status(201)
|
||||
|> Req.Test.json(auth_success_response())
|
||||
end)
|
||||
|
||||
assert {:ok, "test_jwt_token_abc123"} = Client.authenticate(@instance_url, @api_key, @api_secret)
|
||||
end
|
||||
|
||||
test "falls back to admin auth when api_key auth fails" do
|
||||
call_count = :counters.new(1, [:atomics])
|
||||
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
||||
decoded = Jason.decode!(body)
|
||||
:counters.add(call_count, 1, 1)
|
||||
count = :counters.get(call_count, 1)
|
||||
|
||||
if count == 1 do
|
||||
# First call: api_key auth fails
|
||||
assert decoded["auth_type"] == "api_key"
|
||||
|
||||
conn
|
||||
|> Plug.Conn.put_status(401)
|
||||
|> Req.Test.json(%{"error" => %{"code" => 401, "message" => "Authentication failed!"}})
|
||||
else
|
||||
# Second call: admin auth succeeds
|
||||
assert decoded["auth_type"] == "admin"
|
||||
assert decoded["login"] == @api_key
|
||||
assert decoded["password"] == @api_secret
|
||||
|
||||
conn
|
||||
|> Plug.Conn.put_status(201)
|
||||
|> Req.Test.json(auth_success_response())
|
||||
end
|
||||
end)
|
||||
|
||||
assert {:ok, "test_jwt_token_abc123"} = Client.authenticate(@instance_url, @api_key, @api_secret)
|
||||
end
|
||||
|
||||
test "returns error when both auth types fail" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(401)
|
||||
|> Req.Test.json(%{"error" => %{"code" => 401, "message" => "Authentication failed!"}})
|
||||
end)
|
||||
|
||||
assert {:error, :unauthorized} = Client.authenticate(@instance_url, "bad", "creds")
|
||||
end
|
||||
|
||||
test "returns error on rate limit" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(429)
|
||||
|> Req.Test.json(%{"error" => "rate limited"})
|
||||
end)
|
||||
|
||||
assert {:error, {:rate_limited, 60}} = Client.authenticate(@instance_url, @api_key, @api_secret)
|
||||
end
|
||||
end
|
||||
|
||||
describe "test_connection/3" do
|
||||
test "returns ok on successful connection" do
|
||||
stub_auth_then(fn conn ->
|
||||
Req.Test.json(conn, [%{"id" => 1, "name" => "Clark Willis"}])
|
||||
end)
|
||||
|
||||
assert {:ok, %{}} = Client.test_connection(@instance_url, @api_key, @api_secret)
|
||||
end
|
||||
|
||||
test "returns error on unauthorized" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(401)
|
||||
|> Req.Test.json(%{"error" => %{"code" => 401, "message" => "Authentication failed!"}})
|
||||
end)
|
||||
|
||||
assert {:error, :unauthorized} = Client.test_connection(@instance_url, "bad", "creds")
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_customers/3" do
|
||||
test "returns customers from single page" do
|
||||
stub_auth_then(fn conn ->
|
||||
Req.Test.json(conn, customers_response())
|
||||
end)
|
||||
|
||||
assert {:ok, customers} = Client.list_customers(@instance_url, @api_key, @api_secret)
|
||||
assert length(customers) == 3
|
||||
assert Enum.map(customers, & &1["name"]) == ["Clark Willis", "Eugene Castro", "Nelson Jackson"]
|
||||
end
|
||||
|
||||
test "handles paginated results" do
|
||||
call_count = :counters.new(1, [:atomics])
|
||||
|
||||
stub_auth_then(fn conn ->
|
||||
:counters.add(call_count, 1, 1)
|
||||
count = :counters.get(call_count, 1)
|
||||
|
||||
if count == 1 do
|
||||
# Return exactly 100 to trigger pagination
|
||||
items = Enum.map(1..100, fn i ->
|
||||
%{"id" => i, "name" => "Customer #{i}", "status" => "active", "mrr_total" => "50.0000"}
|
||||
end)
|
||||
|
||||
Req.Test.json(conn, items)
|
||||
else
|
||||
# Second page: less than 100, stops pagination
|
||||
Req.Test.json(conn, [
|
||||
%{"id" => 101, "name" => "Customer 101", "status" => "active", "mrr_total" => "50.0000"}
|
||||
])
|
||||
end
|
||||
end)
|
||||
|
||||
assert {:ok, customers} = Client.list_customers(@instance_url, @api_key, @api_secret)
|
||||
assert length(customers) == 101
|
||||
end
|
||||
|
||||
test "returns error on API failure" do
|
||||
stub_auth_then(fn conn ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(401)
|
||||
|> Req.Test.json(%{"error" => "unauthorized"})
|
||||
end)
|
||||
|
||||
assert {:error, :unauthorized} = Client.list_customers(@instance_url, @api_key, @api_secret)
|
||||
end
|
||||
|
||||
test "verifies customer response shape matches real API" do
|
||||
stub_auth_then(fn conn ->
|
||||
Req.Test.json(conn, customers_response())
|
||||
end)
|
||||
|
||||
{:ok, [first | _]} = Client.list_customers(@instance_url, @api_key, @api_secret)
|
||||
|
||||
# These fields come from the real Splynx demo API
|
||||
assert first["id"] == 1
|
||||
assert first["name"] == "Clark Willis"
|
||||
assert first["status"] == "active"
|
||||
assert first["mrr_total"] == "200.0000"
|
||||
assert first["billing_type"] == "recurring"
|
||||
assert first["email"] == "clark.willis@ispframework.com"
|
||||
assert first["city"] == "Praha 5"
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_routers/3" do
|
||||
test "returns routers" do
|
||||
stub_auth_then(fn conn ->
|
||||
Req.Test.json(conn, routers_response())
|
||||
end)
|
||||
|
||||
assert {:ok, routers} = Client.list_routers(@instance_url, @api_key, @api_secret)
|
||||
assert length(routers) == 2
|
||||
assert hd(routers)["title"] == "WiFi router"
|
||||
assert hd(routers)["ip"] == "10.0.20.1"
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_customer_services/4" do
|
||||
test "returns services for a customer" do
|
||||
stub_auth_then(fn conn ->
|
||||
Req.Test.json(conn, customer_services_response())
|
||||
end)
|
||||
|
||||
assert {:ok, services} = Client.list_customer_services(@instance_url, @api_key, @api_secret, 1)
|
||||
assert length(services) == 1
|
||||
|
||||
[service] = services
|
||||
assert service["description"] == "Internet - ADSL 2MB/1MB"
|
||||
assert service["unit_price"] == "200.0000"
|
||||
assert service["status"] == "active"
|
||||
assert service["customer_id"] == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "authorization header" do
|
||||
test "uses Splynx-EA format with access token" do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
case conn.request_path do
|
||||
"/api/2.0/admin/auth/tokens" ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(201)
|
||||
|> Req.Test.json(auth_success_response())
|
||||
|
||||
_ ->
|
||||
# Verify the auth header format
|
||||
auth = Plug.Conn.get_req_header(conn, "authorization")
|
||||
assert auth == ["Splynx-EA (access_token=test_jwt_token_abc123)"]
|
||||
|
||||
Req.Test.json(conn, [%{"id" => 1}])
|
||||
end
|
||||
end)
|
||||
|
||||
Client.list_customers(@instance_url, @api_key, @api_secret)
|
||||
end
|
||||
end
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
defp stub_auth_then(data_fn) do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
case conn.request_path do
|
||||
"/api/2.0/admin/auth/tokens" ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(201)
|
||||
|> Req.Test.json(auth_success_response())
|
||||
|
||||
_ ->
|
||||
data_fn.(conn)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
# Real response shapes captured from demo.splynx.com
|
||||
|
||||
defp auth_success_response do
|
||||
%{
|
||||
"access_token" => "test_jwt_token_abc123",
|
||||
"refresh_token" => "test_refresh_token_abc123",
|
||||
"access_token_expiration" => 1_771_195_460,
|
||||
"refresh_token_expiration" => 1_771_798_460,
|
||||
"permissions" => %{
|
||||
"admin\\customers\\Customer" => %{
|
||||
"index" => "allow",
|
||||
"view" => "allow"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp customers_response do
|
||||
[
|
||||
%{
|
||||
"id" => 1,
|
||||
"name" => "Clark Willis",
|
||||
"email" => "clark.willis@ispframework.com",
|
||||
"billing_email" => "clark.willis@ispframework.com",
|
||||
"phone" => "",
|
||||
"status" => "active",
|
||||
"billing_type" => "recurring",
|
||||
"mrr_total" => "200.0000",
|
||||
"partner_id" => 2,
|
||||
"location_id" => 1,
|
||||
"login" => "000001",
|
||||
"category" => "person",
|
||||
"street_1" => "Voskovcova 1130/28",
|
||||
"zip_code" => "15200",
|
||||
"city" => "Praha 5",
|
||||
"date_add" => "2015-10-21",
|
||||
"account_type" => "regular"
|
||||
},
|
||||
%{
|
||||
"id" => 2,
|
||||
"name" => "Eugene Castro",
|
||||
"email" => "eugene.castro@ispframework.com",
|
||||
"billing_email" => "eugene.castro@ispframework.com",
|
||||
"phone" => "",
|
||||
"status" => "disabled",
|
||||
"billing_type" => "recurring",
|
||||
"mrr_total" => "0.0000",
|
||||
"partner_id" => 1,
|
||||
"location_id" => 2,
|
||||
"login" => "000002",
|
||||
"category" => "person",
|
||||
"street_1" => "Ostrovskeho 1194/21",
|
||||
"zip_code" => "15000",
|
||||
"city" => "Praha 5",
|
||||
"date_add" => "2015-10-21",
|
||||
"account_type" => "regular"
|
||||
},
|
||||
%{
|
||||
"id" => 3,
|
||||
"name" => "Nelson Jackson",
|
||||
"email" => "nelson.jackson@ispframework.com",
|
||||
"billing_email" => "nelson.jackson@ispframework.com",
|
||||
"phone" => "",
|
||||
"status" => "active",
|
||||
"billing_type" => "recurring",
|
||||
"mrr_total" => "300.0000",
|
||||
"partner_id" => 1,
|
||||
"location_id" => 3,
|
||||
"login" => "000003",
|
||||
"category" => "person",
|
||||
"street_1" => "Jeremiasova 2631/16c",
|
||||
"zip_code" => "15500",
|
||||
"city" => "Praha 5",
|
||||
"date_add" => "2015-10-21",
|
||||
"account_type" => "regular"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
defp routers_response do
|
||||
[
|
||||
%{
|
||||
"id" => 1,
|
||||
"title" => "WiFi router",
|
||||
"model" => "RB n",
|
||||
"location_id" => 1,
|
||||
"address" => "",
|
||||
"ip" => "10.0.20.1",
|
||||
"gps" => nil,
|
||||
"authorization_method" => "dhcp_leases",
|
||||
"accounting_method" => "mikrotik_api",
|
||||
"nas_type" => 1,
|
||||
"status" => nil,
|
||||
"partners_ids" => [1, 2]
|
||||
},
|
||||
%{
|
||||
"id" => 2,
|
||||
"title" => "Demo CHR",
|
||||
"model" => "",
|
||||
"location_id" => 5,
|
||||
"address" => "",
|
||||
"ip" => "10.10.10.12",
|
||||
"gps" => nil,
|
||||
"authorization_method" => "ppp_secrets",
|
||||
"accounting_method" => "mikrotik_api",
|
||||
"nas_type" => 1,
|
||||
"status" => nil,
|
||||
"partners_ids" => [1, 2]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
defp customer_services_response do
|
||||
[
|
||||
%{
|
||||
"id" => 4,
|
||||
"type" => "internet",
|
||||
"customer_id" => 1,
|
||||
"tariff_id" => 2,
|
||||
"description" => "Internet - ADSL 2MB/1MB",
|
||||
"quantity" => 1,
|
||||
"unit_price" => "200.0000",
|
||||
"start_date" => "2026-01-01",
|
||||
"end_date" => "0000-00-00",
|
||||
"status" => "active",
|
||||
"login" => "000001",
|
||||
"router_id" => 0,
|
||||
"mac" => "",
|
||||
"ipv4" => "",
|
||||
"taking_ipv4" => "0"
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
210
test/towerops/splynx/sync_test.exs
Normal file
210
test/towerops/splynx/sync_test.exs
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
defmodule Towerops.Splynx.SyncTest do
|
||||
use Towerops.DataCase, async: true
|
||||
|
||||
import Towerops.AccountsFixtures
|
||||
import Towerops.IntegrationsFixtures
|
||||
import Towerops.OrganizationsFixtures
|
||||
|
||||
alias Towerops.Splynx.Client
|
||||
alias Towerops.Splynx.Sync
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
org = organization_fixture(user.id)
|
||||
|
||||
integration =
|
||||
integration_fixture(org.id, %{
|
||||
provider: "splynx",
|
||||
credentials: %{
|
||||
"instance_url" => "https://demo.splynx.com",
|
||||
"api_key" => "test_key",
|
||||
"api_secret" => "test_secret"
|
||||
}
|
||||
})
|
||||
|
||||
%{org: org, integration: integration}
|
||||
end
|
||||
|
||||
describe "sync_organization/1" do
|
||||
test "syncs customers and routers", %{integration: integration} do
|
||||
stub_splynx_api()
|
||||
|
||||
assert {:ok, result} = Sync.sync_organization(integration)
|
||||
assert result.customers == 3
|
||||
assert result.routers == 2
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
assert updated.last_sync_status == "success"
|
||||
assert updated.last_synced_at
|
||||
assert updated.last_sync_message =~ "3 customers"
|
||||
assert updated.last_sync_message =~ "2 active"
|
||||
assert updated.last_sync_message =~ "2 routers"
|
||||
end
|
||||
|
||||
test "calculates MRR from customer mrr_total fields", %{integration: integration} do
|
||||
stub_splynx_api()
|
||||
|
||||
{:ok, _} = Sync.sync_organization(integration)
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
# 200.0000 + 0.0000 + 300.0000 = 500.00
|
||||
assert updated.last_sync_message =~ "MRR: $500.00"
|
||||
end
|
||||
|
||||
test "marks sync as failed on auth error", %{integration: integration} do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(401)
|
||||
|> Req.Test.json(%{"error" => %{"code" => 401, "message" => "Authentication failed!"}})
|
||||
end)
|
||||
|
||||
assert {:error, :unauthorized} = Sync.sync_organization(integration)
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
assert updated.last_sync_status == "failed"
|
||||
assert updated.last_sync_message =~ "Authentication failed"
|
||||
end
|
||||
|
||||
test "marks sync as failed on rate limit", %{integration: integration} do
|
||||
stub_auth_then(fn conn ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(429)
|
||||
|> Req.Test.json(%{"error" => "rate limited"})
|
||||
end)
|
||||
|
||||
assert {:error, {:rate_limited, 60}} = Sync.sync_organization(integration)
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
assert updated.last_sync_status == "failed"
|
||||
assert updated.last_sync_message =~ "Rate limited"
|
||||
end
|
||||
|
||||
test "handles zero MRR gracefully", %{integration: integration} do
|
||||
stub_auth_then_routes(%{
|
||||
"customer" => [%{"id" => 1, "name" => "Test", "status" => "active", "mrr_total" => "0.0000"}],
|
||||
"routers" => []
|
||||
})
|
||||
|
||||
{:ok, _} = Sync.sync_organization(integration)
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
# Should not contain MRR section when total is 0
|
||||
refute updated.last_sync_message =~ "MRR"
|
||||
end
|
||||
|
||||
test "handles customers with nil mrr_total", %{integration: integration} do
|
||||
stub_auth_then_routes(%{
|
||||
"customer" => [%{"id" => 1, "name" => "Test", "status" => "active", "mrr_total" => nil}],
|
||||
"routers" => []
|
||||
})
|
||||
|
||||
{:ok, result} = Sync.sync_organization(integration)
|
||||
assert result.customers == 1
|
||||
end
|
||||
|
||||
test "handles customers with numeric mrr_total", %{integration: integration} do
|
||||
stub_auth_then_routes(%{
|
||||
"customer" => [%{"id" => 1, "name" => "Test", "status" => "active", "mrr_total" => 150.0}],
|
||||
"routers" => []
|
||||
})
|
||||
|
||||
{:ok, _} = Sync.sync_organization(integration)
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
assert updated.last_sync_message =~ "MRR: $150.00"
|
||||
end
|
||||
end
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
defp stub_splynx_api do
|
||||
stub_auth_then_routes(%{
|
||||
"customer" => customers_response(),
|
||||
"routers" => routers_response()
|
||||
})
|
||||
end
|
||||
|
||||
defp stub_auth_then(data_fn) do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
case conn.request_path do
|
||||
"/api/2.0/admin/auth/tokens" ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(201)
|
||||
|> Req.Test.json(auth_success_response())
|
||||
|
||||
_ ->
|
||||
data_fn.(conn)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp stub_auth_then_routes(routes) do
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
case conn.request_path do
|
||||
"/api/2.0/admin/auth/tokens" ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(201)
|
||||
|> Req.Test.json(auth_success_response())
|
||||
|
||||
path ->
|
||||
data =
|
||||
cond do
|
||||
String.contains?(path, "/customer") -> Map.get(routes, "customer", [])
|
||||
String.contains?(path, "/routers") -> Map.get(routes, "routers", [])
|
||||
true -> []
|
||||
end
|
||||
|
||||
Req.Test.json(conn, data)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp auth_success_response do
|
||||
%{
|
||||
"access_token" => "test_jwt_token",
|
||||
"refresh_token" => "test_refresh",
|
||||
"access_token_expiration" => 1_771_195_460,
|
||||
"refresh_token_expiration" => 1_771_798_460,
|
||||
"permissions" => %{}
|
||||
}
|
||||
end
|
||||
|
||||
defp customers_response do
|
||||
[
|
||||
%{
|
||||
"id" => 1,
|
||||
"name" => "Clark Willis",
|
||||
"status" => "active",
|
||||
"mrr_total" => "200.0000",
|
||||
"billing_type" => "recurring",
|
||||
"email" => "clark.willis@ispframework.com",
|
||||
"city" => "Praha 5"
|
||||
},
|
||||
%{
|
||||
"id" => 2,
|
||||
"name" => "Eugene Castro",
|
||||
"status" => "disabled",
|
||||
"mrr_total" => "0.0000",
|
||||
"billing_type" => "recurring",
|
||||
"email" => "eugene.castro@ispframework.com",
|
||||
"city" => "Praha 5"
|
||||
},
|
||||
%{
|
||||
"id" => 3,
|
||||
"name" => "Nelson Jackson",
|
||||
"status" => "active",
|
||||
"mrr_total" => "300.0000",
|
||||
"billing_type" => "recurring",
|
||||
"email" => "nelson.jackson@ispframework.com",
|
||||
"city" => "Praha 5"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
defp routers_response do
|
||||
[
|
||||
%{"id" => 1, "title" => "WiFi router", "ip" => "10.0.20.1"},
|
||||
%{"id" => 2, "title" => "Demo CHR", "ip" => "10.10.10.12"}
|
||||
]
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue