/* * Copyright 2023, gRPC Authors All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import XCTest @testable import GRPCCodeGen final class SnippetBasedTranslatorTests: XCTestCase { typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor func testTypealiasTranslator() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", name: "MethodA", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", outputType: "NamespaceA_ServiceAResponse" ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", namespace: "namespaceA", methods: [method] ) let expectedSwift = """ enum namespaceA { enum ServiceA { enum Methods { enum MethodA { typealias Input = NamespaceA_ServiceARequest typealias Output = NamespaceA_ServiceAResponse static let descriptor = MethodDescriptor( service: "namespaceA.ServiceA", method: "MethodA" ) } } static let methods: [MethodDescriptor] = [ Methods.MethodA.descriptor ] typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol } } """ try self.assertTypealiasTranslation( codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]), expectedSwift: expectedSwift ) } func testTypealiasTranslatorEmptyNamespace() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", name: "MethodA", isInputStreaming: false, isOutputStreaming: false, inputType: "ServiceARequest", outputType: "ServiceAResponse" ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", namespace: "", methods: [method] ) let expectedSwift = """ enum ServiceA { enum Methods { enum MethodA { typealias Input = ServiceARequest typealias Output = ServiceAResponse static let descriptor = MethodDescriptor( service: "ServiceA", method: "MethodA" ) } } static let methods: [MethodDescriptor] = [ Methods.MethodA.descriptor ] typealias StreamingServiceProtocol = ServiceAServiceStreamingProtocol typealias ServiceProtocol = ServiceAServiceProtocol } """ try self.assertTypealiasTranslation( codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]), expectedSwift: expectedSwift ) } func testTypealiasTranslatorCheckMethodsOrder() throws { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", name: "MethodA", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", outputType: "NamespaceA_ServiceAResponse" ) let methodB = MethodDescriptor( documentation: "Documentation for MethodB", name: "MethodB", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", outputType: "NamespaceA_ServiceAResponse" ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", namespace: "namespaceA", methods: [methodA, methodB] ) let expectedSwift = """ enum namespaceA { enum ServiceA { enum Methods { enum MethodA { typealias Input = NamespaceA_ServiceARequest typealias Output = NamespaceA_ServiceAResponse static let descriptor = MethodDescriptor( service: "namespaceA.ServiceA", method: "MethodA" ) } enum MethodB { typealias Input = NamespaceA_ServiceARequest typealias Output = NamespaceA_ServiceAResponse static let descriptor = MethodDescriptor( service: "namespaceA.ServiceA", method: "MethodB" ) } } static let methods: [MethodDescriptor] = [ Methods.MethodA.descriptor, Methods.MethodB.descriptor ] typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol } } """ try self.assertTypealiasTranslation( codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]), expectedSwift: expectedSwift ) } func testTypealiasTranslatorNoMethodsService() throws { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", namespace: "namespaceA", methods: [] ) let expectedSwift = """ enum namespaceA { enum ServiceA { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol } } """ try self.assertTypealiasTranslation( codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]), expectedSwift: expectedSwift ) } func testTypealiasTranslatorServiceAlphabeticalOrder() throws { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", namespace: "namespacea", methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", namespace: "namespacea", methods: [] ) let expectedSwift = """ enum namespacea { enum AService { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = namespacea_AServiceServiceStreamingProtocol typealias ServiceProtocol = namespacea_AServiceServiceProtocol } enum BService { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = namespacea_BServiceServiceStreamingProtocol typealias ServiceProtocol = namespacea_BServiceServiceProtocol } } """ try self.assertTypealiasTranslation( codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]), expectedSwift: expectedSwift ) } func testTypealiasTranslatorServiceAlphabeticalOrderNoNamespace() throws { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", namespace: "", methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", namespace: "", methods: [] ) let expectedSwift = """ enum AService { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = AServiceServiceStreamingProtocol typealias ServiceProtocol = AServiceServiceProtocol } enum BService { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = BServiceServiceStreamingProtocol typealias ServiceProtocol = BServiceServiceProtocol } """ try self.assertTypealiasTranslation( codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]), expectedSwift: expectedSwift ) } func testTypealiasTranslatorNamespaceAlphabeticalOrder() throws { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", namespace: "bnamespace", methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", namespace: "anamespace", methods: [] ) let expectedSwift = """ enum anamespace { enum AService { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = anamespace_AServiceServiceStreamingProtocol typealias ServiceProtocol = anamespace_AServiceServiceProtocol } } enum bnamespace { enum BService { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = bnamespace_BServiceServiceStreamingProtocol typealias ServiceProtocol = bnamespace_BServiceServiceProtocol } } """ try self.assertTypealiasTranslation( codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]), expectedSwift: expectedSwift ) } func testTypealiasTranslatorNamespaceNoNamespaceOrder() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", namespace: "anamespace", methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", namespace: "", methods: [] ) let expectedSwift = """ enum BService { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = BServiceServiceStreamingProtocol typealias ServiceProtocol = BServiceServiceProtocol } enum anamespace { enum AService { enum Methods {} static let methods: [MethodDescriptor] = [] typealias StreamingServiceProtocol = anamespace_AServiceServiceStreamingProtocol typealias ServiceProtocol = anamespace_AServiceServiceProtocol } } """ try self.assertTypealiasTranslation( codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceA, serviceB]), expectedSwift: expectedSwift ) } func testTypealiasTranslatorSameNameServicesNoNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", namespace: "", methods: [] ) let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceA]) let translator = TypealiasTranslator() self.assertThrowsError( ofType: CodeGenError.self, try translator.translate(from: codeGenerationRequest) ) { error in XCTAssertEqual( error as CodeGenError, CodeGenError( code: .nonUniqueServiceName, message: """ Services in an empty namespace must have unique names. \ AService is used as a name for multiple services without namespaces. """ ) ) } } func testTypealiasTranslatorSameNameServicesSameNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", namespace: "namespacea", methods: [] ) let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceA]) let translator = TypealiasTranslator() self.assertThrowsError( ofType: CodeGenError.self, try translator.translate(from: codeGenerationRequest) ) { error in XCTAssertEqual( error as CodeGenError, CodeGenError( code: .nonUniqueServiceName, message: """ Services within the same namespace must have unique names. \ AService is used as a name for multiple services in the namespacea namespace. """ ) ) } } func testTypealiasTranslatorSameNameMethodsSameServiceError() throws { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", name: "MethodA", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", outputType: "NamespaceA_ServiceAResponse" ) let service = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", namespace: "namespacea", methods: [methodA, methodA] ) let codeGenerationRequest = self.makeCodeGenerationRequest(services: [service]) let translator = TypealiasTranslator() self.assertThrowsError( ofType: CodeGenError.self, try translator.translate(from: codeGenerationRequest) ) { error in XCTAssertEqual( error as CodeGenError, CodeGenError( code: .nonUniqueMethodName, message: """ Methods of a service must have unique names. \ MethodA is used as a name for multiple methods of the AService service. """ ) ) } } func testTypealiasTranslatorSameNameNoNamespaceServiceAndNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for SameName service with no namespace", name: "SameName", namespace: "", methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", namespace: "SameName", methods: [] ) let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceB]) let translator = TypealiasTranslator() self.assertThrowsError( ofType: CodeGenError.self, try translator.translate(from: codeGenerationRequest) ) { error in XCTAssertEqual( error as CodeGenError, CodeGenError( code: .nonUniqueServiceName, message: """ Services with no namespace must not have the same names as the namespaces. \ SameName is used as a name for a service with no namespace and a namespace. """ ) ) } } } extension SnippetBasedTranslatorTests { private func assertTypealiasTranslation( codeGenerationRequest: CodeGenerationRequest, expectedSwift: String ) throws { let translator = TypealiasTranslator() let codeBlocks = try translator.translate(from: codeGenerationRequest) let renderer = TextBasedRenderer.default renderer.renderCodeBlocks(codeBlocks) let contents = renderer.renderedContents() try XCTAssertEqualWithDiff(contents, expectedSwift) } private func assertThrowsError( ofType: E.Type, _ expression: @autoclosure () throws -> T, _ errorHandler: (E) -> Void ) { XCTAssertThrowsError(try expression()) { error in guard let error = error as? E else { return XCTFail("Error had unexpected type '\(type(of: error))'") } errorHandler(error) } } } extension SnippetBasedTranslatorTests { private func makeCodeGenerationRequest(services: [ServiceDescriptor]) -> CodeGenerationRequest { return CodeGenerationRequest( fileName: "test.grpc", leadingTrivia: "Some really exciting license header 2023.", dependencies: [], services: services, lookupSerializer: { "ProtobufSerializer<\($0)>()" }, lookupDeserializer: { "ProtobufDeserializer<\($0)>()" } ) } }