Skip to content

Commit

Permalink
Merge branch 'release/v1.4.0'
Browse files Browse the repository at this point in the history
# Conflicts:
#	Ometria.xcodeproj/project.xcworkspace/xcuserdata/cata.xcuserdatad/UserInterfaceState.xcuserstate
  • Loading branch information
CataD-Tapp committed May 17, 2023
2 parents bdbabdb + 0aa9c96 commit 29f061e
Show file tree
Hide file tree
Showing 21 changed files with 890 additions and 136 deletions.
206 changes: 197 additions & 9 deletions Ometria Sample/OmetriaSample.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4EC94EE724BC52D000B8591C"
BuildableName = "OmetriaSample.app"
BlueprintName = "OmetriaSample"
ReferencedContainer = "container:OmetriaSample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4EC94EE724BC52D000B8591C"
BuildableName = "OmetriaSample.app"
BlueprintName = "OmetriaSample"
ReferencedContainer = "container:OmetriaSample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4EC94EE724BC52D000B8591C"
BuildableName = "OmetriaSample.app"
BlueprintName = "OmetriaSample"
ReferencedContainer = "container:OmetriaSample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4E46F65629EE9E5E009E384E"
BuildableName = "OmetriaSampleTests.xctest"
BlueprintName = "OmetriaSampleTests"
ReferencedContainer = "container:OmetriaSample.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
31 changes: 31 additions & 0 deletions Ometria Sample/OmetriaSampleTests/EventCache+Mock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// EventCache+Mock.swift
// OmetriaSampleTests
//
// Created by Catalin Demian on 18.04.2023.
// Copyright © 2023 Ometria. All rights reserved.
//

import Foundation
@testable import Ometria
@testable import OmetriaSample

class MockInMemoryEventCache: EventCaching {
var events: [OmetriaEvent]?
let saveHandler: () -> Void

init(saveHandler: @escaping () -> Void) {
self.saveHandler = saveHandler
}

func saveToFile(_ events: [OmetriaEvent]?, async: Bool) {
self.events = events
DispatchQueue.main.async { [weak self] in
self?.saveHandler()
}
}

func loadFromFile() -> [OmetriaEvent]? {
return events
}
}
64 changes: 64 additions & 0 deletions Ometria Sample/OmetriaSampleTests/EventService+Mock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// EventService+Mock.swift
// OmetriaSampleTests
//
// Created by Catalin Demian on 18.04.2023.
// Copyright © 2023 Ometria. All rights reserved.
//

import Foundation
@testable import Ometria
@testable import OmetriaSample

extension OmetriaEvent: Hashable {
public static func == (lhs: OmetriaEvent, rhs: OmetriaEvent) -> Bool {
return lhs.eventId == rhs.eventId
}

public func hash(into hasher: inout Hasher) {
hasher.combine(eventId)
}
}

struct MockSuccessEventService: EventServiceProtocol {
func validateEvents(_ events: [OmetriaEvent], completion: @escaping (Result<Any>) -> ()) {
completion(.success(""))
}

func flushEvents(_ events: [OmetriaEvent], completion: @escaping (Result<Any>) -> ()) {
completion(.success(""))
}
}

class MockSuccessAndCountEventService: EventServiceProtocol {
var uniqueFlushedEvents: Set<OmetriaEvent> = []

func validateEvents(_ events: [OmetriaEvent], completion: @escaping (Result<Any>) -> ()) {
completion(.success(""))
}

func flushEvents(_ events: [OmetriaEvent], completion: @escaping (Result<Any>) -> ()) {
Task {
try await Task.sleep(nanoseconds: 1_000_000_000)
await MainActor.run {
uniqueFlushedEvents.formUnion(events)
}
completion(.success(""))
}
}
}

enum MockEventError: Error {
case failed
}

struct MockFailureEventService: EventServiceProtocol {
func validateEvents(_ events: [OmetriaEvent], completion: @escaping (Result<Any>) -> ()) {
completion(.failure(MockEventError.failed))
}

func flushEvents(_ events: [OmetriaEvent], completion: @escaping (Result<Any>) -> ()) {
completion(.failure(MockEventError.failed))
}
}

158 changes: 158 additions & 0 deletions Ometria Sample/OmetriaSampleTests/OmetriaSampleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//
// OmetriaSampleTests.swift
// OmetriaSampleTests
//
// Created by Catalin Demian on 18.04.2023.
// Copyright © 2023 Ometria. All rights reserved.
//

import XCTest
@testable import Ometria

final class OmetriaSampleTests: XCTestCase {

override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

func test_ometria_cacheEventSuccess() throws {
//given
let expectation = XCTestExpectation()
expectation.expectedFulfillmentCount = 3
let cache = MockInMemoryEventCache {
expectation.fulfill()
}
let service = MockSuccessEventService()
let ometria = Ometria.init(apiToken: "token", config: OmetriaConfig(), eventService: service, eventCache: cache)

//when
ometria.trackAppInstalledEvent()
ometria.trackAppLaunchedEvent()
ometria.trackErrorOccuredEvent(error: .invalidAPIResponse)


//then
wait(for: [expectation], timeout: 0.1)
XCTAssertTrue(cache.events?.count == 3, "Expected 3 events in cache, but got \(cache.events?.count ?? 0)")
}

func test_ometria_successfulFlush_removesEventsFromCache() throws {
//given
let expectation = XCTestExpectation()
expectation.expectedFulfillmentCount = 4 // 3 times for tracking the events, and 1 time for removing them when successfully flushed
let cache = MockInMemoryEventCache {
expectation.fulfill()
}
let service = MockSuccessEventService()
let ometria = Ometria.init(apiToken: "token", config: OmetriaConfig(), eventService: service, eventCache: cache)

//when
ometria.trackAppInstalledEvent()
ometria.trackAppLaunchedEvent()
ometria.trackErrorOccuredEvent(error: .invalidAPIResponse)
ometria.flush()

//then
wait(for: [expectation], timeout: 0.1)
XCTAssertTrue(cache.events?.count == 0, "Expected 0 events in cache, but got \(cache.events?.count ?? 0)")
}

func test_ometria_sharedInitializer_successfullyPassesCacheAndEventService() {
let expectation = XCTestExpectation()
expectation.expectedFulfillmentCount = 1 // 3 times for tracking the events, and 1 time for removing them when successfully flushed
let cache = MockInMemoryEventCache {
expectation.fulfill()
}
let service = MockSuccessEventService()
Ometria.initialize(apiToken: "token", eventCache: cache, eventService: service)

wait(for: [expectation], timeout: 0.1)
}

func test_ometria_sharedInitializer_withSwizzlingEnabled_addsSwizzlers() {
let cache = MockInMemoryEventCache {
}
let eventService = MockSuccessEventService()
Ometria.initialize(apiToken: "token", eventCache: cache, eventService: eventService, enableSwizzling: true)

XCTAssertTrue(Swizzler.swizzles.count > 0, "Expected a positive, non-null number of swizzles, but got none")
}

func test_ometria_sharedInitializer_withSwizzlingDisabled_doesntAddSwizzles() {
let cache = MockInMemoryEventCache {
}
let eventService = MockSuccessEventService()
Ometria.initialize(apiToken: "token", eventCache: cache, eventService: eventService, enableSwizzling: false)

XCTAssertTrue(Swizzler.swizzles.count == 0, "Expected no swizzles")
}

func test_ometria_reinitializationWithoutSwizzling_removesSwizzles() {
let cache = MockInMemoryEventCache {
}
let eventService = MockSuccessEventService()
Ometria.initialize(apiToken: "token", eventCache: cache, eventService: eventService, enableSwizzling: true)
Ometria.initialize(apiToken: "token", eventCache: cache, eventService: eventService, enableSwizzling: false)

XCTAssertTrue(Swizzler.swizzles.count == 0, "Expected no swizzles")
}

func test_ometria_reinitializationWithSwizzling_resetsSwizzles() {
let cache = MockInMemoryEventCache {
}
let eventService = MockSuccessEventService()
Ometria.initialize(apiToken: "token", eventCache: cache, eventService: eventService, enableSwizzling: true)
let swizzleCount = Swizzler.swizzles.count
Ometria.initialize(apiToken: "token", eventCache: cache, eventService: eventService, enableSwizzling: true)

// we cannot compare number of swizzled methods, as that would ignore the number of methods that were given an implementation at runtime during the first swizzling iteration
XCTAssertTrue(Swizzler.swizzles.count >= swizzleCount && Swizzler.swizzles.count <= 2 * swizzleCount, "Expected to have at least \(swizzleCount), and less than (\(2 * swizzleCount) swizzled methods")
}

func test_ometria_reinitialization_successfullyFlushesAllEvents() {
let eventService1 = MockSuccessAndCountEventService()
let eventService2 = MockSuccessAndCountEventService()
let cache1 = MockInMemoryEventCache {
}
let cache2 = MockInMemoryEventCache {
}
Ometria.initialize(apiToken: "token", eventCache: cache1, eventService: eventService1, enableSwizzling: false)
for _ in 0..<100 {
Ometria.sharedInstance().trackProductViewedEvent(productId: UUID().uuidString)
}
Ometria.initialize(apiToken: "token", eventCache: cache2, eventService: eventService2, enableSwizzling: false)

let expectation = XCTestExpectation()
DispatchQueue.main.asyncAfter(deadline: .now() + 10, execute: {
expectation.fulfill()
})

wait(for: [expectation], timeout: 11)
XCTAssertTrue(eventService1.uniqueFlushedEvents.count >= 100, "Expected to flush more than 100 events, but got only \(eventService1.uniqueFlushedEvents.count)")
}

func test_ometria_instanceReset_clearsCache() {
let eventService = MockFailureEventService()
let cache1 = EventCache(relativePathComponent: "1")
let cache2 = EventCache(relativePathComponent: "2")
Ometria.initialize(apiToken: "test", eventCache: cache1, eventService: eventService)
for _ in 0..<100 {
Ometria.sharedInstance().trackProductViewedEvent(productId: UUID().uuidString)
}
Ometria.initialize(apiToken: "test2", eventCache: cache2, eventService: eventService)


let expectation = XCTestExpectation()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: {
expectation.fulfill()
})

wait(for: [expectation], timeout: 0.5)
let events = cache1.loadFromFile()
XCTAssertTrue(events == nil || events?.count == 0, "Expected to have 0 items in cache, but found some")
}
}
Loading

0 comments on commit 29f061e

Please sign in to comment.