From 422f4426853e897fe177163d9928bda09982e71e Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Mon, 22 Sep 2025 15:12:17 -0400 Subject: [PATCH 1/4] test: Add test coverage for logCommerceEvent and logEvent (#414) * added test for logCommerceEvent when event is nil * full test coverage for logCommerceEvent * full test coverage for logEvent * added test for logCommerceEvent when event is nil * full test coverage for logCommerceEvent * full test coverage for logEvent * result of call with @BrandonStalnaker * remove unnecessary nil check from logCommerceEvent --- UnitTests/MParticle+PrivateMethods.h | 2 + UnitTests/MParticleTestsSwift.swift | 207 +++++++++++++++++++++ UnitTests/Mocks/MPDataPlanFilterMock.swift | 21 +++ mParticle-Apple-SDK/mParticle.m | 8 +- 4 files changed, 231 insertions(+), 7 deletions(-) diff --git a/UnitTests/MParticle+PrivateMethods.h b/UnitTests/MParticle+PrivateMethods.h index c7436f932..35751e778 100644 --- a/UnitTests/MParticle+PrivateMethods.h +++ b/UnitTests/MParticle+PrivateMethods.h @@ -12,6 +12,7 @@ - (void)startWithKeyCallback:(BOOL)firstRun options:(MParticleOptions * _Nonnull)options userDefaults:(id)userDefaults; - (void)beginTimedEventCompletionHandler:(MPEvent *)event execStatus:(MPExecStatus)execStatus; - (void)logEventCallback:(MPEvent *)event execStatus:(MPExecStatus)execStatus; +- (void)logEvent:(MPBaseEvent *)event; - (void)logCustomEvent:(MPEvent *)event; - (void)logScreenCallback:(MPEvent *)event execStatus:(MPExecStatus)execStatus; - (void)leaveBreadcrumbCallback:(MPEvent *)event execStatus:(MPExecStatus)execStatus; @@ -19,6 +20,7 @@ - (void)logExceptionCallback:(NSException * _Nonnull)exception execStatus:(MPExecStatus)execStatus message:(NSString *)message topmostContext:(id _Nullable)topmostContext; - (void)logCrashCallback:(MPExecStatus)execStatus message:(NSString * _Nullable)message; - (void)logCommerceEventCallback:(MPCommerceEvent *)commerceEvent execStatus:(MPExecStatus)execStatus; +- (void)logCommerceEvent:(MPCommerceEvent *)commerceEvent; - (void)logLTVIncreaseCallback:(MPEvent *)event execStatus:(MPExecStatus)execStatus; - (void)logNetworkPerformanceCallback:(MPExecStatus)execStatus; + (void)setSharedInstance:(MParticle *)instance; diff --git a/UnitTests/MParticleTestsSwift.swift b/UnitTests/MParticleTestsSwift.swift index 89b3f765a..a6d1b700d 100644 --- a/UnitTests/MParticleTestsSwift.swift +++ b/UnitTests/MParticleTestsSwift.swift @@ -886,6 +886,105 @@ class MParticleTestsSwift: XCTestCase { #endif } + // MARK: - logEvent + func testLogEventCalledLogCustomEvent() { + let event = MPEvent(name: "test", type: .other)! + mparticle.logEvent(event) + wait(for: [listenerController.onAPICalledExpectation!], timeout: 0.1) + XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logCustomEvent:") + } + + func testLogEventCalledLogCommerceEvent() { + let commerceEvent = MPCommerceEvent(action: .purchase)! + mparticle.logEvent(commerceEvent) + wait(for: [listenerController.onAPICalledExpectation!], timeout: 0.1) + XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logCommerceEvent:") + } + + func testLogEventWithFilterReturningNil_blocksEvent() { + let event = MPBaseEvent(eventType: .other)! + + let executor = ExecutorMock() + mparticle.setExecutor(executor) + + let backendController = MPBackendControllerMock() + mparticle.backendController = backendController + + let dataPlanFilter = MPDataPlanFilterMock() + dataPlanFilter.transformEventForBaseEventReturnValue = nil + mparticle.dataPlanFilter = dataPlanFilter + + mparticle.logEvent(event) + + // Verify listener was called + XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logEvent:") + XCTAssertTrue(listenerController.onAPICalledParameter1 === event) + + // Verify backend was called + XCTAssertTrue(backendController.logBaseEventCalled) + XCTAssertTrue(backendController.logBaseEventEventParam === event) + let completion = backendController.logBaseEventCompletionHandler! + XCTAssertNotNil(completion) + completion(event, .success) + + // Verify executor usage + XCTAssertTrue(executor.executeOnMessageQueueAsync) + + // Verify filter transform event + XCTAssertTrue(dataPlanFilter.transformEventForBaseEventCalled) + XCTAssertTrue(dataPlanFilter.transformEventForBaseEventParam === event) + + // Logger should record the blocked event message + XCTAssertEqual(receivedMessage, "mParticle -> Blocked base event from kits: \(event)") + } + + func testLogBaseEventWithFilterReturningEvent_forwardsTransformedEvent() { + let event = MPBaseEvent(eventType: .other)! + let transformedEvent = MPBaseEvent(eventType: .addToCart)! + + let executor = ExecutorMock() + mparticle.setExecutor(executor) + + let backendController = MPBackendControllerMock() + mparticle.backendController = backendController + + let dataPlanFilter = MPDataPlanFilterMock() + dataPlanFilter.transformEventForBaseEventReturnValue = transformedEvent + mparticle.dataPlanFilter = dataPlanFilter + + let kitContainer = MPKitContainerMock() + mparticle.setKitContainer(kitContainer) + + mparticle.logEvent(event) + + // Verify listener was called + XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logEvent:") + XCTAssertTrue(listenerController.onAPICalledParameter1 === event) + + // Verify backend was called + XCTAssertTrue(backendController.logBaseEventCalled) + XCTAssertTrue(backendController.logBaseEventEventParam === event) + let completion = backendController.logBaseEventCompletionHandler! + XCTAssertNotNil(completion) + completion(event, .success) + + // Verify executor usage + XCTAssertTrue(executor.executeOnMessageQueueAsync) + XCTAssertTrue(executor.executeOnMainAsync) + + // Verify filter transformed event + XCTAssertTrue(dataPlanFilter.transformEventForBaseEventCalled) + XCTAssertTrue(dataPlanFilter.transformEventForBaseEventParam === event) + + // Verify kit container forwarded transformed event + XCTAssertTrue(kitContainer.forwardSDKCallCalled) + XCTAssertEqual(kitContainer.forwardSDKCallSelectorParam?.description, "logBaseEvent:") + XCTAssertEqual(kitContainer.forwardSDKCallMessageTypeParam, .unknown) + XCTAssertTrue(kitContainer.forwardSDKCallEventParam === transformedEvent) + } + + + // MARK: - logCustomEvent func testLogCustomEventWithNilEvent_logsError() { mparticle.logCustomEvent(nil) @@ -975,5 +1074,113 @@ class MParticleTestsSwift: XCTestCase { XCTAssertEqual(kitContainer.forwardSDKCallMessageTypeParam, .event) XCTAssertTrue(kitContainer.forwardSDKCallEventParam === transformedEvent) } + + // MARK: - logCommerceEvent + + func testLogCommerceEvent_assignsTimestampWhenNil() { + let commerceEvent = MPCommerceEvent(action: .purchase)! + commerceEvent.setTimestamp(nil) + + + let executor = ExecutorMock() + mparticle.setExecutor(executor) + + let backendController = MPBackendControllerMock() + mparticle.backendController = backendController + + mparticle.logCommerceEvent(commerceEvent) + + XCTAssertNotNil(commerceEvent.timestamp) + XCTAssertTrue(backendController.logCommerceEventCalled) + XCTAssertTrue(listenerController.onAPICalledParameter1 === commerceEvent) + XCTAssertTrue(executor.executeOnMessageQueueAsync) + } + + + func testLogCommerceEventWithFilterReturningNil_blocksEvent() { + let commerceEvent = MPCommerceEvent(eventType: .other)! + + let executor = ExecutorMock() + mparticle.setExecutor(executor) + + let backendController = MPBackendControllerMock() + mparticle.backendController = backendController + + let dataPlanFilter = MPDataPlanFilterMock() + dataPlanFilter.transformEventForCommerceEventParam = nil + mparticle.dataPlanFilter = dataPlanFilter + + mparticle.logCommerceEvent(commerceEvent) + + // Verify event timestamp added + XCTAssertNotNil(commerceEvent.timestamp) + + // Verify listener was called + XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logCommerceEvent:") + XCTAssertTrue(listenerController.onAPICalledParameter1 === commerceEvent) + + // Verify backend was called + XCTAssertTrue(backendController.logCommerceEventCalled) + XCTAssertTrue(backendController.logCommerceEventParam === commerceEvent) + let completion = backendController.logCommerceEventCompletionHandler! + XCTAssertNotNil(completion) + completion(commerceEvent, .success) + + // Verify executor usage + XCTAssertTrue(executor.executeOnMessageQueueAsync) + + // Verify filter transform event + XCTAssertTrue(dataPlanFilter.transformEventForCommerceEventCalled) + XCTAssertTrue(dataPlanFilter.transformEventForCommerceEventParam === commerceEvent) + + // Logger should record the blocked event message + XCTAssertEqual(receivedMessage, "mParticle -> Blocked commerce event from kits: \(commerceEvent)") + } + + + func testLogCommerceEventWithFilterReturningEvent_forwardsTransformedEvent() { + let commerceEvent = MPCommerceEvent(eventType: .other)! + let transformedCommerceEvent = MPCommerceEvent(eventType: .viewDetail)! + + let executor = ExecutorMock() + mparticle.setExecutor(executor) + + let backendController = MPBackendControllerMock() + mparticle.backendController = backendController + + let dataPlanFilter = MPDataPlanFilterMock() + dataPlanFilter.transformEventForCommerceEventReturnValue = transformedCommerceEvent + mparticle.dataPlanFilter = dataPlanFilter + + let kitContainer = MPKitContainerMock() + mparticle.setKitContainer(kitContainer) + + mparticle.logCommerceEvent(commerceEvent) + + // Verify event timestamp added + XCTAssertNotNil(commerceEvent.timestamp) + + // Verify listener was called + XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logCommerceEvent:") + XCTAssertTrue(listenerController.onAPICalledParameter1 === commerceEvent) + + // Verify backend was called + XCTAssertTrue(backendController.logCommerceEventCalled) + XCTAssertTrue(backendController.logCommerceEventParam === commerceEvent) + let completion = backendController.logCommerceEventCompletionHandler! + XCTAssertNotNil(completion) + completion(commerceEvent, .success) + + // Verify executor usage + XCTAssertTrue(executor.executeOnMessageQueueAsync) + + // Verify filter transformed event + XCTAssertTrue(dataPlanFilter.transformEventForCommerceEventCalled) + XCTAssertTrue(dataPlanFilter.transformEventForCommerceEventParam === commerceEvent) + + // Verify kit container forwarded transformed event + XCTAssertTrue(kitContainer.forwardCommerceEventCallCalled) + XCTAssertTrue(kitContainer.forwardCommerceEventCallCommerceEventParam === transformedCommerceEvent) + } } diff --git a/UnitTests/Mocks/MPDataPlanFilterMock.swift b/UnitTests/Mocks/MPDataPlanFilterMock.swift index 60767b6db..155b9435c 100644 --- a/UnitTests/Mocks/MPDataPlanFilterMock.swift +++ b/UnitTests/Mocks/MPDataPlanFilterMock.swift @@ -5,6 +5,7 @@ import mParticle_Apple_SDK_NoLocation import mParticle_Apple_SDK #endif +@objcMembers class MPDataPlanFilterMock: NSObject, MPDataPlanFilterProtocol { var isBlockedUserIdentityTypeCalled = false var isBlockedUserIdentityTypeUserIdentityTypeParam: MPIdentity? @@ -45,4 +46,24 @@ class MPDataPlanFilterMock: NSObject, MPDataPlanFilterProtocol { transformEventForScreenEventScreenEventParam = screenEvent return transformEventForScreenEventReturnValue } + + var transformEventForCommerceEventCalled = false + var transformEventForCommerceEventParam: MPCommerceEvent? + var transformEventForCommerceEventReturnValue: MPCommerceEvent? + + func transformEvent(forCommerceEvent commerceEvent: MPCommerceEvent) -> MPCommerceEvent? { + transformEventForCommerceEventCalled = true + transformEventForCommerceEventParam = commerceEvent + return transformEventForCommerceEventReturnValue + } + + var transformEventForBaseEventCalled = false + var transformEventForBaseEventParam: MPBaseEvent? + var transformEventForBaseEventReturnValue: MPBaseEvent? + + func transformEvent(forBaseEvent baseEvent: MPBaseEvent) -> MPBaseEvent? { + transformEventForBaseEventCalled = true + transformEventForBaseEventParam = baseEvent + return transformEventForBaseEventReturnValue + } } diff --git a/mParticle-Apple-SDK/mParticle.m b/mParticle-Apple-SDK/mParticle.m index b76fc1f7f..c5e4e1bcf 100644 --- a/mParticle-Apple-SDK/mParticle.m +++ b/mParticle-Apple-SDK/mParticle.m @@ -799,9 +799,7 @@ - (MPEvent *)eventWithName:(NSString *)eventName { } - (void)logEvent:(MPBaseEvent *)event { - if (event == nil) { - [logger error:@"Cannot log nil event!"]; - } else if ([event isKindOfClass:[MPEvent class]]) { + if ([event isKindOfClass:[MPEvent class]]) { [self logCustomEvent:(MPEvent *)event]; } else if ([event isKindOfClass:[MPCommerceEvent class]]) { #pragma clang diagnostic push @@ -1188,10 +1186,6 @@ - (void)logCommerceEventCallback:(MPCommerceEvent *)commerceEvent execStatus:(MP } - (void)logCommerceEvent:(MPCommerceEvent *)commerceEvent { - if (commerceEvent == nil) { - [logger error:@"Cannot log nil commerce event!"]; - return; - } if (!commerceEvent.timestamp) { commerceEvent.timestamp = [NSDate date]; } From 7357c77a48774981b1e3f27bb1c1b4ba80825c62 Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Tue, 23 Sep 2025 13:30:51 -0400 Subject: [PATCH 2/4] test: add test coverage for log ltv increase methods (#417) * Organize tests into a mark * added full test coverage for LTVIncrease * fix misspell of mock parameter * add comments to make more readable --- UnitTests/MParticleTestsSwift.swift | 156 +++++++++++++++++------ UnitTests/Mocks/MPKitContainerMock.swift | 4 +- mParticle-Apple-SDK/mParticle.m | 4 - 3 files changed, 122 insertions(+), 42 deletions(-) diff --git a/UnitTests/MParticleTestsSwift.swift b/UnitTests/MParticleTestsSwift.swift index a6d1b700d..f27e25802 100644 --- a/UnitTests/MParticleTestsSwift.swift +++ b/UnitTests/MParticleTestsSwift.swift @@ -339,32 +339,6 @@ class MParticleTestsSwift: XCTestCase { ) } - func testLogLTVIncreaseCallbackDataFilterNotSet() { - XCTAssertNil(mparticle.dataPlanFilter) - mparticle.logLTVIncreaseCallback(MPEvent(), execStatus: .success) - - XCTAssertNil(receivedMessage) - } - - func testLogLTVIncreaseCallbackDataFilterSetDataFilterReturnNil() { - let dataPlanFilter = MPDataPlanFilterMock() - mparticle.dataPlanFilter = dataPlanFilter - let expectedEvent = MPEvent() - mparticle.logLTVIncreaseCallback(expectedEvent, execStatus: .success) - - XCTAssertTrue(dataPlanFilter.transformEventCalled) - XCTAssertTrue(dataPlanFilter.transformEventEventParam === expectedEvent) - - XCTAssertEqual(receivedMessage, """ - mParticle -> Blocked LTV increase event from kits: Event:{ - Name: <> - Type: Other - Duration: 0 - } - """ - ) - } - func testLogNetworkPerformanceCallbackSuccess() { mparticle.logNetworkPerformanceCallback(.success) @@ -474,16 +448,6 @@ class MParticleTestsSwift: XCTestCase { XCTAssertTrue(listenerController.onAPICalledParameter1 === expectedEvent) } - - func testLogLTVIncreaseListenerControllerCalled() { - mparticle.logLTVIncrease(2.0, eventName: "name", eventInfo: [:]) - wait(for: [listenerController.onAPICalledExpectation!], timeout: 0.1) - XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logLTVIncrease:eventName:eventInfo:") - XCTAssertEqual(listenerController.onAPICalledParameter1 as? Double, 2.0) - XCTAssertEqual(listenerController.onAPICalledParameter2 as? String, "name") - XCTAssertEqual(listenerController.onAPICalledParameter3 as? [String: String], [:]) - } - func testSetIntegrationAttributesListenerControllerCalled() { mparticle.setIntegrationAttributes(["test": "test"], forKit: NSNumber(value: 1)) wait(for: [listenerController.onAPICalledExpectation!], timeout: 0.1) @@ -1182,5 +1146,125 @@ class MParticleTestsSwift: XCTestCase { XCTAssertTrue(kitContainer.forwardCommerceEventCallCalled) XCTAssertTrue(kitContainer.forwardCommerceEventCallCommerceEventParam === transformedCommerceEvent) } + + // MARK: - logLTVIncrease + + func testLogLTVIncrease_withNameAndInfo_createsEventAndCallsBackend() { + let amount = 42.0 + let name = "name" + let info: [String: Any] = ["source": "in_app", "currency": "USD"] + + let backendController = MPBackendControllerMock() + mparticle.backendController = backendController + + mparticle.logLTVIncrease(amount, eventName: name, eventInfo: info) + + // Assert event was passed through + let loggedEvent = backendController.logEventEventParam! + XCTAssertNotNil(loggedEvent) + XCTAssertEqual(loggedEvent.name, name) + XCTAssertEqual(loggedEvent.type, .transaction) + + // Custom attributes should include amount and method name + let attrs = loggedEvent.customAttributes! + XCTAssertEqual(attrs["$Amount"] as? Double, amount) + XCTAssertEqual(attrs["$MethodName"] as? String, "LogLTVIncrease") + + // Check that the eventInfo entries were added + XCTAssertEqual(attrs["source"] as? String, "in_app") + XCTAssertEqual(attrs["currency"] as? String, "USD") + XCTAssertEqual(attrs.count, 4) + + // Listener controller should be notified + XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logLTVIncrease:eventName:eventInfo:") + + // Backend completion handler should be stored + XCTAssertTrue(backendController.logEventCalled) + let completion = backendController.logEventCompletionHandler! + XCTAssertNotNil(completion) + completion(loggedEvent, .success) + } + + func testLogLTVIncrease_withoutEventInfo_defaultsToNilInfo() { + let amount = 12.5 + let name = "name" + + let backendController = MPBackendControllerMock() + mparticle.backendController = backendController + + mparticle.logLTVIncrease(amount, eventName: name) + + // Assert event was passed through + let loggedEvent = backendController.logEventEventParam! + XCTAssertNotNil(loggedEvent) + XCTAssertEqual(loggedEvent.name, name) + XCTAssertEqual(loggedEvent.type, .transaction) + + // Custom attributes should only be amount and method name + let attrs = loggedEvent.customAttributes! + XCTAssertEqual(attrs["$Amount"] as? Double, amount) + XCTAssertEqual(attrs["$MethodName"] as? String, "LogLTVIncrease") + XCTAssertEqual(attrs.count, 2) + + // Listener controller should be notified + XCTAssertEqual(listenerController.onAPICalledApiName?.description, "logLTVIncrease:eventName:eventInfo:") + XCTAssertEqual(listenerController.onAPICalledParameter1 as? Double, amount) + XCTAssertEqual(listenerController.onAPICalledParameter2 as? String, name) + XCTAssertNil(listenerController.onAPICalledParameter3) + + // Backend completion handler should be stored + XCTAssertTrue(backendController.logEventCalled) + let completion = backendController.logEventCompletionHandler! + XCTAssertNotNil(completion) + completion(loggedEvent, .success) + } + + func testLogLTVIncreaseCallback_withSuccessExecStatus_noDataPlanFilter_forwardsEvent() { + let event = MPEvent(name: "ltv", type: .transaction)! + + let dataPlanFilter = MPDataPlanFilterMock() + dataPlanFilter.transformEventReturnValue = nil + mparticle.dataPlanFilter = dataPlanFilter + + let kitContainer = MPKitContainerMock() + mparticle.setKitContainer(kitContainer) + + mparticle.logLTVIncreaseCallback(event, execStatus: .success) + + XCTAssertTrue(dataPlanFilter.transformEventCalled) + XCTAssertTrue(dataPlanFilter.transformEventEventParam === event) + XCTAssertEqual(receivedMessage, "mParticle -> Blocked LTV increase event from kits: \(event)") + } + + func testLogLTVIncreaseCallback_withSuccessExecStatus_filterReturnsTransformedEvent_forwardsTransformedEvent() { + let event = MPEvent(name: "ltv", type: .transaction)! + let transformedEvent = MPEvent(name: "transformed-ltv", type: .other)! + + let dataPlanFilter = MPDataPlanFilterMock() + dataPlanFilter.transformEventReturnValue = transformedEvent + mparticle.dataPlanFilter = dataPlanFilter + + let executor = ExecutorMock() + mparticle.setExecutor(executor) + + let kitContainer = MPKitContainerMock() + mparticle.setKitContainer(kitContainer) + + mparticle.logLTVIncreaseCallback(event, execStatus: .success) + + // Verify filter transformed event + XCTAssertTrue(dataPlanFilter.transformEventCalled) + XCTAssertTrue(dataPlanFilter.transformEventEventParam === event) + + // Verify executor usage + XCTAssertTrue(executor.executeOnMainAsync) + + // Verify kit container forwarded transformed event + XCTAssertTrue(kitContainer.forwardSDKCallCalled) + XCTAssertEqual(kitContainer.forwardSDKCallSelectorParam?.description, "logLTVIncrease:event:") + XCTAssertEqual(kitContainer.forwardSDKCallMessageTypeParam, .unknown) + XCTAssertNil(kitContainer.forwardSDKCallEventParam) + } + } diff --git a/UnitTests/Mocks/MPKitContainerMock.swift b/UnitTests/Mocks/MPKitContainerMock.swift index 97f2c66cf..5d2972295 100644 --- a/UnitTests/Mocks/MPKitContainerMock.swift +++ b/UnitTests/Mocks/MPKitContainerMock.swift @@ -42,14 +42,14 @@ class MPKitContainerMock: MPKitContainerProtocol { forwardSDKCallExpectation?.fulfill() } - var forwardSDKCallBathParam: [AnyHashable : Any]? + var forwardSDKCallBatchParam: [AnyHashable : Any]? func forwardSDKCall(_ selector: Selector, batch: [AnyHashable : Any], kitHandler: @escaping (any MPKitProtocol, [AnyHashable : Any], MPKitConfiguration) -> Void) { forwardSDKCallCalled = true forwardSDKCallSelectorParam = selector - forwardSDKCallBathParam = batch + forwardSDKCallBatchParam = batch forwardSDKCallKitHandlerParam = kitHandler forwardSDKCallExpectation?.fulfill() } diff --git a/mParticle-Apple-SDK/mParticle.m b/mParticle-Apple-SDK/mParticle.m index c5e4e1bcf..f3d12492e 100644 --- a/mParticle-Apple-SDK/mParticle.m +++ b/mParticle-Apple-SDK/mParticle.m @@ -1242,10 +1242,6 @@ - (void)logLTVIncrease:(double)increaseAmount eventName:(NSString *)eventName ev [eventDictionary addEntriesFromDictionary:eventInfo]; } - if (!eventName) { - eventName = @"Increase LTV"; - } - MPEvent *event = [[MPEvent alloc] initWithName:eventName type:MPEventTypeTransaction]; event.customAttributes = eventDictionary; From 5e3464739624c1cf6535a8a038705f7a7b39302e Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Thu, 25 Sep 2025 12:07:18 -0400 Subject: [PATCH 3/4] docs: Updated readme file and add contributing and release file --- CONTRIBUTING.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 ++++ RELEASE.md | 80 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 RELEASE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..516eb24a1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contributing to mParticle Apple SDK + +Thanks for contributing! Please read this document to follow our conventions for contributing to the mParticle SDK. + +## Setting Up + +1. Fork the repository and then clone down your fork +2. Commit your code per the conventions below, and PR into the mParticle SDK main branch +3. Your PR title will be checked automatically against the below convention (view the commit history to see examples of a proper commit/PR title). If it fails, you must update your title +4. Our engineers will work with you to get your code change implemented once a PR is up + +## Development Process + +1. Create your branch from `main` +2. Make your changes +3. Add tests for any new functionality +4. Run the test suite to ensure tests (both new and old) all pass +5. Update the documentation +6. Create a Pull Request + +### Pull Requests + +* Fill in the required template +* Follow the [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/) +* Include screenshots and animated GIFs in your pull request whenever possible +* End all files with a newline + +### PR Title and Commit Convention + +PR titles should follow conventional commit standards. This helps automate the release process. + +The standard format for commit messages is as follows: + +``` +[optional scope]: + +[optional body] + +[optional footer] +``` + +The following lists the different types allowed in the commit message: + +- **feat**: A new feature (automatic minor release) +- **fix**: A bug fix (automatic patch release) +- **docs**: Documentation only changes +- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) +- **refactor**: A code change that neither fixes a bug nor adds a feature +- **perf**: A code change that improves performance +- **test**: Adding missing or correcting existing tests +- **chore**: Changes that don't modify src or test files, such as automatic documentation generation, or building latest assets +- **ci**: Changes to CI configuration files/scripts +- **revert**: Revert commit +- **build**: Changes that affect the build system or other dependencies + +### Testing + +We use XCTest framework for our testing. Please write tests for new code you create. Before submitting your PR, ensure all tests pass by running: + +#### Build and Test +```bash +xcodebuild -workspace mParticle-Apple-SDK.xcworkspace -scheme mParticle-Apple-SDK-iOS test +``` + +#### SwiftLint +```bash +swiftlint +``` + +Make sure all tests pass successfully before submitting your PR. If you encounter any test failures, investigate and fix the issues before proceeding. + +### Reporting Bugs + +This section guides you through submitting a bug report for the mParticle Apple SDK. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports. + +To notify our team about an issue, please submit a ticket through our [mParticles support page](https://support.mparticle.com/hc/en-us/requests/new). + +**When you are creating a ticket, please include as many details as possible:** + +* Use a clear and descriptive title +* Describe the exact steps which reproduce the problem +* Provide specific examples to demonstrate the steps +* Describe the behavior you observed after following the steps +* Explain which behavior you expected to see instead and why +* Include console output and stack traces if applicable +* Include your SDK version and iOS/macOS version + +## License + +By contributing to the mParticle Apple SDK, you agree that your contributions will be licensed under its [Apache License 2.0](LICENSE). diff --git a/README.md b/README.md index 5382c835d..ee57b9095 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ # mParticle Apple SDK +A single SDK to collect analytics data and send it to 100+ marketing, analytics, and data platforms. Simplify your data integration with a single API. + This is the mParticle Apple SDK for iOS and tvOS. At mParticle our mission is straightforward: make it really easy for apps and app services to connect and allow you to take ownership of your 1st party data. @@ -236,6 +238,10 @@ Just by initializing the SDK you'll be set up to track user installs, engagement * [SDK Documentation](http://docs.mparticle.com/#mobile-sdk-guide) +## Contributing + +We welcome contributions! If you're interested in contributing to the mParticle Apple SDK, please read our [Contributing Guidelines](CONTRIBUTING.md). + ## Support Questions? Have an issue? Read the [docs](https://docs.mparticle.com/developers/sdk/ios/) or contact our **Customer Success** team at . diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..411978f47 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,80 @@ +# Release Process + +This document outlines the process for creating a new release of the mParticle Apple SDK. + +## Automated Release Process + +We use GitHub Actions to automate our release process. Follow these steps to create a new release: + +### Pre-release Checklist +- Ensure all commits are in the public main branch +- Review `sdk-release.yml` in the repo for specific workflow details +- The release job deploys the most current snapshot of main branch release tag to main branch + +## Step 2: Release via GitHub Actions + +### What the GitHub Release Job Does + +1. **Initial Setup** + - Verifies job is running from public repo and on main branch + - Creates temporary `release/{run_number}` branch + +2. **Testing Phase** + - Runs unit tests for iOS and tvOS platforms + - Validates CocoaPods spec + - Validates Carthage build + - Validates Swift Package Manager build + - Updates kits and runs additional tests + +3. **Version Management** + - Runs semantic version action + - Automatically bumps version based on commit messages + - No version bump if no new commits (e.g., feat/fix) + - Generates release notes automatically + +4. **Artifact Publishing** + - Publishes to package managers: + - Pushes to CocoaPods trunk + - Updates Carthage JSON spec + - Updates Swift Package Manager + - Creates GitHub release with artifacts + + + +### How to Release + +1. Navigate to the Actions tab in GitHub +2. Select "iOS SDK Release" workflow +3. Run the workflow from main branch with "true" first to perform a dry run + > Important: Always start with a dry run to validate the release process. This will perform all steps up to semantic release without actually publishing, helping catch potential issues early. +4. If the dry run succeeds, run the workflow again with "false" option to perform the actual release + > Note: Only proceed with the actual release after confirming a successful dry run + +### Important Notes + +- **Release Duration**: Expect ~30 minutes due to comprehensive test suite across platforms +- **Platform Requirements**: + - Tests run on macOS runners + - Multiple Xcode versions may be tested + - Both iOS and tvOS platforms are validated +- **Code Reusability**: + - Reusable GitHub Actions are defined in the [mparticle-workflows repo](https://github.com/mParticle/mparticle-workflows) + - This enables other platforms to reuse similar jobs + +## Post-Release Verification + +After a successful build through GitHub Actions, verify: +1. Public repo has a new semantic release tag +2. New version is available on: + - [CocoaPods](https://cocoapods.org/pods/mParticle-Apple-SDK) + - [Carthage](https://github.com/mParticle/mparticle-apple-sdk/releases) + - Swift Package Manager + +## Troubleshooting + +If you encounter issues during testing, check: +- Xcode version compatibility +- Platform-specific test failures (iOS vs tvOS) +- GitHub Actions logs for specific error messages +- CocoaPods trunk status +- Carthage binary framework validation From 342d2df2937d048fc50a6ca013c0abafd272b727 Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Thu, 25 Sep 2025 13:34:45 -0400 Subject: [PATCH 4/4] remove Carthage support note from RELEASE.md --- RELEASE.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 411978f47..61b1bf808 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -22,7 +22,6 @@ We use GitHub Actions to automate our release process. Follow these steps to cre 2. **Testing Phase** - Runs unit tests for iOS and tvOS platforms - Validates CocoaPods spec - - Validates Carthage build - Validates Swift Package Manager build - Updates kits and runs additional tests @@ -35,7 +34,6 @@ We use GitHub Actions to automate our release process. Follow these steps to cre 4. **Artifact Publishing** - Publishes to package managers: - Pushes to CocoaPods trunk - - Updates Carthage JSON spec - Updates Swift Package Manager - Creates GitHub release with artifacts @@ -67,7 +65,6 @@ After a successful build through GitHub Actions, verify: 1. Public repo has a new semantic release tag 2. New version is available on: - [CocoaPods](https://cocoapods.org/pods/mParticle-Apple-SDK) - - [Carthage](https://github.com/mParticle/mparticle-apple-sdk/releases) - Swift Package Manager ## Troubleshooting @@ -76,5 +73,4 @@ If you encounter issues during testing, check: - Xcode version compatibility - Platform-specific test failures (iOS vs tvOS) - GitHub Actions logs for specific error messages -- CocoaPods trunk status -- Carthage binary framework validation +- CocoaPods trunk status \ No newline at end of file