diff --git a/README.md b/README.md index 276124a..3e06a39 100644 --- a/README.md +++ b/README.md @@ -2,34 +2,6 @@ A Swift package for merging multiple YAML files from subdirectories into a single combined file. -## 🏗️ Architecture Overview - -┌─────────────────────-┐ ┌──────────────────┐ ┌─────────────────────┐ -│ Schema YAML Files │───▶ │ YAMLMerger │───▶ │ Combined OpenAPI │ -│ (Organized by Type & │ │ (Test Runner) │ │ Specification │ -│ Function) │ │ │ │ (CombinedSpec.yaml) │ -└─────────────────────-┘ └──────────────────┘ └─────────────────────┘ - │ - ▼ -┌─────────────────────┐ -│ Manual Copy Step │ -│ (Human Action) │ -└─────────────────────┘ - │ - ▼ -┌─────────────────────┐ ┌──────────────────┐ ┌─────────────────────┐ -│ Generated Swift │◀─── │ Swift OpenAPI │◀─── │ SharedApiModels │ -│ Client & Server │ │ Generator │ │ openapi.yaml │ -│ Code │ │ │ │ │ -└─────────────────────┘ └──────────────────┘ └─────────────────────┘ - │ - ▼ -┌─────────────────────┐ -│ ApiClient & │ -│ ApiServer │ -│ Packages │ -└─────────────────────┘ - ## How It Works YAMLMerger recursively scans subdirectories for YAML files and merges them in a predictable order: diff --git a/Tests/YAMLMergerTests/CombinedSpec.yaml b/Tests/YAMLMergerTests/CombinedSpec.yaml deleted file mode 100644 index 0573fe8..0000000 --- a/Tests/YAMLMergerTests/CombinedSpec.yaml +++ /dev/null @@ -1,719 +0,0 @@ -openapi: 3.0.0 -info: - - - title: OpenAPI Generation Demo - version: 1.0.0 - description: Demonstration of OpenAPI specification generation using YAMLMerger - contact: - name: API Support - email: support@example.com - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0.html - -servers: - - - - url: https://dummyjson.com - description: DummyJSON API Server - - - url: http://localhost:8080 - description: Local development server - -tags: - - - - name: auth - description: Authentication operations - - name: users - description: User management operations - - name: posts - description: Post management operations - - name: products - description: Product catalog operations - - name: carts - description: Shopping cart operations - - name: todos - description: Todo management operations - - name: comments - description: Comment operations - -paths: - - - /auth/login: - post: - operationId: loginUser - tags: - - auth - summary: User login - description: Authenticate user and receive access token - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/LoginRequest' - responses: - '200': - description: Successful authentication - content: - application/json: - schema: - $ref: '#/components/schemas/AuthResponse' - '400': - description: Invalid credentials - - /carts: - get: - operationId: getAllCarts - tags: - - carts - summary: Get all carts - description: Retrieve a list of all shopping carts - parameters: - - name: limit - in: query - schema: - type: integer - default: 30 - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - carts: - type: array - items: - $ref: '#/components/schemas/Cart' - total: - type: integer - /carts/{id}: - get: - operationId: getCartById - tags: - - carts - summary: Get a single cart - parameters: - - name: id - in: path - required: true - schema: - type: integer - responses: - '200': - description: Successful response - content: - application/json: - schema: - $ref: '#/components/schemas/Cart' - - /comments: - get: - operationId: getAllComments - tags: - - comments - summary: Get all comments - description: Retrieve a list of all comments - parameters: - - name: limit - in: query - schema: - type: integer - default: 30 - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - comments: - type: array - items: - $ref: '#/components/schemas/Comment' - total: - type: integer - /comments/{id}: - get: - operationId: getCommentById - tags: - - comments - summary: Get a single comment - parameters: - - name: id - in: path - required: true - schema: - type: integer - responses: - '200': - description: Successful response - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - - /posts: - get: - operationId: getAllPosts - tags: - - posts - summary: Get all posts - description: Retrieve a list of all posts - parameters: - - name: limit - in: query - schema: - type: integer - default: 30 - - name: skip - in: query - schema: - type: integer - default: 0 - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - posts: - type: array - items: - $ref: '#/components/schemas/Post' - total: - type: integer - post: - operationId: createPost - tags: - - posts - summary: Add a new post - security: - - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Post' - responses: - '201': - description: Post created successfully - /posts/{id}: - get: - operationId: getPostById - tags: - - posts - summary: Get a single post - parameters: - - name: id - in: path - required: true - schema: - type: integer - responses: - '200': - description: Successful response - content: - application/json: - schema: - $ref: '#/components/schemas/Post' - - /products: - get: - operationId: getAllProducts - tags: - - products - summary: Get all products - description: Retrieve a list of all products - parameters: - - name: limit - in: query - schema: - type: integer - default: 30 - - name: skip - in: query - schema: - type: integer - default: 0 - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - products: - type: array - items: - $ref: '#/components/schemas/Product' - total: - type: integer - post: - operationId: createProduct - tags: - - products - summary: Add a new product - security: - - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Product' - responses: - '201': - description: Product created successfully - /products/{id}: - get: - operationId: getProductById - tags: - - products - summary: Get a single product - parameters: - - name: id - in: path - required: true - schema: - type: integer - responses: - '200': - description: Successful response - content: - application/json: - schema: - $ref: '#/components/schemas/Product' - - /todos: - get: - operationId: getAllTodos - tags: - - todos - summary: Get all todos - description: Retrieve a list of all todos - parameters: - - name: limit - in: query - schema: - type: integer - default: 30 - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - todos: - type: array - items: - $ref: '#/components/schemas/Todo' - total: - type: integer - post: - operationId: createTodo - tags: - - todos - summary: Add a new todo - security: - - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Todo' - responses: - '201': - description: Todo created successfully - /todos/{id}: - get: - operationId: getTodoById - tags: - - todos - summary: Get a single todo - parameters: - - name: id - in: path - required: true - schema: - type: integer - responses: - '200': - description: Successful response - content: - application/json: - schema: - $ref: '#/components/schemas/Todo' - - /users: - get: - operationId: getAllUsers - tags: - - users - summary: Get all users - description: Retrieve a list of all users - parameters: - - name: limit - in: query - schema: - type: integer - default: 30 - - name: skip - in: query - schema: - type: integer - default: 0 - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - total: - type: integer - skip: - type: integer - limit: - type: integer - post: - operationId: createUser - tags: - - users - summary: Add a new user - security: - - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/User' - responses: - '201': - description: User created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/User' - /users/{id}: - get: - operationId: getUserById - tags: - - users - summary: Get a single user - parameters: - - name: id - in: path - required: true - schema: - type: integer - responses: - '200': - description: Successful response - content: - application/json: - schema: - $ref: '#/components/schemas/User' - '404': - description: User not found - -webhooks: - - - newComment: - post: - summary: New comment notification - description: Webhook sent when a new comment is created - operationId: onNewComment - requestBody: - description: Comment data sent to subscriber - required: true - content: - application/json: - schema: - type: object - properties: - event: - type: string - example: comment.created - timestamp: - type: string - format: date-time - data: - $ref: '#/components/schemas/Comment' - responses: - '200': - description: Webhook received successfully - '400': - description: Bad request - - newPost: - post: - summary: New post notification - description: Webhook sent when a new post is published - operationId: onNewPost - requestBody: - description: Post data sent to subscriber - required: true - content: - application/json: - schema: - type: object - properties: - event: - type: string - example: post.published - timestamp: - type: string - format: date-time - data: - $ref: '#/components/schemas/Post' - responses: - '200': - description: Webhook received successfully - '400': - description: Bad request -components: - schemas: - - - AuthResponse: - type: object - properties: - id: - type: integer - username: - type: string - email: - type: string - format: email - firstName: - type: string - lastName: - type: string - gender: - type: string - image: - type: string - format: uri - accessToken: - type: string - refreshToken: - type: string - - Cart: - type: object - properties: - id: - type: integer - products: - type: array - items: - type: object - properties: - id: - type: integer - title: - type: string - price: - type: number - quantity: - type: integer - total: - type: number - discountPercentage: - type: number - discountedTotal: - type: number - total: - type: number - discountedTotal: - type: number - userId: - type: integer - totalProducts: - type: integer - totalQuantity: - type: integer - - Comment: - type: object - properties: - id: - type: integer - body: - type: string - postId: - type: integer - likes: - type: integer - user: - type: object - properties: - id: - type: integer - username: - type: string - fullName: - type: string - - LoginRequest: - type: object - required: - - username - - password - properties: - username: - type: string - example: emilys - password: - type: string - format: password - example: emilyspass - expiresInMins: - type: integer - default: 60 - - Post: - type: object - properties: - id: - type: integer - title: - type: string - body: - type: string - tags: - type: array - items: - type: string - reactions: - type: object - properties: - likes: - type: integer - dislikes: - type: integer - views: - type: integer - userId: - type: integer - - Product: - type: object - properties: - id: - type: integer - title: - type: string - description: - type: string - category: - type: string - price: - type: number - format: float - discountPercentage: - type: number - format: float - rating: - type: number - format: float - stock: - type: integer - brand: - type: string - thumbnail: - type: string - format: uri - images: - type: array - items: - type: string - format: uri - - Todo: - type: object - properties: - id: - type: integer - todo: - type: string - completed: - type: boolean - userId: - type: integer - - User: - type: object - properties: - id: - type: integer - firstName: - type: string - lastName: - type: string - maidenName: - type: string - age: - type: integer - gender: - type: string - email: - type: string - format: email - phone: - type: string - username: - type: string - birthDate: - type: string - format: date - image: - type: string - format: uri - address: - type: object - properties: - address: - type: string - city: - type: string - state: - type: string - postalCode: - type: string - - securitySchemes: - - - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: JWT token obtained from /auth/login endpoint - -externalDocs: - - - description: DummyJSON Documentation - url: https://dummyjson.com/docs diff --git a/Tests/YAMLMergerTests/YAMLMergerTests.swift b/Tests/YAMLMergerTests/YAMLMergerTests.swift index ceb48f4..d64207e 100644 --- a/Tests/YAMLMergerTests/YAMLMergerTests.swift +++ b/Tests/YAMLMergerTests/YAMLMergerTests.swift @@ -3,26 +3,42 @@ import XCTest final class YamlMergerTests: XCTestCase { func testMergeSchemaYAML() throws { - print("📂 CWD:", FileManager.default.currentDirectoryPath) - - // Access the resource from the test bundle + + // 1) INPUT: Schema in the test bundle let bundle = Bundle.module guard let schemaFolder = bundle.url(forResource: "Schema", withExtension: nil) else { XCTFail("Failed to locate Schema folder in resources") return } - // Merge the files + // 2) Merge as before (outputs inside schemaFolder) let merger = YAMLMerger( rootDirectory: schemaFolder, outputFileName: "CombinedSpec.yaml" ) merger.merge() - // Optional: assert output file exists - let outputURL = schemaFolder.appendingPathComponent("CombinedSpec.yaml") - print("📂 CWD:", outputURL) - XCTAssertTrue(FileManager.default.fileExists(atPath: outputURL.path)) + // 3) Verify source file exists where the merger wrote it + let sourceURL = schemaFolder.appendingPathComponent("CombinedSpec.yaml") + XCTAssertTrue(FileManager.default.fileExists(atPath: sourceURL.path), + "Merged file not found at \(sourceURL.path)") + + // 4) DEST: ~/Documents/CombinedSpec.yaml + let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! + let destURL = documentsURL.appendingPathComponent("CombinedSpec.yaml") + + // Ensure Documents exists (it will) and replace if present + let fm = FileManager.default + if fm.fileExists(atPath: destURL.path) { + try fm.removeItem(at: destURL) + } + + // 5) Copy (or use moveItem if you want to move instead of copy) + try fm.copyItem(at: sourceURL, to: destURL) + + // 6) Assert + log + XCTAssertTrue(fm.fileExists(atPath: destURL.path)) + print("✅ Copied merged spec to:", destURL.path) } }