Skip to content

Commit

Permalink
IOS-8651. Add unit test for OfflineViewModel
Browse files Browse the repository at this point in the history
  • Loading branch information
rgmez committed Oct 3, 2024
1 parent 25a8270 commit a54548d
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 102 deletions.
20 changes: 20 additions & 0 deletions MEGA.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,9 @@
94872E952C08886600DC76E6 /* UsageViewRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94872E942C08886600DC76E6 /* UsageViewRouter.swift */; };
94892C931EFBEA4100AEAC25 /* MEGALogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 41A4204E1C4EA02C002E192E /* MEGALogger.m */; };
94892CA11EFBF20800AEAC25 /* UIFont+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B5B4FFE1E681BAC00DBEB3B /* UIFont+MNZCategory.m */; };
948A0E962C9ED7A7004DDF55 /* MockMegaStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948A0E952C9ED7A3004DDF55 /* MockMegaStore.swift */; };
948AE4A42C664AB8000B14C7 /* ChatRoomsEmptyViewStateFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948AE4A32C664AB8000B14C7 /* ChatRoomsEmptyViewStateFactory.swift */; };
948B294A2CA1671D00428787 /* MockFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948B29492CA1671D00428787 /* MockFileManager.swift */; };
948BCE032C206ECE00B719B2 /* MockCameraSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948BCE022C206ECE00B719B2 /* MockCameraSwitcher.swift */; };
948BCE062C20704D00B719B2 /* CameraSwitcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948BCE052C20704D00B719B2 /* CameraSwitcherTests.swift */; };
949086881FD5482E002B12BD /* NSAttributedString+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 949086871FD5482E002B12BD /* NSAttributedString+MNZCategory.m */; };
Expand Down Expand Up @@ -1501,6 +1503,7 @@
94E7180B283D6603000EDDDF /* ArchivedChatRoomsViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E7180A283D6603000EDDDF /* ArchivedChatRoomsViewController+Additions.swift */; };
94E8304A29DAD18100869522 /* OnboardingViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E8304929DAD18100869522 /* OnboardingViewController+Additions.swift */; };
94EC99BC2BEA37F10007E30C /* CallKitProviderDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94EC99BB2BEA37F10007E30C /* CallKitProviderDelegateTests.swift */; };
94EF2DCE2CAD9FC00061071E /* OfflineFileEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94EF2DCD2CAD9FBD0061071E /* OfflineFileEntity.swift */; };
94EFD0B02B0E66C2009247FF /* MyAccountHallRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94EFD0AF2B0E66C2009247FF /* MyAccountHallRouter.swift */; };
94F2DD8029EFED3200718176 /* AchievementsDetailsViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F2DD7F29EFED3200718176 /* AchievementsDetailsViewController+Additions.swift */; };
94F4157C2B2086A300F1BBFD /* CloudDriveViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F4157B2B2086A300F1BBFD /* CloudDriveViewControllerFactory.swift */; };
Expand Down Expand Up @@ -4200,7 +4203,9 @@
9482F73B2B5EAB400061BAD5 /* NodeBrowserViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeBrowserViewModelTests.swift; sourceTree = "<group>"; };
948425D72A94EF020024FF1F /* MainTabBarController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainTabBarController+Additions.swift"; sourceTree = "<group>"; };
94872E942C08886600DC76E6 /* UsageViewRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsageViewRouter.swift; sourceTree = "<group>"; };
948A0E952C9ED7A3004DDF55 /* MockMegaStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMegaStore.swift; sourceTree = "<group>"; };
948AE4A32C664AB8000B14C7 /* ChatRoomsEmptyViewStateFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomsEmptyViewStateFactory.swift; sourceTree = "<group>"; };
948B29492CA1671D00428787 /* MockFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFileManager.swift; sourceTree = "<group>"; };
948BCE022C206ECE00B719B2 /* MockCameraSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCameraSwitcher.swift; sourceTree = "<group>"; };
948BCE052C20704D00B719B2 /* CameraSwitcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSwitcherTests.swift; sourceTree = "<group>"; };
948D92672B2373CE0020E894 /* DisplayMode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DisplayMode.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4287,6 +4292,7 @@
94E7180A283D6603000EDDDF /* ArchivedChatRoomsViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ArchivedChatRoomsViewController+Additions.swift"; sourceTree = "<group>"; };
94E8304929DAD18100869522 /* OnboardingViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingViewController+Additions.swift"; sourceTree = "<group>"; };
94EC99BB2BEA37F10007E30C /* CallKitProviderDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallKitProviderDelegateTests.swift; sourceTree = "<group>"; };
94EF2DCD2CAD9FBD0061071E /* OfflineFileEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineFileEntity.swift; sourceTree = "<group>"; };
94EFD0AF2B0E66C2009247FF /* MyAccountHallRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAccountHallRouter.swift; sourceTree = "<group>"; };
94F2DD7F29EFED3200718176 /* AchievementsDetailsViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AchievementsDetailsViewController+Additions.swift"; sourceTree = "<group>"; };
94F4157B2B2086A300F1BBFD /* CloudDriveViewControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudDriveViewControllerFactory.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8196,6 +8202,7 @@
isa = PBXGroup;
children = (
5D33D66A250D9BC900D74666 /* Client */,
94EF2DCA2CAD9C4B0061071E /* Entity */,
5D52C748250C4F2800286A43 /* Functions */,
949704B52AB427A700B2CA3A /* Router */,
C4944FE62C339F5B00A2C082 /* Transfers */,
Expand All @@ -8206,6 +8213,8 @@
C47E7BDA25D1550200DB6FF5 /* AudioPlayerItem.swift */,
5D33D671250DBA0B00D74666 /* CGSize.swift */,
94A144612C9DE58800C34A98 /* MockChatUploader.swift */,
948B29492CA1671D00428787 /* MockFileManager.swift */,
948A0E952C9ED7A3004DDF55 /* MockMegaStore.swift */,
32D167A92942DA59008C5ED6 /* MockSlideShowDataSource.swift */,
834B51912C9D91E5002E46FB /* MockTimerSequenceFactory.swift */,
25ED56692A98699E0063864F /* MockTransferWidgetResponder.swift */,
Expand Down Expand Up @@ -10482,6 +10491,14 @@
path = NewChatEmptyView;
sourceTree = "<group>";
};
94EF2DCA2CAD9C4B0061071E /* Entity */ = {
isa = PBXGroup;
children = (
94EF2DCD2CAD9FBD0061071E /* OfflineFileEntity.swift */,
);
path = Entity;
sourceTree = "<group>";
};
94EFD0AE2B0E66A1009247FF /* Router */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -16579,11 +16596,13 @@
C4944FE82C339F6800A2C082 /* MockDownloadTransfersListener.swift in Sources */,
B5B4CFC32C3E7D0900783135 /* MockEncourageGuestUserToJoinMegaRouter.swift in Sources */,
B569A2C52C6E03E4005A96AD /* MockEnterMeetingLinkRouter.swift in Sources */,
948B294A2CA1671D00428787 /* MockFileManager.swift in Sources */,
0E5F5E862C6C61280037A99C /* MockHideFilesAndFoldersRouter.swift in Sources */,
949B86E62BBC205D00CB8ADF /* MockImageLoader.swift in Sources */,
B514C5952C730F5000659719 /* MockLinkManager.swift in Sources */,
945A90C82C3D611500E10BBF /* MockMeetingContainerRouter.swift in Sources */,
B5663EE029F8F3DB0080FFDA /* MockMEGAPurchase.swift in Sources */,
948A0E962C9ED7A7004DDF55 /* MockMegaStore.swift in Sources */,
C45A2E2D25D2E09C00915A83 /* MockMiniPlayerViewRouter.swift in Sources */,
A5C0BD492BC3EB86006CF7D7 /* MockMoveToRubbishBinViewModel.swift in Sources */,
949CC15D2B0FD5230047D581 /* MockMyAccountHallRouter.swift in Sources */,
Expand Down Expand Up @@ -16641,6 +16660,7 @@
BF30EF3E2BB56942004B5EBF /* NoInternetViewModelTests.swift in Sources */,
9471F6AA2BA069C700BA873B /* NotificationsViewModelTests.swift in Sources */,
2F01A4332612E46800C1D752 /* NSURL+Deeplinking.swift in Sources */,
94EF2DCE2CAD9FC00061071E /* OfflineFileEntity.swift in Sources */,
C45A2E4A25D2F81800915A83 /* OfflineFileInfoUseCaseTests.swift in Sources */,
530C52522C216B6E00DFF3FE /* OfflineViewModelTests.swift in Sources */,
160711542A558DED00970A19 /* PagerTabViewModelTests.swift in Sources */,
Expand Down
15 changes: 1 addition & 14 deletions MEGADataTests/AppGroupContainerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import XCTest

class AppGroupContainerTests: XCTestCase {
private let url = URL(string: "http://mega.nz")
private lazy var sut = AppGroupContainer(fileManager: mockFileManager(containerURL: url!))
private lazy var sut = AppGroupContainer(fileManager: MockFileManager(containerURL: url!))

func testConatainerURL() throws {
XCTAssertEqual(sut.url, try XCTUnwrap(url))
Expand All @@ -28,16 +28,3 @@ class AppGroupContainerTests: XCTestCase {
}
}
}

private class mockFileManager: FileManager {
private let containerURL: URL

init(containerURL: URL) {
self.containerURL = containerURL
super.init()
}

override func containerURL(forSecurityApplicationGroupIdentifier groupIdentifier: String) -> URL? {
containerURL
}
}
19 changes: 0 additions & 19 deletions MEGADataTests/FileCacheRepositoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,3 @@ class FileCacheRepositoryTests: XCTestCase {
XCTAssertEqual(sut.cachedOriginalImageURL(for: node), expectedTempFileURL)
}
}

private class MockFileManager: FileManager {
private let tempURL: URL
private let containerURL: URL

init(tempURL: URL, containerURL: URL) {
self.tempURL = tempURL
self.containerURL = containerURL
super.init()
}

override var temporaryDirectory: URL {
tempURL
}

override func containerURL(forSecurityApplicationGroupIdentifier groupIdentifier: String) -> URL? {
containerURL
}
}
204 changes: 198 additions & 6 deletions MEGAUnitTests/Account/OfflineViewModelTests.swift
Original file line number Diff line number Diff line change
@@ -1,35 +1,94 @@
import Combine
import CoreData
import MEGADomain
import MEGADomainMock
import MEGASwift
import MEGATest
import XCTest

@testable import MEGA

final class OfflineViewModelTests: XCTestCase {
private let relativePath = "relative/path"
private let directoryPath = "/mock/path/expected"
private let filepath = "/mock/path/expected/logFile"
private let logsDirectoryPath = "/mock/path/expected/logs"
private let logsFileName = "MEGAiOS.docExt.log"
private let anyError = NSError(domain: "OfflineTestsError", code: 1, userInfo: nil)

var logsFilePath: String {
logsDirectoryPath + "/" + logsFileName
}

// MARK: - Helpers

private struct TestConfig {
var offlineUseCase: MockOfflineUseCase = MockOfflineUseCase()
var megaStore: MockMEGAStore = MockMEGAStore()
var fileManager: MockFileManager = MockFileManager()
var documentDirectoryPath: String?
var urls: [URL]
var expectedCommand: OfflineViewModel.Command?
var deleteOfflineAppearancePreferenceCalled: Int = 0
var removeCalled: Int = 0
}

@MainActor
private func makeOfflineViewModelVMSut(
transferUseCase: some NodeTransferUseCaseProtocol = MockNodeTransferUseCase(),
offlineUseCase: some OfflineUseCaseProtocol = MockOfflineUseCase(),
megaStore: MEGAStore = MockMEGAStore(),
fileManager: MockFileManager = MockFileManager(),
documentDirectoryPath: String? = nil,
file: StaticString = #file,
line: UInt = #line
) -> OfflineViewModel {

let sut = OfflineViewModel(
transferUseCase: transferUseCase,
offlineUseCase: offlineUseCase,
megaStore: megaStore
megaStore: megaStore,
fileManager: fileManager,
documentsDirectoryPath: documentDirectoryPath
)
trackForMemoryLeaks(on: sut, timeoutNanoseconds: 1_000_000_000)
return sut
}

private class MockMEGAStore: MEGAStore {}

@MainActor
private func executeRemoveOfflineItemsTest(
config: TestConfig,
function: String = #function,
file: StaticString = #file,
line: UInt = #line
) async {
let sut = makeOfflineViewModelVMSut(
offlineUseCase: config.offlineUseCase,
megaStore: config.megaStore,
fileManager: config.fileManager,
documentDirectoryPath: config.documentDirectoryPath,
file: file,
line: line
)
let expectation = expectation(description: function)

var receivedCommand: OfflineViewModel.Command?

sut.invokeCommand = { command in
receivedCommand = command
expectation.fulfill()
}

sut.dispatch(.removeOfflineItems(config.urls))
await fulfillment(of: [expectation], timeout: 1)

XCTAssertEqual(config.megaStore.deleteOfflineAppearancePreference_calledTimes, config.deleteOfflineAppearancePreferenceCalled, file: file, line: line)
XCTAssertEqual(config.megaStore.remove_calledTimes, config.removeCalled, file: file, line: line)
XCTAssertEqual(receivedCommand, config.expectedCommand, file: file, line: line)
}

// MARK: - Tests

@MainActor
func testAction_onViewAppear_shouldReloadUIWhenNodeDownloadCompletionUpdatesAvaliable() async {
// given
Expand Down Expand Up @@ -80,7 +139,7 @@ final class OfflineViewModelTests: XCTestCase {
// then
XCTAssertNil(receivedCommand)
}

@MainActor
func testAction_onViewWillDisappear_shouldNotReceiveAnyCommands() async {
// given
Expand All @@ -107,7 +166,7 @@ final class OfflineViewModelTests: XCTestCase {
// then
XCTAssertNil(receivedCommand)
}

@MainActor
func testAction_removeOfflineItems_shouldReceiveReloadUI() {
let sut = makeOfflineViewModelVMSut()
Expand All @@ -118,4 +177,137 @@ final class OfflineViewModelTests: XCTestCase {
expectedCommands: [.reloadUI]
)
}

@MainActor
func testAction_removeOfflineItems_shouldCallDeleteOfflineAppearancePreferenceForDirectory() async {
let config = TestConfig(
offlineUseCase: {
let useCase = MockOfflineUseCase()
useCase.stubbedRelativePath = relativePath
return useCase
}(),
urls: [URL(fileURLWithPath: directoryPath, isDirectory: true)],
expectedCommand: .reloadUI,
deleteOfflineAppearancePreferenceCalled: 1
)

await executeRemoveOfflineItemsTest(config: config)
}

@MainActor
func testAction_removeOfflineItems_shouldCallRemoveForOfflineNode() async {
let config = TestConfig(
offlineUseCase: {
let useCase = MockOfflineUseCase()
useCase.stubbedRelativePath = relativePath
return useCase
}(),
megaStore: MockMEGAStore(offlineNode: makeOfflineNode),
urls: [URL(fileURLWithPath: filepath, isDirectory: false)],
expectedCommand: .reloadUI,
removeCalled: 1
)

await executeRemoveOfflineItemsTest(config: config)
}

@MainActor
func testAction_removeOfflineItems_shouldHandleErrorsGracefully() async {
let config = TestConfig(
offlineUseCase: {
let useCase = MockOfflineUseCase()
useCase.stubbedError = anyError
return useCase
}(),
urls: [URL(fileURLWithPath: filepath)],
expectedCommand: .reloadUI,
removeCalled: 0
)

await executeRemoveOfflineItemsTest(config: config)
}

@MainActor
func testRemoveLogFromSharedSandbox_shouldSuccessfullyRemoveLog() async {
let mockFileManager = MockFileManager(tempURL: URL(string: logsFilePath)!, containerURL: URL(string: directoryPath)!)
let config = TestConfig(
offlineUseCase: {
let useCase = MockOfflineUseCase()
useCase.stubbedRelativePath = relativePath
return useCase
}(),
megaStore: MockMEGAStore(offlineNode: makeOfflineNode),
fileManager: mockFileManager,
documentDirectoryPath: directoryPath,
urls: [URL(fileURLWithPath: directoryPath + "/" + logsFileName, isDirectory: false)],
expectedCommand: .reloadUI,
removeCalled: 1
)

await executeRemoveOfflineItemsTest(config: config)

XCTAssertEqual(mockFileManager.lastRemovedPath, logsFilePath, "Expected to remove the log file successfully")
}

@MainActor
func testRemoveLogFromSharedSandbox_shouldHandleErrorGracefully() async {
let mockFileManager = MockFileManager(
containerURL: URL(string: directoryPath)!,
errorToThrow: anyError
)
let config = TestConfig(
offlineUseCase: {
let useCase = MockOfflineUseCase()
useCase.stubbedRelativePath = relativePath
return useCase
}(),
megaStore: MockMEGAStore(offlineNode: makeOfflineNode),
fileManager: mockFileManager,
documentDirectoryPath: directoryPath,
urls: [URL(fileURLWithPath: directoryPath + "/" + logsFileName, isDirectory: false)],
expectedCommand: .reloadUI,
removeCalled: 1
)

await executeRemoveOfflineItemsTest(config: config)

XCTAssertEqual(mockFileManager.lastRemovedPath, logsFilePath, "Expected to attempt removing the log file even with error")
}

private var makeOfflineNode: MOOfflineNode {
let testStack = CoreDataTestStack()
let context = testStack.managedObjectContext

let offlineFileNode = OfflineFileEntity(
base64Handle: "testHandle",
localPath: "/test/path",
parentBase64Handle: "parentHandle",
fingerprint: "testFingerprint",
timestamp: Date()
)

return offlineFileNode.toMOOfflineNode(in: context)
}
}

// MARK: - Core Data Test Stack
private class CoreDataTestStack {
lazy var managedObjectContext: NSManagedObjectContext = {
createInMemoryManagedObjectContext()
}()

private func createInMemoryManagedObjectContext() -> NSManagedObjectContext {
let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main])!
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

do {
try persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
} catch {
fatalError("Error adding in-memory persistent store: \(error)")
}

let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.persistentStoreCoordinator = persistentStoreCoordinator
return context
}
}
Loading

0 comments on commit a54548d

Please sign in to comment.