Skip to content

Commit 29f670a

Browse files
abueideclaude
andcommitted
test: update IDFA test to green regression suite after fix merge
Both tests now pass: - Test 1 confirms the buggy pattern (no execute override) lets events through - Test 2 validates the fix pattern (execute awaits IDFA future) blocks events Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 050bfa0 commit 29f670a

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:mockito/mockito.dart';
5+
import 'package:segment_analytics/analytics.dart';
6+
import 'package:segment_analytics/analytics_platform_interface.dart';
7+
import 'package:segment_analytics/event.dart';
8+
import 'package:segment_analytics/plugin.dart';
9+
import 'package:segment_analytics/state.dart';
10+
11+
import '../mocks/mocks.dart';
12+
import '../mocks/mocks.mocks.dart';
13+
14+
/// Simulates the BUGGY PluginIdfa behavior (before PR #198): fires off async
15+
/// IDFA fetch in constructor without overriding execute() to await it.
16+
class BuggyIdfaPlugin extends Plugin {
17+
final Completer<void> _fetchCompleter;
18+
19+
BuggyIdfaPlugin(this._fetchCompleter) : super(PluginType.enrichment) {
20+
_simulateIdfaFetch();
21+
}
22+
23+
Future<void> _simulateIdfaFetch() async {
24+
await _fetchCompleter.future;
25+
final context = await analytics?.state.context.state;
26+
if (context != null) {
27+
context.device.advertisingId = 'test-advertising-id';
28+
context.device.adTrackingEnabled = true;
29+
analytics?.state.context.setState(context);
30+
}
31+
}
32+
}
33+
34+
/// Simulates the FIXED PluginIdfa behavior (PR #198): stores the future and
35+
/// awaits it in execute(), blocking events until IDFA data is available.
36+
class FixedIdfaPlugin extends Plugin {
37+
final Completer<void> _fetchCompleter;
38+
late final Future<void> _idfaFuture;
39+
40+
FixedIdfaPlugin(this._fetchCompleter) : super(PluginType.enrichment) {
41+
_idfaFuture = _simulateIdfaFetch();
42+
}
43+
44+
Future<void> _simulateIdfaFetch() async {
45+
await _fetchCompleter.future;
46+
final context = await analytics?.state.context.state;
47+
if (context != null) {
48+
context.device.advertisingId = 'test-advertising-id';
49+
context.device.adTrackingEnabled = true;
50+
analytics?.state.context.setState(context);
51+
}
52+
}
53+
54+
@override
55+
Future<RawEvent?> execute(RawEvent event) async {
56+
await _idfaFuture;
57+
return event;
58+
}
59+
}
60+
61+
void main() {
62+
TestWidgetsFlutterBinding.ensureInitialized();
63+
64+
const writeKey = '123';
65+
final batch = [
66+
TrackEvent("Event 1"),
67+
TrackEvent("Event 2"),
68+
TrackEvent("Event 3"),
69+
];
70+
71+
group('IDFA plugin - advertisingId on initial events', () {
72+
late Analytics analytics;
73+
late MockHTTPClient httpClient;
74+
75+
setUp(() async {
76+
AnalyticsPlatform.instance = MockPlatform();
77+
httpClient = Mocks.httpClient();
78+
when(httpClient.settingsFor(writeKey))
79+
.thenAnswer((_) => Future.value(SegmentAPISettings({})));
80+
when(httpClient.startBatchUpload(writeKey, batch))
81+
.thenAnswer((_) => Future.value(true));
82+
analytics = Analytics(
83+
Configuration(writeKey,
84+
trackApplicationLifecycleEvents: false,
85+
token: "test-token"),
86+
Mocks.store(),
87+
httpClient: (_) => httpClient);
88+
await analytics.init();
89+
});
90+
91+
test('regression: without execute() override, events pass through before '
92+
'IDFA is ready', () async {
93+
// Demonstrates the bug from before PR #198: an enrichment plugin that
94+
// does async work in constructor but doesn't override execute() lets
95+
// events through immediately. This causes context.device.advertisingId
96+
// to be null when events are serialized to the queue.
97+
final fetchCompleter = Completer<void>();
98+
final plugin = BuggyIdfaPlugin(fetchCompleter);
99+
analytics.addPlugin(plugin);
100+
101+
bool executeReturned = false;
102+
plugin.execute(TrackEvent("Application Opened")).then((_) {
103+
executeReturned = true;
104+
});
105+
106+
await Future<void>.delayed(Duration.zero);
107+
108+
// Confirms the buggy behavior: execute() returned without waiting
109+
expect(executeReturned, isTrue,
110+
reason: 'Without execute() override, events pass through immediately');
111+
112+
fetchCompleter.complete();
113+
});
114+
115+
test('fix: with execute() override, events are blocked until IDFA resolves',
116+
() async {
117+
// Validates the fix from PR #198: by overriding execute() to await the
118+
// IDFA future, no event can pass through the enrichment phase until
119+
// advertisingId is populated in context.
120+
final fetchCompleter = Completer<void>();
121+
final plugin = FixedIdfaPlugin(fetchCompleter);
122+
analytics.addPlugin(plugin);
123+
124+
bool executeReturned = false;
125+
final executeFuture =
126+
plugin.execute(TrackEvent("Application Opened")).then((result) {
127+
executeReturned = true;
128+
return result;
129+
});
130+
131+
await Future<void>.delayed(Duration.zero);
132+
133+
// execute() is still blocked — waiting for IDFA
134+
expect(executeReturned, isFalse,
135+
reason: 'execute() must block until IDFA data is available');
136+
137+
// Simulate native ATTrackingManager callback
138+
fetchCompleter.complete();
139+
await executeFuture;
140+
141+
// advertisingId is now set before event passes through
142+
expect(executeReturned, isTrue);
143+
final context = await analytics.state.context.state;
144+
expect(context!.device.advertisingId, equals('test-advertising-id'));
145+
});
146+
});
147+
}

0 commit comments

Comments
 (0)