@@ -73,4 +73,99 @@ final class OneSignalCoreTests: XCTestCase {
7373 XCTAssertEqual ( templateId , " templateId123 " )
7474 XCTAssertEqual ( templateName , " Template name " )
7575 }
76+
77+ // MARK: - Codeable data guards
78+
79+ private let guardKey = " OS_CORE_TESTS_CODEABLE_GUARD "
80+
81+ func testOversizedCodeableDataIsDroppedNotDecoded ( ) {
82+ let defaults = OneSignalUserDefaults . initShared ( )
83+ // A real archive over the cap; without the cap it would decode.
84+ defaults . saveCodeableData ( forKey : guardKey , withValue : [ String ( repeating : " x " , count : 2048 ) ] )
85+
86+ let restored = defaults . getSavedCodeableData ( forKey : guardKey , defaultValue : [ " default " ] , maxBytes : 1024 ) as? [ String ]
87+
88+ XCTAssertEqual ( restored , [ " default " ] )
89+ XCTAssertFalse ( defaults . keyExists ( guardKey ) )
90+ }
91+
92+ func testCodeableDataWithinTheLimitIsDecoded ( ) {
93+ let defaults = OneSignalUserDefaults . initShared ( )
94+ defaults . saveCodeableData ( forKey : guardKey , withValue : [ " cached " ] )
95+
96+ let restored = defaults . getSavedCodeableData ( forKey : guardKey , defaultValue : [ ] , maxBytes : 1024 ) as? [ String ]
97+
98+ XCTAssertEqual ( restored , [ " cached " ] )
99+ defaults . removeValue ( forKey : guardKey )
100+ }
101+
102+ func testUnreadableCodeableDataReturnsTheDefaultWithoutThrowing ( ) {
103+ let defaults = OneSignalUserDefaults . initShared ( )
104+ defaults . saveObject ( forKey : guardKey , withValue : Data ( " not an archive " . utf8 ) )
105+
106+ let restored = defaults . getSavedCodeableData ( forKey : guardKey , defaultValue : [ " default " ] ) as? [ String ]
107+
108+ XCTAssertEqual ( restored , [ " default " ] )
109+ defaults . removeValue ( forKey : guardKey )
110+ }
111+
112+ func testUnarchivableValueLeavesTheCachedValueInPlace ( ) {
113+ let defaults = OneSignalUserDefaults . initShared ( )
114+ defaults . saveCodeableData ( forKey : guardKey , withValue : [ " cached " ] )
115+
116+ // NSObject does not adopt NSCoding, so archiving it raises.
117+ defaults . saveCodeableData ( forKey : guardKey , withValue : NSObject ( ) )
118+
119+ XCTAssertEqual ( defaults . getSavedCodeableData ( forKey : guardKey , defaultValue : nil ) as? [ String ] , [ " cached " ] )
120+ defaults . removeValue ( forKey : guardKey )
121+ }
122+
123+ func testValueOverTheLimitIsNotCachedAndThePreviousBlobGoesWithIt ( ) {
124+ let defaults = OneSignalUserDefaults . initShared ( )
125+ defaults . saveCodeableData ( forKey : guardKey , withValue : [ " cached " ] )
126+
127+ defaults . saveCodeableData ( forKey : guardKey , withValue : Data ( count : Int ( OS_USER_DEFAULTS_MAX_VALUE_BYTES ) ) )
128+
129+ XCTAssertFalse ( defaults . keyExists ( guardKey ) )
130+ }
131+
132+ func testBlobThatIsNotArchivedDataIsRemovedOnlyByACappedRead ( ) {
133+ let defaults = OneSignalUserDefaults . initShared ( )
134+ defaults . saveObject ( forKey : guardKey , withValue : " not data " )
135+
136+ XCTAssertEqual ( defaults . getSavedCodeableData ( forKey : guardKey , defaultValue : [ " default " ] ) as? [ String ] , [ " default " ] )
137+ XCTAssertTrue ( defaults . keyExists ( guardKey ) )
138+
139+ XCTAssertEqual ( defaults . getSavedCodeableData ( forKey : guardKey , defaultValue : [ " default " ] , maxBytes : 1024 ) as? [ String ] , [ " default " ] )
140+ XCTAssertFalse ( defaults . keyExists ( guardKey ) )
141+ }
142+
143+ func testUndecodableBlobIsRemovedOnlyByACappedRead ( ) throws {
144+ let defaults = OneSignalUserDefaults . initShared ( )
145+ defaults . saveObject ( forKey : guardKey , withValue : try archiveNamingAnUnknownClass ( ) )
146+
147+ XCTAssertEqual ( defaults . getSavedCodeableData ( forKey : guardKey , defaultValue : [ " default " ] ) as? [ String ] , [ " default " ] )
148+ XCTAssertTrue ( defaults . keyExists ( guardKey ) )
149+
150+ XCTAssertEqual ( defaults . getSavedCodeableData ( forKey : guardKey , defaultValue : [ " default " ] , maxBytes : 1024 ) as? [ String ] , [ " default " ] )
151+ XCTAssertFalse ( defaults . keyExists ( guardKey ) )
152+ }
153+
154+ /// A keyed archive whose root is of a class this process does not have. Decoding it raises, as a queue written
155+ /// by a newer build does.
156+ private func archiveNamingAnUnknownClass ( ) throws -> Data {
157+ let archive = try NSKeyedArchiver . archivedData ( withRootObject : [ " cached " ] , requiringSecureCoding : false )
158+ var plist = try XCTUnwrap ( try PropertyListSerialization . propertyList ( from : archive , format : nil ) as? [ String : Any ] )
159+ var objects = try XCTUnwrap ( plist [ " $objects " ] as? [ Any ] )
160+ for ( index , object ) in objects . enumerated ( ) {
161+ guard var descriptor = object as? [ String : Any ] , descriptor [ " $classname " ] != nil else {
162+ continue
163+ }
164+ descriptor [ " $classname " ] = " OSNoSuchClass "
165+ descriptor [ " $classes " ] = [ " OSNoSuchClass " ]
166+ objects [ index ] = descriptor
167+ }
168+ plist [ " $objects " ] = objects
169+ return try PropertyListSerialization . data ( fromPropertyList : plist , format : . binary , options : 0 )
170+ }
76171}
0 commit comments