test(activity_feed): add comprehensive test coverage

- Test empty activity list
- Test preseem sync logs with correct DateTime format
- Test duration formatting with integer precision
- Test sync logs without duration
- Test grouping by minute
- Test sorting by timestamp descending
- Test limit parameter
- Test date range filtering
- Test organization isolation
- Test different sync statuses (success, failed, partial)
- Test count_by_type functionality
This commit is contained in:
Graham McIntire 2026-03-06 12:11:30 -06:00
parent 70718a53dc
commit 3a5da6f68c
No known key found for this signature in database

View file

@ -0,0 +1,393 @@
defmodule Towerops.ActivityFeedTest do
use Towerops.DataCase
import Towerops.AccountsFixtures
import Towerops.OrganizationsFixtures
alias Towerops.ActivityFeed
alias Towerops.Integrations
alias Towerops.Preseem.SyncLog
alias Towerops.Repo
describe "list_org_activity/2" do
setup do
user = user_fixture()
organization = organization_fixture(user.id)
# Create integration for sync logs
{:ok, integration} =
Integrations.create_integration(organization.id, %{
provider: "preseem",
enabled: true,
credentials: %{"api_key" => "test_key"}
})
%{user: user, organization: organization, integration: integration}
end
test "returns empty list when no activity exists", %{organization: organization} do
activities = ActivityFeed.list_org_activity(organization.id)
assert activities == []
end
test "includes preseem sync logs with correct timestamp format", %{
organization: organization,
integration: integration
} do
# Create sync logs
{:ok, _sync_log1} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 150,
duration_ms: 223,
inserted_at: ~U[2026-03-06 10:00:00Z]
})
|> Repo.insert()
{:ok, _sync_log2} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "failed",
records_synced: 0,
duration_ms: nil,
inserted_at: ~U[2026-03-06 10:01:00Z]
})
|> Repo.insert()
activities = ActivityFeed.list_org_activity(organization.id)
assert length(activities) == 2
# Check first activity (most recent)
[first | _] = activities
assert first.type == :sync
assert first.summary =~ "Preseem sync"
assert first.detail =~ "records synced"
# Verify timestamp is DateTime (not NaiveDateTime)
assert %DateTime{} = first.timestamp
assert first.timestamp.time_zone == "Etc/UTC"
end
test "formats duration with reasonable precision", %{
organization: organization,
integration: integration
} do
# Create sync log with duration that will be averaged
{:ok, _sync_log} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 100,
duration_ms: 223,
inserted_at: ~U[2026-03-06 10:00:00Z]
})
|> Repo.insert()
activities = ActivityFeed.list_org_activity(organization.id)
assert length(activities) == 1
[activity] = activities
# Should show duration (will be 223 from avg, rounded to integer)
assert activity.detail == "100 records synced in 223ms"
end
test "handles sync logs without duration", %{organization: organization, integration: integration} do
{:ok, _sync_log} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 50,
duration_ms: nil,
inserted_at: ~U[2026-03-06 10:00:00Z]
})
|> Repo.insert()
activities = ActivityFeed.list_org_activity(organization.id)
assert length(activities) == 1
[activity] = activities
# Should not include duration
assert activity.detail == "50 records synced"
end
test "groups sync logs by minute", %{organization: organization, integration: integration} do
# Create multiple sync logs within the same minute
base_time = ~U[2026-03-06 10:00:00Z]
for i <- 0..4 do
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 10 * i,
duration_ms: 100 + i,
inserted_at: DateTime.add(base_time, i * 10, :second)
})
|> Repo.insert()
end
activities = ActivityFeed.list_org_activity(organization.id)
# Should be grouped into 1 activity (same minute)
assert length(activities) == 1
[activity] = activities
# Should sum records_synced (0 + 10 + 20 + 30 + 40 = 100)
assert activity.detail =~ "100 records synced"
# Should average duration_ms ((100 + 101 + 102 + 103 + 104) / 5 = 102)
assert activity.detail =~ "102ms"
end
test "sorts activities by timestamp descending", %{
organization: organization,
integration: integration
} do
# Create sync logs at different times
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 1,
inserted_at: ~U[2026-03-06 10:00:00Z]
})
|> Repo.insert()
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 2,
inserted_at: ~U[2026-03-06 11:00:00Z]
})
|> Repo.insert()
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 3,
inserted_at: ~U[2026-03-06 09:00:00Z]
})
|> Repo.insert()
activities = ActivityFeed.list_org_activity(organization.id)
assert length(activities) == 3
# Should be sorted newest first
[first, second, third] = activities
assert first.detail =~ "2 records"
assert second.detail =~ "1 records"
assert third.detail =~ "3 records"
end
test "respects limit parameter", %{organization: organization, integration: integration} do
# Create many sync logs
for i <- 1..10 do
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: i,
inserted_at: DateTime.add(~U[2026-03-06 10:00:00Z], i * 60, :second)
})
|> Repo.insert()
end
activities = ActivityFeed.list_org_activity(organization.id, limit: 5)
assert length(activities) == 5
end
test "filters by date range", %{organization: organization, integration: integration} do
# Create sync logs over several days
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 1,
inserted_at: ~U[2026-03-05 10:00:00Z]
})
|> Repo.insert()
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 2,
inserted_at: ~U[2026-03-06 10:00:00Z]
})
|> Repo.insert()
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 3,
inserted_at: ~U[2026-03-07 10:00:00Z]
})
|> Repo.insert()
activities =
ActivityFeed.list_org_activity(organization.id,
after: ~U[2026-03-06 00:00:00Z],
before: ~U[2026-03-07 00:00:00Z]
)
assert length(activities) == 1
[activity] = activities
assert activity.detail =~ "2 records"
end
test "only returns activities for the specified organization", %{user: user} do
org1 = organization_fixture(user.id)
org2 = organization_fixture(user.id, %{name: "Other Org"})
# Create integrations for both orgs
{:ok, int1} =
Integrations.create_integration(org1.id, %{
provider: "preseem",
enabled: true,
credentials: %{"api_key" => "test_key1"}
})
{:ok, int2} =
Integrations.create_integration(org2.id, %{
provider: "preseem",
enabled: true,
credentials: %{"api_key" => "test_key2"}
})
# Create sync log for org1
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: org1.id,
integration_id: int1.id,
status: "success",
records_synced: 100,
inserted_at: ~U[2026-03-06 10:00:00Z]
})
|> Repo.insert()
# Create sync log for org2
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: org2.id,
integration_id: int2.id,
status: "success",
records_synced: 200,
inserted_at: ~U[2026-03-06 10:00:00Z]
})
|> Repo.insert()
# Should only see org1's activities
activities = ActivityFeed.list_org_activity(org1.id)
assert length(activities) == 1
[activity] = activities
assert activity.detail =~ "100 records"
# Should only see org2's activities
activities = ActivityFeed.list_org_activity(org2.id)
assert length(activities) == 1
[activity] = activities
assert activity.detail =~ "200 records"
end
test "handles different sync statuses", %{organization: organization, integration: integration} do
for status <- ["success", "failed", "partial"] do
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: status,
records_synced: 10,
inserted_at: DateTime.add(~U[2026-03-06 10:00:00Z], :rand.uniform(3600), :second)
})
|> Repo.insert()
end
activities = ActivityFeed.list_org_activity(organization.id)
assert length(activities) == 3
# Check that statuses are reflected in summaries
summaries = Enum.map(activities, & &1.summary)
assert Enum.any?(summaries, &(&1 =~ "completed"))
assert Enum.any?(summaries, &(&1 =~ "failed"))
assert Enum.any?(summaries, &(&1 =~ "partial"))
end
end
describe "count_by_type/1" do
test "returns zero counts when no activity exists" do
user = user_fixture()
organization = organization_fixture(user.id)
counts = ActivityFeed.count_by_type(organization.id)
assert is_map(counts)
assert counts |> Map.values() |> Enum.all?(&(&1 == 0))
end
test "counts recent sync activities" do
user = user_fixture()
organization = organization_fixture(user.id)
# Create integration
{:ok, integration} =
Integrations.create_integration(organization.id, %{
provider: "preseem",
enabled: true,
credentials: %{"api_key" => "test_key"}
})
# Create recent sync log
{:ok, _} =
%SyncLog{}
|> SyncLog.changeset(%{
organization_id: organization.id,
integration_id: integration.id,
status: "success",
records_synced: 100,
inserted_at: DateTime.utc_now()
})
|> Repo.insert()
counts = ActivityFeed.count_by_type(organization.id)
assert counts[:sync] > 0
end
end
end