From 166b837921df0665e60a1275a8ae784083741e4b Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 17:47:48 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[#52]=20UnitTest=20target=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In.xcodeproj/project.pbxproj | 129 +++++++++++++++++- .../xcshareddata/xcschemes/Mark-In.xcscheme | 13 ++ .../xcschemes/[Dev] Mark-In.xcscheme | 13 ++ 3 files changed, 154 insertions(+), 1 deletion(-) diff --git a/Mark-In.xcodeproj/project.pbxproj b/Mark-In.xcodeproj/project.pbxproj index f074719..a0c4a01 100644 --- a/Mark-In.xcodeproj/project.pbxproj +++ b/Mark-In.xcodeproj/project.pbxproj @@ -62,6 +62,13 @@ remoteGlobalIDString = 57AC54A62DA503DE00BA84BD; remoteInfo = LinkMetadataKitInterface; }; + 57664F6C2DF03A9000CA8D68 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 57BFD8BB2DA4E19600648AD4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57BFD8C22DA4E19600648AD4; + remoteInfo = "Mark-In"; + }; 57AC56722DA511E900BA84BD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 57AC53092DA4FC1800BA84BD /* Util.xcodeproj */; @@ -93,6 +100,7 @@ /* Begin PBXFileReference section */ 57588CE42DD9B4D500DBD9A7 /* AppDI.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AppDI.xcodeproj; path = AppDI/AppDI.xcodeproj; sourceTree = ""; }; 57606D652DD63B14005EBE3D /* ReducerKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReducerKit.xcodeproj; path = ReducerKit/ReducerKit.xcodeproj; sourceTree = ""; }; + 57664F682DF03A9000CA8D68 /* Mark-In-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Mark-In-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 57AC52EF2DA4FBC900BA84BD /* DesignSystem.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DesignSystem.xcodeproj; path = DesignSystem/DesignSystem.xcodeproj; sourceTree = ""; }; 57AC53092DA4FC1800BA84BD /* Util.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Util.xcodeproj; path = Util/Util.xcodeproj; sourceTree = ""; }; 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LinkMetadataKit.xcodeproj; path = LinkMetadataKit/LinkMetadataKit.xcodeproj; sourceTree = ""; }; @@ -112,6 +120,11 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ + 57664F692DF03A9000CA8D68 /* Mark-In-Tests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = "Mark-In-Tests"; + sourceTree = ""; + }; 57BFD8C52DA4E19600648AD4 /* Mark-In */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( @@ -123,6 +136,13 @@ /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ + 57664F652DF03A9000CA8D68 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 57BFD8C02DA4E19600648AD4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -217,6 +237,7 @@ 57AC56602DA511AF00BA84BD /* Shared */, 57AC565F2DA511A900BA84BD /* Core */, 57BFD8C52DA4E19600648AD4 /* Mark-In */, + 57664F692DF03A9000CA8D68 /* Mark-In-Tests */, 57AC55422DA507EC00BA84BD /* Frameworks */, 57BFD8C42DA4E19600648AD4 /* Products */, ); @@ -228,6 +249,7 @@ isa = PBXGroup; children = ( 57BFD8C32DA4E19600648AD4 /* Mark-In.app */, + 57664F682DF03A9000CA8D68 /* Mark-In-Tests.xctest */, ); name = Products; sourceTree = ""; @@ -235,6 +257,29 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 57664F672DF03A9000CA8D68 /* Mark-In-Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57664F6E2DF03A9000CA8D68 /* Build configuration list for PBXNativeTarget "Mark-In-Tests" */; + buildPhases = ( + 57664F642DF03A9000CA8D68 /* Sources */, + 57664F652DF03A9000CA8D68 /* Frameworks */, + 57664F662DF03A9000CA8D68 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 57664F6D2DF03A9000CA8D68 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 57664F692DF03A9000CA8D68 /* Mark-In-Tests */, + ); + name = "Mark-In-Tests"; + packageProductDependencies = ( + ); + productName = "Mark-In-Tests"; + productReference = 57664F682DF03A9000CA8D68 /* Mark-In-Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 57BFD8C22DA4E19600648AD4 /* Mark-In */ = { isa = PBXNativeTarget; buildConfigurationList = 57BFD8CF2DA4E19700648AD4 /* Build configuration list for PBXNativeTarget "Mark-In" */; @@ -270,9 +315,13 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1630; + LastSwiftUpdateCheck = 1640; LastUpgradeCheck = 1640; TargetAttributes = { + 57664F672DF03A9000CA8D68 = { + CreatedOnToolsVersion = 16.4; + TestTargetID = 57BFD8C22DA4E19600648AD4; + }; 57BFD8C22DA4E19600648AD4 = { CreatedOnToolsVersion = 16.3; }; @@ -319,6 +368,7 @@ projectRoot = ""; targets = ( 57BFD8C22DA4E19600648AD4 /* Mark-In */, + 57664F672DF03A9000CA8D68 /* Mark-In-Tests */, ); }; /* End PBXProject section */ @@ -369,6 +419,13 @@ /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ + 57664F662DF03A9000CA8D68 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 57BFD8C12DA4E19600648AD4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -379,6 +436,13 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 57664F642DF03A9000CA8D68 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 57BFD8BF2DA4E19600648AD4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -388,7 +452,61 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 57664F6D2DF03A9000CA8D68 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 57BFD8C22DA4E19600648AD4 /* Mark-In */; + targetProxy = 57664F6C2DF03A9000CA8D68 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ + 57664F6F2DF03A9000CA8D68 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5DFZR8RCQR; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "kr.co.ios.swift.apple.Mark-In-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mark-In.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Mark-In"; + XROS_DEPLOYMENT_TARGET = 2.5; + }; + name = Debug; + }; + 57664F702DF03A9000CA8D68 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5DFZR8RCQR; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "kr.co.ios.swift.apple.Mark-In-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mark-In.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Mark-In"; + XROS_DEPLOYMENT_TARGET = 2.5; + }; + name = Release; + }; 57BFD8CD2DA4E19700648AD4 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReferenceAnchor = 57BFD8C52DA4E19600648AD4 /* Mark-In */; @@ -598,6 +716,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 57664F6E2DF03A9000CA8D68 /* Build configuration list for PBXNativeTarget "Mark-In-Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57664F6F2DF03A9000CA8D68 /* Debug */, + 57664F702DF03A9000CA8D68 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 57BFD8BE2DA4E19600648AD4 /* Build configuration list for PBXProject "Mark-In" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Mark-In.xcodeproj/xcshareddata/xcschemes/Mark-In.xcscheme b/Mark-In.xcodeproj/xcshareddata/xcschemes/Mark-In.xcscheme index 2c6fc41..9f3ff0c 100644 --- a/Mark-In.xcodeproj/xcshareddata/xcschemes/Mark-In.xcscheme +++ b/Mark-In.xcodeproj/xcshareddata/xcschemes/Mark-In.xcscheme @@ -29,6 +29,19 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" shouldAutocreateTestPlan = "YES"> + + + + + + + + + + + + Date: Wed, 4 Jun 2025 17:53:02 +0900 Subject: [PATCH 2/9] =?UTF-8?q?[#52]=20CodingKey,=20FieldKey=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DTOFieldKeyMappingTests.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift diff --git a/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift b/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift new file mode 100644 index 0000000..9b5b125 --- /dev/null +++ b/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift @@ -0,0 +1,32 @@ +// +// Mark_In_Tests.swift +// Mark-In-Tests +// +// Created by 이정동 on 6/4/25. +// + +import Testing +@testable import Mark_In + +struct DTOFieldKeyMappingTests { + + @Test + func testCodingKeys_shouldMatchFirestoreFieldKeys_inFolderDTO() async throws { + #expect(FolderDTO.CodingKeys.id.rawValue == FirestoreFieldKey.Folder.id) + #expect(FolderDTO.CodingKeys.name.rawValue == FirestoreFieldKey.Folder.name) + #expect(FolderDTO.CodingKeys.createdAt.rawValue == FirestoreFieldKey.Folder.createdAt) + } + + @Test + func testCodingKeys_shouldMatchFirestoreFieldKeys_inWebLinkDTO() async throws { + #expect(WebLinkDTO.CodingKeys.id.rawValue == FirestoreFieldKey.Link.id) + #expect(WebLinkDTO.CodingKeys.url.rawValue == FirestoreFieldKey.Link.url) + #expect(WebLinkDTO.CodingKeys.title.rawValue == FirestoreFieldKey.Link.title) + #expect(WebLinkDTO.CodingKeys.thumbnailUrl.rawValue == FirestoreFieldKey.Link.thumbnailUrl) + #expect(WebLinkDTO.CodingKeys.faviconUrl.rawValue == FirestoreFieldKey.Link.faviconUrl) + #expect(WebLinkDTO.CodingKeys.isPinned.rawValue == FirestoreFieldKey.Link.isPinned) + #expect(WebLinkDTO.CodingKeys.createdAt.rawValue == FirestoreFieldKey.Link.createdAt) + #expect(WebLinkDTO.CodingKeys.lastAccessedAt.rawValue == FirestoreFieldKey.Link.lastAccessedAt) + #expect(WebLinkDTO.CodingKeys.folderID.rawValue == FirestoreFieldKey.Link.folderID) + } +} From 7927f858c7c67c00b15b5e2f7a7bba917092a1db Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Fri, 6 Jun 2025 14:40:07 +0900 Subject: [PATCH 3/9] =?UTF-8?q?[#52]=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift b/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift index 9b5b125..84eb328 100644 --- a/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift +++ b/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift @@ -11,14 +11,14 @@ import Testing struct DTOFieldKeyMappingTests { @Test - func testCodingKeys_shouldMatchFirestoreFieldKeys_inFolderDTO() async throws { + func test_FolderDTO의_CodingKey와_FirestoreFieldKey가_일치해야_한다() async throws { #expect(FolderDTO.CodingKeys.id.rawValue == FirestoreFieldKey.Folder.id) #expect(FolderDTO.CodingKeys.name.rawValue == FirestoreFieldKey.Folder.name) #expect(FolderDTO.CodingKeys.createdAt.rawValue == FirestoreFieldKey.Folder.createdAt) } @Test - func testCodingKeys_shouldMatchFirestoreFieldKeys_inWebLinkDTO() async throws { + func test_WebLinkDTO의_CodingKey와_FirestoreFieldKey가_일치해야_한다() async throws { #expect(WebLinkDTO.CodingKeys.id.rawValue == FirestoreFieldKey.Link.id) #expect(WebLinkDTO.CodingKeys.url.rawValue == FirestoreFieldKey.Link.url) #expect(WebLinkDTO.CodingKeys.title.rawValue == FirestoreFieldKey.Link.title) From 1a3a0fe0bbf5604c5553ddacd342ccc4952be74b Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Fri, 6 Jun 2025 17:50:22 +0900 Subject: [PATCH 4/9] =?UTF-8?q?[#52]=20Given,=20When,=20Then=20=EA=B5=AC?= =?UTF-8?q?=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DTOFieldKeyMappingTests.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift b/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift index 84eb328..0c7a8b1 100644 --- a/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift +++ b/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift @@ -12,13 +12,26 @@ struct DTOFieldKeyMappingTests { @Test func test_FolderDTO의_CodingKey와_FirestoreFieldKey가_일치해야_한다() async throws { + // Given: 준비 + + // When: 실행 + + // Then: 검증 #expect(FolderDTO.CodingKeys.id.rawValue == FirestoreFieldKey.Folder.id) #expect(FolderDTO.CodingKeys.name.rawValue == FirestoreFieldKey.Folder.name) #expect(FolderDTO.CodingKeys.createdAt.rawValue == FirestoreFieldKey.Folder.createdAt) + + // TearDown: 해제 + } @Test func test_WebLinkDTO의_CodingKey와_FirestoreFieldKey가_일치해야_한다() async throws { + // Given: 준비 + + // When: 실행 + + // Then: 검증 #expect(WebLinkDTO.CodingKeys.id.rawValue == FirestoreFieldKey.Link.id) #expect(WebLinkDTO.CodingKeys.url.rawValue == FirestoreFieldKey.Link.url) #expect(WebLinkDTO.CodingKeys.title.rawValue == FirestoreFieldKey.Link.title) @@ -28,5 +41,8 @@ struct DTOFieldKeyMappingTests { #expect(WebLinkDTO.CodingKeys.createdAt.rawValue == FirestoreFieldKey.Link.createdAt) #expect(WebLinkDTO.CodingKeys.lastAccessedAt.rawValue == FirestoreFieldKey.Link.lastAccessedAt) #expect(WebLinkDTO.CodingKeys.folderID.rawValue == FirestoreFieldKey.Link.folderID) + + // TearDown: 해제 + } } From 3f766e927033951c9cf919a0c400803ca73af7b8 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Fri, 6 Jun 2025 18:15:53 +0900 Subject: [PATCH 5/9] =?UTF-8?q?[#55]=20DeleteFolderUseCaseTests=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeleteFolderUseCaseTests.swift | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift diff --git a/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift b/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift new file mode 100644 index 0000000..ea2229e --- /dev/null +++ b/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift @@ -0,0 +1,61 @@ +// +// DeleteFolderUseCaseTests.swift +// Mark-In-Tests +// +// Created by 이정동 on 6/6/25. +// + +import Testing + +@testable import Mark_In + +struct DeleteFolderUseCaseTests { + + @Test + func test_includingChildren이_true면_링크들을_삭제한다() async throws { + // Given: 준비 + + // When: 실행 + + // Then: 검증 + + // TearDown: 해제 + + } + + @Test + func test_includingChildren이_false면_링크들을_기본_폴더로_이동한다() async throws { + // Given: 준비 + + // When: 실행 + + // Then: 검증 + + // TearDown: 해제 + + } + + @Test + func test_folderID가_nil이면_폴더는_삭제되지_않는다() async throws { + // Given: 준비 + + // When: 실행 + + // Then: 검증 + + // TearDown: 해제 + + } + + @Test + func test_folderID가_존재하면_폴더를_삭제한다() async throws { + // Given: 준비 + + // When: 실행 + + // Then: 검증 + + // TearDown: 해제 + + } +} From 47d6f2a7d8b433a00c9cf47a5c58ce72ee68afdb Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Sat, 7 Jun 2025 16:37:02 +0900 Subject: [PATCH 6/9] =?UTF-8?q?[#55]=20StubAuthUserManager=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In/Sources/App/AuthUserManager.swift | 21 +++++++++- .../Data/Tests/FakeFolderRepository.swift | 26 ++++++++++++ .../Data/Tests/FakeLinkRepository.swift | 40 +++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 Mark-In/Sources/Data/Tests/FakeFolderRepository.swift create mode 100644 Mark-In/Sources/Data/Tests/FakeLinkRepository.swift diff --git a/Mark-In/Sources/App/AuthUserManager.swift b/Mark-In/Sources/App/AuthUserManager.swift index d4265c4..0bd820c 100644 --- a/Mark-In/Sources/App/AuthUserManager.swift +++ b/Mark-In/Sources/App/AuthUserManager.swift @@ -11,6 +11,10 @@ import Foundation import Util +enum AuthError: Error { + case unauthenticated +} + struct AuthUser: Codable { let id: String let name: String @@ -54,6 +58,19 @@ final class AuthUserManagerImpl: AuthUserManager { } } -enum AuthError: Error { - case unauthenticated +final class StubAuthUserManager: AuthUserManager { + var user: AuthUser? + + init(userID: String) { + self.user = .init( + id: userID, + name: userID, + email: "\(userID)@test.com", + provider: .apple + ) + } + + func save(_ user: AuthUser) { } + func load() { } + func clear() { } } diff --git a/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift b/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift new file mode 100644 index 0000000..8cfd210 --- /dev/null +++ b/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift @@ -0,0 +1,26 @@ +// +// MockFolderRepository.swift +// Mark-In +// +// Created by 이정동 on 6/6/25. +// + +import Foundation + +struct FakeFolderRepository: FolderRepository { + func create(userID: String, folder: WriteFolder) async throws -> Folder { + <#code#> + } + + func fetchAll(userID: String) async throws -> [Folder] { + <#code#> + } + + func delete(userID: String, folderID: String) async throws { + <#code#> + } + + func deleteAll(userID: String) async throws { + <#code#> + } +} diff --git a/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift b/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift new file mode 100644 index 0000000..fae6e04 --- /dev/null +++ b/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift @@ -0,0 +1,40 @@ +// +// MockLinkRepository.swift +// Mark-In +// +// Created by 이정동 on 6/6/25. +// + +import Foundation + +final class FakeLinkRepository: LinkRepository { + func create(userID: String, link: WriteLink) async throws -> WebLink { + <#code#> + } + + func fetchAll(userID: String) async throws -> [WebLink] { + <#code#> + } + + func moveLinkInFolder(userID: String, target linkID: String, to folderID: String?) async throws { + <#code#> + } + + func moveLinksInFolder(userID: String, fromFolderID: String?, toFolderID: String?) async throws { + <#code#> + } + + func delete(userID: String, linkID: String) async throws { + <#code#> + } + + func deleteAllInFolder(userID: String, folderID: String?) async throws { + <#code#> + } + + func deleteAll(userID: String) async throws { + <#code#> + } + + +} From 8a9e91a04e2023242508f25c29c0b69440d6acdd Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Sat, 7 Jun 2025 16:38:01 +0900 Subject: [PATCH 7/9] =?UTF-8?q?[#55]=20FakeLinkRepository=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Tests/FakeLinkRepository.swift | 91 +++++++++++++++++-- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift b/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift index fae6e04..1cbfb8a 100644 --- a/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift +++ b/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift @@ -8,33 +8,110 @@ import Foundation final class FakeLinkRepository: LinkRepository { + + var data: [String: [WebLink]] = [:] + func create(userID: String, link: WriteLink) async throws -> WebLink { - <#code#> + let webLink = WebLink( + id: UUID().uuidString, + url: link.url, + title: link.title, + thumbnailUrl: nil, + faviconUrl: nil, + isPinned: false, + createdAt: Date(), + lastAccessedAt: nil, + folderID: link.folderID + ) + + data[userID, default: []].append(webLink) + + return webLink } func fetchAll(userID: String) async throws -> [WebLink] { - <#code#> + return data[userID] ?? [] } func moveLinkInFolder(userID: String, target linkID: String, to folderID: String?) async throws { - <#code#> + let updated = data[userID]?.map { + $0.id == linkID ? $0.updating(folderID: folderID) : $0 + } + + data[userID] = updated } func moveLinksInFolder(userID: String, fromFolderID: String?, toFolderID: String?) async throws { - <#code#> + let updated = data[userID]?.map { + $0.folderID == fromFolderID ? $0.updating(folderID: toFolderID) : $0 + } + + data[userID] = updated } func delete(userID: String, linkID: String) async throws { - <#code#> + data[userID]?.removeAll { $0.id == linkID } } func deleteAllInFolder(userID: String, folderID: String?) async throws { - <#code#> + data[userID]?.removeAll { $0.folderID == folderID } } func deleteAll(userID: String) async throws { - <#code#> + data[userID] = [] + } +} + +extension FakeLinkRepository { + func withTestLinks(userID: String, folderID: String?, count: Int) -> Self { + let testLinks = (0.. Self { + let testLink = WebLink.makeTestObject(id: linkID, folderID: folderID) + data[userID, default: []].append(testLink) + return self + } +} + +private extension WebLink { + static func makeTestObject( + id: String = UUID().uuidString, + url: String = "https://example.com", + title: String = "Test Link", + thumbnailUrl: String? = nil, + faviconUrl: String? = nil, + isPinned: Bool = false, + createdAt: Date = Date(), + lastAccessedAt: Date? = nil, + folderID: String? = "test-folder", + ) -> WebLink { + WebLink( + id: id, + url: url, + title: title, + thumbnailUrl: thumbnailUrl, + faviconUrl: faviconUrl, + isPinned: isPinned, + createdAt: createdAt, + lastAccessedAt: lastAccessedAt, + folderID: folderID + ) + } + func updating(folderID: String?) -> WebLink { + WebLink( + id: id, + url: url, + title: title, + thumbnailUrl: thumbnailUrl, + faviconUrl: faviconUrl, + isPinned: isPinned, + createdAt: createdAt, + lastAccessedAt: lastAccessedAt, + folderID: folderID + ) + } } From 24d7104a80d33558b96a12f2a00001884eb4d898 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Sat, 7 Jun 2025 16:38:32 +0900 Subject: [PATCH 8/9] =?UTF-8?q?[#55]=20FakeFolderRepository=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Tests/FakeFolderRepository.swift | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift b/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift index 8cfd210..38219d3 100644 --- a/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift +++ b/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift @@ -7,20 +7,53 @@ import Foundation -struct FakeFolderRepository: FolderRepository { +final class FakeFolderRepository: FolderRepository { + var data: [String: [Folder]] = [:] + func create(userID: String, folder: WriteFolder) async throws -> Folder { - <#code#> + let folder = Folder( + id: UUID().uuidString, + name: folder.name, + createdAt: Date() + ) + + data[userID, default: []].append(folder) + return folder } func fetchAll(userID: String) async throws -> [Folder] { - <#code#> + return data[userID] ?? [] } func delete(userID: String, folderID: String) async throws { - <#code#> + data[userID]?.removeAll { $0.id == folderID } } func deleteAll(userID: String) async throws { - <#code#> + data[userID] = [] + } +} + +extension FakeFolderRepository { + func withTestFolders(userID: String, count: Int) -> Self { + let dummyFolders = (0.. Self { + let dummyFolder = Folder.makeTestObject(id: folderID) + data[userID, default: []].append(dummyFolder) + return self + } +} + +private extension Folder { + static func makeTestObject( + id: String? = UUID().uuidString, + name: String = "test-folder", + createdAt: Date = Date() + ) -> Folder { + Folder(id: id, name: name, createdAt: createdAt) } } From f8e213885f71a16e56c4aafb7fcea17b47f1c71d Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Sat, 7 Jun 2025 16:38:50 +0900 Subject: [PATCH 9/9] =?UTF-8?q?[#55]=20DeleteFolderUseCase=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeleteFolderUseCaseTests.swift | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift b/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift index ea2229e..1699bb0 100644 --- a/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift +++ b/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift @@ -14,10 +14,29 @@ struct DeleteFolderUseCaseTests { @Test func test_includingChildren이_true면_링크들을_삭제한다() async throws { // Given: 준비 + let userID = "testUser" + let folderID = "testFolderID" + + let stubAuthUserManager = StubAuthUserManager(userID: userID) + let fakeLinkRepo = FakeLinkRepository() + .withTestLinks(userID: userID, folderID: folderID, count: 5) + let fakeFolderRepo = FakeFolderRepository() + .withTestFolder(userID: userID, folderID: folderID) + .withTestFolders(userID: userID, count: 2) + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: fakeLinkRepo, + folderRepository: fakeFolderRepo + ) // When: 실행 + _ = try await sut.execute(folderID: folderID, includingChildren: true) // Then: 검증 + let links = fakeLinkRepo.data[userID]! + + #expect(links.filter { $0.folderID == folderID }.isEmpty) // TearDown: 해제 @@ -26,10 +45,30 @@ struct DeleteFolderUseCaseTests { @Test func test_includingChildren이_false면_링크들을_기본_폴더로_이동한다() async throws { // Given: 준비 + let userID = "testUser" + let folderID = "testFolderID" + let defaultFolderID: String? = nil + + let stubAuthUserManager = StubAuthUserManager(userID: userID) + let fakeLinkRepo = FakeLinkRepository() + .withTestLinks(userID: userID, folderID: folderID, count: 5) + let fakeFolderRepo = FakeFolderRepository() + .withTestFolder(userID: userID, folderID: folderID) + .withTestFolder(userID: userID, folderID: defaultFolderID) + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: fakeLinkRepo, + folderRepository: fakeFolderRepo + ) // When: 실행 + _ = try await sut.execute(folderID: folderID, includingChildren: false) // Then: 검증 + let links = fakeLinkRepo.data[userID]! + + #expect(links.filter { $0.folderID == defaultFolderID }.count == 5) // TearDown: 해제 @@ -38,10 +77,28 @@ struct DeleteFolderUseCaseTests { @Test func test_folderID가_nil이면_폴더는_삭제되지_않는다() async throws { // Given: 준비 + let userID = "testUser" + let folderID: String? = nil + + let stubAuthUserManager = StubAuthUserManager(userID: userID) + let fakeLinkRepo = FakeLinkRepository() + .withTestLinks(userID: userID, folderID: folderID, count: 5) + let fakeFolderRepo = FakeFolderRepository() + .withTestFolder(userID: userID, folderID: folderID) + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: fakeLinkRepo, + folderRepository: fakeFolderRepo + ) // When: 실행 + _ = try await sut.execute(folderID: folderID, includingChildren: false) // Then: 검증 + let folders = fakeFolderRepo.data[userID]! + + #expect(folders.contains { $0.id == folderID }) // TearDown: 해제 @@ -50,10 +107,28 @@ struct DeleteFolderUseCaseTests { @Test func test_folderID가_존재하면_폴더를_삭제한다() async throws { // Given: 준비 + let userID = "testUser" + let folderID = "testFolder" + + let stubAuthUserManager = StubAuthUserManager(userID: userID) + let fakeLinkRepo = FakeLinkRepository() + .withTestLinks(userID: userID, folderID: folderID, count: 5) + let fakeFolderRepo = FakeFolderRepository() + .withTestFolder(userID: userID, folderID: folderID) + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: fakeLinkRepo, + folderRepository: fakeFolderRepo + ) // When: 실행 + _ = try await sut.execute(folderID: folderID, includingChildren: false) // Then: 검증 + let folders = fakeFolderRepo.data[userID]! + + #expect(folders.contains { $0.id == folderID } == false) // TearDown: 해제