mirror of
https://github.com/we-promise/sure.git
synced 2026-09-06 07:11:14 +00:00
Add experimental Swift-native Sure Insights app (#3134)
* Add Swift-native Sure app * Fix push subscriptions schema for CI * Address native app review feedback * Address remaining native app review feedback * Use Flutter app logo for native icon * Honor insight notification preferences and locale --------- Co-authored-by: Juan Jose Mata <2v8shcb6pz@privaterelay.appleid.com> Co-authored-by: sure-admin <sure-admin@splashblot.com>
This commit is contained in:
co-authored by
Juan Jose Mata
sure-admin
parent
311f06e404
commit
0aa43de10a
@@ -0,0 +1,50 @@
|
||||
require "test_helper"
|
||||
|
||||
class Api::V1::InsightsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@user = users(:family_admin)
|
||||
@user.update!(preferences: @user.preferences.merge("preview_features_enabled" => true))
|
||||
key = ApiKey.generate_secure_key
|
||||
@api_key = ApiKey.create!(
|
||||
user: @user,
|
||||
name: "Native insights test",
|
||||
key: key,
|
||||
scopes: [ "read" ],
|
||||
source: "mobile"
|
||||
)
|
||||
@insight = @user.family.insights.create!(
|
||||
insight_type: "idle_cash",
|
||||
priority: "medium",
|
||||
status: "active",
|
||||
title: "Put idle cash to work",
|
||||
body: "One account has more cash than usual.",
|
||||
generated_at: Time.current,
|
||||
dedup_key: "native-insights-test"
|
||||
)
|
||||
end
|
||||
|
||||
test "lists visible family insights" do
|
||||
get api_v1_insights_url, headers: api_headers(@api_key)
|
||||
|
||||
assert_response :success
|
||||
payload = response.parsed_body
|
||||
row = payload.fetch("insights").find { |insight| insight.fetch("id") == @insight.id }
|
||||
assert_equal "idle_cash", row.fetch("type")
|
||||
assert_equal "Put idle cash to work", row.fetch("title")
|
||||
end
|
||||
|
||||
test "rejects requests without an API key" do
|
||||
get api_v1_insights_url
|
||||
|
||||
assert_response :unauthorized
|
||||
end
|
||||
|
||||
test "does not expose insights when the API key owner opted out of preview features" do
|
||||
@user.update!(preferences: @user.preferences.merge("preview_features_enabled" => false))
|
||||
|
||||
get api_v1_insights_url, headers: api_headers(@api_key)
|
||||
|
||||
assert_response :forbidden
|
||||
assert_equal "feature_disabled", response.parsed_body.fetch("error")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,160 @@
|
||||
require "test_helper"
|
||||
|
||||
class Api::V1::PushSubscriptionsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@user = users(:family_admin)
|
||||
key = ApiKey.generate_secure_key
|
||||
@api_key = ApiKey.create!(
|
||||
user: @user,
|
||||
name: "Native push test",
|
||||
key: key,
|
||||
scopes: [ "read_write" ],
|
||||
source: "mobile"
|
||||
)
|
||||
@headers = api_headers(@api_key)
|
||||
@token = "ab" * 32
|
||||
end
|
||||
|
||||
test "registers and refreshes an APNs token" do
|
||||
assert_difference "PushSubscription.count", 1 do
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token, environment: "sandbox", platform: "ios" },
|
||||
headers: @headers,
|
||||
as: :json
|
||||
end
|
||||
|
||||
assert_response :created
|
||||
subscription = PushSubscription.find_by!(token: @token)
|
||||
assert_equal @user, subscription.user
|
||||
assert_equal "sandbox", subscription.environment
|
||||
|
||||
assert_no_difference "PushSubscription.count" do
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token, environment: "production", platform: "ios" },
|
||||
headers: @headers,
|
||||
as: :json
|
||||
end
|
||||
assert_equal "production", subscription.reload.environment
|
||||
end
|
||||
|
||||
test "requires a read write API key" do
|
||||
read_key_value = ApiKey.generate_secure_key
|
||||
read_key = ApiKey.create!(
|
||||
user: @user,
|
||||
name: "Read-only native push test",
|
||||
key: read_key_value,
|
||||
scopes: [ "read" ],
|
||||
source: "mobile"
|
||||
)
|
||||
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token, environment: "sandbox", platform: "ios" },
|
||||
headers: api_headers(read_key),
|
||||
as: :json
|
||||
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
test "rejects malformed tokens" do
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: "not-a-device-token", environment: "sandbox", platform: "ios" },
|
||||
headers: @headers,
|
||||
as: :json
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
end
|
||||
|
||||
test "rejects an invalid APNs environment" do
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token, environment: "staging", platform: "ios" },
|
||||
headers: @headers,
|
||||
as: :json
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
end
|
||||
|
||||
test "normalizes APNs tokens before lookup and persistence" do
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token.upcase, environment: "sandbox", platform: "ios" },
|
||||
headers: @headers,
|
||||
as: :json
|
||||
|
||||
assert_response :created
|
||||
assert PushSubscription.exists?(token: @token)
|
||||
|
||||
assert_no_difference "PushSubscription.count" do
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token, environment: "sandbox", platform: "ios" },
|
||||
headers: @headers,
|
||||
as: :json
|
||||
end
|
||||
end
|
||||
|
||||
test "does not transfer another user's token" do
|
||||
other_user = users(:empty)
|
||||
subscription = other_user.push_subscriptions.create!(
|
||||
token: @token,
|
||||
environment: "sandbox",
|
||||
platform: "ios",
|
||||
last_registered_at: Time.current
|
||||
)
|
||||
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token.upcase, environment: "production", platform: "ios" },
|
||||
headers: @headers,
|
||||
as: :json
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_equal other_user, subscription.reload.user
|
||||
assert_equal "sandbox", subscription.environment
|
||||
end
|
||||
|
||||
test "returns a controlled response when concurrent token registration conflicts" do
|
||||
PushSubscription.any_instance.stubs(:save!).raises(ActiveRecord::RecordNotUnique)
|
||||
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token, environment: "sandbox", platform: "ios" },
|
||||
headers: @headers,
|
||||
as: :json
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_equal "validation_error", response.parsed_body["error"]
|
||||
end
|
||||
|
||||
test "removes the current user's token" do
|
||||
subscription = @user.push_subscriptions.create!(
|
||||
token: @token,
|
||||
environment: "sandbox",
|
||||
platform: "ios",
|
||||
last_registered_at: Time.current
|
||||
)
|
||||
|
||||
assert_difference "PushSubscription.count", -1 do
|
||||
delete api_v1_push_subscription_url(subscription), headers: @headers
|
||||
end
|
||||
|
||||
assert_response :no_content
|
||||
end
|
||||
|
||||
test "does not remove another user's token" do
|
||||
subscription = users(:empty).push_subscriptions.create!(
|
||||
token: @token,
|
||||
environment: "sandbox",
|
||||
platform: "ios",
|
||||
last_registered_at: Time.current
|
||||
)
|
||||
|
||||
delete api_v1_push_subscription_url(subscription), headers: @headers
|
||||
|
||||
assert_response :not_found
|
||||
assert PushSubscription.exists?(subscription.id)
|
||||
end
|
||||
|
||||
test "requires authentication" do
|
||||
post api_v1_push_subscriptions_url,
|
||||
params: { token: @token, environment: "sandbox", platform: "ios" },
|
||||
as: :json
|
||||
|
||||
assert_response :unauthorized
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user