Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,13 @@ internal static class LogLabels
}

// ---- Consent dropdown / status values ----
// Mirrors ConsentLevel.ToLowercaseString().
// Mirrors ConsentLevel.ToLowercaseString() so the sample-app stays aligned with SDK output.

internal static class Consent
{
internal const string None = "none";
internal const string Anonymous = "anonymous";
internal const string Full = "full";
internal const string None = ConsentLevelWireFormat.None;
internal const string Anonymous = ConsentLevelWireFormat.Anonymous;
internal const string Full = ConsentLevelWireFormat.Full;
}

// Log payload JSON keys used by RunAndLog "Ok" row dictionaries.
Expand Down
14 changes: 11 additions & 3 deletions src/Packages/Audience/Runtime/ConsentLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,21 @@ public enum ConsentLevel
Full
}

// Strings the SDK emits for ConsentLevel.
internal static class ConsentLevelWireFormat
{
internal const string None = "none";
internal const string Anonymous = "anonymous";
internal const string Full = "full";
}

internal static class ConsentLevelExtensions
{
internal static string ToLowercaseString(this ConsentLevel level) => level switch
{
ConsentLevel.None => "none",
ConsentLevel.Anonymous => "anonymous",
ConsentLevel.Full => "full",
ConsentLevel.None => ConsentLevelWireFormat.None,
ConsentLevel.Anonymous => ConsentLevelWireFormat.Anonymous,
ConsentLevel.Full => ConsentLevelWireFormat.Full,
_ => throw new System.ArgumentOutOfRangeException(
nameof(level), level, "Unhandled ConsentLevel"),
};
Expand Down
43 changes: 28 additions & 15 deletions src/Packages/Audience/Runtime/IdentityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ public enum IdentityType
Custom,
}

// Strings emitted under MessageFields.IdentityType.
internal static class IdentityTypeWireFormat
{
internal const string Passport = "passport";
internal const string Steam = "steam";
internal const string Epic = "epic";
internal const string Google = "google";
internal const string Apple = "apple";
internal const string Discord = "discord";
internal const string Email = "email";
internal const string Custom = "custom";
}

internal static class IdentityTypeExtensions
{
// Throws on unknown casts. Every identify / alias event must carry an
Expand All @@ -45,28 +58,28 @@ internal static class IdentityTypeExtensions
// loudly rather than ship an event with a missing or empty namespace.
internal static string ToLowercaseString(this IdentityType type) => type switch
{
IdentityType.Passport => "passport",
IdentityType.Steam => "steam",
IdentityType.Epic => "epic",
IdentityType.Google => "google",
IdentityType.Apple => "apple",
IdentityType.Discord => "discord",
IdentityType.Email => "email",
IdentityType.Custom => "custom",
IdentityType.Passport => IdentityTypeWireFormat.Passport,
IdentityType.Steam => IdentityTypeWireFormat.Steam,
IdentityType.Epic => IdentityTypeWireFormat.Epic,
IdentityType.Google => IdentityTypeWireFormat.Google,
IdentityType.Apple => IdentityTypeWireFormat.Apple,
IdentityType.Discord => IdentityTypeWireFormat.Discord,
IdentityType.Email => IdentityTypeWireFormat.Email,
IdentityType.Custom => IdentityTypeWireFormat.Custom,
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type,
"Unknown IdentityType value; cast an out-of-range value."),
};

internal static IdentityType ParseLowercaseString(string? value) =>
(value ?? string.Empty).ToLowerInvariant() switch
{
"passport" => IdentityType.Passport,
"steam" => IdentityType.Steam,
"epic" => IdentityType.Epic,
"google" => IdentityType.Google,
"apple" => IdentityType.Apple,
"discord" => IdentityType.Discord,
"email" => IdentityType.Email,
IdentityTypeWireFormat.Passport => IdentityType.Passport,
IdentityTypeWireFormat.Steam => IdentityType.Steam,
IdentityTypeWireFormat.Epic => IdentityType.Epic,
IdentityTypeWireFormat.Google => IdentityType.Google,
IdentityTypeWireFormat.Apple => IdentityType.Apple,
IdentityTypeWireFormat.Discord => IdentityType.Discord,
IdentityTypeWireFormat.Email => IdentityType.Email,
_ => IdentityType.Custom,
};
}
Expand Down
4 changes: 4 additions & 0 deletions src/Packages/Audience/Runtime/Utility/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ internal static string ConsentSyncThrew(Exception ex) =>

internal static class AudienceLogs
{
// Marker shared by Track / Identify / Alias dropped-event log messages.
internal const string DroppingMarker = "Dropping";


// ---- Init / config validation ----

internal const string InitCalledTwice =
Expand Down
15 changes: 12 additions & 3 deletions src/Packages/Audience/Tests/Runtime/ConsentLevelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ namespace Immutable.Audience.Tests
[TestFixture]
internal class ConsentLevelTests
{
[TestCase(ConsentLevel.None, "none")]
[TestCase(ConsentLevel.Anonymous, "anonymous")]
[TestCase(ConsentLevel.Full, "full")]
[TestCase(ConsentLevel.None, ConsentLevelWireFormat.None)]
[TestCase(ConsentLevel.Anonymous, ConsentLevelWireFormat.Anonymous)]
[TestCase(ConsentLevel.Full, ConsentLevelWireFormat.Full)]
public void ToLowercaseString_MapsEachEnumValueToLowercaseBackendString(ConsentLevel level, string expected)
{
Assert.AreEqual(expected, level.ToLowercaseString());
}

[Test]
public void ConsentLevelWireFormat_PinsExactStringValues()
{
// Pins each emitted string directly so a typo or backend rename fails the build.
Assert.AreEqual("none", ConsentLevelWireFormat.None);
Assert.AreEqual("anonymous", ConsentLevelWireFormat.Anonymous);
Assert.AreEqual("full", ConsentLevelWireFormat.Full);
}

[Test]
public void ToLowercaseString_UnknownValue_Throws()
{
Expand Down
6 changes: 5 additions & 1 deletion src/Packages/Audience/Tests/Runtime/ConsentSyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ namespace Immutable.Audience.Tests
[TestFixture]
internal class ConsentSyncTests
{
// Standard HTTP header name (RFC 7231) the mock CapturingHandler sets
// on 429 responses to override the SDK's default backoff.
private const string RetryAfterHeader = "Retry-After";

private string _testDir;

[SetUp]
Expand Down Expand Up @@ -223,7 +227,7 @@ protected override async Task<HttpResponseMessage> SendAsync(
var response = new HttpResponseMessage(status);
if ((int)status == 429 && RetryAfterSeconds.HasValue)
{
response.Headers.Add("Retry-After", RetryAfterSeconds.Value.ToString());
response.Headers.Add(RetryAfterHeader, RetryAfterSeconds.Value.ToString());
}
return response;
}
Expand Down
11 changes: 7 additions & 4 deletions src/Packages/Audience/Tests/Runtime/Core/SessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ namespace Immutable.Audience.Tests
[TestFixture]
internal class SessionTests
{
// Exception thrown by the ThrowingTrack sabotage delegate across four scenarios.
private const string TrackExplodeMessage = "track explode";

private List<(string name, Dictionary<string, object> props)> _events;

[SetUp]
Expand Down Expand Up @@ -546,7 +549,7 @@ public void OnHeartbeat_TrackCallbackThrows_DoesNotEscape()
void ThrowingTrack(string name, Dictionary<string, object> props)
{
if (name == EventNames.SessionHeartbeat)
throw new InvalidOperationException("track explode");
throw new InvalidOperationException(TrackExplodeMessage);
}

using var session = new Session(ThrowingTrack);
Expand Down Expand Up @@ -581,7 +584,7 @@ public void Start_TrackCallbackThrows_DoesNotEscape()
void ThrowingTrack(string name, Dictionary<string, object> props)
{
if (name == EventNames.SessionStart)
throw new InvalidOperationException("track explode");
throw new InvalidOperationException(TrackExplodeMessage);
}

using var session = new Session(ThrowingTrack);
Expand Down Expand Up @@ -616,7 +619,7 @@ public void End_TrackCallbackThrows_DoesNotEscape()
void ThrowingTrack(string name, Dictionary<string, object> props)
{
if (name == EventNames.SessionEnd)
throw new InvalidOperationException("track explode");
throw new InvalidOperationException(TrackExplodeMessage);
}

using var session = new Session(ThrowingTrack);
Expand Down Expand Up @@ -652,7 +655,7 @@ public void SafeTrack_LogWriterThrows_DoesNotEscape()
void ThrowingTrack(string name, Dictionary<string, object> props)
{
if (name == EventNames.SessionHeartbeat)
throw new InvalidOperationException("track explode");
throw new InvalidOperationException(TrackExplodeMessage);
}

using var session = new Session(ThrowingTrack);
Expand Down
12 changes: 6 additions & 6 deletions src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void Progression_WithoutStatus_ThrowsOnToProperties()
var evt = new Progression { World = TestFixtures.ProgressionWorldTutorial };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain("Status"));
Assert.That(ex!.Message, Does.Contain(nameof(Progression.Status)));
}

[Test]
Expand Down Expand Up @@ -88,7 +88,7 @@ public void Resource_WithoutFlow_ThrowsOnToProperties()
var evt = new Resource { Currency = TestFixtures.ResourceCurrency, Amount = 100 };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain("Flow"));
Assert.That(ex!.Message, Does.Contain(nameof(Resource.Flow)));
}

[Test]
Expand All @@ -97,7 +97,7 @@ public void Resource_WithoutCurrency_ThrowsOnToProperties()
var evt = new Resource { Flow = ResourceFlow.Source, Amount = 100 };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain("Currency"));
Assert.That(ex!.Message, Does.Contain(nameof(Resource.Currency)));
}

[Test]
Expand All @@ -106,7 +106,7 @@ public void Resource_WithoutAmount_ThrowsOnToProperties()
var evt = new Resource { Flow = ResourceFlow.Source, Currency = TestFixtures.ResourceCurrency };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain("Amount"));
Assert.That(ex!.Message, Does.Contain(nameof(Resource.Amount)));
}

[Test]
Expand Down Expand Up @@ -157,7 +157,7 @@ public void Purchase_WithoutCurrency_ThrowsOnToProperties()
var evt = new Purchase { Value = 9.99m };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain("Currency"));
Assert.That(ex!.Message, Does.Contain(nameof(Purchase.Currency)));
}

[Test]
Expand All @@ -166,7 +166,7 @@ public void Purchase_WithoutValue_ThrowsOnToProperties()
var evt = new Purchase { Currency = TestFixtures.UsdCurrency };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain("Value"));
Assert.That(ex!.Message, Does.Contain(nameof(Purchase.Value)));
}

[Test]
Expand Down
54 changes: 34 additions & 20 deletions src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,49 @@
[TestFixture]
internal class IdentityTypeTests
{
[TestCase(IdentityType.Passport, "passport")]
[TestCase(IdentityType.Steam, "steam")]
[TestCase(IdentityType.Epic, "epic")]
[TestCase(IdentityType.Google, "google")]
[TestCase(IdentityType.Apple, "apple")]
[TestCase(IdentityType.Discord, "discord")]
[TestCase(IdentityType.Email, "email")]
[TestCase(IdentityType.Custom, "custom")]
[TestCase(IdentityType.Passport, IdentityTypeWireFormat.Passport)]
[TestCase(IdentityType.Steam, IdentityTypeWireFormat.Steam)]
[TestCase(IdentityType.Epic, IdentityTypeWireFormat.Epic)]
[TestCase(IdentityType.Google, IdentityTypeWireFormat.Google)]
[TestCase(IdentityType.Apple, IdentityTypeWireFormat.Apple)]
[TestCase(IdentityType.Discord, IdentityTypeWireFormat.Discord)]
[TestCase(IdentityType.Email, IdentityTypeWireFormat.Email)]
[TestCase(IdentityType.Custom, IdentityTypeWireFormat.Custom)]
public void ToLowercaseString_MapsEachEnumValueToLowercaseBackendString(IdentityType type, string expected)
{
Assert.AreEqual(expected, type.ToLowercaseString());
}

[TestCase("passport", IdentityType.Passport)]
[TestCase("steam", IdentityType.Steam)]
[TestCase("epic", IdentityType.Epic)]
[TestCase("google", IdentityType.Google)]
[TestCase("apple", IdentityType.Apple)]
[TestCase("discord", IdentityType.Discord)]
[TestCase("email", IdentityType.Email)]
[TestCase("custom", IdentityType.Custom)]
[TestCase(IdentityTypeWireFormat.Passport, IdentityType.Passport)]
[TestCase(IdentityTypeWireFormat.Steam, IdentityType.Steam)]
[TestCase(IdentityTypeWireFormat.Epic, IdentityType.Epic)]
[TestCase(IdentityTypeWireFormat.Google, IdentityType.Google)]
[TestCase(IdentityTypeWireFormat.Apple, IdentityType.Apple)]
[TestCase(IdentityTypeWireFormat.Discord, IdentityType.Discord)]
[TestCase(IdentityTypeWireFormat.Email, IdentityType.Email)]
[TestCase(IdentityTypeWireFormat.Custom, IdentityType.Custom)]
public void ParseLowercaseString_MapsKnownStringToEnum(string wire, IdentityType expected)
{
Assert.AreEqual(expected, IdentityTypeExtensions.ParseLowercaseString(wire));
}

[TestCase("Steam", IdentityType.Steam)]
[TestCase("STEAM", IdentityType.Steam)]
[TestCase("Passport", IdentityType.Passport)]
[Test]
public void IdentityTypeWireFormat_PinsExactStringValues()
{
// Pins each constant's exact string so a typo or backend rename fails the build.
Assert.AreEqual("passport", IdentityTypeWireFormat.Passport);
Assert.AreEqual("steam", IdentityTypeWireFormat.Steam);
Assert.AreEqual("epic", IdentityTypeWireFormat.Epic);
Assert.AreEqual("google", IdentityTypeWireFormat.Google);
Assert.AreEqual("apple", IdentityTypeWireFormat.Apple);
Assert.AreEqual("discord", IdentityTypeWireFormat.Discord);
Assert.AreEqual("email", IdentityTypeWireFormat.Email);
Assert.AreEqual("custom", IdentityTypeWireFormat.Custom);
}

[TestCase(TestFixtures.SteamPascalCase, IdentityType.Steam)]
[TestCase(TestFixtures.SteamUpperCase, IdentityType.Steam)]
[TestCase(TestFixtures.PassportPascalCase, IdentityType.Passport)]
public void ParseLowercaseString_AcceptsMixedCase(string wire, IdentityType expected)
{
Assert.AreEqual(expected, IdentityTypeExtensions.ParseLowercaseString(wire));
Expand All @@ -43,8 +57,8 @@
[TestCase(null)]
[TestCase("")]
[TestCase(TestFixtures.UnknownProvider)]
[TestCase("steamX")]
[TestCase(TestFixtures.SteamSuffixed)]
public void ParseLowercaseString_FallsBackToCustomForUnknownOrEmpty(string? wire)

Check warning on line 61 in src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs

View workflow job for this annotation

GitHub Actions / Unit Tests (.NET)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 61 in src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs

View workflow job for this annotation

GitHub Actions / Unit Tests (.NET)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
// ParseLowercaseString never throws; unknown values map to Custom.
Assert.AreEqual(IdentityType.Custom, IdentityTypeExtensions.ParseLowercaseString(wire));
Expand Down
Loading
Loading