add preseem sync logs schema and migration

This commit is contained in:
Graham McIntire 2026-02-12 17:12:19 -06:00
parent 5f17d5aed1
commit 26e19b00f7
No known key found for this signature in database
3 changed files with 151 additions and 0 deletions

View file

@ -0,0 +1,41 @@
defmodule Towerops.Preseem.SyncLog do
@moduledoc """
Audit log for Preseem sync operations.
"""
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@valid_statuses ~w(success partial failed)
schema "preseem_sync_logs" do
field :status, :string
field :records_synced, :integer, default: 0
field :errors, :map
field :duration_ms, :integer
field :inserted_at, :utc_datetime
belongs_to :organization, Towerops.Organizations.Organization
belongs_to :integration, Towerops.Integrations.Integration
end
def changeset(sync_log, attrs) do
sync_log
|> cast(attrs, [
:organization_id,
:integration_id,
:status,
:records_synced,
:errors,
:duration_ms,
:inserted_at
])
|> validate_required([:organization_id, :integration_id, :status, :inserted_at])
|> validate_inclusion(:status, @valid_statuses)
|> foreign_key_constraint(:organization_id)
|> foreign_key_constraint(:integration_id)
end
end

View file

@ -0,0 +1,26 @@
defmodule Towerops.Repo.Migrations.CreatePreseemSyncLogs do
use Ecto.Migration
def change do
create table(:preseem_sync_logs, primary_key: false) do
add :id, :binary_id, primary_key: true
add :organization_id, references(:organizations, type: :binary_id, on_delete: :delete_all),
null: false
add :integration_id, references(:integrations, type: :binary_id, on_delete: :delete_all),
null: false
add :status, :string, null: false
add :records_synced, :integer, default: 0
add :errors, :map
add :duration_ms, :integer
add :inserted_at, :utc_datetime, null: false
end
create index(:preseem_sync_logs, [:organization_id])
create index(:preseem_sync_logs, [:integration_id])
create index(:preseem_sync_logs, [:inserted_at])
end
end

View file

@ -0,0 +1,84 @@
defmodule Towerops.Preseem.SyncLogTest do
use Towerops.DataCase, async: true
import Towerops.AccountsFixtures
import Towerops.IntegrationsFixtures
import Towerops.OrganizationsFixtures
alias Towerops.Preseem.SyncLog
setup do
user = user_fixture()
org = organization_fixture(user.id)
integration = integration_fixture(org.id)
%{organization: org, integration: integration}
end
describe "changeset/2" do
test "valid changeset", %{organization: org, integration: integration} do
attrs = %{
organization_id: org.id,
integration_id: integration.id,
status: "success",
records_synced: 42,
duration_ms: 1500,
inserted_at: DateTime.truncate(DateTime.utc_now(), :second)
}
changeset = SyncLog.changeset(%SyncLog{}, attrs)
assert changeset.valid?
end
test "requires organization_id and integration_id" do
attrs = %{status: "success", inserted_at: DateTime.truncate(DateTime.utc_now(), :second)}
changeset = SyncLog.changeset(%SyncLog{}, attrs)
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).organization_id
assert "can't be blank" in errors_on(changeset).integration_id
end
test "requires status" do
attrs = %{
organization_id: Ecto.UUID.generate(),
integration_id: Ecto.UUID.generate(),
inserted_at: DateTime.truncate(DateTime.utc_now(), :second)
}
changeset = SyncLog.changeset(%SyncLog{}, attrs)
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).status
end
test "validates status values" do
base = %{
organization_id: Ecto.UUID.generate(),
integration_id: Ecto.UUID.generate(),
inserted_at: DateTime.truncate(DateTime.utc_now(), :second)
}
for status <- ~w(success partial failed) do
changeset = SyncLog.changeset(%SyncLog{}, Map.put(base, :status, status))
assert changeset.valid?, "Expected #{status} to be valid"
end
changeset = SyncLog.changeset(%SyncLog{}, Map.put(base, :status, "bogus"))
refute changeset.valid?
end
test "stores error details", %{organization: org, integration: integration} do
errors = %{"message" => "API timeout", "code" => 504}
attrs = %{
organization_id: org.id,
integration_id: integration.id,
status: "failed",
errors: errors,
inserted_at: DateTime.truncate(DateTime.utc_now(), :second)
}
{:ok, log} = %SyncLog{} |> SyncLog.changeset(attrs) |> Repo.insert()
reloaded = Repo.get!(SyncLog, log.id)
assert reloaded.errors == errors
end
end
end