Files
sure/test/controllers/api/v1/sync_controller_test.rb
Mark Hendriksen b73ac207e0 Add API endpoint for triggering family sync (#423)
* Add API endpoint for triggering family sync

Introduces Api::V1::SyncController with a create action to queue a family sync, applying all active rules and syncing accounts. Adds corresponding route, JSON response view, and comprehensive controller tests for authorization and response validation.

* Rename started_at to syncing_at in sync API response

Updated the sync create JSON response to use 'syncing_at' instead of 'started_at'. Adjusted related controller test to check for 'syncing_at'. Also updated API authentication header in test to use 'X-Api-Key' instead of Bearer token.

* Update app/controllers/api/v1/sync_controller.rb

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Mark Hendriksen <hendriksen-mark@hotmail.com>

---------

Signed-off-by: Mark Hendriksen <hendriksen-mark@hotmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-12 17:07:02 +01:00

90 lines
2.5 KiB
Ruby

# frozen_string_literal: true
require "test_helper"
class Api::V1::SyncControllerTest < ActionDispatch::IntegrationTest
setup do
@user = users(:family_admin)
@family = @user.family
# Destroy existing active API keys to avoid validation errors
@user.api_keys.active.destroy_all
# Create fresh API keys
@api_key = ApiKey.create!(
user: @user,
name: "Test Read-Write Key",
scopes: [ "read_write" ],
display_key: "test_rw_#{SecureRandom.hex(8)}"
)
@read_only_api_key = ApiKey.create!(
user: @user,
name: "Test Read-Only Key",
scopes: [ "read" ],
display_key: "test_ro_#{SecureRandom.hex(8)}",
source: "mobile"
)
# Clear any existing rate limit data
Redis.new.del("api_rate_limit:#{@api_key.id}")
Redis.new.del("api_rate_limit:#{@read_only_api_key.id}")
end
test "should trigger sync with valid write API key" do
assert_enqueued_with(job: SyncJob) do
post api_v1_sync_url, headers: api_headers(@api_key)
end
assert_response :accepted
response_data = JSON.parse(response.body)
assert response_data.key?("id")
assert response_data.key?("status")
assert_equal "Family", response_data["syncable_type"]
assert_equal @family.id, response_data["syncable_id"]
assert response_data.key?("message")
assert_includes response_data["message"], "rules"
end
test "should reject sync with read-only API key" do
post api_v1_sync_url, headers: api_headers(@read_only_api_key)
assert_response :forbidden
response_data = JSON.parse(response.body)
assert_equal "insufficient_scope", response_data["error"]
end
test "should reject sync without API key" do
post api_v1_sync_url
assert_response :unauthorized
response_data = JSON.parse(response.body)
assert response_data.key?("error")
end
test "should return proper sync details in response" do
post api_v1_sync_url, headers: api_headers(@api_key)
assert_response :accepted
response_data = JSON.parse(response.body)
# Check all expected fields are present
assert response_data.key?("id")
assert response_data.key?("status")
assert response_data.key?("syncable_type")
assert response_data.key?("syncable_id")
assert response_data.key?("syncing_at")
assert response_data.key?("completed_at")
assert response_data.key?("window_start_date")
assert response_data.key?("window_end_date")
assert response_data.key?("message")
end
private
def api_headers(api_key)
{ "X-Api-Key" => api_key.display_key }
end
end