Skip to content
Merged
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
84 changes: 84 additions & 0 deletions UnitTests/ObjCTests/MPPersistenceControllerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#import "MPBaseTestCase.h"
#import "MPStateMachine.h"
#import "MPKitFilter.h"
#import "UploadSettingsUtils.h"
#import "MPUploadSettings.h"

@interface MParticle ()

Expand Down Expand Up @@ -1458,4 +1460,86 @@ - (void)testMaxBytesPerBatch {
XCTAssertGreaterThan(maxBytesCrash, maxBytesNormal, @"Crash batch should allow more bytes than normal batch");
}

// Simulates a row written by the old Swift module (mParticle_Apple_SDK_NoLocation)
// by manually encoding MPUploadSettings under the legacy class name and verifying
// fetchUploads can still decode and return it after the ObjC migration.
- (void)testFetchUploads_decodesLegacySwiftModuleClassName {
MPSession *session = [[MPSession alloc] initWithStartTime:[[NSDate date] timeIntervalSince1970]
userId:[MPPersistenceController_PRIVATE mpId]];
NSDictionary *uploadDictionary = @{kMPOptOutKey: @NO,
kMPSessionTimeoutKey: @120,
kMPUploadIntervalKey: @10,
kMPLifeTimeValueKey: @0,
kMPMessagesKey: @[],
kMPMessageIdKey: [[NSUUID UUID] UUIDString]};

MPUploadSettings *settings = [MPUploadSettings currentUploadSettingsWithStateMachine:[MParticle sharedInstance].stateMachine
networkOptions:[MParticle sharedInstance].networkOptions];

// Encode using the legacy Swift module class name to simulate pre-migration persisted data
[NSKeyedArchiver setClassName:@"mParticle_Apple_SDK_NoLocation.MPUploadSettings"
forClass:[MPUploadSettings class]];
NSData *legacyBlob = [NSKeyedArchiver archivedDataWithRootObject:settings
requiringSecureCoding:YES
error:nil];
// Reset so other tests are not affected
[NSKeyedArchiver setClassName:nil forClass:[MPUploadSettings class]];

MPUpload *upload = [[MPUpload alloc] initWithSessionId:@(session.sessionId)
uploadDictionary:uploadDictionary
dataPlanId:nil
dataPlanVersion:nil
uploadSettings:settings];

MPPersistenceController_PRIVATE *persistence = [MParticle sharedInstance].persistenceController;
[persistence saveUpload:upload];

// Patch the saved row's upload_settings blob with the legacy-encoded version
// to simulate the state of a DB row written before the Swift→ObjC migration
NSString *dbPath = [persistence valueForKey:@"databasePath"];
sqlite3 *db;
sqlite3_open([dbPath UTF8String], &db);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(db, "UPDATE uploads SET upload_settings = ? WHERE _id = ?", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, legacyBlob.bytes, (int)legacyBlob.length, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 2, upload.uploadId);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);

NSArray<MPUpload *> *fetched = [persistence fetchUploads];
XCTAssertEqual(fetched.count, 1, @"Upload with legacy class name should be decoded and returned by fetchUploads");
XCTAssertEqualObjects(fetched.firstObject.uploadSettings.apiKey, settings.apiKey,
@"Decoded upload settings apiKey should match original");
}

// Verifies registerUploadSettingsClassMappings enables decoding blobs encoded
// under either the old Swift module name or the current ObjC module name.
- (void)testRegisterUploadSettingsClassMappings_decodesAllModuleNames {
MPUploadSettings *original = [MPUploadSettings currentUploadSettingsWithStateMachine:[MParticle sharedInstance].stateMachine
networkOptions:[MParticle sharedInstance].networkOptions];

NSArray *legacyNames = @[
@"mParticle_Apple_SDK_NoLocation.MPUploadSettings",
@"mParticle_Apple_SDK.MPUploadSettings"
];

for (NSString *legacyName in legacyNames) {
[NSKeyedArchiver setClassName:legacyName forClass:[MPUploadSettings class]];
NSData *blob = [NSKeyedArchiver archivedDataWithRootObject:original
requiringSecureCoding:YES
error:nil];
[NSKeyedArchiver setClassName:nil forClass:[MPUploadSettings class]];

NSError *error = nil;
MPUploadSettings *decoded = [NSKeyedUnarchiver unarchivedObjectOfClass:[MPUploadSettings class]
fromData:blob
error:&error];
XCTAssertNil(error, @"Should decode without error for class name: %@", legacyName);
XCTAssertNotNil(decoded, @"Decoded settings should not be nil for class name: %@", legacyName);
XCTAssertEqualObjects(decoded.apiKey, original.apiKey,
@"apiKey should survive round-trip for class name: %@", legacyName);
}
}

@end
9 changes: 9 additions & 0 deletions mParticle-Apple-SDK/Utils/MPUploadSettings.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@

@implementation MPUploadSettings

+ (void)initialize {
if (self == [MPUploadSettings class]) {
[NSKeyedUnarchiver setClass:[MPUploadSettings class]
forClassName:@"mParticle_Apple_SDK.MPUploadSettings"];
[NSKeyedUnarchiver setClass:[MPUploadSettings class]
forClassName:@"mParticle_Apple_SDK_NoLocation.MPUploadSettings"];
}
}

+ (BOOL)supportsSecureCoding {
return YES;
}
Expand Down
3 changes: 0 additions & 3 deletions mParticle-Apple-SDK/Utils/UploadSettingsUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

@implementation UploadSettingsUtils


+ (void)setLastUploadSettings:(nullable MPUploadSettings *)lastUploadSettings userDefaults:(MPUserDefaults*)userDefaults {
if (lastUploadSettings) {
NSError *error = nil;
Expand Down Expand Up @@ -31,8 +30,6 @@ + (nullable MPUploadSettings*)lastUploadSettingsWithUserDefaults:(MPUserDefaults
}

NSError *error = nil;
[NSKeyedArchiver setClassName:@"mParticle_Apple_SDK.MPUploadSettings" forClass:MPUploadSettings.self];
[NSKeyedArchiver setClassName:@"mParticle_Apple_SDK_NoLocation.MPUploadSettings" forClass:MPUploadSettings.self];
MPUploadSettings *settings =
[NSKeyedUnarchiver unarchivedObjectOfClass:[MPUploadSettings class]
fromData:(NSData *)obj
Expand Down
Loading