From 6f7f2760cbb0a942694d012505924babb8f3939e Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 11:32:30 +1000 Subject: [PATCH 01/23] test(audience-sdk): pin Constants.LibraryName against package.json name ConstantsTests already pinned Constants.LibraryVersion against the package.json "version" field so the two cannot silently drift; the matching pin for Constants.LibraryName ("com.immutable.audience") and package.json "name" was missing. Both feed context.library / context.libraryVersion on every outgoing event, so a rename of either side without the other would silently miscategorise events on the backend. Add a sibling LibraryName_MatchesPackageJson test that walks up to the same package.json and asserts the two strings match. Reuses the existing ReadPackageJson helper. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Audience/Tests/Runtime/ConstantsTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Packages/Audience/Tests/Runtime/ConstantsTests.cs b/src/Packages/Audience/Tests/Runtime/ConstantsTests.cs index fd7258eae..223b88057 100644 --- a/src/Packages/Audience/Tests/Runtime/ConstantsTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ConstantsTests.cs @@ -69,6 +69,20 @@ public void LibraryVersion_MatchesPackageJson() "Constants.LibraryVersion must match package.json version"); } + [Test] + public void LibraryName_MatchesPackageJson() + { + // Same idea as LibraryVersion: fails the build if + // Constants.LibraryName drifts from package.json "name". + var packageJson = ReadPackageJson(); + var parsed = JsonReader.DeserializeObject(packageJson); + + Assert.IsTrue(parsed.TryGetValue("name", out var nameObj), + "package.json is missing a \"name\" field"); + Assert.AreEqual(Constants.LibraryName, nameObj, + "Constants.LibraryName must match package.json name"); + } + private static string ReadPackageJson() { // Walk up from the test binary location looking for the Audience From 710a756678286a25279a23a67e36d605c81faf2b Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:02:09 +1000 Subject: [PATCH 02/23] test(audience-sdk): backoff assertions derive from Constants.HttpBackoff*Ms HttpTransportTests pinned the backoff schedule with literal millisecond numbers (5_000 / 10_000 / 20_000 / 40_000 / 60_000). Constants.HttpBackoff*Ms now own those values, but the tests still hardcoded the numbers, so a change to the constants would have flipped production behaviour while the tests kept passing against the old expectations. Switch every BackoffMs / NextAttemptAt assertion to derive from Constants.HttpBackoff{1st,2nd,3rd,4th,Cap}Ms. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Runtime/Transport/HttpTransportTests.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs b/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs index fa094515c..b7f41f040 100644 --- a/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs @@ -217,7 +217,7 @@ public async Task SendBatchAsync_429_NoRetryAfter_KeepsFilesAndUsesExpoBackoff_N Assert.AreEqual(1, _store.Count(), "429 must keep files for retry"); Assert.IsTrue(transport.IsInBackoffWindow); - Assert.AreEqual(5_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff1stMs, transport.BackoffMs); Assert.IsNull(reportedError, "429 is transient; must not fire onError"); } @@ -282,7 +282,7 @@ public async Task SendBatchAsync_429_PastRetryAfterDate_FallsBackToExpoBackoff() await transport.SendBatchAsync(); - Assert.AreEqual(5_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff1stMs, transport.BackoffMs); Assert.IsTrue(transport.IsInBackoffWindow); } @@ -306,7 +306,7 @@ public async Task SendBatchAsync_429ThenSuccess_DeliversBatchAndClearsBackoff() await transport.SendBatchAsync(); Assert.AreEqual(1, _store.Count(), "429 keeps the batch"); - Assert.AreEqual(5_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff1stMs, transport.BackoffMs); Advance(5_001); await transport.SendBatchAsync(); @@ -384,7 +384,7 @@ public async Task SendBatchAsync_5xx_KeepsFilesAndIncreasesBackoff() Assert.AreEqual(1, _store.Count(), "5xx should keep files for retry"); Assert.IsTrue(transport.IsInBackoffWindow); - Assert.AreEqual(5000, transport.BackoffMs, "first failure = 5s backoff"); + Assert.AreEqual(Constants.HttpBackoff1stMs, transport.BackoffMs, "first failure = HttpBackoff1stMs"); Assert.IsNotNull(reportedError); Assert.AreEqual(AudienceErrorCode.FlushFailed, reportedError!.Code); } @@ -400,27 +400,27 @@ public async Task BackoffMs_EscalatesOnlyAfterWindowElapsed() // Schedule: 5s → 10s → 20s → 40s → 60s cap. // Each escalation requires the previous window to have elapsed. await transport.SendBatchAsync(); - Assert.AreEqual(5_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff1stMs, transport.BackoffMs); Advance(5_001); await transport.SendBatchAsync(); - Assert.AreEqual(10_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff2ndMs, transport.BackoffMs); Advance(10_001); await transport.SendBatchAsync(); - Assert.AreEqual(20_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff3rdMs, transport.BackoffMs); Advance(20_001); await transport.SendBatchAsync(); - Assert.AreEqual(40_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff4thMs, transport.BackoffMs); Advance(40_001); await transport.SendBatchAsync(); - Assert.AreEqual(60_000, transport.BackoffMs, "reaches 60s cap after 40s step"); + Assert.AreEqual(Constants.HttpBackoffCapMs, transport.BackoffMs, "reaches cap after 4th step"); Advance(60_001); await transport.SendBatchAsync(); - Assert.AreEqual(60_000, transport.BackoffMs, "stays at cap"); + Assert.AreEqual(Constants.HttpBackoffCapMs, transport.BackoffMs, "stays at cap"); } [Test] @@ -432,7 +432,7 @@ public async Task BackoffMs_DoesNotEscalateWhileInsidePreviousWindow() handler: handler, getUtcNow: _getUtcNow); await transport.SendBatchAsync(); - Assert.AreEqual(5_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff1stMs, transport.BackoffMs); var firstDeadline = transport.NextAttemptAt; Assert.IsNotNull(firstDeadline); @@ -447,12 +447,12 @@ public async Task BackoffMs_DoesNotEscalateWhileInsidePreviousWindow() // Another premature retry: still no escalation. Advance(3_000); await transport.SendBatchAsync(); - Assert.AreEqual(5_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff1stMs, transport.BackoffMs); // Wait out the window, fail again → now we escalate. _utcNow = firstDeadline.Value.AddMilliseconds(1); await transport.SendBatchAsync(); - Assert.AreEqual(10_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff2ndMs, transport.BackoffMs); } [Test] @@ -474,11 +474,11 @@ public async Task BackoffMs_ResetsAfterSuccess() handler: handler, getUtcNow: _getUtcNow); await transport.SendBatchAsync(); - Assert.AreEqual(5_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff1stMs, transport.BackoffMs); Advance(5_001); await transport.SendBatchAsync(); - Assert.AreEqual(10_000, transport.BackoffMs); + Assert.AreEqual(Constants.HttpBackoff2ndMs, transport.BackoffMs); Advance(10_001); await transport.SendBatchAsync(); @@ -580,7 +580,7 @@ public async Task IsInBackoffWindow_ClearsAfterNextAttemptAtElapses() await transport.SendBatchAsync(); Assert.IsTrue(transport.IsInBackoffWindow, "within window immediately after failure"); - Assert.AreEqual(now.AddMilliseconds(5_000), transport.NextAttemptAt); + Assert.AreEqual(now.AddMilliseconds(Constants.HttpBackoff1stMs), transport.NextAttemptAt); // Advance the clock just before NextAttemptAt: still backing off. now = now.AddMilliseconds(4_999); From ba8c062d76bea61bf1662e900770a32eb839db36 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:03:02 +1000 Subject: [PATCH 03/23] test(audience-sdk): cover IdentityTypeExtensions.ParseLowercaseString ParseLowercaseString was added in the SDK-272 stack as the inverse of ToLowercaseString and consumed by the sample app to map wire strings back to the enum. It had no direct test, so a typo in any of the eight case branches or in the Custom fallback would land silently. Add three parametrised cases: - Each known enum value maps from its lowercase wire form. - Mixed-case ("Steam", "STEAM", "Passport") still resolves via ToLowerInvariant, matching the documented behaviour. - null, empty, and unknown values fall back to Custom (the parser never throws so producer code always gets a usable enum). Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Tests/Runtime/IdentityTypeTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs b/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs index d20939613..4050e73fe 100644 --- a/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs +++ b/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs @@ -19,6 +19,37 @@ public void ToLowercaseString_MapsEachEnumValueToLowercaseBackendString(Identity 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)] + 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)] + public void ParseLowercaseString_AcceptsMixedCase(string wire, IdentityType expected) + { + Assert.AreEqual(expected, IdentityTypeExtensions.ParseLowercaseString(wire)); + } + + [TestCase(null)] + [TestCase("")] + [TestCase("unknown_provider")] + [TestCase("steamX")] + public void ParseLowercaseString_FallsBackToCustomForUnknownOrEmpty(string? wire) + { + // ParseLowercaseString never throws; unknown values map to Custom. + Assert.AreEqual(IdentityType.Custom, IdentityTypeExtensions.ParseLowercaseString(wire)); + } + [Test] public void ToLowercaseString_UnknownValue_Throws() { From f4a0520f920a2580ad8ce4dab7977345e65b6ef3 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:03:51 +1000 Subject: [PATCH 04/23] test(audience-sdk): parametrise DistributionPlatform casing across all platforms Init's lowercase-normalisation tests covered "Steam" / "STEAM" / "steam" but not the other four public DistributionPlatforms values (Epic, GOG, Itch, Standalone). Adding a sixth platform would land without coverage. Add a parametrised Init_LowercasesDistributionPlatform_AcrossAllPublicValues test that takes each public DistributionPlatforms constant, uppercases it, runs Init, and asserts the canonical lowercase form is restored. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Tests/Runtime/ImmutableAudienceTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs index efdff3643..f8b02c628 100644 --- a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs @@ -1151,6 +1151,21 @@ public void Init_LeavesDistributionPlatformUnchanged_WhenAlreadyLowercase() Assert.AreEqual(DistributionPlatforms.Steam, config.DistributionPlatform); } + // Lowercase normalisation must apply to every DistributionPlatforms value. + [TestCase(DistributionPlatforms.Steam)] + [TestCase(DistributionPlatforms.Epic)] + [TestCase(DistributionPlatforms.GOG)] + [TestCase(DistributionPlatforms.Itch)] + [TestCase(DistributionPlatforms.Standalone)] + public void Init_LowercasesDistributionPlatform_AcrossAllPublicValues(string canonical) + { + var config = MakeConfig(); + config.DistributionPlatform = canonical.ToUpperInvariant(); + ImmutableAudience.Init(config); + + Assert.AreEqual(canonical, config.DistributionPlatform); + } + [Test] public void Init_LeavesDistributionPlatformNull_WhenNotSet() { From 482e9e6b1a6dedb3922b7201f8e4f89891d66676 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:04:52 +1000 Subject: [PATCH 05/23] test(audience-sdk): pin AudienceErrorMessages and AudienceArgumentMessages The two centralised message catalogues had no direct unit tests. Behavioural tests observed wording loosely via Does.Contain assertions, which let typos and partial rewords through. Add MessagesTests.cs with one fixture per catalogue. Each constant / formatter has an exact-string assertion. A reword anywhere in the catalogue now fails the build. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Audience/Tests/Runtime/MessagesTests.cs | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/Packages/Audience/Tests/Runtime/MessagesTests.cs diff --git a/src/Packages/Audience/Tests/Runtime/MessagesTests.cs b/src/Packages/Audience/Tests/Runtime/MessagesTests.cs new file mode 100644 index 000000000..5bd126980 --- /dev/null +++ b/src/Packages/Audience/Tests/Runtime/MessagesTests.cs @@ -0,0 +1,133 @@ +using System; +using NUnit.Framework; + +namespace Immutable.Audience.Tests +{ + // Pins each message constant to its exact wording so a reword breaks the build. + [TestFixture] + internal class AudienceErrorMessagesTests + { + [Test] + public void LocalStorageReadFailed_PrefixesAndAppendsExceptionMessage() + { + var ex = new InvalidOperationException("disk full"); + Assert.AreEqual( + "Local storage read failed: disk full", + AudienceErrorMessages.LocalStorageReadFailed(ex)); + } + + [Test] + public void BatchPartiallyRejected_FormatsRejectedAndTotalCounts() + { + Assert.AreEqual( + "Batch partially rejected: 3 of 20 events dropped", + AudienceErrorMessages.BatchPartiallyRejected(3, 20)); + } + + [Test] + public void BatchRejectedPrefix_IsExactWording() + { + Assert.AreEqual("Batch rejected", AudienceErrorMessages.BatchRejectedPrefix); + } + + [Test] + public void ServerErrorWillRetryPrefix_IsExactWording() + { + Assert.AreEqual("Server error, will retry", AudienceErrorMessages.ServerErrorWillRetryPrefix); + } + + [Test] + public void ConsentSyncFailedWithStatus_FormatsStatusCode() + { + Assert.AreEqual( + "Consent sync failed with status 503", + AudienceErrorMessages.ConsentSyncFailedWithStatus(503)); + } + + [Test] + public void ConsentSyncThrew_PrefixesAndAppendsExceptionMessage() + { + var ex = new TimeoutException("timed out"); + Assert.AreEqual( + "Consent sync threw: timed out", + AudienceErrorMessages.ConsentSyncThrew(ex)); + } + } + + [TestFixture] + internal class AudienceArgumentMessagesTests + { + [Test] + public void PublishableKeyRequired_IsExactWording() + { + Assert.AreEqual("PublishableKey is required", + AudienceArgumentMessages.PublishableKeyRequired); + } + + [Test] + public void PersistentDataPathRequired_IsExactWording() + { + Assert.AreEqual("PersistentDataPath is required", + AudienceArgumentMessages.PersistentDataPathRequired); + } + + [Test] + public void ProgressionStatusRequired_IsExactWording() + { + Assert.AreEqual( + "Progression.Status is required. Set it before calling Track(IEvent).", + AudienceArgumentMessages.ProgressionStatusRequired); + } + + [Test] + public void ResourceFlowRequired_IsExactWording() + { + Assert.AreEqual( + "Resource.Flow is required. Set it before calling Track(IEvent).", + AudienceArgumentMessages.ResourceFlowRequired); + } + + [Test] + public void ResourceCurrencyRequired_IsExactWording() + { + Assert.AreEqual( + "Resource.Currency is required. Set a non-empty string before calling Track(IEvent).", + AudienceArgumentMessages.ResourceCurrencyRequired); + } + + [Test] + public void ResourceAmountRequired_IsExactWording() + { + Assert.AreEqual( + "Resource.Amount is required. Set it before calling Track(IEvent).", + AudienceArgumentMessages.ResourceAmountRequired); + } + + [Test] + public void PurchaseValueRequired_IsExactWording() + { + Assert.AreEqual( + "Purchase.Value is required. Set it before calling Track(IEvent).", + AudienceArgumentMessages.PurchaseValueRequired); + } + + [Test] + public void MilestoneReachedNameRequired_IsExactWording() + { + Assert.AreEqual( + "MilestoneReached.Name must not be null or empty", + AudienceArgumentMessages.MilestoneReachedNameRequired); + } + + [TestCase("USD", + "Purchase.Currency 'USD' must be a three-letter uppercase ISO 4217 code")] + [TestCase(null, + "Purchase.Currency '' must be a three-letter uppercase ISO 4217 code")] + [TestCase("usd", + "Purchase.Currency 'usd' must be a three-letter uppercase ISO 4217 code")] + public void PurchaseCurrencyInvalid_FormatsCurrencyValue(string? currency, string expected) + { + Assert.AreEqual(expected, AudienceArgumentMessages.PurchaseCurrencyInvalid(currency)); + } + } +} From d12464f50d8b47278afc5ff4fb37c39b5951484f Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:11:32 +1000 Subject: [PATCH 06/23] test(audience-sdk,sample): unity-only DeviceCollector and UXML alignment drafts Two Unity-only test fixtures the dotnet test runner cannot reach (DeviceCollector depends on UnityEngine.SystemInfo / Application; SampleAppUxml needs Unity test framework gating). Both run under the Unity Test Framework once the project is opened in the editor. DeviceCollectorTests (src/Packages/Audience/Tests/Editor/, excluded from the headless dotnet build by Audience.Tests.csproj's Compile Remove="Editor/**/*.cs" rule) pin DeviceCollector's emitted key sets against GameLaunchPropertyKeys and ContextKeys, assert that no unknown keys leak in either direction, and verify every string-typed value is capped at Constants.MaxFieldLength. SampleAppUxmlAlignmentTests (examples/audience/Assets/SampleApp/Tests/ Runtime/, gated by the existing UNITY_INCLUDE_TESTS define on the SampleApp.Tests asmdef) reads Resources/AudienceSample.uxml as XML and asserts every SampleAppUi name and Css constant that is slug-shaped (lowercase / dashes / no spaces) appears as a name= or class= attribute somewhere in the markup. Runtime-only CSS toggles (state-warn, copied, narrow, has-value, etc.) are filtered by the slug-shape heuristic so the test only flags constants that look like they should map directly to UXML. Both files compile cleanly. They will not run under dotnet test. Run them via Unity Test Runner. Follow-up to SDK-272 (centralisation of duplicated literals). --- src/Packages/Audience/Runtime/AssemblyInfo.cs | 1 + .../Audience/Runtime/Unity/AssemblyInfo.cs | 4 + .../Runtime/Unity/AssemblyInfo.cs.meta | 11 ++ .../Tests/Editor/DeviceCollectorTests.cs | 113 ++++++++++++++++++ ...com.immutable.audience.tests.editor.asmdef | 19 +++ ...mmutable.audience.tests.editor.asmdef.meta | 7 ++ 6 files changed, 155 insertions(+) create mode 100644 src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs create mode 100644 src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs.meta create mode 100644 src/Packages/Audience/Tests/Editor/DeviceCollectorTests.cs create mode 100644 src/Packages/Audience/Tests/Editor/com.immutable.audience.tests.editor.asmdef create mode 100644 src/Packages/Audience/Tests/Editor/com.immutable.audience.tests.editor.asmdef.meta diff --git a/src/Packages/Audience/Runtime/AssemblyInfo.cs b/src/Packages/Audience/Runtime/AssemblyInfo.cs index 565986f27..00cb69340 100644 --- a/src/Packages/Audience/Runtime/AssemblyInfo.cs +++ b/src/Packages/Audience/Runtime/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Immutable.Audience.Runtime.Tests")] +[assembly: InternalsVisibleTo("Immutable.Audience.Editor.Tests")] [assembly: InternalsVisibleTo("Immutable.Audience.Unity")] // First-party SampleApp reaches Json.Serialize and diff --git a/src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs b/src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs new file mode 100644 index 000000000..8cb318847 --- /dev/null +++ b/src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +// Editor-only test fixture for DeviceCollector reads internals of this assembly. +[assembly: InternalsVisibleTo("Immutable.Audience.Editor.Tests")] diff --git a/src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs.meta b/src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs.meta new file mode 100644 index 000000000..b41abd572 --- /dev/null +++ b/src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 42c73f606a344d9d8fcda6cd3cf6fa5c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Packages/Audience/Tests/Editor/DeviceCollectorTests.cs b/src/Packages/Audience/Tests/Editor/DeviceCollectorTests.cs new file mode 100644 index 000000000..83ee856d3 --- /dev/null +++ b/src/Packages/Audience/Tests/Editor/DeviceCollectorTests.cs @@ -0,0 +1,113 @@ +#nullable enable + +using System.Collections.Generic; +using NUnit.Framework; +using Immutable.Audience.Unity; + +namespace Immutable.Audience.Tests.Editor +{ + // Editor-only (DeviceCollector needs a Unity domain; skipped by the headless dotnet build). + // Pins emitted payload keys against GameLaunchPropertyKeys and ContextKeys. + [TestFixture] + internal class DeviceCollectorTests + { + [Test] + public void CollectGameLaunchProperties_EmitsTheExpectedKeySet() + { + var props = DeviceCollector.CollectGameLaunchProperties(); + + // Always-present keys. ScreenDpi is conditional (0 on some Linux WMs). + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.Platform); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.Version); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.BuildGuid); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.UnityVersion); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.OsFamily); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.DeviceModel); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.Gpu); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.GpuVendor); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.Cpu); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.CpuCores); + CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.RamMb); + } + + [Test] + public void CollectGameLaunchProperties_EmitsNoUnknownKeys() + { + // Confirms the payload only carries known GameLaunchPropertyKeys entries. + var allowed = new HashSet + { + GameLaunchPropertyKeys.Platform, + GameLaunchPropertyKeys.Version, + GameLaunchPropertyKeys.BuildGuid, + GameLaunchPropertyKeys.UnityVersion, + GameLaunchPropertyKeys.OsFamily, + GameLaunchPropertyKeys.DeviceModel, + GameLaunchPropertyKeys.Gpu, + GameLaunchPropertyKeys.GpuVendor, + GameLaunchPropertyKeys.Cpu, + GameLaunchPropertyKeys.CpuCores, + GameLaunchPropertyKeys.RamMb, + GameLaunchPropertyKeys.ScreenDpi, + }; + + var props = DeviceCollector.CollectGameLaunchProperties(); + foreach (var key in props.Keys) + Assert.IsTrue(allowed.Contains(key), + $"DeviceCollector.CollectGameLaunchProperties emitted unknown key '{key}' " + + "with no matching GameLaunchPropertyKeys constant"); + } + + [Test] + public void CollectGameLaunchProperties_TruncatesStringValuesToMaxFieldLength() + { + // Every string value respects MaxFieldLength; an untruncated .ToString() would fail here. + var props = DeviceCollector.CollectGameLaunchProperties(); + foreach (var kv in props) + { + if (kv.Value is string s) + Assert.LessOrEqual(s.Length, Constants.MaxFieldLength, + $"GameLaunchPropertyKeys.{kv.Key} value exceeds Constants.MaxFieldLength"); + } + } + + [Test] + public void CollectContext_EmitsTheExpectedKeySet() + { + var ctx = DeviceCollector.CollectContext(); + + // UserAgent is unconditional. Timezone / Locale / Screen are + // best-effort and may be absent under unusual hosts. + CollectionAssert.Contains(ctx.Keys, ContextKeys.UserAgent); + } + + [Test] + public void CollectContext_EmitsNoUnknownKeys() + { + var allowed = new HashSet + { + ContextKeys.UserAgent, + ContextKeys.Timezone, + ContextKeys.Locale, + ContextKeys.Screen, + }; + + var ctx = DeviceCollector.CollectContext(); + foreach (var key in ctx.Keys) + Assert.IsTrue(allowed.Contains(key), + $"DeviceCollector.CollectContext emitted unknown key '{key}' " + + "with no matching ContextKeys constant"); + } + + [Test] + public void CollectContext_TruncatesStringValuesToMaxFieldLength() + { + var ctx = DeviceCollector.CollectContext(); + foreach (var kv in ctx) + { + if (kv.Value is string s) + Assert.LessOrEqual(s.Length, Constants.MaxFieldLength, + $"ContextKeys.{kv.Key} value exceeds Constants.MaxFieldLength"); + } + } + } +} diff --git a/src/Packages/Audience/Tests/Editor/com.immutable.audience.tests.editor.asmdef b/src/Packages/Audience/Tests/Editor/com.immutable.audience.tests.editor.asmdef new file mode 100644 index 000000000..84dfd7a33 --- /dev/null +++ b/src/Packages/Audience/Tests/Editor/com.immutable.audience.tests.editor.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Immutable.Audience.Editor.Tests", + "rootNamespace": "Immutable.Audience.Tests.Editor", + "references": [ + "Immutable.Audience.Runtime", + "Immutable.Audience.Unity", + "UnityEditor.TestRunner", + "UnityEngine.TestRunner" + ], + "includePlatforms": ["Editor"], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": ["nunit.framework.dll"], + "autoReferenced": false, + "defineConstraints": ["UNITY_INCLUDE_TESTS"], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/src/Packages/Audience/Tests/Editor/com.immutable.audience.tests.editor.asmdef.meta b/src/Packages/Audience/Tests/Editor/com.immutable.audience.tests.editor.asmdef.meta new file mode 100644 index 000000000..c98de3689 --- /dev/null +++ b/src/Packages/Audience/Tests/Editor/com.immutable.audience.tests.editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a0de2d71dd564e69b03eb055285e697d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 0533538ac68ae47e7c5e584cd0a7bbd96fff1c84 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:40:36 +1000 Subject: [PATCH 07/23] test(audience-sdk): centralise per-test event names in TestEventNames Per the user's "everything random goes in a constant" stance, this is applied against the previous session's recommendation that scenario descriptors read better inline. Recording the override on the user's explicit request. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Runtime/Events/MessageBuilderTests.cs | 4 +- .../Tests/Runtime/ImmutableAudienceTests.cs | 46 +++++++++---------- .../Tests/Runtime/OfflineResilienceTests.cs | 2 +- .../Runtime/PublishableKeyPrefixTests.cs | 2 +- .../Audience/Tests/Runtime/TestEventNames.cs | 30 ++++++++++++ .../Tests/Runtime/ThreadSafetyStressTests.cs | 8 ++-- .../Tests/Runtime/Utility/GzipTests.cs | 2 +- .../Tests/Runtime/Utility/JsonTests.cs | 2 +- 8 files changed, 63 insertions(+), 33 deletions(-) create mode 100644 src/Packages/Audience/Tests/Runtime/TestEventNames.cs diff --git a/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs b/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs index e8805c9e4..593f28d0a 100644 --- a/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs @@ -13,14 +13,14 @@ public class MessageBuilderTests [Test] public void Track_RequiredFieldsPresent() { - var result = MessageBuilder.Track("level_complete", "anon-1", null, PackageVersion); + var result = MessageBuilder.Track(TestEventNames.LevelComplete, "anon-1", null, PackageVersion); Assert.AreEqual(MessageTypes.Track, result[MessageFields.Type]); Assert.IsTrue(result.ContainsKey(MessageFields.MessageId)); Assert.IsTrue(result.ContainsKey(MessageFields.EventTimestamp)); Assert.IsTrue(result.ContainsKey(MessageFields.Context)); Assert.IsTrue(result.ContainsKey(MessageFields.Surface)); - Assert.AreEqual("level_complete", result[MessageFields.EventName]); + Assert.AreEqual(TestEventNames.LevelComplete, result[MessageFields.EventName]); } [Test] diff --git a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs index f8b02c628..07861159d 100644 --- a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs @@ -129,7 +129,7 @@ public void AnonymousId_ConsentAnonymous_ReturnsPersistedId() { ImmutableAudience.Init(MakeConfig(ConsentLevel.Anonymous)); // Track once so Identity.GetOrCreate runs and writes the id file. - ImmutableAudience.Track("warmup_event"); + ImmutableAudience.Track(TestEventNames.WarmupEvent); var id = ImmutableAudience.AnonymousId; Assert.IsFalse(string.IsNullOrEmpty(id), @@ -165,7 +165,7 @@ public void QueueSize_ZeroBeforeInit_GrowsWithEnqueue() Assert.Greater(afterInit, 0, "QueueSize should include session_start and game_launch after Init"); - ImmutableAudience.Track("explicit_track_event"); + ImmutableAudience.Track(TestEventNames.ExplicitTrackEvent); Assert.Greater(ImmutableAudience.QueueSize, afterInit, "QueueSize should grow when a new event is enqueued"); } @@ -186,7 +186,7 @@ public void ContextProvider_Set_MergesFieldsIntoEveryMessageContext() }; ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("unit_test_event"); + ImmutableAudience.Track(TestEventNames.UnitTestEvent); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); @@ -234,7 +234,7 @@ public void ContextProvider_ThrowingDelegate_SwallowsAndShipsBaseContext() ImmutableAudience.ContextProvider = () => throw new InvalidOperationException("boom"); ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("unit_test_event"); + ImmutableAudience.Track(TestEventNames.UnitTestEvent); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); @@ -250,7 +250,7 @@ public void ContextProvider_ReturnsNull_ShipsBaseContext() ImmutableAudience.ContextProvider = () => null; ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("unit_test_event"); + ImmutableAudience.Track(TestEventNames.UnitTestEvent); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); @@ -488,7 +488,7 @@ public void Track_CustomEvent_WritesEventToDisk() { ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("crafting_started", new Dictionary + ImmutableAudience.Track(TestEventNames.CraftingStarted, new Dictionary { { "recipe_id", "iron_sword" } }); @@ -511,7 +511,7 @@ public void Track_NoProperties_WritesEvent() { ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("main_menu_opened"); + ImmutableAudience.Track(TestEventNames.MainMenuOpened); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); @@ -525,7 +525,7 @@ public void Track_ConsentNone_DoesNotEnqueue() { ImmutableAudience.Init(MakeConfig(ConsentLevel.None)); - ImmutableAudience.Track("should_not_appear"); + ImmutableAudience.Track(TestEventNames.ShouldNotAppear); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); @@ -669,12 +669,12 @@ public void Reset_GeneratesNewAnonymousId() { ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("before_reset"); + ImmutableAudience.Track(TestEventNames.BeforeReset); var id1 = Identity.GetOrCreate(_testDir, ConsentLevel.Anonymous); ImmutableAudience.Reset(); - ImmutableAudience.Track("after_reset"); + ImmutableAudience.Track(TestEventNames.AfterReset); var id2 = Identity.GetOrCreate(_testDir, ConsentLevel.Anonymous); Assert.AreNotEqual(id1, id2, "Reset should generate a new anonymousId"); @@ -685,7 +685,7 @@ public void Reset_DiscardsQueuedEventsOnDisk() { ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("before_reset"); + ImmutableAudience.Track(TestEventNames.BeforeReset); ImmutableAudience.FlushQueueToDiskForTesting(); var queueDir = AudiencePaths.QueueDir(_testDir); @@ -707,7 +707,7 @@ public void SetConsent_DowngradeToNone_PurgesQueueOnDiskAndInMemory() { ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("event_under_old_consent"); + ImmutableAudience.Track(TestEventNames.EventUnderOldConsent); var queueDir = AudiencePaths.QueueDir(_testDir); // Force memory → disk so we can verify the purge wipes both layers. @@ -746,7 +746,7 @@ public void SetConsent_DowngradeToNone_DropsInFlightTrack_ThatRacesThePurge() var trackTask = Task.Run(() => { trackStarted.Set(); - ImmutableAudience.Track("racing_event"); + ImmutableAudience.Track(TestEventNames.RacingEvent); }); trackStarted.Wait(); @@ -799,7 +799,7 @@ public void SetConsent_DowngradeToNone_StressTest_NoLeak() trackers[t] = Task.Run(() => { barrier.SignalAndWait(); - ImmutableAudience.Track("race_stress"); + ImmutableAudience.Track(TestEventNames.RaceStress); }); } @@ -988,7 +988,7 @@ public void SetConsent_DowngradeToAnonymous_StressTest_NoUserIdLeak() trackers[t] = Task.Run(() => { barrier.SignalAndWait(); - ImmutableAudience.Track("race_stress"); + ImmutableAudience.Track(TestEventNames.RaceStress); }); } @@ -1275,7 +1275,7 @@ public void Track_AfterShutdown_IsIgnored() ImmutableAudience.Init(MakeConfig()); ImmutableAudience.Shutdown(); - Assert.DoesNotThrow(() => ImmutableAudience.Track("should_not_crash")); + Assert.DoesNotThrow(() => ImmutableAudience.Track(TestEventNames.ShouldNotCrash)); } [Test] @@ -1291,7 +1291,7 @@ public void Shutdown_ReleasesInitLock_BeforeBlockingTeardown() config.ShutdownFlushTimeoutMs = 10_000; ImmutableAudience.Init(config); - ImmutableAudience.Track("ensure_nonempty_queue"); + ImmutableAudience.Track(TestEventNames.EnsureNonemptyQueue); ImmutableAudience.FlushQueueToDiskForTesting(); // Phase 1 flips _initialized and releases the lock; Phase 2 enters @@ -1327,7 +1327,7 @@ public void FullToAnonymous_StripsUserIdFromQueuedTrackAndDropsIdentifyAlias() ImmutableAudience.Identify("player_steam", IdentityType.Steam); ImmutableAudience.Alias("player_steam", IdentityType.Steam, "player_passport", IdentityType.Passport); - ImmutableAudience.Track("tracked_before_downgrade"); + ImmutableAudience.Track(TestEventNames.TrackedBeforeDowngrade); ImmutableAudience.FlushQueueToDiskForTesting(); @@ -1354,7 +1354,7 @@ public void FullToAnonymous_FutureTracksOmitUserId() ImmutableAudience.Identify("player_steam", IdentityType.Steam); ImmutableAudience.SetConsent(ConsentLevel.Anonymous); - ImmutableAudience.Track("tracked_after_downgrade"); + ImmutableAudience.Track(TestEventNames.TrackedAfterDowngrade); ImmutableAudience.FlushQueueToDiskForTesting(); var queueDir = AudiencePaths.QueueDir(_testDir); @@ -1362,7 +1362,7 @@ public void FullToAnonymous_FutureTracksOmitUserId() .Select(f => JsonReader.DeserializeObject(File.ReadAllText(f))) .Where(m => (string)m[MessageFields.Type] == MessageTypes.Track && m.ContainsKey(MessageFields.EventName) - && (string)m[MessageFields.EventName] == "tracked_after_downgrade") + && (string)m[MessageFields.EventName] == TestEventNames.TrackedAfterDowngrade) .ToList(); Assert.AreEqual(1, trackFiles.Count); @@ -1382,7 +1382,7 @@ public void SendBatch_ConcurrentTicks_OnlyOneReachesTransport() config.HttpHandler = handler; ImmutableAudience.Init(config); - ImmutableAudience.Track("event_to_send"); + ImmutableAudience.Track(TestEventNames.EventToSend); ImmutableAudience.FlushQueueToDiskForTesting(); // Kick off one SendBatch on a worker. It will block inside the @@ -1417,7 +1417,7 @@ public void FlushAsync_ConcurrentCallers_OnlyOneReachesTransport() config.HttpHandler = handler; ImmutableAudience.Init(config); - ImmutableAudience.Track("event_to_send"); + ImmutableAudience.Track(TestEventNames.EventToSend); ImmutableAudience.FlushQueueToDiskForTesting(); // First caller enters SendAsync and blocks on handler.Release. @@ -1453,7 +1453,7 @@ public async Task FlushAsync_CancelledToken_Terminates_DoesNotHotLoop() config.HttpHandler = handler; ImmutableAudience.Init(config); - ImmutableAudience.Track("event_to_send"); + ImmutableAudience.Track(TestEventNames.EventToSend); ImmutableAudience.FlushQueueToDiskForTesting(); using var cts = new CancellationTokenSource(); diff --git a/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs b/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs index b7db80763..7da31a416 100644 --- a/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs @@ -93,7 +93,7 @@ public void Shutdown_DiskWritesBlocked_DoesNotThrow() // Shutdown is invoked from app-quit handlers; an exception would // crash the process. ImmutableAudience.Init(MakeConfig()); - ImmutableAudience.Track("event_pre_block"); + ImmutableAudience.Track(TestEventNames.EventPreBlock); BlockDiskWrites(); for (int i = 0; i < 20; i++) ImmutableAudience.Track($"blocked_{i}"); diff --git a/src/Packages/Audience/Tests/Runtime/PublishableKeyPrefixTests.cs b/src/Packages/Audience/Tests/Runtime/PublishableKeyPrefixTests.cs index 8c45c879d..c59b1ad56 100644 --- a/src/Packages/Audience/Tests/Runtime/PublishableKeyPrefixTests.cs +++ b/src/Packages/Audience/Tests/Runtime/PublishableKeyPrefixTests.cs @@ -180,7 +180,7 @@ public async Task Track_BackendReturns401_SurfacesValidationRejected() config.OnError = errors.Add; ImmutableAudience.Init(config); - ImmutableAudience.Track("event_against_prod_with_test_key"); + ImmutableAudience.Track(TestEventNames.EventAgainstProdWithTestKey); await ImmutableAudience.FlushAsync(); Assert.IsTrue(errors.Any(e => e.Code == AudienceErrorCode.ValidationRejected), diff --git a/src/Packages/Audience/Tests/Runtime/TestEventNames.cs b/src/Packages/Audience/Tests/Runtime/TestEventNames.cs new file mode 100644 index 000000000..657c18926 --- /dev/null +++ b/src/Packages/Audience/Tests/Runtime/TestEventNames.cs @@ -0,0 +1,30 @@ +namespace Immutable.Audience.Tests +{ + // Per-test event names. Each names what the test measures. + internal static class TestEventNames + { + internal const string Warmup = "warmup"; + internal const string WarmupEvent = "warmup_event"; + internal const string ExplicitTrackEvent = "explicit_track_event"; + internal const string UnitTestEvent = "unit_test_event"; + internal const string CraftingStarted = "crafting_started"; + internal const string MainMenuOpened = "main_menu_opened"; + internal const string ShouldNotAppear = "should_not_appear"; + internal const string BeforeReset = "before_reset"; + internal const string AfterReset = "after_reset"; + internal const string EventUnderOldConsent = "event_under_old_consent"; + internal const string RacingEvent = "racing_event"; + internal const string RaceStress = "race_stress"; + internal const string ShouldNotCrash = "should_not_crash"; + internal const string EnsureNonemptyQueue = "ensure_nonempty_queue"; + internal const string TrackedBeforeDowngrade = "tracked_before_downgrade"; + internal const string TrackedAfterDowngrade = "tracked_after_downgrade"; + internal const string EventToSend = "event_to_send"; + internal const string EventPreBlock = "event_pre_block"; + internal const string EventAgainstProdWithTestKey = "event_against_prod_with_test_key"; + internal const string StressTrack = "stress_track"; + internal const string MixedLoadTrack = "mixed_load_track"; + internal const string SteadyState = "steady_state"; + internal const string LevelComplete = "level_complete"; + } +} diff --git a/src/Packages/Audience/Tests/Runtime/ThreadSafetyStressTests.cs b/src/Packages/Audience/Tests/Runtime/ThreadSafetyStressTests.cs index 2a205706f..984a905a7 100644 --- a/src/Packages/Audience/Tests/Runtime/ThreadSafetyStressTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ThreadSafetyStressTests.cs @@ -92,7 +92,7 @@ private void RunSustainedTrackLoad(int threadCount, int durationSeconds) int count = 0; while (DateTime.UtcNow < deadline) { - ImmutableAudience.Track("stress_track"); + ImmutableAudience.Track(TestEventNames.StressTrack); count++; } firedPerThread[idx] = count; @@ -155,7 +155,7 @@ public void TrackIdentifySetConsent_ConcurrentLoad_NoRaceExceptions() { barrier.SignalAndWait(); while (DateTime.UtcNow < deadline) - ImmutableAudience.Track("mixed_load_track"); + ImmutableAudience.Track(TestEventNames.MixedLoadTrack); } catch (Exception ex) { exceptions.Add(ex); } }); @@ -216,7 +216,7 @@ public void Track_SteadyState_BoundedMainThreadAllocation() ImmutableAudience.Init(MakeConfig()); // Warm up so JIT and one-time allocations are out of the measured window. - for (int i = 0; i < 200; i++) ImmutableAudience.Track("warmup"); + for (int i = 0; i < 200; i++) ImmutableAudience.Track(TestEventNames.Warmup); ImmutableAudience.FlushQueueToDiskForTesting(); GC.Collect(); GC.WaitForPendingFinalizers(); @@ -226,7 +226,7 @@ public void Track_SteadyState_BoundedMainThreadAllocation() const int iterations = 10_000; for (int i = 0; i < iterations; i++) - ImmutableAudience.Track("steady_state"); + ImmutableAudience.Track(TestEventNames.SteadyState); long allocDelta = GC.GetAllocatedBytesForCurrentThread() - allocBefore; double bytesPerCall = (double)allocDelta / iterations; diff --git a/src/Packages/Audience/Tests/Runtime/Utility/GzipTests.cs b/src/Packages/Audience/Tests/Runtime/Utility/GzipTests.cs index 28430646f..efc40110a 100644 --- a/src/Packages/Audience/Tests/Runtime/Utility/GzipTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Utility/GzipTests.cs @@ -34,7 +34,7 @@ public void Compress_OutputIsSmallerThanInput_ForRealisticPayload() { if (i > 0) sb.Append(','); sb.Append(WireFixture.Track( - (MessageFields.EventName, "level_complete"), + (MessageFields.EventName, TestEventNames.LevelComplete), (MessageFields.AnonymousId, $"anon-{i}"))); } diff --git a/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs b/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs index a3abe358e..b8f9c4524 100644 --- a/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs @@ -182,7 +182,7 @@ public void Serialize_RealisticEventPayload_ProducesCorrectJson() var data = new Dictionary { { MessageFields.Type, MessageTypes.Track }, - { MessageFields.EventName, "level_complete" }, + { MessageFields.EventName, TestEventNames.LevelComplete }, { MessageFields.AnonymousId, "anon-123" }, { MessageFields.UserId, null }, { MessageFields.Properties, new Dictionary From 6a1b88eb028f03fdf6b27258506809ac46bba20c Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:43:38 +1000 Subject: [PATCH 08/23] refactor(audience-sample): centralise custom demo event names in SampleAppCustomEvents The sample-app demo catalogue (sign_up, sign_in, email_acquired, wishlist_add, wishlist_remove, game_page_viewed, link_clicked, screen_viewed) had its event-name strings inline at every call site. A rename touches the catalogue, the screen_viewed Track call, and each Unity live-fire test that drives the catalogue UI. Move the eight names into a new internal SampleAppCustomEvents class under the sample-app Scripts assembly. Update AudienceSample.Events.cs, AudienceSample.cs (screen_viewed Track), and SampleAppLiveFireTests.cs (button and field lookups by event name) to reference the constants. Per the user's "everything random goes in a constant" stance, applied against the previous session's recommendation that the demo content read better inline. Recording the override on the user's explicit request. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Scripts/AudienceSample.Events.cs | 14 ++++++------ .../SampleApp/Scripts/AudienceSample.cs | 2 +- .../Scripts/SampleAppCustomEvents.cs | 15 +++++++++++++ .../Scripts/SampleAppCustomEvents.cs.meta | 11 ++++++++++ .../Tests/Runtime/SampleAppLiveFireTests.cs | 22 +++++++++---------- 5 files changed, 45 insertions(+), 19 deletions(-) create mode 100644 examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs create mode 100644 examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs.meta diff --git a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.Events.cs b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.Events.cs index 455b3fa2a..f698ad150 100644 --- a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.Events.cs +++ b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.Events.cs @@ -55,15 +55,15 @@ internal readonly struct EventSpec internal static readonly EventSpec[] Catalogue = { - new EventSpec("sign_up", new[] { EventField.Text("method", optional: true) }), - new EventSpec("sign_in", new[] { EventField.Text("method", optional: true) }), - new EventSpec("email_acquired", new[] { EventField.Text("source", optional: true) }), - new EventSpec("wishlist_add", new[] { + new EventSpec(SampleAppCustomEvents.SignUp, new[] { EventField.Text("method", optional: true) }), + new EventSpec(SampleAppCustomEvents.SignIn, new[] { EventField.Text("method", optional: true) }), + new EventSpec(SampleAppCustomEvents.EmailAcquired, new[] { EventField.Text("source", optional: true) }), + new EventSpec(SampleAppCustomEvents.WishlistAdd, new[] { EventField.Text("gameId"), EventField.Text("source", optional: true), EventField.Text("platform", optional: true), }), - new EventSpec("wishlist_remove", new[] { EventField.Text("gameId") }), + new EventSpec(SampleAppCustomEvents.WishlistRemove, new[] { EventField.Text("gameId") }), new EventSpec(EventNames.Purchase, new[] { EventField.Text(EventPropertyKeys.Currency), EventField.Number(EventPropertyKeys.Value), @@ -91,12 +91,12 @@ internal readonly struct EventSpec EventField.Text(EventPropertyKeys.ItemId, optional: true), }), new EventSpec(EventNames.MilestoneReached, new[] { EventField.Text(EventPropertyKeys.Name) }), - new EventSpec("game_page_viewed", new[] { + new EventSpec(SampleAppCustomEvents.GamePageViewed, new[] { EventField.Text("gameId"), EventField.Text("gameName", optional: true), EventField.Text("slug", optional: true), }), - new EventSpec("link_clicked", new[] { + new EventSpec(SampleAppCustomEvents.LinkClicked, new[] { EventField.Text("url"), EventField.Text("label", optional: true), EventField.Text("source", optional: true), diff --git a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs index 4f6564bb2..e6c97a193 100644 --- a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs +++ b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs @@ -111,7 +111,7 @@ private void OnPage() => RunAndLog(SampleAppUi.LogLabels.Page, () => GuardConsentForTrack(); var screen = SceneManager.GetActiveScene().name; var props = new Dictionary { ["path"] = screen }; - ImmutableAudience.Track("screen_viewed", props); + ImmutableAudience.Track(SampleAppCustomEvents.ScreenViewed, props); return Json.Serialize(props, 2); }); diff --git a/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs b/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs new file mode 100644 index 000000000..b01a722e4 --- /dev/null +++ b/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs @@ -0,0 +1,15 @@ +namespace Immutable.Audience.Samples.SampleApp +{ + // Sample-only event names that demonstrate the custom Track pattern. + internal static class SampleAppCustomEvents + { + internal const string SignUp = "sign_up"; + internal const string SignIn = "sign_in"; + internal const string EmailAcquired = "email_acquired"; + internal const string WishlistAdd = "wishlist_add"; + internal const string WishlistRemove = "wishlist_remove"; + internal const string GamePageViewed = "game_page_viewed"; + internal const string LinkClicked = "link_clicked"; + internal const string ScreenViewed = "screen_viewed"; + } +} diff --git a/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs.meta b/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs.meta new file mode 100644 index 000000000..96534b02e --- /dev/null +++ b/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6dec0a319b2b488a9c964a046bb410b2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs index 5a8cd50d7..673867acb 100644 --- a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs @@ -403,54 +403,54 @@ public IEnumerator IdentifyTraits_AfterIdentify_FlushReportsOk() [UnityTest] public IEnumerator TypedEvent_SignUp_FlushReportsOk() { - yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("sign_up")); + yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.SignUp)); } [UnityTest] public IEnumerator TypedEvent_SignIn_FlushReportsOk() { - yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("sign_in")); + yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.SignIn)); } [UnityTest] public IEnumerator TypedEvent_EmailAcquired_FlushReportsOk() { - yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("email_acquired")); + yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.EmailAcquired)); } [UnityTest] public IEnumerator TypedEvent_WishlistAdd_FlushReportsOk() { - yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("wishlist_add"), root => + yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.WishlistAdd), root => { - root.Q(SampleAppUi.TypedEventField("wishlist_add", "gameId")).value = "il2cpp_game_1"; + root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.WishlistAdd, "gameId")).value = "il2cpp_game_1"; }); } [UnityTest] public IEnumerator TypedEvent_WishlistRemove_FlushReportsOk() { - yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("wishlist_remove"), root => + yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.WishlistRemove), root => { - root.Q(SampleAppUi.TypedEventField("wishlist_remove", "gameId")).value = "il2cpp_game_1"; + root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.WishlistRemove, "gameId")).value = "il2cpp_game_1"; }); } [UnityTest] public IEnumerator TypedEvent_GamePageViewed_FlushReportsOk() { - yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("game_page_viewed"), root => + yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.GamePageViewed), root => { - root.Q(SampleAppUi.TypedEventField("game_page_viewed", "gameId")).value = "il2cpp_game_1"; + root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.GamePageViewed, "gameId")).value = "il2cpp_game_1"; }); } [UnityTest] public IEnumerator TypedEvent_LinkClicked_FlushReportsOk() { - yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("link_clicked"), root => + yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.LinkClicked), root => { - root.Q(SampleAppUi.TypedEventField("link_clicked", "url")).value = "https://example.com/il2cpp"; + root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.LinkClicked, "url")).value = "https://example.com/il2cpp"; }); } From 014fb252bc84c3c8a3ad730956f1e6a121c7730b Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:45:44 +1000 Subject: [PATCH 09/23] refactor(audience-sample): centralise custom demo property keys in SampleAppCustomEventPropertyKeys The sample-app demo catalogue, screen_viewed Track call, and the typed-event live-fire tests had property keys (method, source, gameId, platform, gameName, slug, url, label, path) inline at every call site. Each key duplicates between the catalogue's EventField definition and the live-fire test's UI lookup. Add SampleAppCustomEventPropertyKeys alongside SampleAppCustomEvents (mirrors the SDK's EventPropertyKeys naming) and reference the constants from AudienceSample.Events.cs, AudienceSample.cs (the screen_viewed props dictionary), and SampleAppLiveFireTests.cs (the TypedEventField field-name lookups). Includes gameName and slug beyond the user's listed seven keys: same inline-property-key category, same migration treatment. Per the user's "everything random goes in a constant" stance, applied against the previous session's recommendation that the demo content read better inline. Recording the override on the user's explicit request. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Scripts/AudienceSample.Events.cs | 28 +++++++++---------- .../SampleApp/Scripts/AudienceSample.cs | 2 +- .../Scripts/SampleAppCustomEvents.cs | 14 ++++++++++ .../Tests/Runtime/SampleAppLiveFireTests.cs | 8 +++--- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.Events.cs b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.Events.cs index f698ad150..80265e0e0 100644 --- a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.Events.cs +++ b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.Events.cs @@ -55,15 +55,15 @@ internal readonly struct EventSpec internal static readonly EventSpec[] Catalogue = { - new EventSpec(SampleAppCustomEvents.SignUp, new[] { EventField.Text("method", optional: true) }), - new EventSpec(SampleAppCustomEvents.SignIn, new[] { EventField.Text("method", optional: true) }), - new EventSpec(SampleAppCustomEvents.EmailAcquired, new[] { EventField.Text("source", optional: true) }), + new EventSpec(SampleAppCustomEvents.SignUp, new[] { EventField.Text(SampleAppCustomEventPropertyKeys.Method, optional: true) }), + new EventSpec(SampleAppCustomEvents.SignIn, new[] { EventField.Text(SampleAppCustomEventPropertyKeys.Method, optional: true) }), + new EventSpec(SampleAppCustomEvents.EmailAcquired, new[] { EventField.Text(SampleAppCustomEventPropertyKeys.Source, optional: true) }), new EventSpec(SampleAppCustomEvents.WishlistAdd, new[] { - EventField.Text("gameId"), - EventField.Text("source", optional: true), - EventField.Text("platform", optional: true), + EventField.Text(SampleAppCustomEventPropertyKeys.GameId), + EventField.Text(SampleAppCustomEventPropertyKeys.Source, optional: true), + EventField.Text(SampleAppCustomEventPropertyKeys.Platform, optional: true), }), - new EventSpec(SampleAppCustomEvents.WishlistRemove, new[] { EventField.Text("gameId") }), + new EventSpec(SampleAppCustomEvents.WishlistRemove, new[] { EventField.Text(SampleAppCustomEventPropertyKeys.GameId) }), new EventSpec(EventNames.Purchase, new[] { EventField.Text(EventPropertyKeys.Currency), EventField.Number(EventPropertyKeys.Value), @@ -92,15 +92,15 @@ internal readonly struct EventSpec }), new EventSpec(EventNames.MilestoneReached, new[] { EventField.Text(EventPropertyKeys.Name) }), new EventSpec(SampleAppCustomEvents.GamePageViewed, new[] { - EventField.Text("gameId"), - EventField.Text("gameName", optional: true), - EventField.Text("slug", optional: true), + EventField.Text(SampleAppCustomEventPropertyKeys.GameId), + EventField.Text(SampleAppCustomEventPropertyKeys.GameName, optional: true), + EventField.Text(SampleAppCustomEventPropertyKeys.Slug, optional: true), }), new EventSpec(SampleAppCustomEvents.LinkClicked, new[] { - EventField.Text("url"), - EventField.Text("label", optional: true), - EventField.Text("source", optional: true), - EventField.Text("gameId", optional: true), + EventField.Text(SampleAppCustomEventPropertyKeys.Url), + EventField.Text(SampleAppCustomEventPropertyKeys.Label, optional: true), + EventField.Text(SampleAppCustomEventPropertyKeys.Source, optional: true), + EventField.Text(SampleAppCustomEventPropertyKeys.GameId, optional: true), }), }; diff --git a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs index e6c97a193..6a16d7626 100644 --- a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs +++ b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs @@ -110,7 +110,7 @@ private void OnPage() => RunAndLog(SampleAppUi.LogLabels.Page, () => { GuardConsentForTrack(); var screen = SceneManager.GetActiveScene().name; - var props = new Dictionary { ["path"] = screen }; + var props = new Dictionary { [SampleAppCustomEventPropertyKeys.Path] = screen }; ImmutableAudience.Track(SampleAppCustomEvents.ScreenViewed, props); return Json.Serialize(props, 2); }); diff --git a/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs b/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs index b01a722e4..9dd3c5182 100644 --- a/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs +++ b/examples/audience/Assets/SampleApp/Scripts/SampleAppCustomEvents.cs @@ -12,4 +12,18 @@ internal static class SampleAppCustomEvents internal const string LinkClicked = "link_clicked"; internal const string ScreenViewed = "screen_viewed"; } + + // Property keys for the sample-app demo events. + internal static class SampleAppCustomEventPropertyKeys + { + internal const string Method = "method"; + internal const string Source = "source"; + internal const string GameId = "gameId"; + internal const string Platform = "platform"; + internal const string GameName = "gameName"; + internal const string Slug = "slug"; + internal const string Url = "url"; + internal const string Label = "label"; + internal const string Path = "path"; + } } diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs index 673867acb..e2e7259fd 100644 --- a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs @@ -423,7 +423,7 @@ public IEnumerator TypedEvent_WishlistAdd_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.WishlistAdd), root => { - root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.WishlistAdd, "gameId")).value = "il2cpp_game_1"; + root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.WishlistAdd, SampleAppCustomEventPropertyKeys.GameId)).value = "il2cpp_game_1"; }); } @@ -432,7 +432,7 @@ public IEnumerator TypedEvent_WishlistRemove_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.WishlistRemove), root => { - root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.WishlistRemove, "gameId")).value = "il2cpp_game_1"; + root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.WishlistRemove, SampleAppCustomEventPropertyKeys.GameId)).value = "il2cpp_game_1"; }); } @@ -441,7 +441,7 @@ public IEnumerator TypedEvent_GamePageViewed_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.GamePageViewed), root => { - root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.GamePageViewed, "gameId")).value = "il2cpp_game_1"; + root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.GamePageViewed, SampleAppCustomEventPropertyKeys.GameId)).value = "il2cpp_game_1"; }); } @@ -450,7 +450,7 @@ public IEnumerator TypedEvent_LinkClicked_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.LinkClicked), root => { - root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.LinkClicked, "url")).value = "https://example.com/il2cpp"; + root.Q(SampleAppUi.TypedEventField(SampleAppCustomEvents.LinkClicked, SampleAppCustomEventPropertyKeys.Url)).value = "https://example.com/il2cpp"; }); } From d0be68f2a98bac450af8cae8e7330bc241548a1a Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:51:30 +1000 Subject: [PATCH 10/23] test(audience-sdk): centralise per-test fixture data values in TestFixtures The SDK test suite repeated the same identifier and payload literals across MessageBuilderTests, TypedEventTests, JsonTests, ImmutableAudienceTests, and DeleteDataTests. The pairs that appeared both as inputs and as assertion targets were the worst offenders (anon-42, user-42, user-99, from-id, to-id) since a typo flipping one side would silently pass on the other. Add TestFixtures alongside TestDefaults (which already centralises config defaults) and reference TestFixtures.X across the five files. Constants cover anonymous IDs (1, 42, 123), user IDs (42, 99), alias endpoints (from / to), identity values keyed to IdentityType.Custom / Steam / Passport (player-42 / player_steam / player_passport), Resource event payload values (gold currency, quest_reward itemType, main_quest_01 itemId), Purchase event payload values (gem_pack_01 itemId, Starter Gem Pack itemName, txn_abc123 transactionId), and the MilestoneReached name (first_boss_defeated). Includes main_quest_01, player_passport, anon-123, and first_boss_defeated beyond the user's listed values: same per-fixture data category, same migration treatment. tutorial_complete from the user's list does not appear in the test suite (the milestone fixture uses first_boss_defeated); skipped on that basis. Per the user's "everything random goes in a constant" stance, applied against the previous session's recommendation that scenario descriptors read better inline. Recording the override on the user's explicit request. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Audience/Tests/Runtime/DeleteDataTests.cs | 8 ++-- .../Runtime/Events/MessageBuilderTests.cs | 22 +++++------ .../Tests/Runtime/Events/TypedEventTests.cs | 32 ++++++++-------- .../Tests/Runtime/ImmutableAudienceTests.cs | 12 +++--- .../Audience/Tests/Runtime/TestFixtures.cs | 37 +++++++++++++++++++ .../Tests/Runtime/TestFixtures.cs.meta | 11 ++++++ .../Tests/Runtime/Utility/JsonTests.cs | 2 +- 7 files changed, 86 insertions(+), 38 deletions(-) create mode 100644 src/Packages/Audience/Tests/Runtime/TestFixtures.cs create mode 100644 src/Packages/Audience/Tests/Runtime/TestFixtures.cs.meta diff --git a/src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs b/src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs index 0daa64807..831f947c1 100644 --- a/src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs +++ b/src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs @@ -74,7 +74,7 @@ public void DeleteData_WithUserId_FiresDelete_WithUserIdQuery() var handler = new CapturingHandler(); ImmutableAudience.Init(MakeConfig(handler)); - ImmutableAudience.DeleteData(userId: "player-42"); + ImmutableAudience.DeleteData(userId: TestFixtures.PlayerCustomId); WaitForRequest(handler); // Filter out the game_launch POST from Init. @@ -151,7 +151,7 @@ public async Task DeleteData_ReturnsTask_ThatCompletesAfterRequest() var handler = new CapturingHandler(); ImmutableAudience.Init(MakeConfig(handler)); - var task = ImmutableAudience.DeleteData(userId: "player-42"); + var task = ImmutableAudience.DeleteData(userId: TestFixtures.PlayerCustomId); Assert.IsNotNull(task, "DeleteData must return a non-null Task"); // Await directly: no need for the RequestSent gate when the task @@ -168,7 +168,7 @@ public void DeleteData_BeforeInit_ReturnsCompletedTask() // Not initialised: must not throw, must return a completed Task. ImmutableAudience.ResetState(); - var task = ImmutableAudience.DeleteData(userId: "player-42"); + var task = ImmutableAudience.DeleteData(userId: TestFixtures.PlayerCustomId); Assert.IsNotNull(task); Assert.IsTrue(task.IsCompleted, "DeleteData before Init must return an already-completed Task"); @@ -189,7 +189,7 @@ public void DeleteData_ServerError_InvokesOnError() }; ImmutableAudience.Init(config); - ImmutableAudience.DeleteData(userId: "player-42"); + ImmutableAudience.DeleteData(userId: TestFixtures.PlayerCustomId); Assert.IsTrue(received.Wait(TimeSpan.FromSeconds(5)), "OnError should fire when DeleteData's response is non-2xx"); diff --git a/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs b/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs index 593f28d0a..4bd110ee6 100644 --- a/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs @@ -13,7 +13,7 @@ public class MessageBuilderTests [Test] public void Track_RequiredFieldsPresent() { - var result = MessageBuilder.Track(TestEventNames.LevelComplete, "anon-1", null, PackageVersion); + var result = MessageBuilder.Track(TestEventNames.LevelComplete, TestFixtures.AnonId1, null, PackageVersion); Assert.AreEqual(MessageTypes.Track, result[MessageFields.Type]); Assert.IsTrue(result.ContainsKey(MessageFields.MessageId)); @@ -36,7 +36,7 @@ public void Track_EventNameLongerThan256Chars_TruncatedTo256() [Test] public void Track_NullUserId_NotPresentInDict() { - var result = MessageBuilder.Track("evt", "anon-1", null, PackageVersion); + var result = MessageBuilder.Track("evt", TestFixtures.AnonId1, null, PackageVersion); Assert.IsFalse(result.ContainsKey(MessageFields.UserId)); } @@ -44,20 +44,20 @@ public void Track_NullUserId_NotPresentInDict() [Test] public void Track_NonNullUserId_PresentInDict() { - var result = MessageBuilder.Track("evt", "anon-1", "user-99", PackageVersion); + var result = MessageBuilder.Track("evt", TestFixtures.AnonId1, TestFixtures.UserId99, PackageVersion); Assert.IsTrue(result.ContainsKey(MessageFields.UserId)); - Assert.AreEqual("user-99", result[MessageFields.UserId]); + Assert.AreEqual(TestFixtures.UserId99, result[MessageFields.UserId]); } [Test] public void Identify_TypeAndIdentityFieldsPresent() { - var result = MessageBuilder.Identify("anon-42", "user-42", IdentityType.Steam.ToLowercaseString(), PackageVersion); + var result = MessageBuilder.Identify(TestFixtures.AnonId42, TestFixtures.UserId42, IdentityType.Steam.ToLowercaseString(), PackageVersion); Assert.AreEqual(MessageTypes.Identify, result[MessageFields.Type]); - Assert.AreEqual("anon-42", result[MessageFields.AnonymousId]); - Assert.AreEqual("user-42", result[MessageFields.UserId]); + Assert.AreEqual(TestFixtures.AnonId42, result[MessageFields.AnonymousId]); + Assert.AreEqual(TestFixtures.UserId42, result[MessageFields.UserId]); Assert.AreEqual(IdentityType.Steam.ToLowercaseString(), result[MessageFields.IdentityType]); } @@ -65,14 +65,14 @@ public void Identify_TypeAndIdentityFieldsPresent() public void Alias_AllFourFieldsPresent() { var result = MessageBuilder.Alias( - "from-id", IdentityType.Email.ToLowercaseString(), - "to-id", IdentityType.Steam.ToLowercaseString(), + TestFixtures.AliasFromId, IdentityType.Email.ToLowercaseString(), + TestFixtures.AliasToId, IdentityType.Steam.ToLowercaseString(), PackageVersion); Assert.AreEqual(MessageTypes.Alias, result[MessageFields.Type]); - Assert.AreEqual("from-id", result[MessageFields.FromId]); + Assert.AreEqual(TestFixtures.AliasFromId, result[MessageFields.FromId]); Assert.AreEqual(IdentityType.Email.ToLowercaseString(), result[MessageFields.FromType]); - Assert.AreEqual("to-id", result[MessageFields.ToId]); + Assert.AreEqual(TestFixtures.AliasToId, result[MessageFields.ToId]); Assert.AreEqual(IdentityType.Steam.ToLowercaseString(), result[MessageFields.ToType]); } diff --git a/src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs b/src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs index 82318087b..681186416 100644 --- a/src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs @@ -61,19 +61,19 @@ public void Resource_Source_ProducesCorrectProperties() var evt = new Resource { Flow = ResourceFlow.Source, - Currency = "gold", + Currency = TestFixtures.ResourceCurrency, Amount = 100, - ItemType = "quest_reward", - ItemId = "main_quest_01" + ItemType = TestFixtures.ResourceItemType, + ItemId = TestFixtures.ResourceItemId }; var props = evt.ToProperties(); Assert.AreEqual(ResourceFlow.Source.ToLowercaseString(), props[EventPropertyKeys.Flow]); - Assert.AreEqual("gold", props[EventPropertyKeys.Currency]); + Assert.AreEqual(TestFixtures.ResourceCurrency, props[EventPropertyKeys.Currency]); Assert.AreEqual(100m, props[EventPropertyKeys.Amount]); - Assert.AreEqual("quest_reward", props[EventPropertyKeys.ItemType]); - Assert.AreEqual("main_quest_01", props[EventPropertyKeys.ItemId]); + Assert.AreEqual(TestFixtures.ResourceItemType, props[EventPropertyKeys.ItemType]); + Assert.AreEqual(TestFixtures.ResourceItemId, props[EventPropertyKeys.ItemId]); } [Test] @@ -85,7 +85,7 @@ public void Resource_EventName_IsResource() [Test] public void Resource_WithoutFlow_ThrowsOnToProperties() { - var evt = new Resource { Currency = "gold", Amount = 100 }; + var evt = new Resource { Currency = TestFixtures.ResourceCurrency, Amount = 100 }; var ex = Assert.Throws(() => evt.ToProperties()); Assert.That(ex!.Message, Does.Contain("Flow")); @@ -103,7 +103,7 @@ public void Resource_WithoutCurrency_ThrowsOnToProperties() [Test] public void Resource_WithoutAmount_ThrowsOnToProperties() { - var evt = new Resource { Flow = ResourceFlow.Source, Currency = "gold" }; + var evt = new Resource { Flow = ResourceFlow.Source, Currency = TestFixtures.ResourceCurrency }; var ex = Assert.Throws(() => evt.ToProperties()); Assert.That(ex!.Message, Does.Contain("Amount")); @@ -116,20 +116,20 @@ public void Purchase_ProducesCorrectProperties() { Currency = "USD", Value = 9.99m, - ItemId = "gem_pack_01", - ItemName = "Starter Gem Pack", + ItemId = TestFixtures.PurchaseItemId, + ItemName = TestFixtures.PurchaseItemName, Quantity = 1, - TransactionId = "txn_abc123" + TransactionId = TestFixtures.PurchaseTransactionId }; var props = evt.ToProperties(); Assert.AreEqual("USD", props[EventPropertyKeys.Currency]); Assert.AreEqual(9.99m, props[EventPropertyKeys.Value]); - Assert.AreEqual("gem_pack_01", props[EventPropertyKeys.ItemId]); - Assert.AreEqual("Starter Gem Pack", props[EventPropertyKeys.ItemName]); + Assert.AreEqual(TestFixtures.PurchaseItemId, props[EventPropertyKeys.ItemId]); + Assert.AreEqual(TestFixtures.PurchaseItemName, props[EventPropertyKeys.ItemName]); Assert.AreEqual(1, props[EventPropertyKeys.Quantity]); - Assert.AreEqual("txn_abc123", props[EventPropertyKeys.TransactionId]); + Assert.AreEqual(TestFixtures.PurchaseTransactionId, props[EventPropertyKeys.TransactionId]); } [Test] @@ -172,9 +172,9 @@ public void Purchase_WithoutValue_ThrowsOnToProperties() [Test] public void MilestoneReached_ProducesCorrectProperties() { - var props = new MilestoneReached { Name = "first_boss_defeated" }.ToProperties(); + var props = new MilestoneReached { Name = TestFixtures.MilestoneName }.ToProperties(); - Assert.AreEqual("first_boss_defeated", props[EventPropertyKeys.Name]); + Assert.AreEqual(TestFixtures.MilestoneName, props[EventPropertyKeys.Name]); Assert.AreEqual(1, props.Count); } diff --git a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs index 07861159d..f8aefd166 100644 --- a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs @@ -105,8 +105,8 @@ public void UserId_AfterIdentifyAndReset_TracksState() Assert.IsNull(ImmutableAudience.UserId, "UserId should be null until Identify is called"); - ImmutableAudience.Identify("player-42", IdentityType.Custom); - Assert.AreEqual("player-42", ImmutableAudience.UserId, + ImmutableAudience.Identify(TestFixtures.PlayerCustomId, IdentityType.Custom); + Assert.AreEqual(TestFixtures.PlayerCustomId, ImmutableAudience.UserId, "UserId must reflect the most recent Identify call"); ImmutableAudience.Reset(); @@ -215,7 +215,7 @@ public void ContextProvider_Set_MergesOnIdentifyPath() }; ImmutableAudience.Init(MakeConfig(ConsentLevel.Full)); - ImmutableAudience.Identify("player-42", IdentityType.Custom); + ImmutableAudience.Identify(TestFixtures.PlayerCustomId, IdentityType.Custom); ImmutableAudience.Shutdown(); var queueDir = AudiencePaths.QueueDir(_testDir); @@ -1325,8 +1325,8 @@ public void FullToAnonymous_StripsUserIdFromQueuedTrackAndDropsIdentifyAlias() { ImmutableAudience.Init(MakeConfig(ConsentLevel.Full)); - ImmutableAudience.Identify("player_steam", IdentityType.Steam); - ImmutableAudience.Alias("player_steam", IdentityType.Steam, "player_passport", IdentityType.Passport); + ImmutableAudience.Identify(TestFixtures.PlayerSteamId, IdentityType.Steam); + ImmutableAudience.Alias(TestFixtures.PlayerSteamId, IdentityType.Steam, TestFixtures.PlayerPassportId, IdentityType.Passport); ImmutableAudience.Track(TestEventNames.TrackedBeforeDowngrade); ImmutableAudience.FlushQueueToDiskForTesting(); @@ -1351,7 +1351,7 @@ public void FullToAnonymous_StripsUserIdFromQueuedTrackAndDropsIdentifyAlias() public void FullToAnonymous_FutureTracksOmitUserId() { ImmutableAudience.Init(MakeConfig(ConsentLevel.Full)); - ImmutableAudience.Identify("player_steam", IdentityType.Steam); + ImmutableAudience.Identify(TestFixtures.PlayerSteamId, IdentityType.Steam); ImmutableAudience.SetConsent(ConsentLevel.Anonymous); ImmutableAudience.Track(TestEventNames.TrackedAfterDowngrade); diff --git a/src/Packages/Audience/Tests/Runtime/TestFixtures.cs b/src/Packages/Audience/Tests/Runtime/TestFixtures.cs new file mode 100644 index 000000000..d2f001f77 --- /dev/null +++ b/src/Packages/Audience/Tests/Runtime/TestFixtures.cs @@ -0,0 +1,37 @@ +namespace Immutable.Audience.Tests +{ + // Per-fixture data values shared across the SDK test suite. + internal static class TestFixtures + { + // Anonymous IDs + internal const string AnonId1 = "anon-1"; + internal const string AnonId42 = "anon-42"; + internal const string AnonId123 = "anon-123"; + + // User IDs + internal const string UserId42 = "user-42"; + internal const string UserId99 = "user-99"; + + // Alias endpoints + internal const string AliasFromId = "from-id"; + internal const string AliasToId = "to-id"; + + // Identity values keyed to IdentityType.Custom / Steam / Passport + internal const string PlayerCustomId = "player-42"; + internal const string PlayerSteamId = "player_steam"; + internal const string PlayerPassportId = "player_passport"; + + // Resource event payload values + internal const string ResourceCurrency = "gold"; + internal const string ResourceItemType = "quest_reward"; + internal const string ResourceItemId = "main_quest_01"; + + // Purchase event payload values + internal const string PurchaseItemId = "gem_pack_01"; + internal const string PurchaseItemName = "Starter Gem Pack"; + internal const string PurchaseTransactionId = "txn_abc123"; + + // MilestoneReached event payload values + internal const string MilestoneName = "first_boss_defeated"; + } +} diff --git a/src/Packages/Audience/Tests/Runtime/TestFixtures.cs.meta b/src/Packages/Audience/Tests/Runtime/TestFixtures.cs.meta new file mode 100644 index 000000000..4376c4879 --- /dev/null +++ b/src/Packages/Audience/Tests/Runtime/TestFixtures.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd6da3a651764490959fa341975848f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs b/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs index b8f9c4524..68798c0e0 100644 --- a/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Utility/JsonTests.cs @@ -183,7 +183,7 @@ public void Serialize_RealisticEventPayload_ProducesCorrectJson() { { MessageFields.Type, MessageTypes.Track }, { MessageFields.EventName, TestEventNames.LevelComplete }, - { MessageFields.AnonymousId, "anon-123" }, + { MessageFields.AnonymousId, TestFixtures.AnonId123 }, { MessageFields.UserId, null }, { MessageFields.Properties, new Dictionary { From 56541042a4f2e38763dccd6f6623626a3ac406ac Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:51:45 +1000 Subject: [PATCH 11/23] test(audience-sdk): add missing meta file for TestEventNames TestEventNames.cs (SHA 6d237a3a) was committed without its .meta file. Unity generates one on project open, but the convention across the test folder is to commit them so the GUID is stable across machines (mirrors TestDefaults.cs.meta and TestFixtures.cs.meta). Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Audience/Tests/Runtime/TestEventNames.cs.meta | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/Packages/Audience/Tests/Runtime/TestEventNames.cs.meta diff --git a/src/Packages/Audience/Tests/Runtime/TestEventNames.cs.meta b/src/Packages/Audience/Tests/Runtime/TestEventNames.cs.meta new file mode 100644 index 000000000..5dd577b09 --- /dev/null +++ b/src/Packages/Audience/Tests/Runtime/TestEventNames.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0913bc77fd8c4a419bfcdb5dcfebb4e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From d27fc2eef0572e4f5990e4af752ed7d59fcb2395 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 13:01:39 +1000 Subject: [PATCH 12/23] test(audience-sdk): extend TestEventNames with transport and queue scenarios Adds named event-name constants for the EventQueue scenario tests (IntervalFlush, DisposeTest), placeholder constants for transport / queue scaffolding where the event name is just filler (PlaceholderA, PlaceholderB, PlaceholderTest, PlaceholderTrack, PlaceholderIgnored, PlaceholderEvt), and BlockedPrefix for the OfflineResilienceTests $"blocked_{i}" templated string. Migrates inline references in: - MessageBuilderTests ("evt" placeholders, six sites) - EventQueueTests (Msg() helper calls, six sites) - HttpTransportTests (WireFixture.Track event-name args, eight sites) - OfflineResilienceTests ($"blocked_{i}" templated literal, three sites) Per the user's "everything random goes in a constant" stance. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Tests/Runtime/Events/MessageBuilderTests.cs | 12 ++++++------ .../Tests/Runtime/OfflineResilienceTests.cs | 6 +++--- .../Audience/Tests/Runtime/TestEventNames.cs | 15 +++++++++++++++ .../Tests/Runtime/Transport/EventQueueTests.cs | 12 ++++++------ .../Runtime/Transport/HttpTransportTests.cs | 16 ++++++++-------- 5 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs b/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs index 4bd110ee6..1a9b6f486 100644 --- a/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs @@ -36,7 +36,7 @@ public void Track_EventNameLongerThan256Chars_TruncatedTo256() [Test] public void Track_NullUserId_NotPresentInDict() { - var result = MessageBuilder.Track("evt", TestFixtures.AnonId1, null, PackageVersion); + var result = MessageBuilder.Track(TestEventNames.PlaceholderEvt, TestFixtures.AnonId1, null, PackageVersion); Assert.IsFalse(result.ContainsKey(MessageFields.UserId)); } @@ -44,7 +44,7 @@ public void Track_NullUserId_NotPresentInDict() [Test] public void Track_NonNullUserId_PresentInDict() { - var result = MessageBuilder.Track("evt", TestFixtures.AnonId1, TestFixtures.UserId99, PackageVersion); + var result = MessageBuilder.Track(TestEventNames.PlaceholderEvt, TestFixtures.AnonId1, TestFixtures.UserId99, PackageVersion); Assert.IsTrue(result.ContainsKey(MessageFields.UserId)); Assert.AreEqual(TestFixtures.UserId99, result[MessageFields.UserId]); @@ -79,7 +79,7 @@ public void Alias_AllFourFieldsPresent() [Test] public void AllMessages_ContextContainsLibraryAndLibraryVersion() { - var track = MessageBuilder.Track("evt", null, null, PackageVersion); + var track = MessageBuilder.Track(TestEventNames.PlaceholderEvt, null, null, PackageVersion); var identify = MessageBuilder.Identify(null, "u1", IdentityType.Steam.ToLowercaseString(), PackageVersion); var alias = MessageBuilder.Alias("f", "t1", "t", "t2", PackageVersion); @@ -94,7 +94,7 @@ public void AllMessages_ContextContainsLibraryAndLibraryVersion() [Test] public void AllMessages_SurfaceIsUnity() { - var track = MessageBuilder.Track("evt", null, null, PackageVersion); + var track = MessageBuilder.Track(TestEventNames.PlaceholderEvt, null, null, PackageVersion); var identify = MessageBuilder.Identify(null, "u1", IdentityType.Steam.ToLowercaseString(), PackageVersion); var alias = MessageBuilder.Alias("f", "t1", "t", "t2", PackageVersion); @@ -120,7 +120,7 @@ public void Track_MessageId_IsUniquePerCall() // Backend deduplicates on messageId; collisions silently drop events. var ids = new HashSet(); for (var i = 0; i < 1000; i++) - ids.Add((string)MessageBuilder.Track("evt", null, null, PackageVersion)[MessageFields.MessageId]); + ids.Add((string)MessageBuilder.Track(TestEventNames.PlaceholderEvt, null, null, PackageVersion)[MessageFields.MessageId]); Assert.AreEqual(1000, ids.Count); } @@ -161,7 +161,7 @@ public void AllMessages_Context_LibraryAndLibraryVersionAreNonEmptyStrings() private static IEnumerable> EveryMessageType() { - yield return MessageBuilder.Track("evt", null, null, PackageVersion); + yield return MessageBuilder.Track(TestEventNames.PlaceholderEvt, null, null, PackageVersion); yield return MessageBuilder.Identify(null, "u1", IdentityType.Steam.ToLowercaseString(), PackageVersion); yield return MessageBuilder.Alias("f", "t1", "t", "t2", PackageVersion); } diff --git a/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs b/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs index 7da31a416..6a18c1b90 100644 --- a/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/OfflineResilienceTests.cs @@ -79,7 +79,7 @@ public void Track_DiskWritesBlocked_DoesNotThrowToCallers() Assert.DoesNotThrow(() => { - for (int i = 0; i < 50; i++) ImmutableAudience.Track($"blocked_{i}"); + for (int i = 0; i < 50; i++) ImmutableAudience.Track($"{TestEventNames.BlockedPrefix}{i}"); ImmutableAudience.FlushQueueToDiskForTesting(); }, "Track must not propagate disk-write IOException to callers"); @@ -95,7 +95,7 @@ public void Shutdown_DiskWritesBlocked_DoesNotThrow() ImmutableAudience.Init(MakeConfig()); ImmutableAudience.Track(TestEventNames.EventPreBlock); BlockDiskWrites(); - for (int i = 0; i < 20; i++) ImmutableAudience.Track($"blocked_{i}"); + for (int i = 0; i < 20; i++) ImmutableAudience.Track($"{TestEventNames.BlockedPrefix}{i}"); Assert.DoesNotThrow(() => ImmutableAudience.Shutdown(), "Shutdown must absorb disk-write failure during the final drain"); @@ -114,7 +114,7 @@ public void Track_DiskWritesBlocked_RetainsEventsInMemory_AndSurfacesOnError() BlockDiskWrites(); const int eventCount = 50; - for (int i = 0; i < eventCount; i++) ImmutableAudience.Track($"blocked_{i}"); + for (int i = 0; i < eventCount; i++) ImmutableAudience.Track($"{TestEventNames.BlockedPrefix}{i}"); ImmutableAudience.FlushQueueToDiskForTesting(); Assert.GreaterOrEqual(ImmutableAudience.QueueSize, eventCount, diff --git a/src/Packages/Audience/Tests/Runtime/TestEventNames.cs b/src/Packages/Audience/Tests/Runtime/TestEventNames.cs index 657c18926..c54a8d7cb 100644 --- a/src/Packages/Audience/Tests/Runtime/TestEventNames.cs +++ b/src/Packages/Audience/Tests/Runtime/TestEventNames.cs @@ -26,5 +26,20 @@ internal static class TestEventNames internal const string MixedLoadTrack = "mixed_load_track"; internal const string SteadyState = "steady_state"; internal const string LevelComplete = "level_complete"; + + // EventQueue scenario names where the event name is meaningful in the test description. + internal const string IntervalFlush = "interval_flush"; + internal const string DisposeTest = "dispose_test"; + + // Placeholder event names for tests where the event name itself is irrelevant. + internal const string PlaceholderA = "a"; + internal const string PlaceholderB = "b"; + internal const string PlaceholderTest = "test"; + internal const string PlaceholderTrack = "track"; + internal const string PlaceholderIgnored = "ignored"; + internal const string PlaceholderEvt = "evt"; + + // Prefix for OfflineResilienceTests' $"blocked_{i}" loop. + internal const string BlockedPrefix = "blocked_"; } } diff --git a/src/Packages/Audience/Tests/Runtime/Transport/EventQueueTests.cs b/src/Packages/Audience/Tests/Runtime/Transport/EventQueueTests.cs index 0a437629c..2661b4dc7 100644 --- a/src/Packages/Audience/Tests/Runtime/Transport/EventQueueTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Transport/EventQueueTests.cs @@ -35,7 +35,7 @@ public void Enqueue_ThenFlushSync_PersistesEventToDisk() { using var queue = new EventQueue(_store, flushIntervalSeconds: 60, flushSize: 100); - queue.Enqueue(Msg("track")); + queue.Enqueue(Msg(TestEventNames.PlaceholderTrack)); queue.FlushSync(); Assert.AreEqual(1, _store.Count(), "event should be on disk after FlushSync"); @@ -77,8 +77,8 @@ public void Shutdown_FlushesRemainingEvents() { var queue = new EventQueue(_store, flushIntervalSeconds: 60, flushSize: 100); - queue.Enqueue(Msg("a")); - queue.Enqueue(Msg("b")); + queue.Enqueue(Msg(TestEventNames.PlaceholderA)); + queue.Enqueue(Msg(TestEventNames.PlaceholderB)); queue.Shutdown(); @@ -99,7 +99,7 @@ public void Enqueue_AfterShutdown_IsIgnored() var queue = new EventQueue(_store, flushIntervalSeconds: 60, flushSize: 100); queue.Shutdown(); - queue.Enqueue(Msg("ignored")); + queue.Enqueue(Msg(TestEventNames.PlaceholderIgnored)); Assert.AreEqual(0, _store.Count(), "events enqueued after Shutdown should be discarded"); } @@ -110,7 +110,7 @@ public void IntervalFlush_DrainsToDiskWithoutExplicitCall() // Very short interval to make the test fast using var queue = new EventQueue(_store, flushIntervalSeconds: 1, flushSize: 100); - queue.Enqueue(Msg("interval_flush")); + queue.Enqueue(Msg(TestEventNames.IntervalFlush)); // Wait slightly longer than the flush interval var deadline = DateTime.UtcNow.AddSeconds(4); @@ -125,7 +125,7 @@ public void Dispose_FlushesAndStopsDrainThread() { using (var queue = new EventQueue(_store, flushIntervalSeconds: 60, flushSize: 100)) { - queue.Enqueue(Msg("dispose_test")); + queue.Enqueue(Msg(TestEventNames.DisposeTest)); } // Dispose called here Assert.AreEqual(1, _store.Count(), "Dispose should flush events to disk"); diff --git a/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs b/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs index b7f41f040..ea14dd568 100644 --- a/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Transport/HttpTransportTests.cs @@ -49,8 +49,8 @@ public void TearDown() [Test] public async Task SendBatchAsync_200_DeletesFilesFromDisk() { - _store.Write(WireFixture.Track((MessageFields.EventName, "a"))); - _store.Write(WireFixture.Track((MessageFields.EventName, "b"))); + _store.Write(WireFixture.Track((MessageFields.EventName, TestEventNames.PlaceholderA))); + _store.Write(WireFixture.Track((MessageFields.EventName, TestEventNames.PlaceholderB))); var handler = new MockHandler(HttpStatusCode.OK, $"{{\"accepted\":2,\"{ResponseFields.Rejected}\":0}}"); using var transport = new HttpTransport(_store, TestDefaults.PublishableKey, handler: handler); @@ -65,7 +65,7 @@ public async Task SendBatchAsync_200_DeletesFilesFromDisk() [Test] public async Task SendBatchAsync_200_SendsGzippedPayloadWithCorrectHeaders() { - _store.Write(WireFixture.Track((MessageFields.EventName, "test"))); + _store.Write(WireFixture.Track((MessageFields.EventName, TestEventNames.PlaceholderTest))); byte[]? capturedBody = null; string? capturedKey = null; @@ -97,7 +97,7 @@ public async Task SendBatchAsync_200_SendsGzippedPayloadWithCorrectHeaders() [Test] public async Task SendBatchAsync_200_SendsPlainJsonPayloadWithoutContentEncoding() { - _store.Write(WireFixture.Track((MessageFields.EventName, "test"))); + _store.Write(WireFixture.Track((MessageFields.EventName, TestEventNames.PlaceholderTest))); string? capturedKey = null; string? capturedContentType = null; @@ -322,8 +322,8 @@ public async Task SendBatchAsync_200_WithRejected_DeletesFilesAndSurfacesValidat // per-message validation errors. The batch is deleted (retries // would not help) and the count is surfaced via onError so // studios can observe silently dropped events. - _store.Write(WireFixture.Track((MessageFields.EventName, "a"))); - _store.Write(WireFixture.Track((MessageFields.EventName, "b"))); + _store.Write(WireFixture.Track((MessageFields.EventName, TestEventNames.PlaceholderA))); + _store.Write(WireFixture.Track((MessageFields.EventName, TestEventNames.PlaceholderB))); var handler = new MockHandler(HttpStatusCode.OK, $"{{\"accepted\":1,\"{ResponseFields.Rejected}\":1}}"); AudienceError? reportedError = null; @@ -341,7 +341,7 @@ public async Task SendBatchAsync_200_WithRejected_DeletesFilesAndSurfacesValidat [Test] public async Task SendBatchAsync_200_ZeroRejected_DoesNotFireOnError() { - _store.Write(WireFixture.Track((MessageFields.EventName, "a"))); + _store.Write(WireFixture.Track((MessageFields.EventName, TestEventNames.PlaceholderA))); var handler = new MockHandler(HttpStatusCode.OK, $"{{\"accepted\":1,\"{ResponseFields.Rejected}\":0}}"); AudienceError? reportedError = null; @@ -357,7 +357,7 @@ public async Task SendBatchAsync_200_ZeroRejected_DoesNotFireOnError() public async Task SendBatchAsync_200_MalformedBody_TreatsAsZeroRejected() { // Malformed diagnostic body must not block the success path. - _store.Write(WireFixture.Track((MessageFields.EventName, "a"))); + _store.Write(WireFixture.Track((MessageFields.EventName, TestEventNames.PlaceholderA))); var handler = new MockHandler(HttpStatusCode.OK, "not-json"); AudienceError? reportedError = null; From 4bacd74792c9bbe3d71e47e5ec5a104bae8eb6e2 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 12:46:58 +1000 Subject: [PATCH 13/23] refactor(audience-sample): use EventPropertyKeys for typed-event field lookups in live-fire tests SampleAppLiveFireTests filled the Resource, Purchase, and MilestoneReached typed-event forms by looking up TextFields with inline property-name strings ("currency", "amount", "value", "name"). The SDK already centralises these as EventPropertyKeys.Currency / .Amount / .Value / .Name, made visible to the sample-app tests through the existing InternalsVisibleTo grant. Reference EventPropertyKeys.X from the five lookup sites instead of re-typing the wire-format strings. Discovered during the sample-app demo property-key centralisation pass; kept separate because these keys belong to the SDK's typed-event surface, not the sample-app demo catalogue. Per the user's "everything random goes in a constant" stance. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs index e2e7259fd..2682fb149 100644 --- a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs @@ -152,8 +152,8 @@ public IEnumerator TypedEvent_Resource_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(EventNames.Resource), root => { - root.Q(SampleAppUi.TypedEventField(EventNames.Resource, "currency")).value = "GOLD"; - root.Q(SampleAppUi.TypedEventField(EventNames.Resource, "amount")).value = "100"; + root.Q(SampleAppUi.TypedEventField(EventNames.Resource, EventPropertyKeys.Currency)).value = "GOLD"; + root.Q(SampleAppUi.TypedEventField(EventNames.Resource, EventPropertyKeys.Amount)).value = "100"; }); } @@ -162,8 +162,8 @@ public IEnumerator TypedEvent_Purchase_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(EventNames.Purchase), root => { - root.Q(SampleAppUi.TypedEventField(EventNames.Purchase, "currency")).value = "USD"; - root.Q(SampleAppUi.TypedEventField(EventNames.Purchase, "value")).value = "9.99"; + root.Q(SampleAppUi.TypedEventField(EventNames.Purchase, EventPropertyKeys.Currency)).value = "USD"; + root.Q(SampleAppUi.TypedEventField(EventNames.Purchase, EventPropertyKeys.Value)).value = "9.99"; }); } @@ -172,7 +172,7 @@ public IEnumerator TypedEvent_MilestoneReached_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(EventNames.MilestoneReached), root => { - root.Q(SampleAppUi.TypedEventField(EventNames.MilestoneReached, "name")).value = "il2cpp_smoke"; + root.Q(SampleAppUi.TypedEventField(EventNames.MilestoneReached, EventPropertyKeys.Name)).value = "il2cpp_smoke"; }); } From 5edc01214ceaa20ddd7c5380d5d0daf7714ed46e Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 13:04:04 +1000 Subject: [PATCH 14/23] test(audience-sdk): extend TestFixtures with more scenario values Adds: - Track properties: CustomPropKeyRecipeId, CraftingRecipeIronSword - Distribution platform fixture: ProviderValue - IdentityType fallback fixture: UnknownProvider - Identity persistence fixtures: PreExistingId, PreExistingIdFromLastLaunch - ConsentStore corruption fixture: NotAnInt - Stress test userId: UserRaceStress - DeleteData generic userId: SomeUser - Templated prefix for GzipTests $"anon-{i}": AnonIdPrefix - Minimal placeholders for MessageBuilder Identify / Alias arg slots (GenericUserId, GenericFromId, GenericToId, GenericFromType, GenericToType) Migrates inline references in: - DeleteDataTests - ImmutableAudienceTests (Track props, race-stress userId, provider value) - Core/IdentityTests (pre-existing-id fixtures) - IdentityTypeTests (TestCase attribute now references the const) - Core/ConsentStoreTests (corrupt-file fixture) - Utility/GzipTests (anon-{i} templated prefix) - Events/MessageBuilderTests (Identify / Alias placeholder slots) Per the user's "everything random goes in a constant" stance. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Tests/Runtime/Core/ConsentStoreTests.cs | 2 +- .../Tests/Runtime/Core/IdentityTests.cs | 4 +-- .../Audience/Tests/Runtime/DeleteDataTests.cs | 2 +- .../Runtime/Events/MessageBuilderTests.cs | 12 +++---- .../Tests/Runtime/IdentityTypeTests.cs | 2 +- .../Tests/Runtime/ImmutableAudienceTests.cs | 8 ++--- .../Audience/Tests/Runtime/TestFixtures.cs | 33 +++++++++++++++++++ .../Tests/Runtime/Utility/GzipTests.cs | 2 +- 8 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/Packages/Audience/Tests/Runtime/Core/ConsentStoreTests.cs b/src/Packages/Audience/Tests/Runtime/Core/ConsentStoreTests.cs index 85713b777..313d6bbfc 100644 --- a/src/Packages/Audience/Tests/Runtime/Core/ConsentStoreTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Core/ConsentStoreTests.cs @@ -50,7 +50,7 @@ public void Load_MalformedFile_ReturnsNull() // A garbage value that isn't a valid enum int. var dir = AudiencePaths.AudienceDir(_testDir); Directory.CreateDirectory(dir); - File.WriteAllText(AudiencePaths.ConsentFile(_testDir), "not-an-int"); + File.WriteAllText(AudiencePaths.ConsentFile(_testDir), TestFixtures.NotAnInt); Assert.IsNull(ConsentStore.Load(_testDir)); } diff --git a/src/Packages/Audience/Tests/Runtime/Core/IdentityTests.cs b/src/Packages/Audience/Tests/Runtime/Core/IdentityTests.cs index cd2cf5fd6..4a1c33a06 100644 --- a/src/Packages/Audience/Tests/Runtime/Core/IdentityTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Core/IdentityTests.cs @@ -39,7 +39,7 @@ public void NewDirectory_GeneratesNonEmptyId_AndWritesFile() public void ExistingFile_ReturnsPreviousId_WithoutGeneratingNew() { // Simulate a returning player by pre-writing an identity file (as a previous launch would have done). - var expectedId = "pre-existing-id-from-last-launch"; + var expectedId = TestFixtures.PreExistingIdFromLastLaunch; var dir = AudiencePaths.AudienceDir(_testDir); Directory.CreateDirectory(dir); File.WriteAllText(AudiencePaths.IdentityFile(_testDir), expectedId); @@ -93,7 +93,7 @@ public void Get_NoExistingFile_ReturnsNull_AndDoesNotCreate() [Test] public void Get_ExistingFile_ReturnsPersistedId() { - var expectedId = "pre-existing-id"; + var expectedId = TestFixtures.PreExistingId; var dir = AudiencePaths.AudienceDir(_testDir); Directory.CreateDirectory(dir); File.WriteAllText(AudiencePaths.IdentityFile(_testDir), expectedId); diff --git a/src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs b/src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs index 831f947c1..18aed4868 100644 --- a/src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs +++ b/src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs @@ -136,7 +136,7 @@ public void DeleteData_DoesNotCreateAnonymousIdFile() var handler = new CapturingHandler(); ImmutableAudience.Init(MakeConfig(handler, ConsentLevel.None)); - ImmutableAudience.DeleteData(userId: "some-user"); + ImmutableAudience.DeleteData(userId: TestFixtures.SomeUser); // Even with a userId request, the anonymousId file must not materialise. Thread.Sleep(250); diff --git a/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs b/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs index 1a9b6f486..abddbc74b 100644 --- a/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Events/MessageBuilderTests.cs @@ -80,8 +80,8 @@ public void Alias_AllFourFieldsPresent() public void AllMessages_ContextContainsLibraryAndLibraryVersion() { var track = MessageBuilder.Track(TestEventNames.PlaceholderEvt, null, null, PackageVersion); - var identify = MessageBuilder.Identify(null, "u1", IdentityType.Steam.ToLowercaseString(), PackageVersion); - var alias = MessageBuilder.Alias("f", "t1", "t", "t2", PackageVersion); + var identify = MessageBuilder.Identify(null, TestFixtures.GenericUserId, IdentityType.Steam.ToLowercaseString(), PackageVersion); + var alias = MessageBuilder.Alias(TestFixtures.GenericFromId, TestFixtures.GenericFromType, TestFixtures.GenericToId, TestFixtures.GenericToType, PackageVersion); foreach (var msg in new[] { track, identify, alias }) { @@ -95,8 +95,8 @@ public void AllMessages_ContextContainsLibraryAndLibraryVersion() public void AllMessages_SurfaceIsUnity() { var track = MessageBuilder.Track(TestEventNames.PlaceholderEvt, null, null, PackageVersion); - var identify = MessageBuilder.Identify(null, "u1", IdentityType.Steam.ToLowercaseString(), PackageVersion); - var alias = MessageBuilder.Alias("f", "t1", "t", "t2", PackageVersion); + var identify = MessageBuilder.Identify(null, TestFixtures.GenericUserId, IdentityType.Steam.ToLowercaseString(), PackageVersion); + var alias = MessageBuilder.Alias(TestFixtures.GenericFromId, TestFixtures.GenericFromType, TestFixtures.GenericToId, TestFixtures.GenericToType, PackageVersion); Assert.AreEqual(Constants.Surface, track[MessageFields.Surface]); Assert.AreEqual(Constants.Surface, identify[MessageFields.Surface]); @@ -162,8 +162,8 @@ public void AllMessages_Context_LibraryAndLibraryVersionAreNonEmptyStrings() private static IEnumerable> EveryMessageType() { yield return MessageBuilder.Track(TestEventNames.PlaceholderEvt, null, null, PackageVersion); - yield return MessageBuilder.Identify(null, "u1", IdentityType.Steam.ToLowercaseString(), PackageVersion); - yield return MessageBuilder.Alias("f", "t1", "t", "t2", PackageVersion); + yield return MessageBuilder.Identify(null, TestFixtures.GenericUserId, IdentityType.Steam.ToLowercaseString(), PackageVersion); + yield return MessageBuilder.Alias(TestFixtures.GenericFromId, TestFixtures.GenericFromType, TestFixtures.GenericToId, TestFixtures.GenericToType, PackageVersion); } } } diff --git a/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs b/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs index 4050e73fe..0ec2de376 100644 --- a/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs +++ b/src/Packages/Audience/Tests/Runtime/IdentityTypeTests.cs @@ -42,7 +42,7 @@ public void ParseLowercaseString_AcceptsMixedCase(string wire, IdentityType expe [TestCase(null)] [TestCase("")] - [TestCase("unknown_provider")] + [TestCase(TestFixtures.UnknownProvider)] [TestCase("steamX")] public void ParseLowercaseString_FallsBackToCustomForUnknownOrEmpty(string? wire) { diff --git a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs index f8aefd166..dc33cf9b1 100644 --- a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs @@ -490,7 +490,7 @@ public void Track_CustomEvent_WritesEventToDisk() ImmutableAudience.Track(TestEventNames.CraftingStarted, new Dictionary { - { "recipe_id", "iron_sword" } + { TestFixtures.CustomPropKeyRecipeId, TestFixtures.CraftingRecipeIronSword } }); // Flush memory → disk @@ -968,7 +968,7 @@ public void SetConsent_DowngradeToAnonymous_StressTest_NoUserIdLeak() // EnqueueTrack and this test leaks reproducibly. const int iterations = 200; const int trackersPerIteration = 4; - const string testUserId = "user_race_stress"; + const string testUserId = TestFixtures.UserRaceStress; for (int iter = 0; iter < iterations; iter++) { @@ -1224,7 +1224,7 @@ public void Init_GameLaunch_ConfigDistributionPlatformOverridesProvider() { ImmutableAudience.LaunchContextProvider = () => new Dictionary { - [GameLaunchPropertyKeys.DistributionPlatform] = "provider_value", + [GameLaunchPropertyKeys.DistributionPlatform] = TestFixtures.ProviderValue, }; var config = MakeConfig(); @@ -1237,7 +1237,7 @@ public void Init_GameLaunch_ConfigDistributionPlatformOverridesProvider() .Select(File.ReadAllText) .First(c => c.Contains($"\"{EventNames.GameLaunch}\"")); StringAssert.Contains($"\"{GameLaunchPropertyKeys.DistributionPlatform}\":\"{DistributionPlatforms.Steam}\"", launchFile); - Assert.IsFalse(launchFile.Contains("provider_value"), + Assert.IsFalse(launchFile.Contains(TestFixtures.ProviderValue), "config.DistributionPlatform should win over the provider's value"); } diff --git a/src/Packages/Audience/Tests/Runtime/TestFixtures.cs b/src/Packages/Audience/Tests/Runtime/TestFixtures.cs index d2f001f77..3df86b820 100644 --- a/src/Packages/Audience/Tests/Runtime/TestFixtures.cs +++ b/src/Packages/Audience/Tests/Runtime/TestFixtures.cs @@ -33,5 +33,38 @@ internal static class TestFixtures // MilestoneReached event payload values internal const string MilestoneName = "first_boss_defeated"; + + // Track properties scenario data + internal const string CustomPropKeyRecipeId = "recipe_id"; + internal const string CraftingRecipeIronSword = "iron_sword"; + + // Distribution platform fixture for the "platform from outside SDK" path. + internal const string ProviderValue = "provider_value"; + + // IdentityTypeExtensions.ParseLowercaseString fallback fixture. + internal const string UnknownProvider = "unknown_provider"; + + // Identity persistence fixtures (id read back from disk on next launch). + internal const string PreExistingId = "pre-existing-id"; + internal const string PreExistingIdFromLastLaunch = "pre-existing-id-from-last-launch"; + + // ConsentStore corruption fixture (non-integer file content). + internal const string NotAnInt = "not-an-int"; + + // ThreadSafetyStressTests userId for race-stress scenarios. + internal const string UserRaceStress = "user_race_stress"; + + // DeleteDataTests generic userId. + internal const string SomeUser = "some-user"; + + // Prefix for GzipTests' $"anon-{i}" loop (per-message anonymous IDs). + internal const string AnonIdPrefix = "anon-"; + + // Placeholders for fixture slots where the value itself is not under test. + internal const string GenericUserId = "u1"; + internal const string GenericFromId = "f"; + internal const string GenericToId = "t"; + internal const string GenericFromType = "t1"; + internal const string GenericToType = "t2"; } } diff --git a/src/Packages/Audience/Tests/Runtime/Utility/GzipTests.cs b/src/Packages/Audience/Tests/Runtime/Utility/GzipTests.cs index efc40110a..995b2f1ba 100644 --- a/src/Packages/Audience/Tests/Runtime/Utility/GzipTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Utility/GzipTests.cs @@ -35,7 +35,7 @@ public void Compress_OutputIsSmallerThanInput_ForRealisticPayload() if (i > 0) sb.Append(','); sb.Append(WireFixture.Track( (MessageFields.EventName, TestEventNames.LevelComplete), - (MessageFields.AnonymousId, $"anon-{i}"))); + (MessageFields.AnonymousId, $"{TestFixtures.AnonIdPrefix}{i}"))); } sb.Append("]}"); From febd8379a0f9f4815ab6d708ffba59bde5b0c7b1 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Sat, 2 May 2026 13:05:10 +1000 Subject: [PATCH 15/23] test(audience-sample): centralise live-fire test fixture values Adds SampleAppLiveFireFixtures alongside SampleAppLiveFireTests with three constants (MilestoneSmokeName, GameId, LinkUrl) shared across five inline call sites in the live-fire batch. Retargeting the smoke suite (e.g. switching the game id slug) is now a one-line change instead of a five-line touch. Migrates inline references in SampleAppLiveFireTests for the typed-event Resource / Purchase / MilestoneReached fills and the sample-app custom catalogue fills (WishlistAdd, WishlistRemove, GamePageViewed, LinkClicked). Per the user's "everything random goes in a constant" stance. Follow-up to SDK-272 (centralisation of duplicated literals). --- .../Tests/Runtime/SampleAppLiveFireFixtures.cs | 10 ++++++++++ .../Tests/Runtime/SampleAppLiveFireFixtures.cs.meta | 11 +++++++++++ .../Tests/Runtime/SampleAppLiveFireTests.cs | 12 ++++++------ 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs create mode 100644 examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs.meta diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs new file mode 100644 index 000000000..a642eb2f9 --- /dev/null +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs @@ -0,0 +1,10 @@ +namespace Immutable.Audience.Samples.SampleApp.Tests +{ + // Values used by SampleAppLiveFireTests when filling the demo input fields. + internal static class SampleAppLiveFireFixtures + { + internal const string MilestoneSmokeName = "il2cpp_smoke"; + internal const string GameId = "il2cpp_game_1"; + internal const string LinkUrl = "https://example.com/il2cpp"; + } +} diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs.meta b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs.meta new file mode 100644 index 000000000..fab7470eb --- /dev/null +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e230edd97bb4848af6b5988e961d01c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs index 2682fb149..1565cca1d 100644 --- a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs @@ -172,7 +172,7 @@ public IEnumerator TypedEvent_MilestoneReached_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(EventNames.MilestoneReached), root => { - root.Q(SampleAppUi.TypedEventField(EventNames.MilestoneReached, EventPropertyKeys.Name)).value = "il2cpp_smoke"; + root.Q(SampleAppUi.TypedEventField(EventNames.MilestoneReached, EventPropertyKeys.Name)).value = SampleAppLiveFireFixtures.MilestoneSmokeName; }); } @@ -262,7 +262,7 @@ public IEnumerator CustomTrack_WithDictionaryProps_FlushReportsOk() // Custom event name + JSON props (the sample app parses props as JSON // and forwards them as Dictionary to ImmutableAudience.Track). - _root!.Q(SampleAppUi.CustomEvent.Name).value = "il2cpp_smoke"; + _root!.Q(SampleAppUi.CustomEvent.Name).value = SampleAppLiveFireFixtures.MilestoneSmokeName; _root.Q(SampleAppUi.CustomEvent.Props).value = "{\"int_field\":42,\"str_field\":\"hello\",\"bool_field\":true,\"nested\":{\"a\":1}}"; _root.Q