Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine;

namespace RevenueCat.Tests
{
public class AttributionPassthroughTests
{
private GameObject _gameObject;
private Purchases _purchases;
private PurchasesWrapperSpy _wrapper;

[SetUp]
public void SetUp()
{
_gameObject = new GameObject("RevenueCatTests");
_purchases = _gameObject.AddComponent<Purchases>();
_wrapper = new PurchasesWrapperSpy();
_purchases.SetWrapper(_wrapper);
}

[TearDown]
public void TearDown()
{
// Qualified because `using System` above makes a bare `Object` ambiguous.
UnityEngine.Object.DestroyImmediate(_gameObject);
}

/// <summary>
/// Every string attribution setter on <see cref="Purchases"/>. Each case carries the wrapper method it is
/// expected to reach, the call to make, and the value that must arrive unchanged.
/// </summary>
private static IEnumerable StringSetters()
{
yield return Setter(nameof(IPurchasesWrapper.SetEmail),
(purchases, value) => purchases.SetEmail(value), "support@revenuecat.com");
yield return Setter(nameof(IPurchasesWrapper.SetPhoneNumber),
(purchases, value) => purchases.SetPhoneNumber(value), "+15551234567");
yield return Setter(nameof(IPurchasesWrapper.SetDisplayName),
(purchases, value) => purchases.SetDisplayName(value), "Ada Lovelace");
yield return Setter(nameof(IPurchasesWrapper.SetPushToken),
(purchases, value) => purchases.SetPushToken(value), "push_token_1");
yield return Setter(nameof(IPurchasesWrapper.SetAdjustID),
(purchases, value) => purchases.SetAdjustID(value), "adjust_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetAppsflyerID),
(purchases, value) => purchases.SetAppsflyerID(value), "appsflyer_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetFBAnonymousID),
(purchases, value) => purchases.SetFBAnonymousID(value), "fb_anonymous_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetMparticleID),
(purchases, value) => purchases.SetMparticleID(value), "mparticle_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetOnesignalID),
(purchases, value) => purchases.SetOnesignalID(value), "onesignal_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetOnesignalUserID),
(purchases, value) => purchases.SetOnesignalUserID(value), "onesignal_user_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetAirshipChannelID),
(purchases, value) => purchases.SetAirshipChannelID(value), "airship_channel_1");
yield return Setter(nameof(IPurchasesWrapper.SetCleverTapID),
(purchases, value) => purchases.SetCleverTapID(value), "clever_tap_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetMixpanelDistinctID),
(purchases, value) => purchases.SetMixpanelDistinctID(value), "mixpanel_distinct_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetFirebaseAppInstanceID),
(purchases, value) => purchases.SetFirebaseAppInstanceID(value), "firebase_app_instance_id_1");
yield return Setter(nameof(IPurchasesWrapper.SetMediaSource),
(purchases, value) => purchases.SetMediaSource(value), "media_source_1");
yield return Setter(nameof(IPurchasesWrapper.SetCampaign),
(purchases, value) => purchases.SetCampaign(value), "campaign_1");
yield return Setter(nameof(IPurchasesWrapper.SetAdGroup),
(purchases, value) => purchases.SetAdGroup(value), "ad_group_1");
yield return Setter(nameof(IPurchasesWrapper.SetAd),
(purchases, value) => purchases.SetAd(value), "ad_1");
yield return Setter(nameof(IPurchasesWrapper.SetKeyword),
(purchases, value) => purchases.SetKeyword(value), "keyword_1");
yield return Setter(nameof(IPurchasesWrapper.SetCreative),
(purchases, value) => purchases.SetCreative(value), "creative_1");
}

private static TestCaseData Setter(string method, Action<Purchases, string> invoke, string value)
{
return new TestCaseData(method, invoke, value)
.SetName($"{nameof(ForwardsValueToWrapper)}({method})");
}

[TestCaseSource(nameof(StringSetters))]
public void ForwardsValueToWrapper(string method, Action<Purchases, string> invoke, string value)
{
invoke(_purchases, value);

var invocation = AssertOnlyInvocation(method, 1);
Assert.That(invocation.Arguments[0], Is.EqualTo(value));
}

/// <summary>
/// A null value means "delete this subscriber attribute", so the facade must not filter it out before it
/// reaches the wrapper. All of the setters above share the same single-statement body, so one representative
/// case is enough to catch a null guard being introduced.
/// </summary>
[Test]
public void ForwardsNullToWrapper()
{
_purchases.SetEmail(null);

var invocation = AssertOnlyInvocation(nameof(IPurchasesWrapper.SetEmail), 1);
Assert.That(invocation.Arguments[0], Is.Null);
}

private PurchasesWrapperSpy.Invocation AssertOnlyInvocation(string method, int argumentCount)
{
Assert.That(_wrapper.Invocations, Has.Count.EqualTo(1));

var invocation = _wrapper.Invocations[0];
Assert.That(invocation.Method, Is.EqualTo(method));
Assert.That(invocation.Arguments, Has.Length.EqualTo(argumentCount));
return invocation;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

221 changes: 215 additions & 6 deletions IntegrationTests/Assets/Tests/EditMode/WrapperPassthroughTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,40 @@ public void SetAppsFlyerConversionDataSerializesNestedValues()
["media_source"] = null,
["click_count"] = 3,
["nested"] = new Dictionary<string, object> { ["key"] = "value" },
["list"] = new List<object> { "a", "b" }
["list"] = new List<object> { "a", "b" },
["is_first_launch"] = true,
["is_retargeting"] = false,
["install_time_ms"] = 1717171717171L,
["revenue_float"] = 9.99f,
["revenue_double"] = 12.34d
});

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.SetAppsFlyerConversionData), 1);
var conversionData = JSONNode.Parse((string)invocation.Arguments[0]);
var conversionDataJson = (string)invocation.Arguments[0];
var conversionData = JSONNode.Parse(conversionDataJson);
Assert.That(conversionData["af_status"].Value, Is.EqualTo("Organic"));
Assert.That(conversionData["media_source"].IsNull, Is.True);
Assert.That(conversionData["click_count"].IsNumber, Is.True);
Assert.That(conversionData["click_count"].AsInt, Is.EqualTo(3));
Assert.That(conversionData["nested"]["key"].Value, Is.EqualTo("value"));
Assert.That(conversionData["list"][0].Value, Is.EqualTo("a"));
Assert.That(conversionData["list"][1].Value, Is.EqualTo("b"));
// The IsBoolean/IsNumber checks carry the weight here. AsBool and AsFloat fall back to
// parsing the node's string value, so a number or bool that regressed into being
// serialized as a quoted string still satisfies the value assertion on its own.
Assert.That(conversionData["is_first_launch"].IsBoolean, Is.True);
Assert.That(conversionData["is_first_launch"].AsBool, Is.True);
Assert.That(conversionData["is_retargeting"].IsBoolean, Is.True);
Assert.That(conversionData["is_retargeting"].AsBool, Is.False);
Assert.That(conversionData["install_time_ms"].IsNumber, Is.True);
Assert.That(conversionData["install_time_ms"].AsLong, Is.EqualTo(1717171717171L));
Assert.That(conversionData["revenue_float"].IsNumber, Is.True);
Assert.That(conversionData["revenue_float"].AsFloat, Is.EqualTo(9.99f).Within(0.0001f));
Assert.That(conversionData["revenue_double"].IsNumber, Is.True);
Assert.That(conversionData["revenue_double"].AsDouble, Is.EqualTo(12.34d).Within(0.0001d));
Assert.That(conversionDataJson, Does.Contain("\"is_first_launch\":true"));
Assert.That(conversionDataJson, Does.Contain("\"is_retargeting\":false"));
Assert.That(conversionDataJson, Does.Contain("\"install_time_ms\":1717171717171"));
}

[Test]
Expand Down Expand Up @@ -139,12 +162,198 @@ public void SetSimulatesAskToBuyInSandboxForwardsFlag()
}

[Test]
public void SetAdjustIdForwardsValue()
public void SetLogLevelForwardsLevel()
{
_purchases.SetAdjustID("adjust_id_1");
_purchases.SetLogLevel(Purchases.LogLevel.Warn);

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.SetAdjustID), 1);
Assert.That(invocation.Arguments[0], Is.EqualTo("adjust_id_1"));
var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.SetLogLevel), 1);
Assert.That(invocation.Arguments[0], Is.EqualTo(Purchases.LogLevel.Warn));
}

[Test]
public void SetDebugLogsEnabledForwardsFlag()
{
#pragma warning disable 618
_purchases.SetDebugLogsEnabled(true);
#pragma warning restore 618

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.SetDebugLogsEnabled), 1);
Assert.That(invocation.Arguments[0], Is.True);
}

[Test]
public void SetAllowSharingStoreAccountForwardsFlag()
{
#pragma warning disable 618
_purchases.SetAllowSharingStoreAccount(true);
#pragma warning restore 618

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.SetAllowSharingStoreAccount), 1);
Assert.That(invocation.Arguments[0], Is.True);
}

[Test]
public void SyncPurchasesCallsWrapper()
{
_purchases.SyncPurchases();

AssertLastInvocation(nameof(IPurchasesWrapper.SyncPurchases), 0);
}

[Test]
public void SyncPurchasesWithCallbackCallsWrapper()
{
_purchases.SyncPurchases((customerInfo, error) => { });

AssertLastInvocation(nameof(IPurchasesWrapper.SyncPurchases), 0);
}

[Test]
public void SyncAttributesAndOfferingsIfNeededCallsWrapper()
{
_purchases.SyncAttributesAndOfferingsIfNeeded((offerings, error) => { });

AssertLastInvocation(nameof(IPurchasesWrapper.SyncAttributesAndOfferingsIfNeeded), 0);
}

[Test]
public void SetLogHandlerCallsWrapper()
{
_purchases.SetLogHandler((logLevel, message) => { });

AssertLastInvocation(nameof(IPurchasesWrapper.SetLogHandler), 0);
}

[Test]
public void PresentCodeRedemptionSheetCallsWrapper()
{
_purchases.PresentCodeRedemptionSheet();

AssertLastInvocation(nameof(IPurchasesWrapper.PresentCodeRedemptionSheet), 0);
}

[Test]
public void EnableAdServicesAttributionTokenCollectionCallsWrapper()
{
_purchases.EnableAdServicesAttributionTokenCollection();

AssertLastInvocation(nameof(IPurchasesWrapper.EnableAdServicesAttributionTokenCollection), 0);
}

[Test]
public void GetAppUserIdCallsWrapper()
{
_purchases.GetAppUserId();

AssertLastInvocation(nameof(IPurchasesWrapper.GetAppUserId), 0);
}

[Test]
public void IsAnonymousCallsWrapper()
{
_purchases.IsAnonymous();

AssertLastInvocation(nameof(IPurchasesWrapper.IsAnonymous), 0);
}

[Test]
public void IsConfiguredCallsWrapper()
{
_purchases.IsConfigured();

AssertLastInvocation(nameof(IPurchasesWrapper.IsConfigured), 0);
}

[Test]
public void GetCachedVirtualCurrenciesReturnsNullWhenWrapperHasNothingCached()
{
var virtualCurrencies = _purchases.GetCachedVirtualCurrencies();

AssertLastInvocation(nameof(IPurchasesWrapper.GetCachedVirtualCurrencies), 0);
Assert.That(virtualCurrencies, Is.Null);
}

[Test]
public void TrackCustomPaywallImpressionForwardsParameters()
{
var parameters = new Purchases.CustomPaywallImpressionParams("paywall_1");

_purchases.TrackCustomPaywallImpression(parameters);

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.TrackCustomPaywallImpression), 1);
Assert.That(invocation.Arguments[0], Is.SameAs(parameters));
}

[Test]
public void TrackCustomPaywallImpressionWithoutParametersForwardsDefaultParameters()
Comment thread
vegaro marked this conversation as resolved.
Outdated
{
_purchases.TrackCustomPaywallImpression();

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.TrackCustomPaywallImpression), 1);
var parameters = invocation.Arguments[0] as Purchases.CustomPaywallImpressionParams;
Assert.That(parameters, Is.Not.Null);
Assert.That(parameters.PaywallId, Is.Null);
Assert.That(parameters.OfferingId, Is.Null);
}

[Test]
public void TrackAdDisplayedForwardsData()
{
var data = new AdDisplayedData(AdTracker.MediatorName.AdMob, AdTracker.Format.Interstitial,
"ad_unit_1", "impression_1");

_purchases.AdTracker.TrackAdDisplayed(data);

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.TrackAdDisplayed), 1);
Assert.That(invocation.Arguments[0], Is.SameAs(data));
}

[Test]
public void TrackAdOpenedForwardsData()
{
var data = new AdOpenedData(AdTracker.MediatorName.AppLovin, AdTracker.Format.Banner,
"ad_unit_1", "impression_1");

_purchases.AdTracker.TrackAdOpened(data);

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.TrackAdOpened), 1);
Assert.That(invocation.Arguments[0], Is.SameAs(data));
}

[Test]
public void TrackAdRevenueForwardsData()
{
var data = new AdRevenueData(AdTracker.MediatorName.AdMob, AdTracker.Format.Rewarded,
"ad_unit_1", "impression_1", 1234, "USD", AdTracker.Precision.Exact);

_purchases.AdTracker.TrackAdRevenue(data);

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.TrackAdRevenue), 1);
Assert.That(invocation.Arguments[0], Is.SameAs(data));
}

[Test]
public void TrackAdLoadedForwardsData()
{
var data = new AdLoadedData(AdTracker.MediatorName.AdMob, AdTracker.Format.Native,
"ad_unit_1", "impression_1");

_purchases.AdTracker.TrackAdLoaded(data);

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.TrackAdLoaded), 1);
Assert.That(invocation.Arguments[0], Is.SameAs(data));
}

[Test]
public void TrackAdFailedToLoadForwardsData()
{
var data = new AdFailedToLoadData(AdTracker.MediatorName.AppLovin, AdTracker.Format.AppOpen,
"ad_unit_1", "placement_1", 42);

_purchases.AdTracker.TrackAdFailedToLoad(data);

var invocation = AssertLastInvocation(nameof(IPurchasesWrapper.TrackAdFailedToLoad), 1);
Assert.That(invocation.Arguments[0], Is.SameAs(data));
}

private PurchasesWrapperSpy.Invocation AssertLastInvocation(string method, int argumentCount)
Expand Down