171 lines
5.4 KiB
Elixir
171 lines
5.4 KiB
Elixir
defmodule Towerops.Workers.PreseemSyncWorkerTest do
|
|
use Towerops.DataCase, async: true
|
|
use Oban.Testing, repo: Towerops.Repo
|
|
|
|
import Towerops.AccountsFixtures
|
|
import Towerops.IntegrationsFixtures
|
|
import Towerops.OrganizationsFixtures
|
|
|
|
alias Towerops.Preseem.Client
|
|
alias Towerops.Workers.PreseemSyncWorker
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
org = organization_fixture(user.id)
|
|
%{org: org}
|
|
end
|
|
|
|
describe "perform/1 dispatcher (cron)" do
|
|
test "enqueues individual sync jobs for enabled integrations", %{org: org} do
|
|
integration = integration_fixture(org.id)
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{})
|
|
|
|
# Should have enqueued a job with this integration's ID
|
|
assert [job] = all_enqueued(worker: PreseemSyncWorker)
|
|
assert job.args == %{"integration_id" => integration.id}
|
|
assert job.scheduled_at
|
|
end
|
|
|
|
test "staggers jobs across the 600-second window", %{org: org} do
|
|
# Create a second org with its own integration
|
|
user2 = user_fixture()
|
|
org2 = organization_fixture(user2.id)
|
|
|
|
_int1 = integration_fixture(org.id)
|
|
_int2 = integration_fixture(org2.id)
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{})
|
|
|
|
jobs = all_enqueued(worker: PreseemSyncWorker)
|
|
assert length(jobs) == 2
|
|
|
|
# Jobs should have scheduled_at times within 0-600 seconds from now
|
|
now = DateTime.utc_now()
|
|
|
|
for job <- jobs do
|
|
diff = DateTime.diff(job.scheduled_at, now, :second)
|
|
assert diff >= 0 and diff < 600, "Expected offset in [0, 600), got #{diff}"
|
|
end
|
|
end
|
|
|
|
test "skips integrations synced recently", %{org: org} do
|
|
_integration =
|
|
integration_fixture(org.id, %{
|
|
last_synced_at: Towerops.Time.now(),
|
|
last_sync_status: "success"
|
|
})
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{})
|
|
|
|
# No jobs should be enqueued for recently-synced integrations
|
|
assert [] = all_enqueued(worker: PreseemSyncWorker)
|
|
end
|
|
|
|
test "handles no enabled integrations" do
|
|
assert :ok = perform_job(PreseemSyncWorker, %{})
|
|
assert [] = all_enqueued(worker: PreseemSyncWorker)
|
|
end
|
|
|
|
test "respects sync_interval_minutes", %{org: org} do
|
|
# Last synced 5 minutes ago with a 10-minute interval -> should skip
|
|
five_minutes_ago =
|
|
DateTime.utc_now()
|
|
|> DateTime.add(-5, :minute)
|
|
|> DateTime.truncate(:second)
|
|
|
|
_integration =
|
|
integration_fixture(org.id, %{
|
|
sync_interval_minutes: 10,
|
|
last_synced_at: five_minutes_ago,
|
|
last_sync_status: "success"
|
|
})
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{})
|
|
assert [] = all_enqueued(worker: PreseemSyncWorker)
|
|
end
|
|
|
|
test "enqueues job when sync interval has elapsed", %{org: org} do
|
|
# Last synced 15 minutes ago with a 10-minute interval -> should sync
|
|
fifteen_minutes_ago =
|
|
DateTime.utc_now()
|
|
|> DateTime.add(-15, :minute)
|
|
|> DateTime.truncate(:second)
|
|
|
|
integration =
|
|
integration_fixture(org.id, %{
|
|
sync_interval_minutes: 10,
|
|
last_synced_at: fifteen_minutes_ago,
|
|
last_sync_status: "success"
|
|
})
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{})
|
|
|
|
assert [job] = all_enqueued(worker: PreseemSyncWorker)
|
|
assert job.args == %{"integration_id" => integration.id}
|
|
end
|
|
|
|
test "enqueues job for integrations that have never been synced", %{org: org} do
|
|
integration = integration_fixture(org.id)
|
|
assert integration.last_synced_at == nil
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{})
|
|
|
|
assert [job] = all_enqueued(worker: PreseemSyncWorker)
|
|
assert job.args == %{"integration_id" => integration.id}
|
|
end
|
|
end
|
|
|
|
describe "perform/1 individual sync" do
|
|
test "syncs the given integration", %{org: org} do
|
|
integration = integration_fixture(org.id)
|
|
|
|
Req.Test.stub(Client, fn conn ->
|
|
Req.Test.json(conn, [
|
|
%{
|
|
"element_uuid" => "ap-1",
|
|
"name" => "Test AP",
|
|
"system_mac_address" => "AA:BB:CC:DD:EE:FF",
|
|
"management_ip" => "10.0.0.1"
|
|
}
|
|
])
|
|
end)
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{"integration_id" => integration.id})
|
|
|
|
updated = Repo.reload!(integration)
|
|
assert updated.last_synced_at
|
|
assert updated.last_sync_status in ["success", "partial"]
|
|
end
|
|
|
|
test "handles empty string response body from API", %{org: org} do
|
|
integration = integration_fixture(org.id)
|
|
|
|
Req.Test.stub(Client, fn conn ->
|
|
# Preseem sometimes returns an empty string for the scores endpoint
|
|
conn
|
|
|> Plug.Conn.put_resp_content_type("application/json")
|
|
|> Plug.Conn.send_resp(200, ~s(""))
|
|
end)
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{"integration_id" => integration.id})
|
|
end
|
|
|
|
test "handles API errors without crashing", %{org: org} do
|
|
integration = integration_fixture(org.id)
|
|
|
|
Req.Test.stub(Client, fn conn ->
|
|
conn
|
|
|> Plug.Conn.put_status(401)
|
|
|> Req.Test.json(%{"error" => "unauthorized"})
|
|
end)
|
|
|
|
assert :ok = perform_job(PreseemSyncWorker, %{"integration_id" => integration.id})
|
|
end
|
|
|
|
test "returns error for nonexistent integration" do
|
|
assert {:error, :not_found} =
|
|
perform_job(PreseemSyncWorker, %{"integration_id" => Ecto.UUID.generate()})
|
|
end
|
|
end
|
|
end
|