diff --git a/examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs b/examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs index d842cf876..01da185d9 100644 --- a/examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs +++ b/examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs @@ -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. diff --git a/src/Packages/Audience/Runtime/ConsentLevel.cs b/src/Packages/Audience/Runtime/ConsentLevel.cs index 2b3db4724..98a4f75ce 100644 --- a/src/Packages/Audience/Runtime/ConsentLevel.cs +++ b/src/Packages/Audience/Runtime/ConsentLevel.cs @@ -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"), }; diff --git a/src/Packages/Audience/Runtime/IdentityType.cs b/src/Packages/Audience/Runtime/IdentityType.cs index 6d5fe1d3b..08dd5351e 100644 --- a/src/Packages/Audience/Runtime/IdentityType.cs +++ b/src/Packages/Audience/Runtime/IdentityType.cs @@ -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 @@ -45,14 +58,14 @@ 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."), }; @@ -60,13 +73,13 @@ internal static class IdentityTypeExtensions 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, }; } diff --git a/src/Packages/Audience/Runtime/Utility/Log.cs b/src/Packages/Audience/Runtime/Utility/Log.cs index 788f09323..dec6b7de4 100644 --- a/src/Packages/Audience/Runtime/Utility/Log.cs +++ b/src/Packages/Audience/Runtime/Utility/Log.cs @@ -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 = diff --git a/src/Packages/Audience/Tests/Runtime/ConsentLevelTests.cs b/src/Packages/Audience/Tests/Runtime/ConsentLevelTests.cs index 27ed22710..89a556fd1 100644 --- a/src/Packages/Audience/Tests/Runtime/ConsentLevelTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ConsentLevelTests.cs @@ -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() { diff --git a/src/Packages/Audience/Tests/Runtime/ConsentSyncTests.cs b/src/Packages/Audience/Tests/Runtime/ConsentSyncTests.cs index fdf37f599..c63cf7d2b 100644 --- a/src/Packages/Audience/Tests/Runtime/ConsentSyncTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ConsentSyncTests.cs @@ -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] @@ -223,7 +227,7 @@ protected override async Task 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; } diff --git a/src/Packages/Audience/Tests/Runtime/Core/SessionTests.cs b/src/Packages/Audience/Tests/Runtime/Core/SessionTests.cs index 8c9fdec1e..fd7a385df 100644 --- a/src/Packages/Audience/Tests/Runtime/Core/SessionTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Core/SessionTests.cs @@ -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 props)> _events; [SetUp] @@ -546,7 +549,7 @@ public void OnHeartbeat_TrackCallbackThrows_DoesNotEscape() void ThrowingTrack(string name, Dictionary props) { if (name == EventNames.SessionHeartbeat) - throw new InvalidOperationException("track explode"); + throw new InvalidOperationException(TrackExplodeMessage); } using var session = new Session(ThrowingTrack); @@ -581,7 +584,7 @@ public void Start_TrackCallbackThrows_DoesNotEscape() void ThrowingTrack(string name, Dictionary props) { if (name == EventNames.SessionStart) - throw new InvalidOperationException("track explode"); + throw new InvalidOperationException(TrackExplodeMessage); } using var session = new Session(ThrowingTrack); @@ -616,7 +619,7 @@ public void End_TrackCallbackThrows_DoesNotEscape() void ThrowingTrack(string name, Dictionary props) { if (name == EventNames.SessionEnd) - throw new InvalidOperationException("track explode"); + throw new InvalidOperationException(TrackExplodeMessage); } using var session = new Session(ThrowingTrack); @@ -652,7 +655,7 @@ public void SafeTrack_LogWriterThrows_DoesNotEscape() void ThrowingTrack(string name, Dictionary props) { if (name == EventNames.SessionHeartbeat) - throw new InvalidOperationException("track explode"); + throw new InvalidOperationException(TrackExplodeMessage); } using var session = new Session(ThrowingTrack); diff --git a/src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs b/src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs index 1cd0ac742..afd0fb353 100644 --- a/src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs @@ -18,7 +18,7 @@ public void Progression_WithoutStatus_ThrowsOnToProperties() var evt = new Progression { World = TestFixtures.ProgressionWorldTutorial }; var ex = Assert.Throws(() => evt.ToProperties()); - Assert.That(ex!.Message, Does.Contain("Status")); + Assert.That(ex!.Message, Does.Contain(nameof(Progression.Status))); } [Test] @@ -88,7 +88,7 @@ public void Resource_WithoutFlow_ThrowsOnToProperties() var evt = new Resource { Currency = TestFixtures.ResourceCurrency, Amount = 100 }; var ex = Assert.Throws(() => evt.ToProperties()); - Assert.That(ex!.Message, Does.Contain("Flow")); + Assert.That(ex!.Message, Does.Contain(nameof(Resource.Flow))); } [Test] @@ -97,7 +97,7 @@ public void Resource_WithoutCurrency_ThrowsOnToProperties() var evt = new Resource { Flow = ResourceFlow.Source, Amount = 100 }; var ex = Assert.Throws(() => evt.ToProperties()); - Assert.That(ex!.Message, Does.Contain("Currency")); + Assert.That(ex!.Message, Does.Contain(nameof(Resource.Currency))); } [Test] @@ -106,7 +106,7 @@ public void Resource_WithoutAmount_ThrowsOnToProperties() var evt = new Resource { Flow = ResourceFlow.Source, Currency = TestFixtures.ResourceCurrency }; var ex = Assert.Throws(() => evt.ToProperties()); - Assert.That(ex!.Message, Does.Contain("Amount")); + Assert.That(ex!.Message, Does.Contain(nameof(Resource.Amount))); } [Test] @@ -157,7 +157,7 @@ public void Purchase_WithoutCurrency_ThrowsOnToProperties() var evt = new Purchase { Value = 9.99m }; var ex = Assert.Throws(() => evt.ToProperties()); - Assert.That(ex!.Message, Does.Contain("Currency")); + Assert.That(ex!.Message, Does.Contain(nameof(Purchase.Currency))); } [Test] @@ -166,7 +166,7 @@ public void Purchase_WithoutValue_ThrowsOnToProperties() var evt = new Purchase { Currency = TestFixtures.UsdCurrency }; var ex = Assert.Throws(() => evt.ToProperties()); - Assert.That(ex!.Message, Does.Contain("Value")); + Assert.That(ex!.Message, Does.Contain(nameof(Purchase.Value))); } [Test] diff --git a/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs b/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs index 0ec2de376..2fa47f45c 100644 --- a/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs +++ b/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs @@ -6,35 +6,49 @@ namespace Immutable.Audience.Tests [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)); @@ -43,7 +57,7 @@ public void ParseLowercaseString_AcceptsMixedCase(string wire, IdentityType expe [TestCase(null)] [TestCase("")] [TestCase(TestFixtures.UnknownProvider)] - [TestCase("steamX")] + [TestCase(TestFixtures.SteamSuffixed)] public void ParseLowercaseString_FallsBackToCustomForUnknownOrEmpty(string? wire) { // ParseLowercaseString never throws; unknown values map to Custom. diff --git a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs index 7384c56a2..26647d259 100644 --- a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs @@ -323,7 +323,7 @@ public void Track_IEventMissingRequiredField_DropsWithWarn() // Assert the stable parts (event-type name and trailing "Dropping") // so the test survives any change to the exception type or message. Assert.That(lines, Has.Some.Contains(nameof(Purchase))); - Assert.That(lines, Has.Some.Contains("Dropping")); + Assert.That(lines, Has.Some.Contains(AudienceLogs.DroppingMarker)); } finally { Log.Writer = null; } @@ -331,7 +331,7 @@ public void Track_IEventMissingRequiredField_DropsWithWarn() var queueDir = AudiencePaths.QueueDir(_testDir); var contents = Directory.GetFiles(queueDir, AudiencePaths.QueueGlob) .Select(File.ReadAllText).ToList(); - Assert.IsFalse(contents.Any(c => c.Contains("\"purchase\"")), + Assert.IsFalse(contents.Any(c => c.Contains($"\"{EventNames.Purchase}\"")), "purchase event with missing required Value must be dropped, not enqueued"); } @@ -395,7 +395,7 @@ public void Identify_InvalidIdentityTypeCast_Throws() var invalid = (IdentityType)999; Assert.Throws( - () => ImmutableAudience.Identify("user1", invalid), + () => ImmutableAudience.Identify(TestFixtures.GenericUserSingleId, invalid), "invalid enum cast must throw so a broken call fails loud rather than " + "shipping an identify event that cannot be matched for deletion"); } @@ -608,7 +608,7 @@ public void Track_TypedPurchase_WritesCorrectEventName() var queueDir = AudiencePaths.QueueDir(_testDir); var contents = Directory.GetFiles(queueDir, AudiencePaths.QueueGlob) .Select(File.ReadAllText).ToList(); - Assert.IsTrue(contents.Any(c => c.Contains("\"purchase\""))); + Assert.IsTrue(contents.Any(c => c.Contains($"\"{EventNames.Purchase}\""))); } // ----------------------------------------------------------------- @@ -620,14 +620,14 @@ public void Identify_FullConsent_WritesIdentifyEvent() { ImmutableAudience.Init(MakeConfig(ConsentLevel.Full)); - ImmutableAudience.Identify("76561198012345", IdentityType.Steam); + ImmutableAudience.Identify(TestFixtures.SteamId64, IdentityType.Steam); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); var contents = Directory.GetFiles(queueDir, AudiencePaths.QueueGlob) .Select(File.ReadAllText).ToList(); Assert.IsTrue(contents.Any(c => - c.Contains("\"identify\"") && c.Contains("\"76561198012345\""))); + c.Contains($"\"{MessageTypes.Identify}\"") && c.Contains($"\"{TestFixtures.SteamId64}\""))); } [Test] @@ -635,13 +635,13 @@ public void Identify_AnonymousConsent_IsIgnored() { ImmutableAudience.Init(MakeConfig(ConsentLevel.Anonymous)); - ImmutableAudience.Identify("user1", IdentityType.Steam); + ImmutableAudience.Identify(TestFixtures.GenericUserSingleId, IdentityType.Steam); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); var contents = Directory.GetFiles(queueDir, AudiencePaths.QueueGlob) .Select(File.ReadAllText).ToList(); - Assert.IsFalse(contents.Any(c => c.Contains("\"identify\"")), + Assert.IsFalse(contents.Any(c => c.Contains($"\"{MessageTypes.Identify}\"")), "identify should be discarded at Anonymous consent"); } @@ -650,14 +650,14 @@ public void Alias_FullConsent_WritesAliasEvent() { ImmutableAudience.Init(MakeConfig(ConsentLevel.Full)); - ImmutableAudience.Alias("steam123", IdentityType.Steam, "user_456", IdentityType.Passport); + ImmutableAudience.Alias(TestFixtures.SteamId, IdentityType.Steam, TestFixtures.PassportId, IdentityType.Passport); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); var contents = Directory.GetFiles(queueDir, AudiencePaths.QueueGlob) .Select(File.ReadAllText).ToList(); Assert.IsTrue(contents.Any(c => - c.Contains("\"alias\"") && c.Contains("\"steam123\""))); + c.Contains($"\"{MessageTypes.Alias}\"") && c.Contains($"\"{TestFixtures.SteamId}\""))); } // ----------------------------------------------------------------- @@ -1124,7 +1124,7 @@ public void Init_GameLaunch_IncludesDistributionPlatform() public void Init_LowercasesDistributionPlatform_WhenCallerPassesMixedCase() { var config = MakeConfig(); - config.DistributionPlatform = TestFixtures.DistributionPlatformSteamCased; + config.DistributionPlatform = TestFixtures.SteamPascalCase; ImmutableAudience.Init(config); Assert.AreEqual(DistributionPlatforms.Steam, config.DistributionPlatform, @@ -1135,7 +1135,7 @@ public void Init_LowercasesDistributionPlatform_WhenCallerPassesMixedCase() public void Init_LowercasesDistributionPlatform_WhenCallerPassesAllUpperCase() { var config = MakeConfig(); - config.DistributionPlatform = TestFixtures.DistributionPlatformSteamUppercase; + config.DistributionPlatform = TestFixtures.SteamUpperCase; ImmutableAudience.Init(config); Assert.AreEqual(DistributionPlatforms.Steam, config.DistributionPlatform); diff --git a/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs b/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs index 341835b80..974f2eb58 100644 --- a/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs @@ -44,7 +44,7 @@ protected override Task SendAsync(HttpRequestMessage reques private AudienceConfig MakeConfig() => new AudienceConfig { - PublishableKey = "pk_imapik-test-key", + PublishableKey = TestDefaults.PublishableKey, Consent = ConsentLevel.Anonymous, PersistentDataPath = _testDir, FlushIntervalSeconds = TestDefaults.FlushIntervalSeconds, diff --git a/src/Packages/Audience/Tests/Runtime/TestFixtures.cs b/src/Packages/Audience/Tests/Runtime/TestFixtures.cs index dfd7407d3..a8a971df6 100644 --- a/src/Packages/Audience/Tests/Runtime/TestFixtures.cs +++ b/src/Packages/Audience/Tests/Runtime/TestFixtures.cs @@ -80,10 +80,13 @@ internal static class TestFixtures // File body that occupies the queue directory path so directory creation fails. internal const string DiskBlockerContent = "blocker"; - // DistributionPlatform mixed-case fixtures for Init's lowercase - // normalisation test. - internal const string DistributionPlatformSteamCased = "Steam"; - internal const string DistributionPlatformSteamUppercase = "STEAM"; + // Mixed-case "Steam" / "Passport" fixtures shared by DistributionPlatform and IdentityType tests. + internal const string SteamPascalCase = "Steam"; + internal const string SteamUpperCase = "STEAM"; + internal const string PassportPascalCase = "Passport"; + + // Steam-prefixed-but-not-exact-match fixture for the Custom-fallback test. + internal const string SteamSuffixed = "steamX"; // Unity Application.platform string for the GameLaunch.Platform test. internal const string PlatformWindows = "WindowsPlayer"; @@ -92,5 +95,15 @@ internal static class TestFixtures internal const string GenericAliasFromId = "fromId"; internal const string GenericAliasToId = "toId"; internal const string GenericAliasFromShort = "from"; + + // Real-shape 64-bit Steam community ID. Asserts the input is carried through faithfully. + internal const string SteamId64 = "76561198012345"; + + // Generic Steam / Passport ID fixtures used by alias and consent tests. + internal const string SteamId = "steam123"; + internal const string PassportId = "user_456"; + + // Generic single-user fixture for tests that just need any userId. + internal const string GenericUserSingleId = "user1"; } } diff --git a/src/Packages/Audience/Tests/Runtime/ThreadSafetyStressTests.cs b/src/Packages/Audience/Tests/Runtime/ThreadSafetyStressTests.cs index 984a905a7..82f36b599 100644 --- a/src/Packages/Audience/Tests/Runtime/ThreadSafetyStressTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ThreadSafetyStressTests.cs @@ -45,7 +45,7 @@ protected override Task SendAsync(HttpRequestMessage reques private AudienceConfig MakeConfig(ConsentLevel consent = ConsentLevel.Full) => new AudienceConfig { - PublishableKey = "pk_imapik-test-stress", + PublishableKey = TestDefaults.PublishableKey, Consent = consent, PersistentDataPath = _testDir, FlushIntervalSeconds = TestDefaults.FlushIntervalSeconds, diff --git a/src/Packages/Audience/Tests/Runtime/Transport/DiskStoreTests.cs b/src/Packages/Audience/Tests/Runtime/Transport/DiskStoreTests.cs index 416a50931..ddb522b0e 100644 --- a/src/Packages/Audience/Tests/Runtime/Transport/DiskStoreTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Transport/DiskStoreTests.cs @@ -207,8 +207,13 @@ public void ApplyAnonymousDowngrade_PurchaseValue_RoundsTripsExactlyForRealistic TearDown(); SetUp(); - var json = "{\"type\":\"track\",\"eventName\":\"purchase\",\"anonymousId\":\"a\",\"userId\":\"u\"," - + "\"properties\":{\"currency\":\"USD\",\"value\":" + amount + "}}"; + var json = $"{{\"{MessageFields.Type}\":\"{MessageTypes.Track}\"," + + $"\"{MessageFields.EventName}\":\"{EventNames.Purchase}\"," + + $"\"{MessageFields.AnonymousId}\":\"{TestEventNames.PlaceholderA}\"," + + $"\"{MessageFields.UserId}\":\"u\"," + + $"\"{MessageFields.Properties}\":{{" + + $"\"{EventPropertyKeys.Currency}\":\"{TestFixtures.UsdCurrency}\"," + + $"\"{EventPropertyKeys.Value}\":{amount}}}}}"; _store.Write(json); _store.ApplyAnonymousDowngrade(); diff --git a/src/Packages/Audience/Tests/Runtime/Transport/EventQueueTests.cs b/src/Packages/Audience/Tests/Runtime/Transport/EventQueueTests.cs index 2661b4dc7..85ed79786 100644 --- a/src/Packages/Audience/Tests/Runtime/Transport/EventQueueTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Transport/EventQueueTests.cs @@ -9,6 +9,9 @@ namespace Immutable.Audience.Tests [TestFixture] internal class EventQueueTests { + // Test-scaffold key used by the local Msg helper, not MessageFields.EventName. + private const string MsgEventKey = "event"; + private string _testDir; private DiskStore _store; @@ -28,7 +31,7 @@ public void TearDown() } private static Dictionary Msg(string evt) => - new Dictionary { ["event"] = evt }; + new Dictionary { [MsgEventKey] = evt }; [Test] public void Enqueue_ThenFlushSync_PersistesEventToDisk() diff --git a/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs b/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs index 2c599c723..026f162a8 100644 --- a/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs @@ -17,6 +17,12 @@ namespace Immutable.Audience.Tests [TestFixture] internal class HttpTransportTests { + // Non-test-prefix publishable key. Must not carry pk_imapik-test- (asserts production BaseUrl). + private const string ProdPublishableKey = "pk_imapik-prodkey"; + + // Standard HTTP header (RFC 7231) the server sets to override the SDK's 429 backoff. + private const string RetryAfterHeader = "Retry-After"; + // Response body fixtures. private const string MalformedResponseBody = "not-json"; private const string EmptyJsonObjectBody = "{}"; @@ -156,7 +162,7 @@ public async Task SendBatchAsync_200_UsesCorrectUrlForProdKey() HttpRequestMessage? captured = null; var handler = new MockHandler(HttpStatusCode.OK, $"{{\"accepted\":1,\"{ResponseFields.Rejected}\":0}}", onRequest: req => captured = req); - using var transport = new HttpTransport(_store, "pk_imapik-prodkey", handler: handler); + using var transport = new HttpTransport(_store, ProdPublishableKey, handler: handler); await transport.SendBatchAsync(); @@ -238,7 +244,7 @@ public async Task SendBatchAsync_429_RetryAfterDeltaSeconds_OverridesExpoBackoff var handler = new MockHandler(() => { var resp = new HttpResponseMessage((HttpStatusCode)429); - resp.Headers.Add("Retry-After", "12"); + resp.Headers.Add(RetryAfterHeader, "12"); return resp; }); using var transport = new HttpTransport(_store, TestDefaults.PublishableKey, @@ -261,7 +267,7 @@ public async Task SendBatchAsync_429_RetryAfterHttpDate_OverridesExpoBackoff() var handler = new MockHandler(() => { var resp = new HttpResponseMessage((HttpStatusCode)429); - resp.Headers.Add("Retry-After", DateTimeOffset.UtcNow.AddSeconds(20).ToString("R")); + resp.Headers.Add(RetryAfterHeader, DateTimeOffset.UtcNow.AddSeconds(20).ToString("R")); return resp; }); using var transport = new HttpTransport(_store, TestDefaults.PublishableKey, @@ -283,7 +289,7 @@ public async Task SendBatchAsync_429_PastRetryAfterDate_FallsBackToExpoBackoff() var handler = new MockHandler(() => { var resp = new HttpResponseMessage((HttpStatusCode)429); - resp.Headers.Add("Retry-After", DateTimeOffset.UtcNow.AddSeconds(-30).ToString("R")); + resp.Headers.Add(RetryAfterHeader, DateTimeOffset.UtcNow.AddSeconds(-30).ToString("R")); return resp; }); using var transport = new HttpTransport(_store, TestDefaults.PublishableKey, diff --git a/src/Packages/Audience/Tests/Runtime/Utility/JsonReaderTests.cs b/src/Packages/Audience/Tests/Runtime/Utility/JsonReaderTests.cs index a1e8bb796..389c94c79 100644 --- a/src/Packages/Audience/Tests/Runtime/Utility/JsonReaderTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Utility/JsonReaderTests.cs @@ -6,6 +6,24 @@ namespace Immutable.Audience.Tests [TestFixture] public class JsonReaderTests { + // Per-scenario fixture keys / values used by the deserialise tests. + private const string IntFixtureKey = "small"; + private const string LongFixtureKey = "big"; + private const string BoolTrueKey = "t"; + private const string BoolFalseKey = "f"; + private const string NullKey = "n"; + private const string ArrayKey = "arr"; + private const string StringElementValue = "two"; + + // Anonymous ID placeholder for RoundTripViaSerializer. + private const string AnonymousIdFixture = "abc"; + + // MalformedThrows test: three deliberately invalid JSON inputs that + // exercise distinct parser failure modes. + private const string MalformedNotValid = "{not valid}"; + private const string MalformedEmptyValue = "{\"a\":}"; + private const string MalformedUnterminatedString = "{\"a\":\"unterminated"; + [Test] public void EmptyObject() { @@ -30,18 +48,18 @@ public void StringWithEscapes() [Test] public void IntAndLong() { - var result = JsonReader.DeserializeObject("{\"small\":42,\"big\":12345678901234}"); - Assert.AreEqual(42, result["small"]); - Assert.AreEqual(12345678901234L, result["big"]); + var result = JsonReader.DeserializeObject($"{{\"{IntFixtureKey}\":42,\"{LongFixtureKey}\":12345678901234}}"); + Assert.AreEqual(42, result[IntFixtureKey]); + Assert.AreEqual(12345678901234L, result[LongFixtureKey]); } [Test] public void BoolAndNull() { - var result = JsonReader.DeserializeObject("{\"t\":true,\"f\":false,\"n\":null}"); - Assert.AreEqual(true, result["t"]); - Assert.AreEqual(false, result["f"]); - Assert.IsNull(result["n"]); + var result = JsonReader.DeserializeObject($"{{\"{BoolTrueKey}\":true,\"{BoolFalseKey}\":false,\"{NullKey}\":null}}"); + Assert.AreEqual(true, result[BoolTrueKey]); + Assert.AreEqual(false, result[BoolFalseKey]); + Assert.IsNull(result[NullKey]); } [Test] @@ -55,11 +73,11 @@ public void NestedObject() [Test] public void Array() { - var result = JsonReader.DeserializeObject("{\"arr\":[1,\"two\",true,null]}"); - var arr = (List)result["arr"]; + var result = JsonReader.DeserializeObject($"{{\"{ArrayKey}\":[1,\"{StringElementValue}\",true,null]}}"); + var arr = (List)result[ArrayKey]; Assert.AreEqual(4, arr.Count); Assert.AreEqual(1, arr[0]); - Assert.AreEqual("two", arr[1]); + Assert.AreEqual(StringElementValue, arr[1]); Assert.AreEqual(true, arr[2]); Assert.IsNull(arr[3]); } @@ -76,8 +94,8 @@ public void RoundTripViaSerializer() [EventPropertyKeys.Status] = ProgressionStatus.Complete.ToLowercaseString(), [EventPropertyKeys.Score] = 1500 }, - [MessageFields.AnonymousId] = "abc", - [MessageFields.UserId] = "76561198012345" + [MessageFields.AnonymousId] = AnonymousIdFixture, + [MessageFields.UserId] = TestFixtures.SteamId64 }; var serialized = Json.Serialize(original); @@ -85,8 +103,8 @@ public void RoundTripViaSerializer() Assert.AreEqual(MessageTypes.Track, parsed[MessageFields.Type]); Assert.AreEqual(EventNames.Progression, parsed[MessageFields.EventName]); - Assert.AreEqual("abc", parsed[MessageFields.AnonymousId]); - Assert.AreEqual("76561198012345", parsed[MessageFields.UserId]); + Assert.AreEqual(AnonymousIdFixture, parsed[MessageFields.AnonymousId]); + Assert.AreEqual(TestFixtures.SteamId64, parsed[MessageFields.UserId]); var props = (Dictionary)parsed[MessageFields.Properties]; Assert.AreEqual(ProgressionStatus.Complete.ToLowercaseString(), props[EventPropertyKeys.Status]); Assert.AreEqual(1500, props[EventPropertyKeys.Score]); @@ -95,9 +113,9 @@ public void RoundTripViaSerializer() [Test] public void MalformedThrows() { - Assert.Throws(() => JsonReader.DeserializeObject("{not valid}")); - Assert.Throws(() => JsonReader.DeserializeObject("{\"a\":}")); - Assert.Throws(() => JsonReader.DeserializeObject("{\"a\":\"unterminated")); + Assert.Throws(() => JsonReader.DeserializeObject(MalformedNotValid)); + Assert.Throws(() => JsonReader.DeserializeObject(MalformedEmptyValue)); + Assert.Throws(() => JsonReader.DeserializeObject(MalformedUnterminatedString)); } } } diff --git a/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs b/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs index fcb9d08ce..c4d012799 100644 --- a/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs @@ -7,6 +7,33 @@ namespace Immutable.Audience.Tests [TestFixture] public class JsonTests { + // Per-scenario fixture keys/values for the serialise tests. + private const string BoolFixtureKey = "flag"; + private const string NumericFixtureKey = "n"; + private const string NullFixtureKey = "x"; + private const string VShortFixture = "v"; + private const string ArrayFixtureKey = "items"; + + // RealisticEventPayload: nested keys and arrays exercise nested-list serialisation. + private const string PropLevelKey = "level"; + private const string PropScoreKey = "score"; + private const string PropPerfectKey = "perfect"; + private const string PropTagsKey = "tags"; + private const string TagFastValue = "fast"; + private const string TagCleanValue = "clean"; + + // Cycle / depth guard fixtures. + private const string DeepNestNextKey = "next"; + private const string SelfRefKey = "self"; + private const string CycleErrorMarker = "cycle"; + private const string NestingExceedsErrorMarker = "nesting exceeds"; + + // Diamond scenario (shared child under sibling keys is NOT a cycle). + private const string DiamondInnerKey = "k"; + private const string DiamondInnerValue = "v"; + private const string DiamondLeftKey = "a"; + private const string DiamondRightKey = "b"; + [Test] public void Serialize_EmptyDict_ReturnsEmptyObject() { @@ -41,41 +68,41 @@ public void Serialize_StringWithSpecialChars_EscapesCorrectly() [Test] public void Serialize_BoolTrue_ReturnsLowercaseTrue() { - var data = new Dictionary { { "flag", true } }; + var data = new Dictionary { { BoolFixtureKey, true } }; - Assert.AreEqual("{\"flag\":true}", Json.Serialize(data)); + Assert.AreEqual($"{{\"{BoolFixtureKey}\":true}}", Json.Serialize(data)); } [Test] public void Serialize_BoolFalse_ReturnsLowercaseFalse() { - var data = new Dictionary { { "flag", false } }; + var data = new Dictionary { { BoolFixtureKey, false } }; - Assert.AreEqual("{\"flag\":false}", Json.Serialize(data)); + Assert.AreEqual($"{{\"{BoolFixtureKey}\":false}}", Json.Serialize(data)); } [Test] public void Serialize_IntValue_ReturnsIntegerLiteral() { - var data = new Dictionary { { "n", 42 } }; + var data = new Dictionary { { NumericFixtureKey, 42 } }; - Assert.AreEqual("{\"n\":42}", Json.Serialize(data)); + Assert.AreEqual($"{{\"{NumericFixtureKey}\":42}}", Json.Serialize(data)); } [Test] public void Serialize_LongValue_ReturnsIntegerLiteral() { - var data = new Dictionary { { "n", 9876543210L } }; + var data = new Dictionary { { NumericFixtureKey, 9876543210L } }; - Assert.AreEqual("{\"n\":9876543210}", Json.Serialize(data)); + Assert.AreEqual($"{{\"{NumericFixtureKey}\":9876543210}}", Json.Serialize(data)); } [Test] public void Serialize_NullValue_ReturnsJsonNull() { - var data = new Dictionary { { "x", null } }; + var data = new Dictionary { { NullFixtureKey, null } }; - Assert.AreEqual("{\"x\":null}", Json.Serialize(data)); + Assert.AreEqual($"{{\"{NullFixtureKey}\":null}}", Json.Serialize(data)); } [Test] @@ -97,47 +124,47 @@ public void Serialize_NestedDict_ReturnsNestedObject() [Test] public void Serialize_FloatNaN_SerializesAsNull() { - Assert.AreEqual("{\"v\":null}", Json.Serialize(new Dictionary { { "v", float.NaN } })); + Assert.AreEqual($"{{\"{VShortFixture}\":null}}", Json.Serialize(new Dictionary { { VShortFixture, float.NaN } })); } [Test] public void Serialize_FloatPositiveInfinity_SerializesAsNull() { - Assert.AreEqual("{\"v\":null}", Json.Serialize(new Dictionary { { "v", float.PositiveInfinity } })); + Assert.AreEqual($"{{\"{VShortFixture}\":null}}", Json.Serialize(new Dictionary { { VShortFixture, float.PositiveInfinity } })); } [Test] public void Serialize_FloatNegativeInfinity_SerializesAsNull() { - Assert.AreEqual("{\"v\":null}", Json.Serialize(new Dictionary { { "v", float.NegativeInfinity } })); + Assert.AreEqual($"{{\"{VShortFixture}\":null}}", Json.Serialize(new Dictionary { { VShortFixture, float.NegativeInfinity } })); } [Test] public void Serialize_DoubleNaN_SerializesAsNull() { - Assert.AreEqual("{\"v\":null}", Json.Serialize(new Dictionary { { "v", double.NaN } })); + Assert.AreEqual($"{{\"{VShortFixture}\":null}}", Json.Serialize(new Dictionary { { VShortFixture, double.NaN } })); } [Test] public void Serialize_DoubleInfinity_SerializesAsNull() { - Assert.AreEqual("{\"v\":null}", Json.Serialize(new Dictionary { { "v", double.PositiveInfinity } })); + Assert.AreEqual($"{{\"{VShortFixture}\":null}}", Json.Serialize(new Dictionary { { VShortFixture, double.PositiveInfinity } })); } [Test] public void Serialize_FloatValue_NormalRange() { - var data = new Dictionary { { "v", 3.14f } }; + var data = new Dictionary { { VShortFixture, 3.14f } }; var result = Json.Serialize(data); - StringAssert.Contains("\"v\":", result); - StringAssert.DoesNotContain("\"v\":\"", result); // must not be quoted + StringAssert.Contains($"\"{VShortFixture}\":", result); + StringAssert.DoesNotContain($"\"{VShortFixture}\":\"", result); } [Test] public void Serialize_FloatValue_LargeExponent_PreservesValue() { // 1e30f in scientific notation is valid JSON; must not be silently zeroed - var data = new Dictionary { { "v", 1e30f } }; + var data = new Dictionary { { VShortFixture, 1e30f } }; var result = Json.Serialize(data); var serialised = result.Substring(result.IndexOf(':') + 1, result.Length - result.IndexOf(':') - 2); Assert.AreNotEqual("0", serialised); @@ -148,7 +175,7 @@ public void Serialize_FloatValue_LargeExponent_PreservesValue() public void Serialize_FloatValue_SmallNegativeExponent_PreservesValue() { // 1e-30f: the old F6 fallback turned this into "0.000000" - var data = new Dictionary { { "v", 1e-30f } }; + var data = new Dictionary { { VShortFixture, 1e-30f } }; var result = Json.Serialize(data); var serialised = result.Substring(result.IndexOf(':') + 1, result.Length - result.IndexOf(':') - 2); Assert.AreNotEqual("0", serialised); @@ -158,7 +185,7 @@ public void Serialize_FloatValue_SmallNegativeExponent_PreservesValue() [Test] public void Serialize_DoubleValue_SmallNegativeExponent_PreservesValue() { - var data = new Dictionary { { "v", 1e-300 } }; + var data = new Dictionary { { VShortFixture, 1e-300 } }; var result = Json.Serialize(data); var serialised = result.Substring(result.IndexOf(':') + 1, result.Length - result.IndexOf(':') - 2); Assert.AreNotEqual("0", serialised); @@ -170,10 +197,10 @@ public void Serialize_ListValue_ReturnsJsonArray() { var data = new Dictionary { - { "items", new List { "a", 1, true } } + { ArrayFixtureKey, new List { TestEventNames.PlaceholderA, 1, true } } }; - Assert.AreEqual("{\"items\":[\"a\",1,true]}", Json.Serialize(data)); + Assert.AreEqual($"{{\"{ArrayFixtureKey}\":[\"{TestEventNames.PlaceholderA}\",1,true]}}", Json.Serialize(data)); } [Test] @@ -187,10 +214,10 @@ public void Serialize_RealisticEventPayload_ProducesCorrectJson() { MessageFields.UserId, null }, { MessageFields.Properties, new Dictionary { - { "level", 5 }, - { "score", 9800L }, - { "perfect", true }, - { "tags", new List { "fast", "clean" } } + { PropLevelKey, 5 }, + { PropScoreKey, 9800L }, + { PropPerfectKey, true }, + { PropTagsKey, new List { TagFastValue, TagCleanValue } } } } }; @@ -198,11 +225,11 @@ public void Serialize_RealisticEventPayload_ProducesCorrectJson() var result = Json.Serialize(data); StringAssert.Contains($"\"{MessageFields.Type}\":\"{MessageTypes.Track}\"", result); - StringAssert.Contains($"\"{MessageFields.EventName}\":\"level_complete\"", result); + StringAssert.Contains($"\"{MessageFields.EventName}\":\"{TestEventNames.LevelComplete}\"", result); StringAssert.Contains($"\"{MessageFields.UserId}\":null", result); - StringAssert.Contains("\"level\":5", result); - StringAssert.Contains("\"perfect\":true", result); - StringAssert.Contains("\"tags\":[\"fast\",\"clean\"]", result); + StringAssert.Contains($"\"{PropLevelKey}\":5", result); + StringAssert.Contains($"\"{PropPerfectKey}\":true", result); + StringAssert.Contains($"\"{PropTagsKey}\":[\"{TagFastValue}\",\"{TagCleanValue}\"]", result); } [Test] @@ -213,39 +240,39 @@ public void Serialize_NestingExceedsMaxDepth_ThrowsFormatException() for (var i = 0; i < Json.MaxDepth; i++) { var next = new Dictionary(); - current["next"] = next; + current[DeepNestNextKey] = next; current = next; } var ex = Assert.Throws(() => Json.Serialize(root)); - StringAssert.Contains("nesting exceeds", ex.Message); + StringAssert.Contains(NestingExceedsErrorMarker, ex.Message); } [Test] public void Serialize_SelfReferentialDict_ThrowsFormatException() { var root = new Dictionary(); - root["self"] = root; + root[SelfRefKey] = root; var ex = Assert.Throws(() => Json.Serialize(root)); - StringAssert.Contains("cycle", ex.Message); + StringAssert.Contains(CycleErrorMarker, ex.Message); } [Test] public void Serialize_SharedChildInSiblingKeys_IsNotTreatedAsCycle() { // Diamond: visited set tracks the current recursion stack, not all objects ever seen. - var shared = new Dictionary { ["k"] = "v" }; + var shared = new Dictionary { [DiamondInnerKey] = DiamondInnerValue }; var root = new Dictionary { - ["a"] = shared, - ["b"] = shared, + [DiamondLeftKey] = shared, + [DiamondRightKey] = shared, }; var result = Json.Serialize(root); - StringAssert.Contains("\"a\":{\"k\":\"v\"}", result); - StringAssert.Contains("\"b\":{\"k\":\"v\"}", result); + StringAssert.Contains($"\"{DiamondLeftKey}\":{{\"{DiamondInnerKey}\":\"{DiamondInnerValue}\"}}", result); + StringAssert.Contains($"\"{DiamondRightKey}\":{{\"{DiamondInnerKey}\":\"{DiamondInnerValue}\"}}", result); } } } diff --git a/src/Packages/Audience/Tests/Runtime/Utility/LogTests.cs b/src/Packages/Audience/Tests/Runtime/Utility/LogTests.cs index 42a3cda0c..a890a691a 100644 --- a/src/Packages/Audience/Tests/Runtime/Utility/LogTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Utility/LogTests.cs @@ -6,6 +6,14 @@ namespace Immutable.Audience.Tests [TestFixture] internal class LogTests { + // Inputs to Log.Debug / Log.Warn used across the fixture. + private const string SilentDebugInput = "silent"; + private const string EnabledDebugInput = "hello"; + private const string WarnInput = "something off"; + + // Substring marker that Log.Warn injects between the prefix and the user message. + private const string WarnMarker = "WARN"; + private List _captured; [SetUp] @@ -28,7 +36,7 @@ public void Debug_WhenDisabled_EmitsNothing() { Log.Enabled = false; - Log.Debug("silent"); + Log.Debug(SilentDebugInput); Assert.AreEqual(0, _captured.Count); } @@ -38,11 +46,11 @@ public void Debug_WhenEnabled_EmitsWithPrefix() { Log.Enabled = true; - Log.Debug("hello"); + Log.Debug(EnabledDebugInput); Assert.AreEqual(1, _captured.Count); - StringAssert.StartsWith("[ImmutableAudience]", _captured[0]); - StringAssert.Contains("hello", _captured[0]); + StringAssert.StartsWith(Log.Prefix, _captured[0]); + StringAssert.Contains(EnabledDebugInput, _captured[0]); } [Test] @@ -50,11 +58,11 @@ public void Warn_AlwaysEmits_EvenWhenDisabled() { Log.Enabled = false; - Log.Warn("something off"); + Log.Warn(WarnInput); Assert.AreEqual(1, _captured.Count); - StringAssert.Contains("WARN", _captured[0]); - StringAssert.Contains("something off", _captured[0]); + StringAssert.Contains(WarnMarker, _captured[0]); + StringAssert.Contains(WarnInput, _captured[0]); } } }