diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift index 73a7f17..7661742 100644 --- a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -31,9 +31,10 @@ public extension Project { "NSAppTransportSecurity": .dictionary([ "NSAllowsArbitraryLoads": .boolean(true) ]), - "ITSAppUsesNonExemptEncryption": .boolean(false) + "ITSAppUsesNonExemptEncryption": .boolean(false), + "BASE_URL": .string("$(BASE_URL)") ] - + static let demoInfoPlist: [String: Plist.Value] = [ "CFBundleShortVersionString": .string("1.0.0"), "CFBundleDevelopmentRegion": .string("ko"), @@ -57,9 +58,10 @@ public extension Project { "NSAppTransportSecurity": .dictionary([ "NSAllowsArbitraryLoads": .boolean(true) ]), - "ITSAppUsesNonExemptEncryption": .boolean(false) + "ITSAppUsesNonExemptEncryption": .boolean(false), + "BASE_URL": .string("$(BASE_URL)") ] - + static let framework: InfoPlist = .extendingDefault(with: [ "CFBundlePackageType": "FMWK" ]) diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/Settings+Extension.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Settings+Extension.swift index 1af74fa..180bb54 100644 --- a/Plugins/EnvPlugin/ProjectDescriptionHelpers/Settings+Extension.swift +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Settings+Extension.swift @@ -12,7 +12,7 @@ public extension Settings { static let frameworkSettings: Settings = .settings( base: [ "SKIP_INSTALL": "YES", - "BUILD_LIBRARY_FOR_DISTRIBUTION": "YES", + "BUILD_LIBRARY_FOR_DISTRIBUTION": "NO", "DEFINES_MODULE": "YES", "ENABLE_BITCODE": "NO", "IPHONEOS_DEPLOYMENT_TARGET": .string(Environment.deploymentTarget), diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index eb201ca..c029072 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -21,6 +21,7 @@ let project = Project.makeModule( scripts: [.swiftLint], dependencies: [ .data, + .Modules.networks, .Features.rootFeature ], settings: .appSettings() diff --git a/Projects/Data/Sources/DI/AuthRepositoryFactory.swift b/Projects/Data/Sources/DI/AuthRepositoryFactory.swift new file mode 100644 index 0000000..257ca89 --- /dev/null +++ b/Projects/Data/Sources/DI/AuthRepositoryFactory.swift @@ -0,0 +1,19 @@ +// +// AuthRepositoryFactory.swift +// Data +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Domain +import Foundation +import Networks + +public func makeAuthService() -> AuthServiceProtocol { + AuthService() +} + +public func makeAuthRepository(authService: AuthServiceProtocol) -> AuthRepositoryProtocol { + AuthRepository(authService: authService) +} diff --git a/Projects/Data/Sources/Repository/Auth/AuthRepository.swift b/Projects/Data/Sources/Repository/Auth/AuthRepository.swift new file mode 100644 index 0000000..9f778dc --- /dev/null +++ b/Projects/Data/Sources/Repository/Auth/AuthRepository.swift @@ -0,0 +1,40 @@ +// +// AuthRepository.swift +// Data +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Domain +import Foundation +import Networks + +public final class AuthRepository: AuthRepositoryProtocol, @unchecked Sendable { + private let authService: AuthServiceProtocol + + public init(authService: AuthServiceProtocol) { + self.authService = authService + } + + public func signup(info: SignupInfo) async -> Result { + let request = SignupRequest(fcmToken: info.fcmToken) + let result = await authService.signup(request: request) + + switch result { + case .success(let response): + let signupResult = SignupResult( + uuid: response.uuid, + accessToken: response.accessToken, + nickname: response.nickname + ) + return .success(signupResult) + + case .failure(let error): + return .failure(error) + + case .networkFailure(let networkError): + return .failure(.networkError(message: networkError.localizedDescription)) + } + } +} diff --git a/Projects/Data/Sources/Transform/Auth/AuthTransform.swift b/Projects/Data/Sources/Transform/Auth/AuthTransform.swift new file mode 100644 index 0000000..8d76bac --- /dev/null +++ b/Projects/Data/Sources/Transform/Auth/AuthTransform.swift @@ -0,0 +1,27 @@ +// +// AuthTransform.swift +// Data +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Domain +import Foundation +import Networks + +extension SignupResult { + init(from response: SignupResponse) { + self.init( + uuid: response.uuid, + accessToken: response.accessToken, + nickname: response.nickname + ) + } +} + +extension SignupRequest { + init(from info: SignupInfo) { + self.init(fcmToken: info.fcmToken) + } +} diff --git a/Projects/Domain/Sources/Interface/Auth/AuthRepositoryProtocol.swift b/Projects/Domain/Sources/Interface/Auth/AuthRepositoryProtocol.swift new file mode 100644 index 0000000..8e52a0f --- /dev/null +++ b/Projects/Domain/Sources/Interface/Auth/AuthRepositoryProtocol.swift @@ -0,0 +1,13 @@ +// +// AuthRepositoryProtocol.swift +// Domain +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +public protocol AuthRepositoryProtocol: Sendable { + func signup(info: SignupInfo) async -> Result +} diff --git a/Projects/Domain/Sources/Model/Auth/SignupError.swift b/Projects/Domain/Sources/Model/Auth/SignupError.swift new file mode 100644 index 0000000..c0dcd9b --- /dev/null +++ b/Projects/Domain/Sources/Model/Auth/SignupError.swift @@ -0,0 +1,26 @@ +// +// SignupError.swift +// Domain +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +// MARK: - SignupField + +public enum SignupField: Sendable, Equatable { + case fcmToken + case email + case unknown(String) +} + +// MARK: - SignupError + +public enum SignupError: Error, Sendable { + case validationFailed(field: SignupField, message: String) + case serverError(message: String?) + case networkError(message: String) + case unknown(code: String, message: String) +} diff --git a/Projects/Domain/Sources/Model/Auth/SignupInfo.swift b/Projects/Domain/Sources/Model/Auth/SignupInfo.swift new file mode 100644 index 0000000..67ae93b --- /dev/null +++ b/Projects/Domain/Sources/Model/Auth/SignupInfo.swift @@ -0,0 +1,17 @@ +// +// SignupInfo.swift +// Domain +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +public struct SignupInfo: Sendable { + public let fcmToken: String + + public init(fcmToken: String) { + self.fcmToken = fcmToken + } +} diff --git a/Projects/Domain/Sources/Model/Auth/SignupResult.swift b/Projects/Domain/Sources/Model/Auth/SignupResult.swift new file mode 100644 index 0000000..775fabb --- /dev/null +++ b/Projects/Domain/Sources/Model/Auth/SignupResult.swift @@ -0,0 +1,21 @@ +// +// SignupResult.swift +// Domain +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +public struct SignupResult: Sendable { + public let uuid: String + public let accessToken: String + public let nickname: String + + public init(uuid: String, accessToken: String, nickname: String) { + self.uuid = uuid + self.accessToken = accessToken + self.nickname = nickname + } +} diff --git a/Projects/Modules/Networks/Project.swift b/Projects/Modules/Networks/Project.swift index 2414eb4..aab2aec 100644 --- a/Projects/Modules/Networks/Project.swift +++ b/Projects/Modules/Networks/Project.swift @@ -8,6 +8,7 @@ import ProjectDescription import ProjectDescriptionHelpers import DependencyPlugin +import EnvPlugin let project = Project.makeModule( name: "Networks", @@ -15,11 +16,30 @@ let project = Project.makeModule( .makeFrameworkTarget( name: "Networks", dependencies: [ - .core + .core, + .domain ], scripts: [.swiftLint], isStatic: true, hasResources: false + ), + .target( + name: "NetworksTests", + destinations: .iOS, + product: .unitTests, + bundleId: "org.yapp.NDGL.NetworksTests", + deploymentTargets: .iOS("17.0"), + infoPlist: .extendingDefault(with: [ + "BASE_URL": .string("$(BASE_URL)") + ]), + sources: ["Tests/**"], + dependencies: [ + .target(name: "Networks") + ], + settings: .settings(configurations: [ + .debug(name: "Debug", xcconfig: .relativeToRoot("xcconfigs/Debug.xcconfig")), + .release(name: "Release", xcconfig: .relativeToRoot("xcconfigs/Release.xcconfig")) + ]) ) ] ) diff --git a/Projects/Modules/Networks/Sources/API/APIEmpty.swift b/Projects/Modules/Networks/Sources/API/APIEmpty.swift deleted file mode 100644 index cc161f1..0000000 --- a/Projects/Modules/Networks/Sources/API/APIEmpty.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// APIEmpty.swift -// 27th-App-Team-1-iOSManifests -// -// Created by 최안용 on 1/13/26. -// - -import Foundation diff --git a/Projects/Modules/Networks/Sources/Base/BaseResponse.swift b/Projects/Modules/Networks/Sources/Base/BaseResponse.swift new file mode 100644 index 0000000..0f398d6 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Base/BaseResponse.swift @@ -0,0 +1,15 @@ +// +// BaseResponse.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +struct BaseResponse: Decodable, Sendable { + let code: String + let message: String + let data: T? +} diff --git a/Projects/Modules/Networks/Sources/Base/EmptyResponse.swift b/Projects/Modules/Networks/Sources/Base/EmptyResponse.swift new file mode 100644 index 0000000..841084e --- /dev/null +++ b/Projects/Modules/Networks/Sources/Base/EmptyResponse.swift @@ -0,0 +1,11 @@ +// +// EmptyResponse.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +struct EmptyResponse: Decodable, Sendable {} diff --git a/Projects/Modules/Networks/Sources/Base/ErrorResponse.swift b/Projects/Modules/Networks/Sources/Base/ErrorResponse.swift new file mode 100644 index 0000000..f154a75 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Base/ErrorResponse.swift @@ -0,0 +1,20 @@ +// +// ErrorResponse.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +public struct ErrorResponse: Decodable, Sendable { + public let code: String? + public let message: String? + public let errors: [ErrorDetail]? + + public struct ErrorDetail: Decodable, Sendable { + public let field: String? + public let message: String? + } +} diff --git a/Projects/Modules/Networks/Sources/Base/NBaseEmpty.swift b/Projects/Modules/Networks/Sources/Base/NBaseEmpty.swift deleted file mode 100644 index c0b8f52..0000000 --- a/Projects/Modules/Networks/Sources/Base/NBaseEmpty.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// NBaseEmpty.swift -// 27th-App-Team-1-iOSManifests -// -// Created by 최안용 on 1/13/26. -// - -import Foundation diff --git a/Projects/Modules/Networks/Sources/Base/NetworkError.swift b/Projects/Modules/Networks/Sources/Base/NetworkError.swift new file mode 100644 index 0000000..f1c1bb3 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Base/NetworkError.swift @@ -0,0 +1,26 @@ +// +// NetworkError.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +public enum NetworkError: Error, Sendable { + case connectionFailed + case decodingFailed + case unknown(String) + + public var message: String { + switch self { + case .connectionFailed: + return "네트워크 연결을 확인해주세요" + case .decodingFailed: + return "데이터 처리 중 오류가 발생했습니다" + case .unknown(let description): + return "알 수 없는 오류: \(description)" + } + } +} diff --git a/Projects/Modules/Networks/Sources/Base/NetworkResult.swift b/Projects/Modules/Networks/Sources/Base/NetworkResult.swift new file mode 100644 index 0000000..f2c5593 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Base/NetworkResult.swift @@ -0,0 +1,16 @@ +// +// NetworkResult.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +@frozen +public enum NetworkResult: Sendable { + case success(T) + case failure(E) + case networkFailure(NetworkError) +} diff --git a/Projects/Modules/Networks/Sources/DTO/Auth/SignupDTO.swift b/Projects/Modules/Networks/Sources/DTO/Auth/SignupDTO.swift new file mode 100644 index 0000000..1389403 --- /dev/null +++ b/Projects/Modules/Networks/Sources/DTO/Auth/SignupDTO.swift @@ -0,0 +1,37 @@ +// +// Signin.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +public struct SignupRequest: Encodable, Sendable { + public let fcmToken: String + public let deviceModel: String? + public let deviceOs: String? + public let deviceOsVersion: String? + public let appVersion: String? + + public init( + fcmToken: String, + deviceModel: String? = nil, + deviceOs: String? = nil, + deviceOsVersion: String? = nil, + appVersion: String? = nil + ) { + self.fcmToken = fcmToken + self.deviceModel = deviceModel + self.deviceOs = deviceOs + self.deviceOsVersion = deviceOsVersion + self.appVersion = appVersion + } +} + +public struct SignupResponse: Decodable, Sendable { + public let uuid: String + public let accessToken: String + public let nickname: String +} diff --git a/Projects/Modules/Networks/Sources/Entity/EntityEmpty.swift b/Projects/Modules/Networks/Sources/Entity/EntityEmpty.swift deleted file mode 100644 index be4230d..0000000 --- a/Projects/Modules/Networks/Sources/Entity/EntityEmpty.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// EntityEmpty.swift -// 27th-App-Team-1-iOSManifests -// -// Created by 최안용 on 1/13/26. -// - -import Foundation diff --git a/Projects/Modules/Networks/Sources/ErrorMapping/SignupError+Mapping.swift b/Projects/Modules/Networks/Sources/ErrorMapping/SignupError+Mapping.swift new file mode 100644 index 0000000..2bd5200 --- /dev/null +++ b/Projects/Modules/Networks/Sources/ErrorMapping/SignupError+Mapping.swift @@ -0,0 +1,46 @@ +// +// SignupError+Mapping.swift +// Networks +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Domain +import Foundation + +extension SignupError { + public init(code: String, message: String, errors: [ErrorResponse.ErrorDetail]) { + let fieldMessage = errors.first?.message ?? message + + let field: SignupField = { + guard let fieldString = errors.first?.field else { + return .unknown("unknown") + } + switch fieldString { + case "fcmToken": return .fcmToken + case "email": return .email + default: return .unknown(fieldString) + } + }() + + switch code { + case "COMM-01-005": + self = .validationFailed(field: field, message: fieldMessage) + case "COMM-08-001": + self = .serverError(message: message) + default: + self = .unknown(code: code, message: message) + } + } +} + +extension SignupField { + public init(rawValue: String) { + switch rawValue { + case "fcmToken": self = .fcmToken + case "email": self = .email + default: self = .unknown(rawValue) + } + } +} diff --git a/Projects/Modules/Networks/Sources/Extensions/MoyaProvider+Async.swift b/Projects/Modules/Networks/Sources/Extensions/MoyaProvider+Async.swift new file mode 100644 index 0000000..5599e7a --- /dev/null +++ b/Projects/Modules/Networks/Sources/Extensions/MoyaProvider+Async.swift @@ -0,0 +1,118 @@ +// +// MoyaProvider+Async.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation +import Moya + +extension MoyaProvider { + private static var successCode: String { "2000" } + + func request( + _ target: Target, + errorMapper: @escaping @Sendable (String, String, [ErrorResponse.ErrorDetail]) -> E + ) async -> NetworkResult { + NetworkLogger.logRequest(target) + + return await withCheckedContinuation { continuation in + self.request(target) { result in + switch result { + case .success(let response): + NetworkLogger.logResponse(response) + + do { + let baseResponse = try JSONDecoder().decode( + BaseResponse.self, + from: response.data + ) + + if baseResponse.code == Self.successCode { + guard let data = baseResponse.data else { + continuation.resume(returning: .networkFailure(.decodingFailed)) + return + } + continuation.resume(returning: .success(data)) + } else { + let errorResponse = try JSONDecoder().decode( + ErrorResponse.self, + from: response.data + ) + let apiError = errorMapper( + errorResponse.code ?? "", + errorResponse.message ?? "", + errorResponse.errors ?? [] + ) + continuation.resume(returning: .failure(apiError)) + } + } catch { + continuation.resume(returning: .networkFailure(.decodingFailed)) + } + + case .failure(let moyaError): + NetworkLogger.logError(moyaError) + let networkError = Self.mapMoyaError(moyaError) + continuation.resume(returning: .networkFailure(networkError)) + } + } + } + } + + func requestPlain( + _ target: Target, + errorMapper: @escaping @Sendable (String, String, [ErrorResponse.ErrorDetail]) -> E + ) async -> NetworkResult { + NetworkLogger.logRequest(target) + + return await withCheckedContinuation { continuation in + self.request(target) { result in + switch result { + case .success(let response): + NetworkLogger.logResponse(response) + + do { + let baseResponse = try JSONDecoder().decode( + BaseResponse.self, + from: response.data + ) + + if baseResponse.code == Self.successCode { + continuation.resume(returning: .success(())) + } else { + let errorResponse = try JSONDecoder().decode( + ErrorResponse.self, + from: response.data + ) + let apiError = errorMapper( + errorResponse.code ?? "", + errorResponse.message ?? "", + errorResponse.errors ?? [] + ) + continuation.resume(returning: .failure(apiError)) + } + } catch { + continuation.resume(returning: .networkFailure(.decodingFailed)) + } + + case .failure(let moyaError): + NetworkLogger.logError(moyaError) + let networkError = Self.mapMoyaError(moyaError) + continuation.resume(returning: .networkFailure(networkError)) + } + } + } + } + + private static func mapMoyaError(_ error: MoyaError) -> NetworkError { + switch error { + case .underlying(let nsError as NSError, _) + where nsError.domain == NSURLErrorDomain: + return .connectionFailed + default: + return .unknown(error.localizedDescription) + } + } +} diff --git a/Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift b/Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift new file mode 100644 index 0000000..5b7d197 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift @@ -0,0 +1,24 @@ +// +// NetworkConfiguration.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation + +public enum NetworkConfiguration { + public static var baseURL: URL { + // 테스트 환경에서는 Bundle.main이 Xcode 도구를 가리키므로 테스트 번들에서 찾음 + let bundle = Bundle.allBundles.first { $0.bundlePath.hasSuffix(".xctest") } ?? Bundle.main + + guard let urlString = bundle.infoDictionary?["BASE_URL"] as? String, + let url = URL(string: urlString) else { + fatalError("BASE_URL not found in Info.plist") + } + return url + } +} + + diff --git a/Projects/Modules/Networks/Sources/Logger/NetworkLogger.swift b/Projects/Modules/Networks/Sources/Logger/NetworkLogger.swift new file mode 100644 index 0000000..4789893 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Logger/NetworkLogger.swift @@ -0,0 +1,75 @@ +// +// NetworkLogger.swift +// Networks +// +// Created by kimnahun on 1/21/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Foundation +import Moya + +enum NetworkLogger { + + static func logRequest(_ target: TargetType) { + #if DEBUG + print("──────────────────────────────────────") + print("[REQUEST] \(target.method.rawValue) \(target.baseURL)\(target.path)") + if let body = requestBody(from: target.task) { + print("[BODY] \(body)") + } + print("──────────────────────────────────────") + #endif + } + + static func logResponse(_ response: Response) { + #if DEBUG + let statusCode = response.statusCode + let url = response.request?.url?.absoluteString ?? "" + + print("──────────────────────────────────────") + print("[RESPONSE] \(statusCode) \(url)") + if let json = try? JSONSerialization.jsonObject(with: response.data), + let prettyData = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted), + let prettyString = String(data: prettyData, encoding: .utf8) { + print("[DATA]\n\(prettyString)") + } + print("──────────────────────────────────────") + #endif + } + + static func logError(_ error: MoyaError) { + #if DEBUG + print("──────────────────────────────────────") + print("[ERROR] \(error.localizedDescription)") + print("──────────────────────────────────────") + #endif + } + + private static func requestBody(from task: Moya.Task) -> String? { + switch task { + case .requestParameters(let parameters, _): + return parameters.description + case .requestJSONEncodable(let encodable): + if let data = try? JSONEncoder().encode(AnyEncodable(encodable)), + let string = String(data: data, encoding: .utf8) { + return string + } + return nil + default: + return nil + } + } +} + +private struct AnyEncodable: Encodable { + private let encode: (Encoder) throws -> Void + + init(_ value: T) { + encode = value.encode + } + + func encode(to encoder: Encoder) throws { + try encode(encoder) + } +} diff --git a/Projects/Modules/Networks/Sources/Service/AuthService.swift b/Projects/Modules/Networks/Sources/Service/AuthService.swift new file mode 100644 index 0000000..3dc7084 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Service/AuthService.swift @@ -0,0 +1,27 @@ +// +// AuthService.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Domain +import Foundation +import Moya + +public protocol AuthServiceProtocol: Sendable { + func signup(request: SignupRequest) async -> NetworkResult +} + +public final class AuthService: AuthServiceProtocol, @unchecked Sendable { + private let provider: MoyaProvider + + public init(provider: MoyaProvider = MoyaProvider()) { + self.provider = provider + } + + public func signup(request: SignupRequest) async -> NetworkResult { + await provider.request(.signup(request: request), errorMapper: SignupError.init) + } +} diff --git a/Projects/Modules/Networks/Sources/Service/ServiceEmpty.swift b/Projects/Modules/Networks/Sources/Service/ServiceEmpty.swift deleted file mode 100644 index a45ef69..0000000 --- a/Projects/Modules/Networks/Sources/Service/ServiceEmpty.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// ServiceEmpty.swift -// 27th-App-Team-1-iOSManifests -// -// Created by 최안용 on 1/13/26. -// - -import Foundation diff --git a/Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift b/Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift new file mode 100644 index 0000000..6589767 --- /dev/null +++ b/Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift @@ -0,0 +1,47 @@ +// +// AuthAPI.swift +// Networks +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import Alamofire +import Foundation +import Moya + +public enum AuthAPI { + case signup(request: SignupRequest) +} + +extension AuthAPI: TargetType { + public var baseURL: URL { + return NetworkConfiguration.baseURL + } + + public var path: String { + switch self { + case .signup: + return "/api/v1/auth/users" + } + } + + public var method: Moya.Method { + switch self { + case .signup: + return .post + } + } + + public var task: Moya.Task { + switch self { + case .signup(let request): + return .requestJSONEncodable(request) + } + } + + public var headers: [String: String]? { + return ["Content-Type": "application/json"] + } + +} diff --git a/Projects/Modules/Networks/Tests/AuthServiceTests.swift b/Projects/Modules/Networks/Tests/AuthServiceTests.swift new file mode 100644 index 0000000..894a540 --- /dev/null +++ b/Projects/Modules/Networks/Tests/AuthServiceTests.swift @@ -0,0 +1,77 @@ +// +// AuthServiceTests.swift +// NetworksTests +// +// Created by kimnahun on 1/19/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import XCTest +@testable import Networks + +final class AuthServiceTests: XCTestCase { + + var sut: AuthService! + + override func setUp() { + super.setUp() + sut = AuthService() + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Signup 실제 API 테스트 + func test_signup_실제API호출() async throws { + // Given + print("========== 테스트 시작 ==========") + let request = SignupRequest( + fcmToken: "test-fcm-token-12345", + deviceModel: "iPhone", + deviceOs: "iOS", + deviceOsVersion: "17.0", + appVersion: "1.0.0" + ) + print("Request 생성 완료") + + // When + print("API 호출 시작...") + let result = await sut.signup(request: request) + print("API 호출 완료") + + // Then + switch result { + case .success(let response): + print("========== 성공 ==========") + print("UUID: \(response.uuid)") + print("AccessToken: \(response.accessToken)") + print("Nickname: \(response.nickname)") + print("==============================") + XCTAssertFalse(response.uuid.isEmpty, "UUID가 비어있음") + XCTAssertFalse(response.accessToken.isEmpty, "AccessToken이 비어있음") + + case .failure(let error): + print("========== API 에러 ==========") + print("Type: \(error.type)") + print("Message: \(error.message)") + if let field = error.field { + print("Field: \(field)") + } + if let fieldMessage = error.fieldMessage { + print("FieldMessage: \(fieldMessage)") + } + print("=================================") + XCTAssertFalse(error.message.isEmpty, "에러 메시지가 비어있음") + + case .networkFailure(let error): + print("==========네트워크 에러 ==========") + print("Error: \(error.message)") + print("=====================================") + XCTFail("네트워크 연결 실패: \(error.message)") + } + + print("========== 테스트 종료 ==========") + } +}