@@ -10,20 +10,27 @@ namespace Backtrace.Unity.Tests.Runtime.Native.Windows
1010{
1111 public sealed class ScopedNativeAttributesTests
1212 {
13+ // PlayerPrefs key used by the Windows NativeClient to store the scoped list
14+ private const string ScopedKeyList = "backtrace-scoped-attributes" ;
15+ private const string ScopedValuePattern = "bt-{0}" ;
16+
1317 [ TearDown ]
1418 public void Setup ( )
1519 {
1620 CleanLegacyAttributes ( ) ;
1721 NativeClient . CleanScopedAttributes ( ) ;
22+ PlayerPrefs . DeleteAll ( ) ;
1823 }
1924
25+ [ Test ]
2026 public void FreshStartup_ShouldntIncludeAnyAttributeFromPlayerPrefs_AttributesAreEmpty ( )
2127 {
2228 var attributes = NativeClient . GetScopedAttributes ( ) ;
2329
2430 Assert . IsEmpty ( attributes ) ;
2531 }
2632
33+ [ Test ]
2734 public void LegacyAttributesSupport_ShouldIncludeLegacyAttributesWhenScopedAttributesAreNotAvailable_AllLegacyAttributesArePresent ( )
2835 {
2936 string testVersion = "0.1.0" ;
@@ -82,7 +89,6 @@ public void NativeCrashUploadAttributesSetting_ShouldReadPlayerPrefsWithLegacyAt
8289 [ Test ]
8390 public void NativeCrashUploadAttributes_ShouldSetScopedAttributeViaNativeClientApi_AttributePresentsInScopedAttributes ( )
8491 {
85-
8692 var configuration = ScriptableObject . CreateInstance < BacktraceConfiguration > ( ) ;
8793 configuration . SendUnhandledGameCrashesOnGameStartup = true ;
8894 const string testAttributeKey = "foo-key-bar-baz" ;
@@ -93,7 +99,6 @@ public void NativeCrashUploadAttributes_ShouldSetScopedAttributeViaNativeClientA
9399 var scopedAttributes = NativeClient . GetScopedAttributes ( ) ;
94100
95101 Assert . AreEqual ( scopedAttributes [ testAttributeKey ] , testAttributeValue ) ;
96-
97102 }
98103
99104 [ Test ]
@@ -119,6 +124,63 @@ public void NativeCrashAttributesCleanMethod_ShouldCleanAllScopedAttribtues_Scop
119124 Assert . IsNotEmpty ( attributesBeforeCleanup ) ;
120125 }
121126
127+ [ Test ]
128+ public void ScopedAttributes_ShouldNotDuplicateKeys_OnRepeatedAdds ( )
129+ {
130+ var configuration = ScriptableObject . CreateInstance < BacktraceConfiguration > ( ) ;
131+ configuration . SendUnhandledGameCrashesOnGameStartup = true ;
132+
133+ const string k = "dup-key" ;
134+ const string v = "v1" ;
135+
136+ var client = new NativeClient ( configuration , null , new Dictionary < string , string > ( ) , new List < string > ( ) ) ;
137+
138+ // Add the same key/value multiple times
139+ client . SetAttribute ( k , v ) ;
140+ client . SetAttribute ( k , v ) ;
141+ client . SetAttribute ( k , v ) ;
142+
143+ // Verify the stored list
144+ var scoped = NativeClient . GetScopedAttributes ( ) ;
145+ Assert . IsTrue ( scoped . ContainsKey ( k ) ) ;
146+ Assert . AreEqual ( v , scoped [ k ] ) ;
147+
148+ // Inspect the JSON list to ensure one occurrence of the key
149+ var json = PlayerPrefs . GetString ( ScopedKeyList ) ;
150+ var occurrences = json . Split ( '"' ) ;
151+ int count = 0 ;
152+ foreach ( var s in occurrences )
153+ {
154+ if ( s == k ) count ++ ;
155+ }
156+ Assert . AreEqual ( 1 , count , "Key should be stored once in the scoped key list." ) ;
157+ }
158+
159+ [ Test ]
160+ public void ScopedAttributes_ShouldSkipWrites_WhenValueUnchanged ( )
161+ {
162+ var configuration = ScriptableObject . CreateInstance < BacktraceConfiguration > ( ) ;
163+ configuration . SendUnhandledGameCrashesOnGameStartup = true ;
164+
165+ const string k = "stable-key" ;
166+ const string v = "same-value" ;
167+
168+ var client = new NativeClient ( configuration , null , new Dictionary < string , string > ( ) , new List < string > ( ) ) ;
169+
170+ // First write
171+ client . SetAttribute ( k , v ) ;
172+ var json1 = PlayerPrefs . GetString ( ScopedKeyList ) ;
173+ var val1 = PlayerPrefs . GetString ( string . Format ( ScopedValuePattern , k ) ) ;
174+
175+ // Second write with same value should be a no-op
176+ client . SetAttribute ( k , v ) ;
177+ var json2 = PlayerPrefs . GetString ( ScopedKeyList ) ;
178+ var val2 = PlayerPrefs . GetString ( string . Format ( ScopedValuePattern , k ) ) ;
179+
180+ Assert . AreEqual ( json1 , json2 , "Scoped key list JSON should not change when value is unchanged." ) ;
181+ Assert . AreEqual ( val1 , val2 , "Stored value should remain the same." ) ;
182+ }
183+
122184 private void CleanLegacyAttributes ( )
123185 {
124186 PlayerPrefs . DeleteKey ( NativeClient . VersionKey ) ;
0 commit comments