Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Completed All Unit testing for Controllers and Interactors. Separated RQESKit Logic to a standalone controller to improve Interactor testing #36

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Sources/Domain/Controller/LocalizationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,20 @@ protocol LocalizationController: Sendable {
final class LocalizationControllerImpl: LocalizationController {

private let config: any EudiRQESUiConfig
private let locale: Locale

init(config: any EudiRQESUiConfig) {
init(
config: any EudiRQESUiConfig,
locale: Locale
) {
self.config = config
self.locale = locale
}

func get(with key: LocalizableKey, args: [String]) -> String {
guard
!config.translations.isEmpty,
let translations = config.translations[Locale.current.identifier],
let translations = config.translations[locale.identifier],
let translation = translations[key]
else {
return key.defaultTranslation(args: args)
Expand Down
83 changes: 83 additions & 0 deletions Sources/Domain/Controller/RQESController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (c) 2023 European Commission
*
* Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European
* Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work
* except in compliance with the Licence.
*
* You may obtain a copy of the Licence at:
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the Licence for the specific language
* governing permissions and limitations under the Licence.
*/
import RqesKit
import Foundation

protocol RQESController: Sendable {
func getRSSPMetadata() async throws -> RSSPMetadata
func getServiceAuthorizationUrl() async throws -> URL
func authorizeService(_ authorizationCode: String) async throws -> RQESServiceAuthorized
func authorizeCredential(_ authorizationCode: String) async throws -> RQESServiceCredentialAuthorized
func signDocuments(_ authorizationCode: String) async throws -> [Document]
func getCredentialsList() async throws -> [CredentialInfo]
func getCredentialAuthorizationUrl(credentialInfo: CredentialInfo, documents: [Document]) async throws -> URL
}

final class RQESControllerImpl: RQESController {

private let rqesUi: EudiRQESUi

init(rqesUi: EudiRQESUi) {
self.rqesUi = rqesUi
}

func getRSSPMetadata() async throws -> RSSPMetadata {
guard let rqesService = await self.rqesUi.getRQESService() else {
throw EudiRQESUiError.noRQESServiceProvided
}
return try await rqesService.getRSSPMetadata()
}

func getServiceAuthorizationUrl() async throws -> URL {
guard let rqesService = await self.rqesUi.getRQESService() else {
throw EudiRQESUiError.noRQESServiceProvided
}
return try await rqesService.getServiceAuthorizationUrl()
}

func authorizeService(_ authorizationCode: String) async throws -> RQESServiceAuthorized {
guard let rqesService = await self.rqesUi.getRQESService() else {
throw EudiRQESUiError.noRQESServiceProvided
}
return try await rqesService.authorizeService(authorizationCode: authorizationCode)
}

func authorizeCredential(_ authorizationCode: String) async throws -> RQESServiceCredentialAuthorized {
guard let rqesService = await self.rqesUi.getRQESServiceAuthorized() else {
throw EudiRQESUiError.noRQESServiceProvided
}
return try await rqesService.authorizeCredential(authorizationCode: authorizationCode)
}

func signDocuments(_ authorizationCode: String) async throws -> [Document] {
let authorized = try await authorizeCredential(authorizationCode)
return try await authorized.signDocuments()
}

func getCredentialsList() async throws -> [CredentialInfo] {
guard let rqesService = await self.rqesUi.getRQESServiceAuthorized() else {
throw EudiRQESUiError.noRQESServiceProvided
}
return try await rqesService.getCredentialsList()
}

func getCredentialAuthorizationUrl(credentialInfo: CredentialInfo, documents: [Document]) async throws -> URL {
guard let rqesService = await self.rqesUi.getRQESServiceAuthorized() else {
throw EudiRQESUiError.noRQESServiceProvided
}
return try await rqesService.getCredentialAuthorizationUrl(credentialInfo: credentialInfo, documents: documents)
}
}
10 changes: 9 additions & 1 deletion Sources/Domain/DI/Assembly/ControllerAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ final class ControllerAssembly: Assembly {
func assemble(container: Container) {

container.register(LocalizationController.self) { r in
LocalizationControllerImpl(config: EudiRQESUi.forceConfig())
LocalizationControllerImpl(
config: EudiRQESUi.forceConfig(),
locale: Locale.current
)
}
.inObjectScope(ObjectScope.container)

Expand All @@ -35,5 +38,10 @@ final class ControllerAssembly: Assembly {
PreferencesControllerImpl()
}
.inObjectScope(ObjectScope.transient)

container.register(RQESController.self) { r in
RQESControllerImpl(rqesUi: EudiRQESUi.forceInstance())
}
.inObjectScope(ObjectScope.transient)
}
}
5 changes: 4 additions & 1 deletion Sources/Domain/DI/Assembly/InteractorAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ final class InteractorAssembly: Assembly {

func assemble(container: Container) {
container.register(RQESInteractor.self) { r in
RQESInteractorImpl(rqesUi: EudiRQESUi.forceInstance())
RQESInteractorImpl(
rqesUi: EudiRQESUi.forceInstance(),
rqesController: r.force(RQESController.self)
)
}
.inObjectScope(ObjectScope.transient)
}
Expand Down
42 changes: 16 additions & 26 deletions Sources/Domain/Interactor/RQESInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@ protocol RQESInteractor: Sendable {
final class RQESInteractorImpl: RQESInteractor {

private let rqesUi: EudiRQESUi
private let rqesController: RQESController

init(rqesUi: EudiRQESUi) {
init(rqesUi: EudiRQESUi, rqesController: RQESController) {
self.rqesUi = rqesUi
self.rqesController = rqesController
}

func createRQESService(_ qtsp: QTSPData) async throws {
let rQESConfig = await rqesUi.getRQESConfig()
guard
let fileExtension = await getSession()?.document?.uri.pathExtension
let fileExtension = await getSession()?.document?.uri.pathExtension,
fileExtension.isEmpty == false
else {
throw EudiRQESUiError.noDocumentProvided
}
Expand All @@ -62,10 +65,8 @@ final class RQESInteractorImpl: RQESInteractor {

func signDocument() async throws -> Document? {
let authorizationCode = await self.getSession()?.code
let rQESServiceAuthorized = await self.rqesUi.getRQESServiceAuthorized()
if let authorizationCode, let rQESServiceAuthorized {
let authorizedCredential = try await rQESServiceAuthorized.authorizeCredential(authorizationCode: authorizationCode)
let signedDocuments = try await authorizedCredential.signDocuments()
if let authorizationCode {
let signedDocuments = try await rqesController.signDocuments(authorizationCode)
return signedDocuments.first
} else {
throw EudiRQESUiError.unableToSignHashDocument
Expand Down Expand Up @@ -93,51 +94,40 @@ final class RQESInteractorImpl: RQESInteractor {
}

func openAuthrorizationURL() async throws -> URL {
guard let rqesService = await self.rqesUi.getRQESService() else {
throw EudiRQESUiError.noRQESServiceProvided
}
let _ = try await rqesService.getRSSPMetadata()
let authorizationUrl = try await rqesService.getServiceAuthorizationUrl()
let _ = try await rqesController.getRSSPMetadata()
let authorizationUrl = try await rqesController.getServiceAuthorizationUrl()
return authorizationUrl
}

func openCredentialAuthrorizationURL() async throws -> URL {
if let uri = await self.getSession()?.document?.uri,
let certificate = await self.getSession()?.certificate {

let unsignedDocuments = [
Document(
id: UUID().uuidString,
fileURL: uri
)
]

let credentialAuthorizationUrl = try await self.rqesUi.getRQESServiceAuthorized()?.getCredentialAuthorizationUrl(
let credentialAuthorizationUrl = try await rqesController.getCredentialAuthorizationUrl(
credentialInfo: certificate,
documents: unsignedDocuments
)
return credentialAuthorizationUrl

if let credentialAuthorizationUrl {
return credentialAuthorizationUrl
} else {
throw EudiRQESUiError.unableToOpenURL
}
} else {
throw EudiRQESUiError.noDocumentProvided
}
}

func fetchCredentials() async throws -> Result<[CredentialInfo], any Error> {
if let rqesService = await self.rqesUi.getRQESService(),
let authorizationCode = await self.getSession()?.code {
if let authorizationCode = await self.getSession()?.code {
do {
let rQESServiceAuthorized = try await rqesService.authorizeService(authorizationCode: authorizationCode)
let rQESServiceAuthorized = try await rqesController.authorizeService(authorizationCode)
await self.rqesUi.setRQESServiceAuthorized(rQESServiceAuthorized)
let credentials = try? await self.rqesUi.getRQESServiceAuthorized()?.getCredentialsList()
if let credentials {
return .success(credentials)
} else {
return .failure(EudiRQESUiError.unableToFetchCredentials)
}
let credentials = try await rqesController.getCredentialsList()
return .success(credentials)
} catch {
return .failure(error)
}
Expand Down
14 changes: 7 additions & 7 deletions Sources/Infrastructure/EudiRQESUi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public final actor EudiRQESUi {
private var session = SessionData()

private static var _rqesService: RQESService?
private static var _rQESServiceAuthorized: RQESServiceAuthorized?
private static var _rqesServiceAuthorized: RQESServiceAuthorized?

@discardableResult
public init(config: any EudiRQESUiConfig) {
Expand All @@ -41,18 +41,18 @@ public final actor EudiRQESUi {
config: any EudiRQESUiConfig,
router: any RouterGraph,
state: State = .none,
selection: SessionData = .init(),
session: SessionData = .init(),
rqesService: RQESService? = nil,
rQESServiceAuthorized: RQESServiceAuthorized? = nil
rqesServiceAuthorized: RQESServiceAuthorized? = nil
) {
DIGraph.shared.load()
self.router = router
self.session = selection
self.session = session
Self._config = config
Self._state = state
Self._shared = self
Self._rqesService = rqesService
Self._rQESServiceAuthorized = rQESServiceAuthorized
Self._rqesServiceAuthorized = rqesServiceAuthorized
}

@MainActor
Expand Down Expand Up @@ -191,11 +191,11 @@ extension EudiRQESUi {
}

func getRQESServiceAuthorized() -> RQESServiceAuthorized? {
Self._rQESServiceAuthorized
Self._rqesServiceAuthorized
}

func setRQESServiceAuthorized(_ service: RQESServiceAuthorized?) {
Self._rQESServiceAuthorized = service
Self._rqesServiceAuthorized = service
}

func getRQESConfig() -> RqesServiceConfig {
Expand Down
61 changes: 61 additions & 0 deletions Tests/Domain/Controller/TestLocalizationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,75 @@
* governing permissions and limitations under the Licence.
*/
import XCTest
import Cuckoo
@testable import EudiRQESUi

final class TestLocalizationController: XCTestCase {

var config: MockEudiRQESUiConfig!
var controller: LocalizationController!

override func setUp() {
self.config = MockEudiRQESUiConfig()
self.controller = LocalizationControllerImpl(
config: config,
locale: .init(identifier: "en_US")
)
}

override func tearDown() {
self.config = nil
self.controller = nil
}

func testGet_WhenTranslationIsAvailableWithoutArgs_ReturnsStringTranslation() {
// Given
let customGenericErrorTranslation = "CustomGenericError"
stub(config) { mock in
when(mock.translations.get).thenReturn(
["en_US": [.genericErrorMessage: customGenericErrorTranslation]]
)
}

let result = self.controller.get(with: .genericErrorMessage)

XCTAssertEqual(result, customGenericErrorTranslation)
}

func testGet_WhenTranslationIsAvailableWithoutArgs_ReturnsLocalizedStringKeyTranslation() {
// Given
let customGenericErrorTranslation = "CustomGenericError"
stub(config) { mock in
when(mock.translations.get).thenReturn(
["en_US": [.genericErrorMessage: customGenericErrorTranslation]]
)
}

let result: LocalizedStringKey = self.controller.get(with: .genericErrorMessage, args: [])

XCTAssertEqual(result, customGenericErrorTranslation.toLocalizedStringKey)
}

func testGet_WhenTranslationIsNotAvailableWithoutArgs_ReturnsDefaultStringTranslation() {
// Given
stub(config) { mock in
when(mock.translations.get).thenReturn([:])
}

let result = self.controller.get(with: .genericErrorMessage)

XCTAssertEqual(result, LocalizableKey.genericErrorMessage.defaultTranslation(args: []))
}

func testGet_WhenTranslationIsNotAvailableWithArgs_ReturnsDefaultStringTranslation() {
// Given
stub(config) { mock in
when(mock.translations.get).thenReturn([:])
}

let result: String = self.controller.get(with: .signedBy, args: ["NISCY"])

XCTAssertEqual(result, LocalizableKey.signedBy.defaultTranslation(args: ["NISCY"]))
}

}
Loading
Loading