Skip to content

Commit 1ab10fe

Browse files
Add SnippetBasedReferenceTests
Signed-off-by: Si Beaumont <beaumont@apple.com>
1 parent 8e90397 commit 1ab10fe

File tree

1 file changed

+351
-0
lines changed

1 file changed

+351
-0
lines changed
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import OpenAPIKit30
15+
import XCTest
16+
import Yams
17+
@testable import _OpenAPIGeneratorCore
18+
19+
/// Tests that the generator produces Swift code that match a reference.
20+
final class SnippetBasedReferenceTests: XCTestCase {
21+
func testComponentsHeadersInline() throws {
22+
try self.assertHeadersTranslation(
23+
"""
24+
openapi: "3.0.3"
25+
info:
26+
title: "Test"
27+
version: "0.0.0"
28+
paths: {}
29+
components:
30+
headers:
31+
MyHeader:
32+
schema:
33+
type: string
34+
""",
35+
"""
36+
public enum Headers {
37+
public typealias MyHeader = Swift.String
38+
}
39+
"""
40+
)
41+
}
42+
43+
func testComponentsHeadersReference() throws {
44+
try self.assertHeadersTranslation(
45+
"""
46+
openapi: "3.0.3"
47+
info:
48+
title: "Test"
49+
version: "0.0.0"
50+
paths: {}
51+
components:
52+
headers:
53+
MyHeader:
54+
schema:
55+
$ref: "#/components/schemas/MySchema"
56+
""",
57+
"""
58+
public enum Headers {
59+
public typealias MyHeader = Components.Schemas.MySchema
60+
}
61+
"""
62+
)
63+
}
64+
65+
func testComponentsParametersInline() throws {
66+
try self.assertParametersTranslation(
67+
"""
68+
openapi: "3.0.3"
69+
info:
70+
title: "Test"
71+
version: "0.0.0"
72+
paths: {}
73+
components:
74+
parameters:
75+
MyParam:
76+
in: query
77+
name: my_param
78+
schema:
79+
type: string
80+
""",
81+
"""
82+
public enum Parameters {
83+
public typealias MyParam = Swift.String
84+
}
85+
"""
86+
)
87+
}
88+
89+
func testComponentsParametersReference() throws {
90+
try self.assertParametersTranslation(
91+
"""
92+
openapi: "3.0.3"
93+
info:
94+
title: "Test"
95+
version: "0.0.0"
96+
paths: {}
97+
components:
98+
parameters:
99+
MyParam:
100+
in: query
101+
name: my_param
102+
schema:
103+
$ref: "#/components/schemas/MySchema"
104+
""",
105+
"""
106+
public enum Parameters {
107+
public typealias MyParam = Components.Schemas.MySchema
108+
}
109+
"""
110+
)
111+
}
112+
113+
func testComponentsSchemasObject() throws {
114+
let openAPIDocument = """
115+
openapi: "3.0.3"
116+
info:
117+
title: "Test"
118+
version: "0.0.0"
119+
paths: {}
120+
components:
121+
schemas:
122+
MyObject:
123+
type: object
124+
title: My object
125+
properties:
126+
id:
127+
type: integer
128+
format: int64
129+
alias:
130+
type: string
131+
required:
132+
- id
133+
"""
134+
let expectedSwift = """
135+
public enum Schemas {
136+
public struct MyObject: Codable, Equatable, Hashable, Sendable {
137+
public var id: Swift.Int64
138+
public var alias: Swift.String?
139+
public init(id: Swift.Int64, alias: Swift.String? = nil) {
140+
self.id = id
141+
self.alias = alias
142+
}
143+
public enum CodingKeys: String, CodingKey {
144+
case id
145+
case alias
146+
}
147+
}
148+
}
149+
"""
150+
let translator = try makeTypesTranslator(openAPIDocument)
151+
let translation = try translator.translateSchemas(translator.components.schemas)
152+
try XCTAssertSwiftEquivalent(translation, expectedSwift)
153+
}
154+
155+
func testComponentsSchemasEnum() throws {
156+
let openAPIDocument = """
157+
openapi: "3.0.3"
158+
info:
159+
title: "Test"
160+
version: "0.0.0"
161+
paths: {}
162+
components:
163+
schemas:
164+
MyEnum:
165+
type: string
166+
enum:
167+
- one
168+
# empty
169+
-
170+
# illegal character
171+
- $tart
172+
# keyword
173+
- public
174+
"""
175+
let expectedSwift = """
176+
public enum Schemas {
177+
@frozen
178+
public enum MyEnum: RawRepresentable, Codable, Equatable, Hashable, Sendable,
179+
_AutoLosslessStringConvertible, CaseIterable
180+
{
181+
case one
182+
case _empty
183+
case _tart
184+
case _public
185+
case undocumented(String)
186+
public init?(rawValue: String) {
187+
switch rawValue {
188+
case "one": self = .one
189+
case "": self = ._empty
190+
case "$tart": self = ._tart
191+
case "public": self = ._public
192+
default: self = .undocumented(rawValue)
193+
}
194+
}
195+
public var rawValue: String {
196+
switch self {
197+
case let .undocumented(string): return string
198+
case .one: return "one"
199+
case ._empty: return ""
200+
case ._tart: return "$tart"
201+
case ._public: return "public"
202+
}
203+
}
204+
public static var allCases: [MyEnum] { [.one, ._empty, ._tart, ._public] }
205+
}
206+
}
207+
"""
208+
let translator = try makeTypesTranslator(openAPIDocument)
209+
let translation = try translator.translateSchemas(translator.components.schemas)
210+
try XCTAssertSwiftEquivalent(translation, expectedSwift)
211+
}
212+
}
213+
214+
extension SnippetBasedReferenceTests {
215+
func makeTypesTranslator(_ openAPIDocument: String) throws -> TypesFileTranslator {
216+
let document = try YAMLDecoder().decode(OpenAPI.Document.self, from: openAPIDocument)
217+
return TypesFileTranslator(
218+
config: Config(mode: .types),
219+
diagnostics: XCTestDiagnosticCollector(test: self),
220+
components: document.components
221+
)
222+
}
223+
224+
func assertHeadersTranslation(
225+
_ openAPIDocument: String,
226+
_ expectedSwift: String,
227+
file: StaticString = #filePath,
228+
line: UInt = #line
229+
) throws {
230+
let translator = try makeTypesTranslator(openAPIDocument)
231+
let translation = try translator.translateComponentHeaders(translator.components.headers)
232+
try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line)
233+
}
234+
235+
func assertParametersTranslation(
236+
_ openAPIDocument: String,
237+
_ expectedSwift: String,
238+
file: StaticString = #filePath,
239+
line: UInt = #line
240+
) throws {
241+
let translator = try makeTypesTranslator(openAPIDocument)
242+
let translation = try translator.translateComponentParameters(translator.components.parameters)
243+
try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line)
244+
}
245+
}
246+
247+
private func XCTAssertEqualWithDiff(
248+
_ actual: String,
249+
_ expected: String,
250+
file: StaticString = #filePath,
251+
line: UInt = #line
252+
) throws {
253+
if actual == expected { return }
254+
XCTFail(
255+
"""
256+
XCTAssertEqualWithDiff failed (click for diff)
257+
\(try diff(expected: expected, actual: actual))
258+
""",
259+
file: file,
260+
line: line
261+
)
262+
}
263+
264+
private func XCTAssertSwiftEquivalent(
265+
_ declaration: Declaration,
266+
_ expectedSwift: String,
267+
file: StaticString = #filePath,
268+
line: UInt = #line
269+
) throws {
270+
try XCTAssertEqualWithDiff(
271+
TextBasedRenderer().renderedDeclaration(declaration.strippingComments).swiftFormatted,
272+
expectedSwift.swiftFormatted,
273+
file: file,
274+
line: line
275+
)
276+
}
277+
278+
private func diff(expected: String, actual: String) throws -> String {
279+
let process = Process()
280+
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
281+
process.arguments = [
282+
"bash", "-c",
283+
"diff -U5 --label=expected <(echo '\(expected)') --label=actual <(echo '\(actual)')",
284+
]
285+
let pipe = Pipe()
286+
process.standardOutput = pipe
287+
try process.run()
288+
process.waitUntilExit()
289+
let pipeData = try XCTUnwrap(
290+
pipe.fileHandleForReading.readToEnd(),
291+
"""
292+
No output from command:
293+
\(process.executableURL!.path) \(process.arguments!.joined(separator: " "))
294+
"""
295+
)
296+
return String(decoding: pipeData, as: UTF8.self)
297+
}
298+
299+
fileprivate extension Declaration {
300+
var strippingComments: Self { stripComments(self) }
301+
302+
func stripComments(_ decl: Declaration) -> Declaration {
303+
switch decl {
304+
case let .commentable(_, d):
305+
return stripComments(d)
306+
case let .deprecated(a, b):
307+
return .deprecated(a, stripComments(b))
308+
case var .protocol(p):
309+
p.members = p.members.map(stripComments(_:))
310+
return .protocol(p)
311+
case var .function(f):
312+
f.body = f.body?.map(stripComments(_:))
313+
return .function(f)
314+
case var .extension(e):
315+
e.declarations = e.declarations.map(stripComments(_:))
316+
return .extension(e)
317+
case var .struct(s):
318+
s.members = s.members.map(stripComments(_:))
319+
return .struct(s)
320+
case var .enum(e):
321+
e.members = e.members.map(stripComments(_:))
322+
return .enum(e)
323+
case var .variable(v):
324+
v.body = stripComments(v.body)
325+
return .variable(v)
326+
case let .typealias(t):
327+
return .typealias(t)
328+
case let .enumCase(e):
329+
return .enumCase(e)
330+
}
331+
}
332+
333+
func stripComments(_ body: [CodeBlock]?) -> [CodeBlock]? {
334+
body.map(stripComments(_:))
335+
}
336+
337+
func stripComments(_ body: [CodeBlock]) -> [CodeBlock] {
338+
body.map(stripComments(_:))
339+
}
340+
341+
func stripComments(_ codeBlock: CodeBlock) -> CodeBlock {
342+
CodeBlock(comment: nil, item: stripComments(codeBlock.item))
343+
}
344+
345+
func stripComments(_ codeBlockItem: CodeBlockItem) -> CodeBlockItem {
346+
switch codeBlockItem {
347+
case let .declaration(d): return .declaration(stripComments(d))
348+
case .expression: return codeBlockItem
349+
}
350+
}
351+
}

0 commit comments

Comments
 (0)