Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ namespace Immutable.Audience.Samples.SampleApp.Tests
[TestFixture]
internal class SampleAppLiveFireTests
{
// Reflection target for ImmutableAudience.ResetState (invoked from SetUp).
private const string ResetStateMethodName = "ResetState";

// Per-test prefix combined with UtcNow.Ticks for a unique userId per Identify run.
private const string PanelUserPrefix = "panel-user-";

// Cached allocations for fixed-delay yields (SDK needs time to flush
// rather than "wait up to N seconds for a condition"). Static readonly
// so the WaitForSecondsRealtime instance is created once per class load.
Expand All @@ -28,7 +34,7 @@ public void SetUp()
// ResetState is internal — reached via reflection (BindingFlags.NonPublic
// bypasses C# access checks; no InternalsVisibleTo required).
var t = typeof(ImmutableAudience);
var m = t.GetMethod("ResetState",
var m = t.GetMethod(ResetStateMethodName,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
m?.Invoke(null, null);

Expand Down Expand Up @@ -687,7 +693,7 @@ public IEnumerator IdentityPanel_PopulatesUserIdAfterIdentify()
{
yield return LoadAndInit(initialConsent: SampleAppUi.Consent.Full);

var userId = "panel-user-" + DateTime.UtcNow.Ticks;
var userId = PanelUserPrefix + DateTime.UtcNow.Ticks;
_root!.Q<TextField>(SampleAppUi.IdentityFields.Id).value = userId;
_root.Q<Button>(SampleAppUi.Buttons.Identify).Click();
yield return SampleAppTestHelpers.WaitForLogEntry(_root, SampleAppUi.LogLabels.Identify, LogLevels.Ok, 5f);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ namespace Immutable.Audience.Samples.SampleApp.Tests
// the log pane via the userData stash on each log row.
internal static class SampleAppTestHelpers
{
// Reflection field names for AudienceSample's LogEntry.
// No InternalsVisibleTo on the sample-app assembly, so nameof() is unreachable.
private const string LogEntryLabelField = "Label";
private const string LogEntryBodyField = "Body";
private const string LogEntryLevelField = "Level";

// UI Toolkit's Clickable.clicked is non-public; ButtonTestExtensions.Click reflects through this name.
internal const string ClickableClickedField = "clicked";

// Polls predicate once per frame until it returns true or the deadline
// elapses. Calls Assert.Fail with description when the deadline is hit.
// Use this instead of WaitForSecondsRealtime when a test is waiting
Expand Down Expand Up @@ -104,16 +113,16 @@ private readonly struct LogEntryShim
internal LogEntryShim(object entry) { _entry = entry; }

internal string Label =>
(string)(_entry.GetType().GetField("Label")?.GetValue(_entry) ?? "");
(string)(_entry.GetType().GetField(LogEntryLabelField)?.GetValue(_entry) ?? "");

internal string Body =>
(string)(_entry.GetType().GetField("Body")?.GetValue(_entry) ?? "");
(string)(_entry.GetType().GetField(LogEntryBodyField)?.GetValue(_entry) ?? "");

internal int Level
{
get
{
var v = _entry.GetType().GetField("Level")?.GetValue(_entry);
var v = _entry.GetType().GetField(LogEntryLevelField)?.GetValue(_entry);
return v == null ? -1 : Convert.ToInt32(v);
}
}
Expand Down Expand Up @@ -146,7 +155,7 @@ internal static void Click(this Button button)
{
var clickable = button?.clickable;
if (clickable == null) return;
var field = typeof(Clickable).GetField("clicked",
var field = typeof(Clickable).GetField(SampleAppTestHelpers.ClickableClickedField,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var handler = field?.GetValue(clickable) as Delegate;
handler?.DynamicInvoke();
Expand Down
7 changes: 7 additions & 0 deletions src/Packages/Audience/README.md.meta

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

5 changes: 4 additions & 1 deletion src/Packages/Audience/Runtime/Transport/EventQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ namespace Immutable.Audience
// Call Shutdown before process exit.
internal sealed class EventQueue : IDisposable
{
// Drain thread name. Surfaces in profilers, debuggers, and crash dumps.
private const string DrainThreadName = "imtbl-audience-drain";

private readonly DiskStore _store;
private readonly int _flushIntervalMs;
private readonly int _flushSize;
Expand Down Expand Up @@ -44,7 +47,7 @@ internal EventQueue(DiskStore store, int flushIntervalSeconds, int flushSize)
_drainThread = new Thread(DrainLoop)
{
IsBackground = true,
Name = "imtbl-audience-drain"
Name = DrainThreadName
};
_drainThread.Start();
}
Expand Down
8 changes: 8 additions & 0 deletions src/Packages/Audience/Tests/Editor.meta

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

11 changes: 11 additions & 0 deletions src/Packages/Audience/Tests/Editor/DeviceCollectorTests.cs.meta

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

4 changes: 2 additions & 2 deletions src/Packages/Audience/Tests/Runtime/ConsentSyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ private AudienceConfig MakeConfig(CapturingHandler handler, ConsentLevel consent
PublishableKey = TestDefaults.PublishableKey,
Consent = consent,
PersistentDataPath = _testDir,
FlushIntervalSeconds = 600,
FlushSize = 1000,
FlushIntervalSeconds = TestDefaults.FlushIntervalSeconds,
FlushSize = TestDefaults.FlushSize,
HttpHandler = handler,
};

Expand Down
29 changes: 16 additions & 13 deletions src/Packages/Audience/Tests/Runtime/Core/SessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ internal class SessionTests
// Exception thrown by the ThrowingTrack sabotage delegate across four scenarios.
private const string TrackExplodeMessage = "track explode";

// Fixed virtual "now" used as the starting point for every Session lifecycle test.
private static readonly DateTime FixedNowUtc = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);

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

[SetUp]
Expand Down Expand Up @@ -59,7 +62,7 @@ public void Start_GeneratesUniqueSessionId()
[Test]
public void End_FiresSessionEnd_WithDuration()
{
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
using var session = new Session(MockTrack, getUtcNow: () => now);
session.Start();
now = now.AddSeconds(2);
Expand Down Expand Up @@ -136,7 +139,7 @@ void Track(string name, Dictionary<string, object> props)
[Test]
public void Pause_ThenResume_ShortPause_ContinuesSession()
{
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
using var session = new Session(MockTrack, getUtcNow: () => now);
session.Start();
var originalId = session.SessionId;
Expand All @@ -155,7 +158,7 @@ public void Pause_ThenResume_LongPause_StartsNewSession()
{
// Uses the injected clock to jump past the 30-second threshold
// without sleeping.
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
using var session = new Session(MockTrack, getUtcNow: () => now);
session.Start();
var id1 = session.SessionId;
Expand All @@ -182,7 +185,7 @@ public void Pause_CalledTwice_SecondCallIsNoOp()
// _pausedAt jump to the second Pause and this test reports a
// larger duration (over-crediting engagement by the double-pause
// gap).
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
DateTime Clock() => now;

using var session = new Session(MockTrack, getUtcNow: Clock);
Expand Down Expand Up @@ -233,7 +236,7 @@ public void Resume_NegativePauseDuration_ClampsAccumulatorToZero()
// artificial engagement credit. Sabotage: removing the clamp
// would let this test report a duration that exceeds the
// wall-clock window, which the assertion below pins.
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
DateTime Clock() => now;

using var session = new Session(MockTrack, getUtcNow: Clock);
Expand Down Expand Up @@ -270,7 +273,7 @@ public void End_ClockRewindsSinceStart_ClampsDurationToZero()
// because it only fires when _pausedAt was set. Sabotage:
// removing `if (engagedSeconds < 0) return 0;` would let this
// test report -3 instead of 0.
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
DateTime Clock() => now;

using var session = new Session(MockTrack, getUtcNow: Clock);
Expand All @@ -296,7 +299,7 @@ public void End_ClockRewindsWhilePaused_DoesNotInflateDuration()
// (the final engagedSeconds ≥ 0 clamp only catches negatives,
// not over-credit). Sabotage: removing the livePause clamp lets
// this test report 15s instead of the ≤ 5s wall-clock window.
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
DateTime Clock() => now;

using var session = new Session(MockTrack, getUtcNow: Clock);
Expand All @@ -318,7 +321,7 @@ public void End_ClockRewindsWhilePaused_DoesNotInflateDuration()
public void End_AfterShortPause_ReportsDurationMinusPause()
{
// 10 seconds session, 3 seconds paused inside it → 7 seconds engaged.
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
DateTime Clock() => now;

using var session = new Session(MockTrack, getUtcNow: Clock);
Expand All @@ -343,7 +346,7 @@ public void End_AfterShortPause_ReportsDurationMinusPause()
public void End_WhilePaused_ExcludesInFlightPauseFromDuration()
{
// Session running 5s, then paused for 2s and ended without resuming.
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
DateTime Clock() => now;

using var session = new Session(MockTrack, getUtcNow: Clock);
Expand Down Expand Up @@ -371,7 +374,7 @@ public void End_AfterExtendedPauseRollover_ReportsPrePauseDuration()
// for the extended-pause rollover path: a naive duration that
// forgot to credit the in-flight pause before End fires would
// ship wall-clock seconds and break engagement dashboards.
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
DateTime Clock() => now;

using var session = new Session(MockTrack, getUtcNow: Clock);
Expand All @@ -393,7 +396,7 @@ public void End_AfterExtendedPauseRollover_ReportsPrePauseDuration()
public void Heartbeat_AfterShortPause_ReportsPauseAdjustedDuration()
{
// Engaged 6s, paused 2s, resumed, then heartbeat → 6 s engaged.
var now = new DateTime(2026, 4, 20, 12, 0, 0, DateTimeKind.Utc);
var now = FixedNowUtc;
DateTime Clock() => now;

using var session = new Session(MockTrack, getUtcNow: Clock);
Expand Down Expand Up @@ -700,8 +703,8 @@ private AudienceConfig MakeConfig(ConsentLevel consent = ConsentLevel.Anonymous)
PublishableKey = TestDefaults.PublishableKey,
Consent = consent,
PersistentDataPath = _testDir,
FlushIntervalSeconds = 600,
FlushSize = 1000,
FlushIntervalSeconds = TestDefaults.FlushIntervalSeconds,
FlushSize = TestDefaults.FlushSize,
HttpHandler = new KeepOnDiskHandler()
};
}
Expand Down
4 changes: 2 additions & 2 deletions src/Packages/Audience/Tests/Runtime/DeleteDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ private AudienceConfig MakeConfig(CapturingHandler handler, ConsentLevel consent
PublishableKey = TestDefaults.PublishableKey,
Consent = consent,
PersistentDataPath = _testDir,
FlushIntervalSeconds = 600,
FlushSize = 1000,
FlushIntervalSeconds = TestDefaults.FlushIntervalSeconds,
FlushSize = TestDefaults.FlushSize,
HttpHandler = handler
};
}
Expand Down
32 changes: 16 additions & 16 deletions src/Packages/Audience/Tests/Runtime/Events/TypedEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ public void Progression_Complete_ProducesCorrectProperties()
{
Status = ProgressionStatus.Complete,
World = TestFixtures.ProgressionWorldTutorial,
Level = "1",
Score = 1500,
DurationSec = 120.5f
Level = TestFixtures.ProgressionLevelFixture,
Score = TestFixtures.ProgressionScoreFixture,
DurationSec = TestFixtures.ProgressionDurationSecFixture
};

var props = evt.ToProperties();

Assert.AreEqual(ProgressionStatus.Complete.ToLowercaseString(), props[EventPropertyKeys.Status]);
Assert.AreEqual(TestFixtures.ProgressionWorldTutorial, props[EventPropertyKeys.World]);
Assert.AreEqual("1", props[EventPropertyKeys.Level]);
Assert.AreEqual(1500, props[EventPropertyKeys.Score]);
Assert.AreEqual(120.5f, props[EventPropertyKeys.DurationSec]);
Assert.AreEqual(TestFixtures.ProgressionLevelFixture, props[EventPropertyKeys.Level]);
Assert.AreEqual(TestFixtures.ProgressionScoreFixture, props[EventPropertyKeys.Score]);
Assert.AreEqual(TestFixtures.ProgressionDurationSecFixture, props[EventPropertyKeys.DurationSec]);
}

[Test]
Expand All @@ -62,7 +62,7 @@ public void Resource_Source_ProducesCorrectProperties()
{
Flow = ResourceFlow.Source,
Currency = TestFixtures.ResourceCurrency,
Amount = 100,
Amount = TestFixtures.ResourceAmountFixture,
ItemType = TestFixtures.ResourceItemType,
ItemId = TestFixtures.ResourceItemId
};
Expand All @@ -71,7 +71,7 @@ public void Resource_Source_ProducesCorrectProperties()

Assert.AreEqual(ResourceFlow.Source.ToLowercaseString(), props[EventPropertyKeys.Flow]);
Assert.AreEqual(TestFixtures.ResourceCurrency, props[EventPropertyKeys.Currency]);
Assert.AreEqual(100m, props[EventPropertyKeys.Amount]);
Assert.AreEqual(TestFixtures.ResourceAmountFixture, props[EventPropertyKeys.Amount]);
Assert.AreEqual(TestFixtures.ResourceItemType, props[EventPropertyKeys.ItemType]);
Assert.AreEqual(TestFixtures.ResourceItemId, props[EventPropertyKeys.ItemId]);
}
Expand All @@ -85,7 +85,7 @@ public void Resource_EventName_IsResource()
[Test]
public void Resource_WithoutFlow_ThrowsOnToProperties()
{
var evt = new Resource { Currency = TestFixtures.ResourceCurrency, Amount = 100 };
var evt = new Resource { Currency = TestFixtures.ResourceCurrency, Amount = TestFixtures.ResourceAmountFixture };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain(nameof(Resource.Flow)));
Expand All @@ -94,7 +94,7 @@ public void Resource_WithoutFlow_ThrowsOnToProperties()
[Test]
public void Resource_WithoutCurrency_ThrowsOnToProperties()
{
var evt = new Resource { Flow = ResourceFlow.Source, Amount = 100 };
var evt = new Resource { Flow = ResourceFlow.Source, Amount = TestFixtures.ResourceAmountFixture };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain(nameof(Resource.Currency)));
Expand All @@ -115,27 +115,27 @@ public void Purchase_ProducesCorrectProperties()
var evt = new Purchase
{
Currency = TestFixtures.UsdCurrency,
Value = 9.99m,
Value = TestFixtures.PurchaseValueFixture,
ItemId = TestFixtures.PurchaseItemId,
ItemName = TestFixtures.PurchaseItemName,
Quantity = 1,
Quantity = TestFixtures.PurchaseQuantityFixture,
TransactionId = TestFixtures.PurchaseTransactionId
};

var props = evt.ToProperties();

Assert.AreEqual(TestFixtures.UsdCurrency, props[EventPropertyKeys.Currency]);
Assert.AreEqual(9.99m, props[EventPropertyKeys.Value]);
Assert.AreEqual(TestFixtures.PurchaseValueFixture, props[EventPropertyKeys.Value]);
Assert.AreEqual(TestFixtures.PurchaseItemId, props[EventPropertyKeys.ItemId]);
Assert.AreEqual(TestFixtures.PurchaseItemName, props[EventPropertyKeys.ItemName]);
Assert.AreEqual(1, props[EventPropertyKeys.Quantity]);
Assert.AreEqual(TestFixtures.PurchaseQuantityFixture, props[EventPropertyKeys.Quantity]);
Assert.AreEqual(TestFixtures.PurchaseTransactionId, props[EventPropertyKeys.TransactionId]);
}

[Test]
public void Purchase_OptionalFieldsOmitted_WhenNull()
{
var props = new Purchase { Currency = TestFixtures.EurCurrency, Value = 5.00m }.ToProperties();
var props = new Purchase { Currency = TestFixtures.EurCurrency, Value = TestFixtures.PurchaseValueLowFixture }.ToProperties();

Assert.IsTrue(props.ContainsKey(EventPropertyKeys.Currency));
Assert.IsTrue(props.ContainsKey(EventPropertyKeys.Value));
Expand All @@ -154,7 +154,7 @@ public void Purchase_EventName_IsPurchase()
[Test]
public void Purchase_WithoutCurrency_ThrowsOnToProperties()
{
var evt = new Purchase { Value = 9.99m };
var evt = new Purchase { Value = TestFixtures.PurchaseValueFixture };

var ex = Assert.Throws<ArgumentException>(() => evt.ToProperties());
Assert.That(ex!.Message, Does.Contain(nameof(Purchase.Currency)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ private AudienceConfig MakeConfig(ConsentLevel consent = ConsentLevel.Anonymous)
PublishableKey = TestDefaults.PublishableKey,
Consent = consent,
PersistentDataPath = _testDir,
FlushIntervalSeconds = 600, // large; we flush manually in tests
FlushSize = 1000,
FlushIntervalSeconds = TestDefaults.FlushIntervalSeconds, // large; we flush manually in tests
FlushSize = TestDefaults.FlushSize,
HttpHandler = new KeepOnDiskHandler()
};
}
Expand Down Expand Up @@ -582,7 +582,7 @@ public void Track_TypedProgression_WritesCorrectEventName()
{
Status = ProgressionStatus.Complete,
World = TestFixtures.ProgressionWorldTutorial,
Level = "1"
Level = TestFixtures.ProgressionLevelFixture
});
ImmutableAudience.Shutdown();

Expand All @@ -601,7 +601,7 @@ public void Track_TypedPurchase_WritesCorrectEventName()
ImmutableAudience.Track(new Purchase
{
Currency = TestFixtures.UsdCurrency,
Value = 9.99m
Value = TestFixtures.PurchaseValueFixture
});
ImmutableAudience.Shutdown();

Expand Down
Loading
Loading