diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4695914..82d0768 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,51 @@ version: 2 updates: + # Keep bundler dependencies up to date - package-ecosystem: "bundler" directory: "/" schedule: - interval: "daily" + interval: "weekly" + groups: + # Group Rails and related gems together + rails: + patterns: + - "rails*" + - "actionpack" + - "actionview" + - "activerecord" + - "activejob" + - "actioncable" + - "actionmailer" + - "activestorage" + - "actionmailbox" + - "actiontext" + # Group testing gems together + testing: + patterns: + - "rspec*" + - "capybara" + - "selenium-webdriver" + labels: + - "dependencies" + - "ruby" + open-pull-requests-limit: 10 + + # Keep GitHub Actions up to date + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "dependencies" + - "github-actions" + open-pull-requests-limit: 5 + + # Keep Docker base image up to date + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + labels: + - "dependencies" + - "docker" + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ba4118..5e629eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: branches: [main] schedule: - cron: "50 9 * * *" + workflow_dispatch: jobs: run_tests: @@ -23,12 +24,30 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.3.0 + ruby-version: 3.4.1 + bundler-cache: true - name: Install dependencies run: bundle install - - name: Run tests - run: bundle exec rspec test/integration + - name: Validate Couchbase configuration + run: | + if [ -z "$DB_CONN_STR" ] || [ -z "$DB_USERNAME" ] || [ -z "$DB_PASSWORD" ]; then + echo "Error: Missing required Couchbase configuration" + echo "DB_CONN_STR, DB_USERNAME, and DB_PASSWORD must all be set" + exit 1 + fi + echo "Couchbase configuration validated successfully" + + - name: Run integration tests + run: bundle exec rspec spec/requests/api/v1 + + - name: Verify Swagger documentation generates + run: bundle exec rake rswag:specs:swaggerize + env: + DB_CONN_STR: ${{ vars.DB_CONN_STR }} + DB_USERNAME: ${{ vars.DB_USERNAME }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + CI: true - name: Report Status if: always() diff --git a/.gitignore b/.gitignore index 805b3b4..d6cf442 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ Gemfile.lock # Ignore all environment files (except templates). /.env* !/.env*.erb +/dev.env # Ignore all logfiles and tempfiles. /log/* diff --git a/.ruby-version b/.ruby-version index 03463f3..47b322c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-3.3.0 +3.4.1 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..041df9a --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.4.1 diff --git a/Dockerfile b/Dockerfile index aa8f510..d53e3b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# Use the official Ruby 3.0.0 image as the base image -FROM ruby:3.3.0 +# Use the official Ruby 3.4.1 image as the base image +FROM ruby:3.4.1 # Install essential Linux packages RUN apt-get update -qq && \ diff --git a/Gemfile b/Gemfile index 09cb5d7..f6f2bd3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby '3.3.0' +ruby '3.4.1' # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem 'rails', '~> 7.1.3', '>= 7.1.3.2' diff --git a/README.md b/README.md index e21c7b1..b821cb7 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To run this prebuilt project, you will need: - [Couchbase Capella cluster](https://www.couchbase.com/products/capella/) with [travel-sample bucket](https://docs.couchbase.com/ruby-sdk/current/ref/travel-app-data-model.html) loaded. - To run this tutorial using a [self-managed Couchbase cluster](https://docs.couchbase.com/capella/current/getting-started/self-managed-cluster.html), please refer to the appendix. -- [Ruby 3.3.0](https://www.ruby-lang.org/en/documentation/installation/) is installed on the local machine. +- [Ruby 3.4.1](https://www.ruby-lang.org/en/documentation/installation/) is installed on the local machine. - Basic knowledge of [Ruby](https://www.ruby-lang.org/en/documentation/), [Ruby on Rails](https://rubyonrails.org/), and [RSpec](https://rspec.info/). ## Loading Travel Sample Bucket @@ -48,7 +48,27 @@ To learn more about connecting to your Capella cluster, please follow the instru Specifically, you need to do the following: -Open the `config/couchbase.yml` file and update the connection string, username, password, and bucket name for the development and test environments. +#### For Development and Test Environments + +Copy the `dev.env.example` file to `dev.env` and update the connection details: + +```sh +cp dev.env.example dev.env +``` + +Edit `dev.env` with your Couchbase credentials: + +```sh +DB_USERNAME="your_username" +DB_PASSWORD="your_password" +DB_CONN_STR="couchbases://your-cluster.cloud.couchbase.com" +``` + +The application will automatically load these environment variables in development and test environments. + +#### Configuration File Reference + +The `config/couchbase.yml` file defines how the application connects to Couchbase: ```yml common: &common @@ -109,14 +129,105 @@ The application will run on the port specified by Rails on your local machine (e ## Running The Tests -The application comes with a set of integration tests that can be run to verify the functionality of the application. The tests are written using RSpec, a popular testing framework for Ruby. +The application includes two types of tests: + +### Integration Tests -To run the tests, you can use the following command: +Integration tests verify the actual functionality of the API endpoints. They test the full request-response cycle including database operations. ```sh -bundle exec rspec test/integration +bundle exec rspec spec/requests/api/v1 ``` +### Swagger Documentation Tests + +These tests generate the OpenAPI/Swagger documentation and verify the API contract without performing full integration testing. + +```sh +bundle exec rake rswag:specs:swaggerize +``` + +## Health Check Endpoint + +The application provides a health check endpoint to monitor the status of the service and its dependencies: + +```sh +GET /api/v1/health +``` + +This endpoint returns the health status of the application and its connection to Couchbase. Example response: + +```json +{ + "status": "healthy", + "timestamp": "2025-12-02T10:30:00Z", + "services": { + "couchbase": { + "status": "up", + "message": "Connected to Couchbase bucket: travel-sample" + } + } +} +``` + +You can use this endpoint for monitoring and alerting in production environments. + +## Troubleshooting + +### Couchbase ORM Connection Issues + +If you encounter connection issues with Couchbase ORM, verify the following: + +1. **Check configuration**: Ensure `config/couchbase.yml` has the correct connection string, username, and password. +2. **Connection string format**: The connection string should start with `couchbase://` (for non-TLS) or `couchbases://` (for TLS). +3. **Bucket access**: Verify that the user has read/write permissions to the travel-sample bucket. +4. **Network connectivity**: Ensure your application can reach the Couchbase cluster on the required ports (typically 8091-8096, 11210). + +### Common couchbase-orm Errors + +**Error: `Couchbase::Error::BucketNotFound`** +- The specified bucket doesn't exist or isn't accessible +- Solution: Verify the bucket name in `config/couchbase.yml` and ensure the travel-sample bucket is loaded + +**Error: `Couchbase::Error::AuthenticationFailure`** +- Invalid credentials +- Solution: Check username and password in `config/couchbase.yml` or environment variables + +**Error: `Couchbase::Error::Timeout`** +- Network connectivity issues or cluster overload +- Solution: Check network connectivity, increase timeout in connection options, or verify cluster health + +**Error: `NoMethodError: undefined method 'bucket' for Model`** +- ORM not properly initialized +- Solution: Ensure `config/couchbase.yml` is correctly configured and the Rails application has loaded the configuration + +### Test Setup Issues + +**Tests failing with connection errors:** +- Ensure environment variables `DB_CONN_STR`, `DB_USERNAME`, and `DB_PASSWORD` are set for the test environment +- Verify the travel-sample bucket is loaded and accessible +- Check that the test configuration in `config/couchbase.yml` references these environment variables + +**Swagger generation fails:** +- Run `bundle exec rake rswag:specs:swaggerize` with proper environment variables +- Check that all swagger specs in `spec/requests/swagger/` are valid + +### CI/CD Configuration + +When setting up GitHub Actions or other CI/CD pipelines: + +1. Set required secrets/variables: + - `DB_CONN_STR`: Connection string to your Couchbase cluster + - `DB_USERNAME`: Username with bucket access + - `DB_PASSWORD`: Password (set as a secret, not a variable) + +2. Ensure the CI environment can access your Couchbase cluster (check firewall rules and allowed IP addresses) + +3. The CI workflow runs: + - Configuration validation + - Integration tests: `bundle exec rspec spec/requests/api/v1` + - Swagger generation: `bundle exec rake rswag:specs:swaggerize` + # Appendix ## Data Model @@ -151,53 +262,64 @@ If you would like to add another entity to the APIs, follow these steps: - Implement the necessary CRUD actions (index, show, create, update, destroy) in the controller. - Example: `app/controllers/api/v1/customers_controller.rb` -4. Add Swagger documentation: - - Open the `spec/requests/api/v1/customers_spec.rb` file. +4. Add Swagger documentation (for API documentation only): + - Create a new swagger spec file in `spec/requests/swagger/customers_spec.rb`. - Define the Swagger documentation for the new entity's API endpoints using RSpec and the `rswag` gem. - - Specify the request and response parameters, headers, and schemas for each endpoint. + - These specs should be documentation-only (no actual database operations). - Example: ```ruby require 'swagger_helper' + # Documentation-only specs for Swagger/OpenAPI generation + # Actual integration testing done in spec/requests/api/v1/customers_spec.rb describe 'Customers API', type: :request do - path '/api/v1/customers' do - get 'Retrieves all customers' do + path '/api/v1/customers/{id}' do + get 'Retrieves a customer' do tags 'Customers' produces 'application/json' - - response '200', 'customers retrieved' do - schema type: :array, - items: { - type: :object, - properties: { - id: { type: :integer }, - name: { type: :string }, - email: { type: :string } - }, - required: ['id', 'name', 'email'] - } - - run_test! + parameter name: :id, in: :path, type: :string + + response '200', 'customer found' do + schema type: :object, + properties: { + name: { type: :string }, + email: { type: :string } + }, + required: ['name', 'email'] + + let(:id) { 'customer_123' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/customers_spec.rb + end end end end end ``` -5. Add integration tests: - - Create a new integration test file for the entity in the `test/integration` folder. - - Write integration tests to cover the CRUD operations of the new entity. - - Example: `test/integration/customers_spec.rb` +5. Add integration tests (for actual functionality testing): + - Create a new integration test file for the entity in the `spec/requests/api/v1/` folder. + - Write comprehensive integration tests to verify CRUD operations work correctly. + - Example: `spec/requests/api/v1/customers_spec.rb` ```ruby require 'rails_helper' RSpec.describe 'Customers API', type: :request do - describe 'GET /api/v1/customers' do - # Add tests for retrieving customers + describe 'GET /api/v1/customers/{id}' do + it 'returns the customer' do + get '/api/v1/customers/customer_123' + expect(response).to have_http_status(:ok) + # Add more assertions + end end - describe 'POST /api/v1/customers' do - # Add tests for creating a customer + describe 'POST /api/v1/customers/{id}' do + it 'creates a customer' do + post '/api/v1/customers/customer_new', params: { customer: { name: 'Test', email: 'test@example.com' } } + expect(response).to have_http_status(:created) + # Clean up + delete '/api/v1/customers/customer_new' + end end # Add more tests for other CRUD operations @@ -205,8 +327,9 @@ If you would like to add another entity to the APIs, follow these steps: ``` 6. Run tests and verify: - - Run the integration tests using the command `bundle exec rspec test/integration`. - - Ensure that all tests pass and the new entity's CRUD operations are working as expected. + - Run the integration tests: `bundle exec rspec spec/requests/api/v1/customers_spec.rb` + - Generate swagger documentation: `bundle exec rake rswag:specs:swaggerize` + - Ensure that all tests pass and the new entity's CRUD operations work correctly. By following these steps, you can systematically extend the API functionality with a new entity while maintaining a well-structured and tested codebase. diff --git a/Ruby-on-Rails-install.md b/Ruby-on-Rails-install.md new file mode 100644 index 0000000..812c8da --- /dev/null +++ b/Ruby-on-Rails-install.md @@ -0,0 +1,403 @@ +# Install Ruby and Rails on macOS/Linux + +This guide will help you install Ruby 3.4.1 and Ruby on Rails using rbenv, which is the recommended Ruby version manager for this project. + +## Quick Start (macOS) + +```sh +# 1. Install rbenv +brew install rbenv ruby-build + +# 2. Add to shell config +echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc + +# 3. RESTART YOUR TERMINAL (or run: source ~/.zshrc) + +# 4. Install Ruby 3.4.1 +rbenv install 3.4.1 + +# 5. Install Bundler +gem install bundler + +# 6. Clone project and install dependencies +cd ruby-on-rails-quickstart +bundle install +``` + +**Important**: You MUST restart your terminal (or run `source ~/.zshrc`) after step 2 for rbenv to work! + +## Quick Start (Ubuntu/Linux) + +```sh +# 1. Install dependencies +sudo apt update +sudo apt install -y git curl autoconf bison build-essential \ + libssl-dev libyaml-dev libreadline6-dev zlib1g-dev \ + libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev + +# 2. Install rbenv +git clone https://github.com/rbenv/rbenv.git ~/.rbenv +git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build + +# 3. Add to shell config +echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc +echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc + +# 4. RESTART YOUR TERMINAL (or run: source ~/.bashrc) + +# 5. Install Ruby 3.4.1 +rbenv install 3.4.1 + +# 6. Install Bundler +gem install bundler + +# 7. Clone project and install dependencies +cd ruby-on-rails-quickstart +bundle install +``` + +**Important**: You MUST restart your terminal (or run `source ~/.bashrc`) after step 3 for rbenv to work! + +## Why rbenv? + +rbenv is: +- The most popular Ruby version manager in the community +- Lightweight and transparent +- Works automatically with the `.ruby-version` file in this repository +- Compatible with all UNIX-like systems + +## Prerequisites + +Before installing Ruby and Rails, ensure you have the following: + +- macOS, Linux, or WSL2 on Windows +- Git installed +- A terminal/shell (bash or zsh) + +## Installation Steps + +### For macOS + +#### 1. Install Homebrew (if not already installed) + +```sh +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +#### 2. Install rbenv and ruby-build + +```sh +brew install rbenv ruby-build +``` + +#### 3. Initialize rbenv in your shell + +For **zsh** (default on macOS Catalina and later): + +```sh +echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc +``` + +For **bash**: + +```sh +echo 'eval "$(rbenv init - bash)"' >> ~/.bash_profile +``` + +**Important**: After adding rbenv to your shell config, you MUST do one of the following: +- **Restart your terminal** (recommended - close and reopen), OR +- Run `source ~/.zshrc` (zsh) or `source ~/.bash_profile` (bash) to reload your shell config + +The rbenv commands will not work until you do this! + +#### 4. Verify rbenv installation + +```sh +curl -fsSL https://github.com/rbenv/rbenv-installer/raw/main/bin/rbenv-doctor | bash +``` + +This should show that rbenv is properly set up. + +#### 5. Install Ruby 3.4.1 + +```sh +rbenv install 3.4.1 +``` + +This will take several minutes as it compiles Ruby from source. + +#### 6. Set Ruby 3.4.1 as the global default (optional) + +```sh +rbenv global 3.4.1 +``` + +**Note**: The project's `.ruby-version` file will automatically use Ruby 3.4.1 when you're in the project directory, regardless of your global setting. + +#### 7. Verify Ruby installation + +```sh +ruby -v +# Should output: ruby 3.4.1 (...) +``` + +#### 8. Install Bundler + +```sh +gem install bundler +``` + +#### 9. Install Rails + +The project specifies Rails in its Gemfile, so Rails will be installed when you run `bundle install` in the project directory. However, if you want to install Rails globally: + +```sh +gem install rails -v 7.1.5.1 +``` + +#### 10. Verify Rails installation + +```sh +rails -v +# Should output: Rails 7.1.5.1 (or the version specified in the Gemfile) +``` + +### For Ubuntu/Debian Linux + +#### 1. Install dependencies + +```sh +sudo apt update +sudo apt install -y git curl autoconf bison build-essential \ + libssl-dev libyaml-dev libreadline6-dev zlib1g-dev \ + libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev +``` + +#### 2. Install rbenv and ruby-build + +```sh +# Clone rbenv repository +git clone https://github.com/rbenv/rbenv.git ~/.rbenv + +# Clone ruby-build repository +git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build + +# Add rbenv to PATH and initialize +echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc +echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc + +# For zsh users, use these instead: +# echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.zshrc +# echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc +``` + +**Important**: After adding rbenv to your shell config, you MUST do one of the following: +- **Restart your terminal** (recommended - close and reopen), OR +- Run `source ~/.bashrc` (bash) or `source ~/.zshrc` (zsh) to reload your shell config + +The rbenv commands will not work until you do this! + +#### 3. Verify rbenv installation + +```sh +curl -fsSL https://github.com/rbenv/rbenv-installer/raw/main/bin/rbenv-doctor | bash +``` + +#### 4. Install Ruby 3.4.1 + +```sh +rbenv install 3.4.1 +rbenv global 3.4.1 +``` + +#### 5. Verify Ruby installation + +```sh +ruby -v +# Should output: ruby 3.4.1 (...) +``` + +#### 6. Install Bundler and Rails + +```sh +gem install bundler +gem install rails -v 7.1.5.1 +``` + +## Working with the Project + +Once you have Ruby and rbenv installed: + +1. Clone the project repository +2. Navigate to the project directory +3. rbenv will automatically use Ruby 3.4.1 (specified in `.ruby-version`) +4. Run `bundle install` to install all project dependencies + +```sh +git clone https://github.com/couchbase-examples/ruby-on-rails-quickstart.git +cd ruby-on-rails-quickstart +ruby -v # Should show 3.4.1 +bundle install +``` + +## Managing Ruby Versions + +### Check installed Ruby versions + +```sh +rbenv versions +``` + +### Install a new Ruby version + +```sh +rbenv install 3.4.1 +``` + +### Set Ruby version for a specific project + +rbenv automatically reads the `.ruby-version` file. You can create or update it: + +```sh +echo "3.4.1" > .ruby-version +``` + +### Set global Ruby version + +```sh +rbenv global 3.4.1 +``` + +### Update rbenv and ruby-build + +**macOS (Homebrew):** + +```sh +brew upgrade rbenv ruby-build +``` + +**Linux:** + +```sh +cd ~/.rbenv +git pull +cd ~/.rbenv/plugins/ruby-build +git pull +``` + +## Troubleshooting + +### rbenv command not found + +If you see `rbenv: command not found`, it means your shell hasn't loaded the rbenv initialization yet. + +**Solution 1** (Recommended): Restart your terminal (close and reopen) + +**Solution 2**: Reload your shell config: +```sh +# For zsh +source ~/.zshrc + +# For bash +source ~/.bashrc +``` + +If the problem persists, ensure rbenv initialization was added to your shell config: + +```sh +# For zsh - check if this line exists in ~/.zshrc +grep rbenv ~/.zshrc + +# For bash - check if this line exists in ~/.bashrc +grep rbenv ~/.bashrc +``` + +If it's missing, add it: +```sh +# For zsh +echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc + +# For bash +echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc +``` + +Then restart your terminal or run `source ~/.zshrc` (or `source ~/.bashrc`). + +### Ruby version shows old/system Ruby instead of rbenv Ruby + +If `ruby -v` shows an old Ruby version (like `ruby 2.6.10`) instead of the rbenv Ruby (3.4.1), it means your shell hasn't loaded rbenv yet. + +**Cause**: You haven't restarted your terminal or sourced your shell config after installing rbenv. + +**Solution**: +```sh +# For zsh +source ~/.zshrc + +# For bash +source ~/.bashrc +``` + +Or simply restart your terminal (close and reopen). + +After this, `ruby -v` should show Ruby 3.4.1. + +### Ruby version not changing + +After installing a new Ruby version or changing `.ruby-version`, run: + +```sh +rbenv rehash +``` + +### OpenSSL errors during Ruby installation + +On macOS, you may need to specify OpenSSL paths: + +```sh +RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@3)" rbenv install 3.4.1 +``` + +On Linux: + +```sh +sudo apt install libssl-dev +rbenv install 3.4.1 +``` + +### Bundle install fails + +Ensure you're using the correct Ruby version: + +```sh +ruby -v +``` + +If incorrect, run: + +```sh +rbenv rehash +cd /path/to/project +ruby -v # Should now show correct version +``` + +## Additional Resources + +- [rbenv GitHub Repository](https://github.com/rbenv/rbenv) +- [Ruby Official Documentation](https://www.ruby-lang.org/en/documentation/) +- [Rails Guides](https://guides.rubyonrails.org/) +- [rbenv Command Reference](https://github.com/rbenv/rbenv#command-reference) + +## Migrating from RVM + +If you previously used RVM, you should uninstall it before using rbenv: + +```sh +# Uninstall RVM +rvm implode + +# Remove RVM from shell configuration +# Edit ~/.bashrc or ~/.zshrc and remove lines containing 'rvm' + +# Then follow the rbenv installation instructions above +``` diff --git a/app/controllers/api/v1/health_controller.rb b/app/controllers/api/v1/health_controller.rb new file mode 100644 index 0000000..38f036b --- /dev/null +++ b/app/controllers/api/v1/health_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Api + module V1 + # Health check endpoint for monitoring service status + class HealthController < ApplicationController + def show + health_status = { + status: 'healthy', + timestamp: Time.current.iso8601, + services: { + couchbase: check_couchbase_orm + } + } + + all_up = health_status[:services].values.all? { |s| s[:status] == 'up' } + status_code = all_up ? :ok : :service_unavailable + + render json: health_status, status: status_code + end + + private + + def check_couchbase_orm + # Test ORM connection by attempting to access the bucket + bucket_name = Airline.bucket.name + { status: 'up', message: "Connected to Couchbase bucket: #{bucket_name}" } + rescue StandardError => e + { status: 'down', message: e.message } + end + end + end +end diff --git a/config/application.rb b/config/application.rb index 7c503a5..9a7a7f7 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,6 +6,12 @@ # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) +# Load environment variables from dev.env in development and test environments +if ENV['RAILS_ENV'] != 'production' + require 'dotenv' + Dotenv.load('dev.env') +end + module RubyCouchbaseOrmQuickstart class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. diff --git a/config/routes.rb b/config/routes.rb index b932c71..7b891cc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,9 @@ namespace :api do namespace :v1 do + # Health check endpoint + get 'health', to: 'health#show' + # Airlines resource routes get 'airlines/list', to: 'airlines#index' get 'airlines/to-airport', to: 'airlines#to_airport' diff --git a/dev.env.example b/dev.env.example new file mode 100644 index 0000000..44248d7 --- /dev/null +++ b/dev.env.example @@ -0,0 +1,3 @@ +DB_USERNAME="Administrator" +DB_PASSWORD="password" +DB_CONN_STR="couchbase://localhost" \ No newline at end of file diff --git a/spec/requests/api/v1/airlines_spec.rb b/spec/requests/api/v1/airlines_spec.rb index 004a265..64809ba 100644 --- a/spec/requests/api/v1/airlines_spec.rb +++ b/spec/requests/api/v1/airlines_spec.rb @@ -1,171 +1,237 @@ -require 'swagger_helper' - -describe 'Airlines API', type: :request do - path '/api/v1/airlines/{id}' do - get 'Retrieves an airline by ID' do - tags 'Airlines' - produces 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the airline' - - response '200', 'airline found' do - schema type: :object, - properties: { - name: { type: :string }, - iata: { type: :string }, - icao: { type: :string }, - callsign: { type: :string }, - country: { type: :string } - }, - required: %w[name iata icao callsign country] - - let(:id) { 'airline_10' } - run_test! - end +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Airlines API', type: :request do + describe 'GET /api/v1/airlines/{id}' do + let(:airline_id) { 'airline_10' } + let(:expected_airline) do + { + 'name' => '40-Mile Air', + 'iata' => 'Q5', + 'icao' => 'MLA', + 'callsign' => 'MILE-AIR', + 'country' => 'United States' + } + end - response '404', 'airline not found' do - let(:id) { 'invalid_id' } - run_test! - end + it 'returns the airline' do + get "/api/v1/airlines/#{airline_id}" + + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to eq(expected_airline) end + end - post 'Creates an airline' do - tags 'Airlines' - consumes 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the airline' - parameter name: :airline, in: :body, schema: { - type: :object, - properties: { - name: { type: :string }, - iata: { type: :string }, - icao: { type: :string }, - callsign: { type: :string }, - country: { type: :string } - }, - required: %w[name iata icao callsign country] + describe 'POST /api/v1/airlines/{id}' do + let(:airline_id) { 'airline_post' } + let(:airline_params) do + { + 'name' => '40-Mile Air', + 'iata' => 'Q5', + 'icao' => 'MLA', + 'callsign' => 'MILE-AIR', + 'country' => 'United States' } + end - response '201', 'airline created' do - let(:airline) { { name: 'Foo Airlines', iata: 'FA', icao: 'FOO', callsign: 'FOO', country: 'US' } } - run_test! + context 'when the airline is created successfully' do + it 'returns the created airline' do + post "/api/v1/airlines/#{airline_id}", params: { airline: airline_params } + + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(airline_params) + rescue StandardError => e + puts e + ensure + delete "/api/v1/airlines/#{airline_id}" end + end - response '400', 'bad request' do - let(:airline) { { name: 'Foo Airlines', iata: 'FA', icao: 'FOO', callsign: 'FOO' } } - run_test! - end + context 'when the airline already exists' do + let(:airline_id) { 'airline_137' } + it 'returns a conflict error' do + post "/api/v1/airlines/#{airline_id}", params: { airline: airline_params } - response '409', 'airline already exists' do - let(:airline) { { name: 'Foo Airlines', iata: 'FA', icao: 'FOO', callsign: 'FOO', country: 'US' } } - run_test! + expect(response).to have_http_status(:conflict) + expect(JSON.parse(response.body)).to include({ 'message' => "Airline with ID #{airline_id} already exists" }) end end + end + + describe 'PUT /api/v1/airlines/{id}' do + let(:airline_id) { 'airline_put' } - put 'Updates an airline' do - tags 'Airlines' - consumes 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the airline' - parameter name: :airline, in: :body, schema: { - type: :object, - properties: { - name: { type: :string }, - iata: { type: :string }, - icao: { type: :string }, - callsign: { type: :string }, - country: { type: :string } - } + let(:current_params) do + { + 'name' => '40-Mile Air', + 'iata' => 'U5', + 'icao' => 'UPD', + 'callsign' => 'MILE-AIR', + 'country' => 'United States' + } + end + let(:updated_params) do + { + 'name' => '41-Mile Air', + 'iata' => 'U6', + 'icao' => 'UPE', + 'callsign' => 'UPDA-AIR', + 'country' => 'Updated States' } + end - response '200', 'airline updated' do - let(:id) { 'airline_10' } - let(:airline) { { name: 'Updated Airline' } } - run_test! + context 'when the airline is updated successfully' do + it 'returns the updated airline' do + put "/api/v1/airlines/#{airline_id}", params: { airline: updated_params } + + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + # should be updated_params without 'id' key + expect(JSON.parse(response.body)).to include(updated_params.except('id')) + rescue StandardError => e + puts e + ensure + delete "/api/v1/airlines/#{airline_id}" end + end + + context 'when the airline is not updated successfully' do + it 'returns a bad request error' do + post "/api/v1/airlines/#{airline_id}", params: { airline: current_params } + + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(current_params) - response '400', 'bad request' do - let(:id) { 'airline_10' } - let(:airline) { { name: '' } } - run_test! + put "/api/v1/airlines/#{airline_id}", params: { airline: { name: 'temp' } } + + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to include({ 'error' => 'Invalid request', + 'message' => ["Callsign can't be blank", "Iata can't be blank", + 'Iata is the wrong length (should be 2 characters)', "Icao can't be blank", 'Icao is the wrong length (should be 3 characters)', "Country can't be blank"] }) + rescue StandardError => e + puts e + ensure + delete "/api/v1/airlines/#{airline_id}" end end + end + + describe 'DELETE /api/v1/airlines/{id}' do + let(:airline_id) { 'airline_delete' } + let(:airline_params) do + { + 'name' => '40-Mile Air', + 'iata' => 'Q5', + 'icao' => 'MLA', + 'callsign' => 'MILE-AIR', + 'country' => 'United States' + } + end - delete 'Deletes an airline' do - tags 'Airlines' - parameter name: :id, in: :path, type: :string, description: 'ID of the airline' + context 'when the airline is deleted successfully' do + it 'returns a success message' do + post "/api/v1/airlines/#{airline_id}", params: { airline: airline_params } - response '204', 'airline deleted' do - let(:id) { 'airline_10' } - run_test! + delete "/api/v1/airlines/#{airline_id}" + + expect(response).to have_http_status(:accepted) + expect(JSON.parse(response.body)).to eq({ 'message' => 'Airline deleted successfully' }) end + end - response '404', 'airline not found' do - let(:id) { 'invalid_id' } - run_test! + context 'when the airline does not exist' do + it 'returns a not found error' do + delete "/api/v1/airlines/#{airline_id}" + + expect(response).to have_http_status(:not_found) + expect(JSON.parse(response.body)).to eq({ 'message' => "Airline with ID #{airline_id} not found" }) end end end - path '/api/v1/airlines/list' do - get 'Retrieves all airlines by country' do - tags 'Airlines' - produces 'application/json' - parameter name: :country, in: :query, type: :string, description: 'Country of the airline' - parameter name: :limit, in: :query, type: :integer, description: 'Maximum number of results to return' - parameter name: :offset, in: :query, type: :integer, description: 'Number of results to skip for pagination' - - response '200', 'airlines found' do - schema type: :array, - items: { - type: :object, - properties: { - name: { type: :string }, - iata: { type: :string }, - icao: { type: :string }, - callsign: { type: :string }, - country: { type: :string } - }, - required: %w[name iata icao callsign country] - } - - let(:country) { 'United States' } - let(:limit) { 10 } - let(:offset) { 0 } - run_test! - end + describe 'GET /api/v1/airlines/list' do + let(:country) { 'France' } + let(:limit) { '10' } + let(:offset) { '0' } + + let(:expected_airlines) do + [ + { 'callsign' => 'REUNION', 'country' => 'France', 'iata' => 'UU', 'icao' => 'REU', 'name' => 'Air Austral' }, + { 'callsign' => 'AIRLINAIR', 'country' => 'France', 'iata' => 'A5', 'icao' => 'RLA', 'name' => 'Airlinair' }, + { 'callsign' => 'AIRFRANS', 'country' => 'France', 'iata' => 'AF', 'icao' => 'AFR', 'name' => 'Air France' }, + { 'callsign' => 'AIRCALIN', 'country' => 'France', 'iata' => 'SB', 'icao' => 'ACI', + 'name' => 'Air Caledonie International' }, + { 'callsign' => 'T&', 'country' => 'France', 'iata' => '&T', 'icao' => 'T&O', + 'name' => "Tom\\'s & co airliners" }, + { 'callsign' => 'BRITAIR', 'country' => 'France', 'iata' => 'DB', 'icao' => 'BZH', 'name' => 'Brit Air' }, + { 'callsign' => 'Vickjet', 'country' => 'France', 'iata' => 'KT', 'icao' => 'VKJ', 'name' => 'VickJet' }, + { 'callsign' => 'CORSAIR', 'country' => 'France', 'iata' => 'SS', 'icao' => 'CRL', 'name' => 'Corsairfly' }, + { 'callsign' => 'CORSICA', 'country' => 'France', 'iata' => 'XK', 'icao' => 'CCM', + 'name' => 'Corse-Mediterranee' }, + { 'callsign' => 'AIGLE AZUR', 'country' => 'France', 'iata' => 'ZI', 'icao' => 'AAF', 'name' => 'Aigle Azur' } + ] + end + + it 'returns a list of airlines for a given country' do + get '/api/v1/airlines/list', params: { country:, limit:, offset: } + + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to eq(expected_airlines) end end - path '/api/v1/airlines/to-airport' do - get 'Retrieves airlines flying to a destination airport' do - tags 'Airlines' - produces 'application/json' - parameter name: :destinationAirportCode, in: :query, type: :string, - description: 'The ICAO or IATA code of the destination airport' - parameter name: :limit, in: :query, type: :integer, description: 'Maximum number of results to return' - parameter name: :offset, in: :query, type: :integer, description: 'Number of results to skip for pagination' - - response '200', 'airlines found' do - schema type: :array, - items: { - type: :object, - properties: { - name: { type: :string }, - iata: { type: :string }, - icao: { type: :string }, - callsign: { type: :string }, - country: { type: :string } - }, - required: %w[name iata icao callsign country] - } - - let(:destinationAirportCode) { 'LAX' } - let(:limit) { 10 } - let(:offset) { 0 } - run_test! + describe 'GET /api/v1/airlines/to-airport' do + let(:destination_airport_code) { 'JFK' } + let(:limit) { '10' } + let(:offset) { '0' } + let(:expected_airlines) do + [ + { 'callsign' => 'JETBLUE', 'country' => 'United States', 'iata' => 'B6', 'icao' => 'JBU', + 'name' => 'JetBlue Airways' }, + { 'callsign' => 'SPEEDBIRD', 'country' => 'United Kingdom', 'iata' => 'BA', 'icao' => 'BAW', + 'name' => 'British Airways' }, + { 'callsign' => 'DELTA', 'country' => 'United States', 'iata' => 'DL', 'icao' => 'DAL', + 'name' => 'Delta Air Lines' }, + { 'callsign' => 'HAWAIIAN', 'country' => 'United States', 'iata' => 'HA', 'icao' => 'HAL', + 'name' => 'Hawaiian Airlines' }, + { 'callsign' => 'FLAGSHIP', 'country' => 'United States', 'iata' => '9E', 'icao' => 'FLG', + 'name' => 'Pinnacle Airlines' }, + { 'callsign' => 'AMERICAN', 'country' => 'United States', 'iata' => 'AA', 'icao' => 'AAL', + 'name' => 'American Airlines' }, + { 'callsign' => 'STARWAY', 'country' => 'France', 'iata' => 'SE', 'icao' => 'SEU', + 'name' => 'XL Airways France' }, + { 'callsign' => 'SUN COUNTRY', 'country' => 'United States', 'iata' => 'SY', 'icao' => 'SCX', + 'name' => 'Sun Country Airlines' }, + { 'callsign' => 'UNITED', 'country' => 'United States', 'iata' => 'UA', 'icao' => 'UAL', + 'name' => 'United Airlines' }, + { 'callsign' => 'U S AIR', 'country' => 'United States', 'iata' => 'US', 'icao' => 'USA', + 'name' => 'US Airways' } + ] + end + + context 'when destinationAirportCode is provided' do + it 'returns a list of airlines flying to the destination airport' do + get '/api/v1/airlines/to-airport', + params: { destinationAirportCode: destination_airport_code, limit:, offset: } + + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to eq(expected_airlines) end + end + + context 'when destinationAirportCode is not provided' do + it 'returns a bad request error' do + get '/api/v1/airlines/to-airport', params: { limit:, offset: } - response '400', 'bad request' do - let(:destinationAirportCode) { '' } - run_test! + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to eq({ 'error' => 'Invalid request', + 'message' => 'Destination airport is missing' }) end end end diff --git a/spec/requests/api/v1/airports_spec.rb b/spec/requests/api/v1/airports_spec.rb index e25cebc..cd3a152 100644 --- a/spec/requests/api/v1/airports_spec.rb +++ b/spec/requests/api/v1/airports_spec.rb @@ -1,201 +1,219 @@ -require 'swagger_helper' - -describe 'Airports API', type: :request do - path '/api/v1/airports/{id}' do - get 'Retrieves an airport by ID' do - tags 'Airports' - produces 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the airport' - - response '200', 'airport found' do - schema type: :object, - properties: { - airportname: { type: :string }, - city: { type: :string }, - country: { type: :string }, - faa: { type: :string }, - icao: { type: :string }, - tz: { type: :string }, - geo: { - type: :object, - properties: { - alt: { type: :number }, - lat: { type: :number }, - lon: { type: :number } - } - } - }, - required: %w[airportname city country faa icao tz geo] - - let(:id) { 'airport_1262' } - run_test! +require 'rails_helper' + +RSpec.describe 'Airports API', type: :request do + describe 'GET /api/v1/airports/{id}' do + let(:airport_id) { 'airport_1262' } + + context 'when the airport exists' do + let(:expected_airport) do + { + 'airportname' => 'La Garenne', + 'city' => 'Agen', + 'country' => 'France', + 'faa' => 'AGF', + 'icao' => 'LFBA', + 'tz' => 'Europe/Paris', + 'geo' => { + 'lat' => 44.174721, + 'lon' => 0.590556, + 'alt' => 204 + } + } end - response '404', 'airport not found' do - let(:id) { 'invalid_id' } - run_test! + it 'returns the airport' do + get "/api/v1/airports/#{airport_id}" + + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to eq(expected_airport) end end - post 'Creates an airport' do - tags 'Airports' - consumes 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the airport' - parameter name: :airport, in: :body, schema: { - type: :object, - properties: { - airportname: { type: :string }, - city: { type: :string }, - country: { type: :string }, - faa: { type: :string }, - icao: { type: :string }, - tz: { type: :string }, - geo: { - type: :object, - properties: { - alt: { type: :number }, - lat: { type: :number }, - lon: { type: :number } - } - } - }, - required: %w[airportname city country faa icao tz geo] - } + context 'when the airport does not exist' do + it 'returns a not found error' do + get '/api/v1/airports/invalid_id' - response '201', 'airport created' do - let(:airport) do - { - airportname: 'Test Airport', - city: 'Test City', - country: 'Test Country', - faa: '', - icao: 'Test LFAG', - tz: 'Test Europe/Paris', - geo: { - lat: 49.868547, - lon: 3.029578, - alt: 295.0 - } - } - end - run_test! + expect(response).to have_http_status(:not_found) + expect(JSON.parse(response.body)).to eq({ 'message' => 'Airport with ID invalid_id not found' }) end + end + end - response '400', 'bad request' do - let(:airport) do - { - airportname: 'Test Airport', - city: 'Test City', - country: 'Test Country', - faa: '', - icao: 'Test LFAG', - tz: 'Test Europe/Paris', - geo: { - lat: 49.868547, - lon: 3.029578 - } - } - end - run_test! + describe 'POST /api/v1/airports/{id}' do + let(:airport_id) { 'airport_post' } + let(:airport_params) do + { + 'airportname' => 'Test Airport', + 'city' => 'Test City', + 'country' => 'Test Country', + 'faa' => 'Tst', # 'faa' should be 3 characters long + 'icao' => 'Test', # 'icao' should be 4 characters long + 'tz' => 'Test Europe/Paris', + 'geo' => { + 'lat' => 49.868547, + 'lon' => 3.029578, + 'alt' => 295.0 + } + } + end + + context 'when the airport is created successfully' do + it 'returns the created airport' do + post "/api/v1/airports/#{airport_id}", params: { airport: airport_params } + + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(airport_params) + rescue StandardError => e + puts e + ensure + delete "/api/v1/airports/#{airport_id}" end + end - response '409', 'airport already exists' do - let(:airport) do - { - airportname: 'Test Airport', - city: 'Test City', - country: 'Test Country', - faa: '', - icao: 'Test LFAG', - tz: 'Test Europe/Paris', - geo: { - lat: 49.868547, - lon: 3.029578, - alt: 295.0 - } - } - end - run_test! + context 'when the airport already exists' do + let(:airport_id) { 'airport_1262' } + it 'returns a conflict error' do + post "/api/v1/airports/#{airport_id}", params: { airport: airport_params } + + expect(response).to have_http_status(:conflict) + expect(JSON.parse(response.body)).to include({ 'message' => "Airport with ID #{airport_id} already exists" }) end end + end - put 'Updates an airport' do - tags 'Airports' - consumes 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the airport' - parameter name: :airport, in: :body, schema: { - type: :object, - properties: { - airportname: { type: :string }, - city: { type: :string }, - country: { type: :string }, - faa: { type: :string }, - icao: { type: :string }, - tz: { type: :string }, - geo: { - type: :object, - properties: { - alt: { type: :number }, - lat: { type: :number }, - lon: { type: :number } - } - } + describe 'PUT /api/v1/airports/{id}' do + let(:airport_id) { 'airport_put' } + let(:current_params) do + { + 'airportname' => 'Test Airport', + 'city' => 'Test City', + 'country' => 'Test Country', + 'faa' => 'BCD', + 'icao' => 'TEST', + 'tz' => 'Test Europe/Paris', + 'geo' => { + 'lat' => 49.868547, + 'lon' => 3.029578, + 'alt' => 295.0 } } + end + let(:updated_params) do + { + 'airportname' => 'Updated Airport', + 'city' => 'Updated City', + 'country' => 'Updated Country', + 'faa' => 'UPD', + 'icao' => 'UPDT', + 'tz' => 'Updated Europe/Paris', + 'geo' => { + 'lat' => 50.868547, + 'lon' => 4.029578, + 'alt' => 300.0 + } + } + end - response '200', 'airport updated' do - let(:id) { 'airport_1262' } - let(:airport) { { airportname: 'Updated Airport' } } - run_test! + context 'when the airport is updated successfully' do + it 'returns the updated airport' do + put "/api/v1/airports/#{airport_id}", params: { airport: updated_params } + + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(updated_params) + rescue StandardError => e + puts e + ensure + delete "/api/v1/airports/#{airport_id}" end + end - response '400', 'bad request' do - let(:id) { 'airport_1262' } - let(:airport) { { airportname: '' } } - run_test! + context 'when the airport is not updated successfully' do + it 'returns a bad request error' do + post "/api/v1/airports/#{airport_id}", params: { airport: current_params } + + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(current_params) + + put "/api/v1/airports/#{airport_id}", params: { airport: { airportname: 'temp' } } + + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to include({ 'error' => 'Invalid request', + 'message' => ["City can't be blank", "Country can't be blank", + "Faa can't be blank", 'Faa is the wrong length (should be 3 characters)', "Icao can't be blank", 'Icao is the wrong length (should be 4 characters)', "Tz can't be blank", "Geo can't be blank"] }) + rescue StandardError => e + puts e + ensure + delete "/api/v1/airports/#{airport_id}" end end + end + + describe 'DELETE /api/v1/airports/{id}' do + let(:airport_id) { 'airport_delete' } + let(:airport_params) do + { + 'airportname' => 'Test Airport', + 'city' => 'Test City', + 'country' => 'Test Country', + 'faa' => 'BCD', + 'icao' => 'TEST', + 'tz' => 'Test Europe/Paris', + 'geo' => { + 'lat' => 49.868547, + 'lon' => 3.029578, + 'alt' => 295.0 + } + } + end - delete 'Deletes an airport' do - tags 'Airports' - parameter name: :id, in: :path, type: :string, description: 'ID of the airport' + context 'when the airport is deleted successfully' do + it 'returns a success message' do + post "/api/v1/airports/#{airport_id}", params: { airport: airport_params } + expect(response).to have_http_status(:created) - response '204', 'airport deleted' do - let(:id) { 'airport_1262' } - run_test! + delete "/api/v1/airports/#{airport_id}" + expect(response).to have_http_status(:accepted) + expect(JSON.parse(response.body)).to eq({ 'message' => 'Airport deleted successfully' }) end + end - response '404', 'airport not found' do - let(:id) { 'invalid_id' } - run_test! + context 'when the airport does not exist' do + it 'returns a not found error' do + delete "/api/v1/airports/#{airport_id}" + expect(response).to have_http_status(:not_found) + expect(JSON.parse(response.body)).to eq({ 'message' => 'Airport with ID airport_delete not found' }) end end end - path '/api/v1/airports/direct-connections' do - get 'Retrieves all direct connections from a target airport' do - tags 'Airports' - produces 'application/json' - parameter name: :destinationAirportCode, in: :query, type: :string, - description: 'FAA code of the target airport', required: true - parameter name: :limit, in: :query, type: :integer, description: 'Maximum number of results to return' - parameter name: :offset, in: :query, type: :integer, description: 'Number of results to skip for pagination' - - response '200', 'direct connections found' do - schema type: :array, - items: { - type: :string - } - - let(:destinationAirportCode) { 'LAX' } - let(:limit) { 10 } - let(:offset) { 0 } - run_test! + describe 'GET /api/v1/airports/direct-connections' do + let(:destination_airport_code) { 'LAX' } + let(:limit) { 10 } + let(:offset) { 0 } + let(:expected_connections) { %w[NRT CUN GDL HMO MEX MZT PVR SJD ZIH ZLO] } + + context 'when the destination airport code is provided' do + it 'returns the direct connections' do + get '/api/v1/airports/direct-connections', + params: { destinationAirportCode: destination_airport_code, limit:, offset: } + + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to eq(expected_connections) end + end + + context 'when the destination airport code is not provided' do + it 'returns a bad request error' do + get '/api/v1/airports/direct-connections' - response '400', 'bad request' do - let(:destinationAirportCode) { '' } - run_test! + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to eq({ 'error' => 'Invalid request', + 'message' => 'Destination airport is missing' }) end end end diff --git a/spec/requests/api/v1/routes_spec.rb b/spec/requests/api/v1/routes_spec.rb index 139e130..780fbfe 100644 --- a/spec/requests/api/v1/routes_spec.rb +++ b/spec/requests/api/v1/routes_spec.rb @@ -1,188 +1,176 @@ -require 'swagger_helper' - -describe 'Routes API', type: :request do - path '/api/v1/routes/{id}' do - get 'Retrieves a route by ID' do - tags 'Routes' - produces 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the route' - - response '200', 'route found' do - schema type: :object, - properties: { - airline: { type: :string }, - airlineid: { type: :string }, - sourceairport: { type: :string }, - destinationairport: { type: :string }, - stops: { type: :integer }, - equipment: { type: :string }, - schedule: { - type: :array, - items: { - type: :object, - properties: { - day: { type: :integer }, - flight: { type: :string }, - utc: { type: :string } - } - } - }, - distance: { type: :number } - }, - required: %w[airline airlineid sourceairport destinationairport stops equipment schedule - distance] - - let(:id) { 'route_10209' } - run_test! - end +require 'rails_helper' - response '404', 'route not found' do - let(:id) { 'invalid_id' } - run_test! - end +RSpec.describe 'Routes API', type: :request do + describe 'GET /api/v1/routes/{id}' do + let(:route_id) { 'route_10209' } + let(:expected_route) do + { + 'airline' => 'AH', + 'airlineid' => 'airline_794', + 'sourceairport' => 'MRS', + 'destinationairport' => 'TLM', + 'stops' => 0, + 'equipment' => '736', + 'schedule' => [ + { 'day' => 0, 'flight' => 'AH705', + 'utc' => '22:18:00' }, { 'day' => 0, 'flight' => 'AH413', 'utc' => '08:47:00' }, { 'day' => 0, 'flight' => 'AH284', 'utc' => '04:25:00' }, { 'day' => 1, 'flight' => 'AH800', 'utc' => '10:05:00' }, { 'day' => 1, 'flight' => 'AH448', 'utc' => '04:59:00' }, { 'day' => 1, 'flight' => 'AH495', 'utc' => '20:17:00' }, { 'day' => 1, 'flight' => 'AH837', 'utc' => '08:30:00' }, { 'day' => 2, 'flight' => 'AH344', 'utc' => '08:32:00' }, { 'day' => 2, 'flight' => 'AH875', 'utc' => '06:28:00' }, { 'day' => 3, 'flight' => 'AH781', 'utc' => '21:15:00' }, { 'day' => 4, 'flight' => 'AH040', 'utc' => '12:57:00' }, { 'day' => 5, 'flight' => 'AH548', 'utc' => '23:09:00' }, { 'day' => 6, 'flight' => 'AH082', 'utc' => '22:47:00' }, { 'day' => 6, 'flight' => 'AH434', 'utc' => '06:12:00' }, { 'day' => 6, 'flight' => 'AH831', 'utc' => '13:10:00' }, { 'day' => 6, 'flight' => 'AH144', 'utc' => '02:48:00' }, { 'day' => 6, 'flight' => 'AH208', 'utc' => '22:39:00' } + ], + 'distance' => 1097.2184613947677 + } + end + + it 'returns the route' do + get "/api/v1/routes/#{route_id}" + + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(expected_route) end + end - post 'Creates a route' do - tags 'Routes' - consumes 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the route' - parameter name: :route, in: :body, schema: { - type: :object, - properties: { - airline: { type: :string }, - airlineid: { type: :string }, - sourceairport: { type: :string }, - destinationairport: { type: :string }, - stops: { type: :integer }, - equipment: { type: :string }, - schedule: { - type: :array, - items: { - type: :object, - properties: { - day: { type: :integer }, - flight: { type: :string }, - utc: { type: :string } - } - } - }, - distance: { type: :number } - }, - required: %w[airline airlineid sourceairport destinationairport stops equipment schedule - distance] + describe 'POST /api/v1/routes/{id}' do + let(:route_id) { 'route_post' } + let(:route_params) do + { + 'airline' => 'AF', + 'airlineid' => 'airline_137', + 'sourceairport' => 'TLV', + 'destinationairport' => 'MRS', + 'stops' => 0, + 'equipment' => '320', + 'schedule' => [ + { 'day' => 0, 'utc' => '10:13:00', 'flight' => 'AF198' }, + { 'day' => 0, 'utc' => '19:14:00', 'flight' => 'AF547' } + # Add more schedule items as needed + ], + 'distance' => 2881.617376098415 } + end - response '201', 'route created' do - let(:route) do - { - airline: 'AF', - airlineid: 'airline_137', - sourceairport: 'TLV', - destinationairport: 'MRS', - stops: 0, - equipment: '320', - schedule: [ - { day: 0, utc: '10:13:00', flight: 'AF198' }, - { day: 0, utc: '19:14:00', flight: 'AF547' } - ], - distance: 2881.617376098415 - } - end - run_test! - end + context 'when the route is created successfully' do + it 'returns the created route' do + post "/api/v1/routes/#{route_id}", params: { route: route_params } + + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(route_params) - response '400', 'bad request' do - let(:route) do - { - airline: 'AF', - airlineid: 'airline_137', - sourceairport: 'TLV', - destinationairport: 'MRS', - stops: 0, - equipment: '320', - schedule: [ - { day: 0, utc: '10:13:00', flight: 'AF198' }, - { day: 0, utc: '19:14:00' } - ], - distance: 2881.617376098415 - } - end - run_test! + delete "/api/v1/routes/#{route_id}" end + end + + context 'when the route already exists' do + let(:route_id) { 'route_10209' } + it 'returns a conflict error' do + post "/api/v1/routes/#{route_id}", params: { route: route_params } - response '409', 'route already exists' do - let(:route) do - { - airline: 'AF', - airlineid: 'airline_137', - sourceairport: 'TLV', - destinationairport: 'MRS', - stops: 0, - equipment: '320', - schedule: [ - { day: 0, utc: '10:13:00', flight: 'AF198' }, - { day: 0, utc: '19:14:00', flight: 'AF547' } - ], - distance: 2881.617376098415 - } - end - run_test! + expect(response).to have_http_status(:conflict) + expect(JSON.parse(response.body)).to include({ 'message' => "Route with ID #{route_id} already exists" }) end end + end - put 'Updates a route' do - tags 'Routes' - consumes 'application/json' - parameter name: :id, in: :path, type: :string, description: 'ID of the route' - parameter name: :route, in: :body, schema: { - type: :object, - properties: { - airline: { type: :string }, - airlineid: { type: :string }, - sourceairport: { type: :string }, - destinationairport: { type: :string }, - stops: { type: :integer }, - equipment: { type: :string }, - schedule: { - type: :array, - items: { - type: :object, - properties: { - day: { type: :integer }, - flight: { type: :string }, - utc: { type: :string } - } - } - }, - distance: { type: :number } - } + describe 'PUT /api/v1/routes/{id}' do + let(:route_id) { 'route_put' } + let(:current_params) do + { + 'airline' => 'AF', + 'airlineid' => 'airline_137', + 'sourceairport' => 'TLV', + 'destinationairport' => 'MRS', + 'stops' => 0, + 'equipment' => '320', + 'schedule' => [ + { 'day' => 0, 'utc' => '10:13:00', 'flight' => 'AF198' }, + { 'day' => 0, 'utc' => '19:14:00', 'flight' => 'AF547' } + # Add more schedule items as needed + ], + 'distance' => 3000 + } + end + let(:updated_params) do + { + 'airline' => 'AF', + 'airlineid' => 'airline_137', + 'sourceairport' => 'TLV', + 'destinationairport' => 'CDG', + 'stops' => 1, + 'equipment' => '321', + 'schedule' => [ + { 'day' => 1, 'utc' => '11:13:00', 'flight' => 'AF199' }, + { 'day' => 1, 'utc' => '20:14:00', 'flight' => 'AF548' } + # Add more schedule items as needed + ], + 'distance' => 3500 } + end + + context 'when the route is updated successfully' do + it 'returns the updated route' do + put "/api/v1/routes/#{route_id}", params: { route: updated_params } - response '200', 'route updated' do - let(:id) { 'route_10209' } - let(:route) { { stops: 1 } } - run_test! + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(updated_params) + + delete "/api/v1/routes/#{route_id}" end + end + + context 'when the route is not updated successfully' do + it 'returns a bad request error' do + post "/api/v1/routes/#{route_id}", params: { route: current_params } + + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(JSON.parse(response.body)).to include(current_params) + + put "/api/v1/routes/#{route_id}", params: { route: { airline: '' } } + + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to include({ 'error' => 'Invalid request' }) - response '400', 'bad request' do - let(:id) { 'route_10209' } - let(:route) { { stops: 'invalid' } } - run_test! + delete "/api/v1/routes/#{route_id}" end end + end + + describe 'DELETE /api/v1/routes/{id}' do + let(:route_id) { 'route_delete' } + let(:route_params) do + { + 'airline' => 'AF', + 'airlineid' => 'airline_137', + 'sourceairport' => 'TLV', + 'destinationairport' => 'MRS', + 'stops' => 0, + 'equipment' => '320', + 'schedule' => [ + { 'day' => 0, 'utc' => '10:13:00', 'flight' => 'AF198' }, + { 'day' => 0, 'utc' => '19:14:00', 'flight' => 'AF547' } + # Add more schedule items as needed + ], + 'distance' => 2881.617376098415 + } + end - delete 'Deletes a route' do - tags 'Routes' - parameter name: :id, in: :path, type: :string, description: 'ID of the route' + context 'when the route is deleted successfully' do + it 'returns a success message' do + post "/api/v1/routes/#{route_id}", params: { route: route_params } - response '204', 'route deleted' do - let(:id) { 'route_10209' } - run_test! + delete "/api/v1/routes/#{route_id}" + + expect(response).to have_http_status(:accepted) + expect(JSON.parse(response.body)).to eq({ 'message' => 'Route deleted successfully' }) end + end + + context 'when the route does not exist' do + it 'returns a not found error' do + delete "/api/v1/routes/#{route_id}" - response '404', 'route not found' do - let(:id) { 'invalid_id' } - run_test! + expect(response).to have_http_status(:not_found) + expect(JSON.parse(response.body)).to eq({ 'message' => 'Route with ID route_delete not found' }) end end end diff --git a/spec/requests/swagger/airlines_spec.rb b/spec/requests/swagger/airlines_spec.rb new file mode 100644 index 0000000..36ef400 --- /dev/null +++ b/spec/requests/swagger/airlines_spec.rb @@ -0,0 +1,195 @@ +require 'swagger_helper' + +# Documentation-only specs for Swagger/OpenAPI generation +# Actual integration testing done in spec/requests/api/v1/airlines_spec.rb +describe 'Airlines API', type: :request do + path '/api/v1/airlines/{id}' do + get 'Retrieves an airline by ID' do + tags 'Airlines' + produces 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the airline' + + response '200', 'airline found' do + schema type: :object, + properties: { + name: { type: :string }, + iata: { type: :string }, + icao: { type: :string }, + callsign: { type: :string }, + country: { type: :string } + }, + required: %w[name iata icao callsign country] + + let(:id) { 'airline_10' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + + response '404', 'airline not found' do + let(:id) { 'invalid_id' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + end + + post 'Creates an airline' do + tags 'Airlines' + consumes 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the airline' + parameter name: :airline, in: :body, schema: { + type: :object, + properties: { + name: { type: :string }, + iata: { type: :string }, + icao: { type: :string }, + callsign: { type: :string }, + country: { type: :string } + }, + required: %w[name iata icao callsign country] + } + + response '201', 'airline created' do + let(:id) { 'airline_new_123' } + let(:airline) { { name: 'Foo Airlines', iata: 'FA', icao: 'FOO', callsign: 'FOO', country: 'US' } } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + + response '400', 'bad request' do + let(:id) { 'airline_bad' } + let(:airline) { { name: 'Foo Airlines', iata: 'FA', icao: 'FOO', callsign: 'FOO' } } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + + response '409', 'airline already exists' do + let(:id) { 'airline_137' } + let(:airline) { { name: 'Foo Airlines', iata: 'FA', icao: 'FOO', callsign: 'FOO', country: 'US' } } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + end + + put 'Updates an airline' do + tags 'Airlines' + consumes 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the airline' + parameter name: :airline, in: :body, schema: { + type: :object, + properties: { + name: { type: :string }, + iata: { type: :string }, + icao: { type: :string }, + callsign: { type: :string }, + country: { type: :string } + } + } + + response '200', 'airline updated' do + let(:id) { 'airline_10' } + let(:airline) { { name: 'Updated Airline', iata: 'UA', icao: 'UPD', callsign: 'UPDATED', country: 'United States' } } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + + response '400', 'bad request' do + let(:id) { 'airline_10' } + let(:airline) { { name: '' } } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + end + + delete 'Deletes an airline' do + tags 'Airlines' + parameter name: :id, in: :path, type: :string, description: 'ID of the airline' + + response '204', 'airline deleted' do + let(:id) { 'airline_to_delete' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + + response '404', 'airline not found' do + let(:id) { 'invalid_id' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airlines_spec.rb + end + end + end + end + + path '/api/v1/airlines/list' do + get 'Retrieves all airlines by country' do + tags 'Airlines' + produces 'application/json' + parameter name: :country, in: :query, type: :string, description: 'Country of the airline' + parameter name: :limit, in: :query, type: :integer, description: 'Maximum number of results to return' + parameter name: :offset, in: :query, type: :integer, description: 'Number of results to skip for pagination' + + response '200', 'airlines found' do + schema type: :array, + items: { + type: :object, + properties: { + name: { type: :string }, + iata: { type: :string }, + icao: { type: :string }, + callsign: { type: :string }, + country: { type: :string } + }, + required: %w[name iata icao callsign country] + } + + let(:country) { 'United States' } + let(:limit) { 10 } + let(:offset) { 0 } + run_test! + end + end + end + + path '/api/v1/airlines/to-airport' do + get 'Retrieves airlines flying to a destination airport' do + tags 'Airlines' + produces 'application/json' + parameter name: :destinationAirportCode, in: :query, type: :string, + description: 'The ICAO or IATA code of the destination airport' + parameter name: :limit, in: :query, type: :integer, description: 'Maximum number of results to return' + parameter name: :offset, in: :query, type: :integer, description: 'Number of results to skip for pagination' + + response '200', 'airlines found' do + schema type: :array, + items: { + type: :object, + properties: { + name: { type: :string }, + iata: { type: :string }, + icao: { type: :string }, + callsign: { type: :string }, + country: { type: :string } + }, + required: %w[name iata icao callsign country] + } + + let(:destinationAirportCode) { 'LAX' } + let(:limit) { 10 } + let(:offset) { 0 } + run_test! + end + + response '400', 'bad request' do + let(:destinationAirportCode) { '' } + run_test! + end + end + end +end diff --git a/spec/requests/swagger/airports_spec.rb b/spec/requests/swagger/airports_spec.rb new file mode 100644 index 0000000..aa6dba2 --- /dev/null +++ b/spec/requests/swagger/airports_spec.rb @@ -0,0 +1,243 @@ +require 'swagger_helper' + +# Documentation-only specs for Swagger/OpenAPI generation +# Actual integration testing done in spec/requests/api/v1/airports_spec.rb +describe 'Airports API', type: :request do + path '/api/v1/airports/{id}' do + get 'Retrieves an airport by ID' do + tags 'Airports' + produces 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the airport' + + response '200', 'airport found' do + schema type: :object, + properties: { + airportname: { type: :string }, + city: { type: :string }, + country: { type: :string }, + faa: { type: :string }, + icao: { type: :string }, + tz: { type: :string }, + geo: { + type: :object, + properties: { + alt: { type: :number }, + lat: { type: :number }, + lon: { type: :number } + } + } + }, + required: %w[airportname city country faa icao tz geo] + + let(:id) { 'airport_1262' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + + response '404', 'airport not found' do + let(:id) { 'invalid_id' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + end + + post 'Creates an airport' do + tags 'Airports' + consumes 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the airport' + parameter name: :airport, in: :body, schema: { + type: :object, + properties: { + airportname: { type: :string }, + city: { type: :string }, + country: { type: :string }, + faa: { type: :string }, + icao: { type: :string }, + tz: { type: :string }, + geo: { + type: :object, + properties: { + alt: { type: :number }, + lat: { type: :number }, + lon: { type: :number } + } + } + }, + required: %w[airportname city country faa icao tz geo] + } + + response '201', 'airport created' do + let(:id) { 'airport_new_123' } + let(:airport) do + { + airportname: 'Test Airport', + city: 'Test City', + country: 'Test Country', + faa: '', + icao: 'Test LFAG', + tz: 'Test Europe/Paris', + geo: { + lat: 49.868547, + lon: 3.029578, + alt: 295.0 + } + } + end + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + + response '400', 'bad request' do + let(:id) { 'airport_bad' } + let(:airport) do + { + airportname: 'Test Airport', + city: 'Test City', + country: 'Test Country', + faa: '', + icao: 'Test LFAG', + tz: 'Test Europe/Paris', + geo: { + lat: 49.868547, + lon: 3.029578 + } + } + end + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + + response '409', 'airport already exists' do + let(:id) { 'airport_1262' } + let(:airport) do + { + airportname: 'Test Airport', + city: 'Test City', + country: 'Test Country', + faa: '', + icao: 'Test LFAG', + tz: 'Test Europe/Paris', + geo: { + lat: 49.868547, + lon: 3.029578, + alt: 295.0 + } + } + end + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + end + + put 'Updates an airport' do + tags 'Airports' + consumes 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the airport' + parameter name: :airport, in: :body, schema: { + type: :object, + properties: { + airportname: { type: :string }, + city: { type: :string }, + country: { type: :string }, + faa: { type: :string }, + icao: { type: :string }, + tz: { type: :string }, + geo: { + type: :object, + properties: { + alt: { type: :number }, + lat: { type: :number }, + lon: { type: :number } + } + } + } + } + + response '200', 'airport updated' do + let(:id) { 'airport_1262' } + let(:airport) do + { + airportname: 'Updated Airport', + city: 'Updated City', + country: 'Updated Country', + faa: 'UPD', + icao: 'UPDT', + tz: 'Updated Europe/Paris', + geo: { + lat: 50.868547, + lon: 4.029578, + alt: 300.0 + } + } + end + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + + response '400', 'bad request' do + let(:id) { 'airport_1262' } + let(:airport) { { airportname: '' } } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + end + + delete 'Deletes an airport' do + tags 'Airports' + parameter name: :id, in: :path, type: :string, description: 'ID of the airport' + + response '204', 'airport deleted' do + let(:id) { 'airport_to_delete' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + + response '404', 'airport not found' do + let(:id) { 'invalid_id' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + end + end + + path '/api/v1/airports/direct-connections' do + get 'Retrieves all direct connections from a target airport' do + tags 'Airports' + produces 'application/json' + parameter name: :destinationAirportCode, in: :query, type: :string, + description: 'FAA code of the target airport', required: true + parameter name: :limit, in: :query, type: :integer, description: 'Maximum number of results to return' + parameter name: :offset, in: :query, type: :integer, description: 'Number of results to skip for pagination' + + response '200', 'direct connections found' do + schema type: :array, + items: { + type: :string + } + + let(:destinationAirportCode) { 'JFK' } + let(:limit) { 10 } + let(:offset) { 0 } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + + response '400', 'bad request' do + let(:destinationAirportCode) { '' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/airports_spec.rb + end + end + end + end +end diff --git a/spec/requests/swagger/routes_spec.rb b/spec/requests/swagger/routes_spec.rb new file mode 100644 index 0000000..19cbeb4 --- /dev/null +++ b/spec/requests/swagger/routes_spec.rb @@ -0,0 +1,226 @@ +require 'swagger_helper' + +# Documentation-only specs for Swagger/OpenAPI generation +# Actual integration testing done in spec/requests/api/v1/routes_spec.rb +describe 'Routes API', type: :request do + path '/api/v1/routes/{id}' do + get 'Retrieves a route by ID' do + tags 'Routes' + produces 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the route' + + response '200', 'route found' do + schema type: :object, + properties: { + airline: { type: :string }, + airlineid: { type: :string }, + sourceairport: { type: :string }, + destinationairport: { type: :string }, + stops: { type: :integer }, + equipment: { type: :string }, + schedule: { + type: :array, + items: { + type: :object, + properties: { + day: { type: :integer }, + flight: { type: :string }, + utc: { type: :string } + } + } + }, + distance: { type: :number } + }, + required: %w[airline airlineid sourceairport destinationairport stops equipment schedule + distance] + + let(:id) { 'route_10209' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + + response '404', 'route not found' do + let(:id) { 'invalid_id' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + end + + post 'Creates a route' do + tags 'Routes' + consumes 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the route' + parameter name: :route, in: :body, schema: { + type: :object, + properties: { + airline: { type: :string }, + airlineid: { type: :string }, + sourceairport: { type: :string }, + destinationairport: { type: :string }, + stops: { type: :integer }, + equipment: { type: :string }, + schedule: { + type: :array, + items: { + type: :object, + properties: { + day: { type: :integer }, + flight: { type: :string }, + utc: { type: :string } + } + } + }, + distance: { type: :number } + }, + required: %w[airline airlineid sourceairport destinationairport stops equipment schedule + distance] + } + + response '201', 'route created' do + let(:id) { 'route_new_123' } + let(:route) do + { + airline: 'AF', + airlineid: 'airline_137', + sourceairport: 'TLV', + destinationairport: 'MRS', + stops: 0, + equipment: '320', + schedule: [ + { day: 0, utc: '10:13:00', flight: 'AF198' }, + { day: 0, utc: '19:14:00', flight: 'AF547' } + ], + distance: 2881.617376098415 + } + end + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + + response '400', 'bad request' do + let(:id) { 'route_bad' } + let(:route) do + { + airline: 'AF', + airlineid: 'airline_137', + sourceairport: 'TLV', + destinationairport: 'MRS', + stops: 0, + equipment: '320', + schedule: [ + { day: 0, utc: '10:13:00', flight: 'AF198' }, + { day: 0, utc: '19:14:00' } + ], + distance: 2881.617376098415 + } + end + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + + response '409', 'route already exists' do + let(:id) { 'route_10209' } + let(:route) do + { + airline: 'AF', + airlineid: 'airline_137', + sourceairport: 'TLV', + destinationairport: 'MRS', + stops: 0, + equipment: '320', + schedule: [ + { day: 0, utc: '10:13:00', flight: 'AF198' }, + { day: 0, utc: '19:14:00', flight: 'AF547' } + ], + distance: 2881.617376098415 + } + end + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + end + + put 'Updates a route' do + tags 'Routes' + consumes 'application/json' + parameter name: :id, in: :path, type: :string, description: 'ID of the route' + parameter name: :route, in: :body, schema: { + type: :object, + properties: { + airline: { type: :string }, + airlineid: { type: :string }, + sourceairport: { type: :string }, + destinationairport: { type: :string }, + stops: { type: :integer }, + equipment: { type: :string }, + schedule: { + type: :array, + items: { + type: :object, + properties: { + day: { type: :integer }, + flight: { type: :string }, + utc: { type: :string } + } + } + }, + distance: { type: :number } + } + } + + response '200', 'route updated' do + let(:id) { 'route_10209' } + let(:route) do + { + airline: 'AF', + airlineid: 'airline_137', + sourceairport: 'TLV', + destinationairport: 'CDG', + stops: 1, + equipment: '321', + schedule: [ + { day: 1, utc: '11:13:00', flight: 'AF199' }, + { day: 1, utc: '20:14:00', flight: 'AF548' } + ], + distance: 3500 + } + end + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + + response '400', 'bad request' do + let(:id) { 'route_10209' } + let(:route) { { airline: '' } } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + end + + delete 'Deletes a route' do + tags 'Routes' + parameter name: :id, in: :path, type: :string, description: 'ID of the route' + + response '204', 'route deleted' do + let(:id) { 'route_to_delete' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + + response '404', 'route not found' do + let(:id) { 'invalid_id' } + run_test! do |response| + # Documentation-only - actual testing in spec/requests/api/v1/routes_spec.rb + end + end + end + end +end diff --git a/test/integration/airlines_spec.rb b/test/integration/airlines_spec.rb deleted file mode 100644 index 8e088c2..0000000 --- a/test/integration/airlines_spec.rb +++ /dev/null @@ -1,238 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe 'Airlines API', type: :request do - describe 'GET /api/v1/airlines/{id}' do - let(:airline_id) { 'airline_10' } - let(:expected_airline) do - { - 'name' => '40-Mile Air', - 'iata' => 'Q5', - 'icao' => 'MLA', - 'callsign' => 'MILE-AIR', - 'country' => 'United States' - } - end - - it 'returns the airline' do - get "/api/v1/airlines/#{airline_id}" - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to eq(expected_airline) - end - end - - describe 'POST /api/v1/airlines/{id}' do - let(:airline_id) { 'airline_post' } - let(:airline_params) do - { - 'name' => '40-Mile Air', - 'iata' => 'Q5', - 'icao' => 'MLA', - 'callsign' => 'MILE-AIR', - 'country' => 'United States' - } - end - - context 'when the airline is created successfully' do - it 'returns the created airline' do - post "/api/v1/airlines/#{airline_id}", params: { airline: airline_params } - - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(airline_params) - rescue StandardError => e - puts e - ensure - delete "/api/v1/airlines/#{airline_id}" - end - end - - context 'when the airline already exists' do - let(:airline_id) { 'airline_137' } - it 'returns a conflict error' do - post "/api/v1/airlines/#{airline_id}", params: { airline: airline_params } - - expect(response).to have_http_status(:conflict) - expect(JSON.parse(response.body)).to include({ 'message' => "Airline with ID #{airline_id} already exists" }) - end - end - end - - describe 'PUT /api/v1/airlines/{id}' do - let(:airline_id) { 'airline_put' } - - let(:current_params) do - { - 'name' => '40-Mile Air', - 'iata' => 'U5', - 'icao' => 'UPD', - 'callsign' => 'MILE-AIR', - 'country' => 'United States' - } - end - let(:updated_params) do - { - 'name' => '41-Mile Air', - 'iata' => 'U6', - 'icao' => 'UPE', - 'callsign' => 'UPDA-AIR', - 'country' => 'Updated States' - } - end - - context 'when the airline is updated successfully' do - it 'returns the updated airline' do - put "/api/v1/airlines/#{airline_id}", params: { airline: updated_params } - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - # should be updated_params without 'id' key - expect(JSON.parse(response.body)).to include(updated_params.except('id')) - rescue StandardError => e - puts e - ensure - delete "/api/v1/airlines/#{airline_id}" - end - end - - context 'when the airline is not updated successfully' do - it 'returns a bad request error' do - post "/api/v1/airlines/#{airline_id}", params: { airline: current_params } - - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(current_params) - - put "/api/v1/airlines/#{airline_id}", params: { airline: { name: 'temp' } } - - expect(response).to have_http_status(:bad_request) - expect(JSON.parse(response.body)).to include({ 'error' => 'Invalid request', - 'message' => ["Callsign can't be blank", "Iata can't be blank", - 'Iata is the wrong length (should be 2 characters)', "Icao can't be blank", 'Icao is the wrong length (should be 3 characters)', "Country can't be blank"] }) - rescue StandardError => e - puts e - ensure - delete "/api/v1/airlines/#{airline_id}" - end - end - end - - describe 'DELETE /api/v1/airlines/{id}' do - let(:airline_id) { 'airline_delete' } - let(:airline_params) do - { - 'name' => '40-Mile Air', - 'iata' => 'Q5', - 'icao' => 'MLA', - 'callsign' => 'MILE-AIR', - 'country' => 'United States' - } - end - - context 'when the airline is deleted successfully' do - it 'returns a success message' do - post "/api/v1/airlines/#{airline_id}", params: { airline: airline_params } - - delete "/api/v1/airlines/#{airline_id}" - - expect(response).to have_http_status(:accepted) - expect(JSON.parse(response.body)).to eq({ 'message' => 'Airline deleted successfully' }) - end - end - - context 'when the airline does not exist' do - it 'returns a not found error' do - delete "/api/v1/airlines/#{airline_id}" - - expect(response).to have_http_status(:not_found) - expect(JSON.parse(response.body)).to eq({ 'message' => "Airline with ID #{airline_id} not found" }) - end - end - end - - describe 'GET /api/v1/airlines/list' do - let(:country) { 'France' } - let(:limit) { '10' } - let(:offset) { '0' } - - let(:expected_airlines) do - [ - { 'callsign' => 'REUNION', 'country' => 'France', 'iata' => 'UU', 'icao' => 'REU', 'name' => 'Air Austral' }, - { 'callsign' => 'AIRLINAIR', 'country' => 'France', 'iata' => 'A5', 'icao' => 'RLA', 'name' => 'Airlinair' }, - { 'callsign' => 'AIRFRANS', 'country' => 'France', 'iata' => 'AF', 'icao' => 'AFR', 'name' => 'Air France' }, - { 'callsign' => 'AIRCALIN', 'country' => 'France', 'iata' => 'SB', 'icao' => 'ACI', - 'name' => 'Air Caledonie International' }, - { 'callsign' => 'T&', 'country' => 'France', 'iata' => '&T', 'icao' => 'T&O', - 'name' => "Tom's & co airliners" }, - { 'callsign' => 'BRITAIR', 'country' => 'France', 'iata' => 'DB', 'icao' => 'BZH', 'name' => 'Brit Air' }, - { 'callsign' => 'Vickjet', 'country' => 'France', 'iata' => 'KT', 'icao' => 'VKJ', 'name' => 'VickJet' }, - { 'callsign' => 'CORSAIR', 'country' => 'France', 'iata' => 'SS', 'icao' => 'CRL', 'name' => 'Corsairfly' }, - { 'callsign' => 'CORSICA', 'country' => 'France', 'iata' => 'XK', 'icao' => 'CCM', - 'name' => 'Corse-Mediterranee' }, - { 'callsign' => 'AIGLE AZUR', 'country' => 'France', 'iata' => 'ZI', 'icao' => 'AAF', 'name' => 'Aigle Azur' } - ] - end - - it 'returns a list of airlines for a given country' do - get '/api/v1/airlines/list', params: { country:, limit:, offset: } - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to eq(expected_airlines) - end - end - - describe 'GET /api/v1/airlines/to-airport' do - let(:destination_airport_code) { 'JFK' } - let(:limit) { '10' } - let(:offset) { '0' } - let(:expected_airlines) do - [ - { 'callsign' => 'JETBLUE', 'country' => 'United States', 'iata' => 'B6', 'icao' => 'JBU', - 'name' => 'JetBlue Airways' }, - { 'callsign' => 'SPEEDBIRD', 'country' => 'United Kingdom', 'iata' => 'BA', 'icao' => 'BAW', - 'name' => 'British Airways' }, - { 'callsign' => 'DELTA', 'country' => 'United States', 'iata' => 'DL', 'icao' => 'DAL', - 'name' => 'Delta Air Lines' }, - { 'callsign' => 'HAWAIIAN', 'country' => 'United States', 'iata' => 'HA', 'icao' => 'HAL', - 'name' => 'Hawaiian Airlines' }, - { 'callsign' => 'FLAGSHIP', 'country' => 'United States', 'iata' => '9E', 'icao' => 'FLG', - 'name' => 'Pinnacle Airlines' }, - { 'callsign' => 'AMERICAN', 'country' => 'United States', 'iata' => 'AA', 'icao' => 'AAL', - 'name' => 'American Airlines' }, - { 'callsign' => 'STARWAY', 'country' => 'France', 'iata' => 'SE', 'icao' => 'SEU', - 'name' => 'XL Airways France' }, - { 'callsign' => 'SUN COUNTRY', 'country' => 'United States', 'iata' => 'SY', 'icao' => 'SCX', - 'name' => 'Sun Country Airlines' }, - { 'callsign' => 'UNITED', 'country' => 'United States', 'iata' => 'UA', 'icao' => 'UAL', - 'name' => 'United Airlines' }, - { 'callsign' => 'U S AIR', 'country' => 'United States', 'iata' => 'US', 'icao' => 'USA', - 'name' => 'US Airways' } - ] - end - - context 'when destinationAirportCode is provided' do - it 'returns a list of airlines flying to the destination airport' do - get '/api/v1/airlines/to-airport', - params: { destinationAirportCode: destination_airport_code, limit:, offset: } - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to eq(expected_airlines) - end - end - - context 'when destinationAirportCode is not provided' do - it 'returns a bad request error' do - get '/api/v1/airlines/to-airport', params: { limit:, offset: } - - expect(response).to have_http_status(:bad_request) - expect(JSON.parse(response.body)).to eq({ 'error' => 'Invalid request', - 'message' => 'Destination airport is missing' }) - end - end - end -end diff --git a/test/integration/airports_spec.rb b/test/integration/airports_spec.rb deleted file mode 100644 index cd3a152..0000000 --- a/test/integration/airports_spec.rb +++ /dev/null @@ -1,220 +0,0 @@ -require 'rails_helper' - -RSpec.describe 'Airports API', type: :request do - describe 'GET /api/v1/airports/{id}' do - let(:airport_id) { 'airport_1262' } - - context 'when the airport exists' do - let(:expected_airport) do - { - 'airportname' => 'La Garenne', - 'city' => 'Agen', - 'country' => 'France', - 'faa' => 'AGF', - 'icao' => 'LFBA', - 'tz' => 'Europe/Paris', - 'geo' => { - 'lat' => 44.174721, - 'lon' => 0.590556, - 'alt' => 204 - } - } - end - - it 'returns the airport' do - get "/api/v1/airports/#{airport_id}" - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to eq(expected_airport) - end - end - - context 'when the airport does not exist' do - it 'returns a not found error' do - get '/api/v1/airports/invalid_id' - - expect(response).to have_http_status(:not_found) - expect(JSON.parse(response.body)).to eq({ 'message' => 'Airport with ID invalid_id not found' }) - end - end - end - - describe 'POST /api/v1/airports/{id}' do - let(:airport_id) { 'airport_post' } - let(:airport_params) do - { - 'airportname' => 'Test Airport', - 'city' => 'Test City', - 'country' => 'Test Country', - 'faa' => 'Tst', # 'faa' should be 3 characters long - 'icao' => 'Test', # 'icao' should be 4 characters long - 'tz' => 'Test Europe/Paris', - 'geo' => { - 'lat' => 49.868547, - 'lon' => 3.029578, - 'alt' => 295.0 - } - } - end - - context 'when the airport is created successfully' do - it 'returns the created airport' do - post "/api/v1/airports/#{airport_id}", params: { airport: airport_params } - - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(airport_params) - rescue StandardError => e - puts e - ensure - delete "/api/v1/airports/#{airport_id}" - end - end - - context 'when the airport already exists' do - let(:airport_id) { 'airport_1262' } - it 'returns a conflict error' do - post "/api/v1/airports/#{airport_id}", params: { airport: airport_params } - - expect(response).to have_http_status(:conflict) - expect(JSON.parse(response.body)).to include({ 'message' => "Airport with ID #{airport_id} already exists" }) - end - end - end - - describe 'PUT /api/v1/airports/{id}' do - let(:airport_id) { 'airport_put' } - let(:current_params) do - { - 'airportname' => 'Test Airport', - 'city' => 'Test City', - 'country' => 'Test Country', - 'faa' => 'BCD', - 'icao' => 'TEST', - 'tz' => 'Test Europe/Paris', - 'geo' => { - 'lat' => 49.868547, - 'lon' => 3.029578, - 'alt' => 295.0 - } - } - end - let(:updated_params) do - { - 'airportname' => 'Updated Airport', - 'city' => 'Updated City', - 'country' => 'Updated Country', - 'faa' => 'UPD', - 'icao' => 'UPDT', - 'tz' => 'Updated Europe/Paris', - 'geo' => { - 'lat' => 50.868547, - 'lon' => 4.029578, - 'alt' => 300.0 - } - } - end - - context 'when the airport is updated successfully' do - it 'returns the updated airport' do - put "/api/v1/airports/#{airport_id}", params: { airport: updated_params } - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(updated_params) - rescue StandardError => e - puts e - ensure - delete "/api/v1/airports/#{airport_id}" - end - end - - context 'when the airport is not updated successfully' do - it 'returns a bad request error' do - post "/api/v1/airports/#{airport_id}", params: { airport: current_params } - - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(current_params) - - put "/api/v1/airports/#{airport_id}", params: { airport: { airportname: 'temp' } } - - expect(response).to have_http_status(:bad_request) - expect(JSON.parse(response.body)).to include({ 'error' => 'Invalid request', - 'message' => ["City can't be blank", "Country can't be blank", - "Faa can't be blank", 'Faa is the wrong length (should be 3 characters)', "Icao can't be blank", 'Icao is the wrong length (should be 4 characters)', "Tz can't be blank", "Geo can't be blank"] }) - rescue StandardError => e - puts e - ensure - delete "/api/v1/airports/#{airport_id}" - end - end - end - - describe 'DELETE /api/v1/airports/{id}' do - let(:airport_id) { 'airport_delete' } - let(:airport_params) do - { - 'airportname' => 'Test Airport', - 'city' => 'Test City', - 'country' => 'Test Country', - 'faa' => 'BCD', - 'icao' => 'TEST', - 'tz' => 'Test Europe/Paris', - 'geo' => { - 'lat' => 49.868547, - 'lon' => 3.029578, - 'alt' => 295.0 - } - } - end - - context 'when the airport is deleted successfully' do - it 'returns a success message' do - post "/api/v1/airports/#{airport_id}", params: { airport: airport_params } - expect(response).to have_http_status(:created) - - delete "/api/v1/airports/#{airport_id}" - expect(response).to have_http_status(:accepted) - expect(JSON.parse(response.body)).to eq({ 'message' => 'Airport deleted successfully' }) - end - end - - context 'when the airport does not exist' do - it 'returns a not found error' do - delete "/api/v1/airports/#{airport_id}" - expect(response).to have_http_status(:not_found) - expect(JSON.parse(response.body)).to eq({ 'message' => 'Airport with ID airport_delete not found' }) - end - end - end - - describe 'GET /api/v1/airports/direct-connections' do - let(:destination_airport_code) { 'LAX' } - let(:limit) { 10 } - let(:offset) { 0 } - let(:expected_connections) { %w[NRT CUN GDL HMO MEX MZT PVR SJD ZIH ZLO] } - - context 'when the destination airport code is provided' do - it 'returns the direct connections' do - get '/api/v1/airports/direct-connections', - params: { destinationAirportCode: destination_airport_code, limit:, offset: } - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to eq(expected_connections) - end - end - - context 'when the destination airport code is not provided' do - it 'returns a bad request error' do - get '/api/v1/airports/direct-connections' - - expect(response).to have_http_status(:bad_request) - expect(JSON.parse(response.body)).to eq({ 'error' => 'Invalid request', - 'message' => 'Destination airport is missing' }) - end - end - end -end diff --git a/test/integration/routes_spec.rb b/test/integration/routes_spec.rb deleted file mode 100644 index 780fbfe..0000000 --- a/test/integration/routes_spec.rb +++ /dev/null @@ -1,177 +0,0 @@ -require 'rails_helper' - -RSpec.describe 'Routes API', type: :request do - describe 'GET /api/v1/routes/{id}' do - let(:route_id) { 'route_10209' } - let(:expected_route) do - { - 'airline' => 'AH', - 'airlineid' => 'airline_794', - 'sourceairport' => 'MRS', - 'destinationairport' => 'TLM', - 'stops' => 0, - 'equipment' => '736', - 'schedule' => [ - { 'day' => 0, 'flight' => 'AH705', - 'utc' => '22:18:00' }, { 'day' => 0, 'flight' => 'AH413', 'utc' => '08:47:00' }, { 'day' => 0, 'flight' => 'AH284', 'utc' => '04:25:00' }, { 'day' => 1, 'flight' => 'AH800', 'utc' => '10:05:00' }, { 'day' => 1, 'flight' => 'AH448', 'utc' => '04:59:00' }, { 'day' => 1, 'flight' => 'AH495', 'utc' => '20:17:00' }, { 'day' => 1, 'flight' => 'AH837', 'utc' => '08:30:00' }, { 'day' => 2, 'flight' => 'AH344', 'utc' => '08:32:00' }, { 'day' => 2, 'flight' => 'AH875', 'utc' => '06:28:00' }, { 'day' => 3, 'flight' => 'AH781', 'utc' => '21:15:00' }, { 'day' => 4, 'flight' => 'AH040', 'utc' => '12:57:00' }, { 'day' => 5, 'flight' => 'AH548', 'utc' => '23:09:00' }, { 'day' => 6, 'flight' => 'AH082', 'utc' => '22:47:00' }, { 'day' => 6, 'flight' => 'AH434', 'utc' => '06:12:00' }, { 'day' => 6, 'flight' => 'AH831', 'utc' => '13:10:00' }, { 'day' => 6, 'flight' => 'AH144', 'utc' => '02:48:00' }, { 'day' => 6, 'flight' => 'AH208', 'utc' => '22:39:00' } - ], - 'distance' => 1097.2184613947677 - } - end - - it 'returns the route' do - get "/api/v1/routes/#{route_id}" - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(expected_route) - end - end - - describe 'POST /api/v1/routes/{id}' do - let(:route_id) { 'route_post' } - let(:route_params) do - { - 'airline' => 'AF', - 'airlineid' => 'airline_137', - 'sourceairport' => 'TLV', - 'destinationairport' => 'MRS', - 'stops' => 0, - 'equipment' => '320', - 'schedule' => [ - { 'day' => 0, 'utc' => '10:13:00', 'flight' => 'AF198' }, - { 'day' => 0, 'utc' => '19:14:00', 'flight' => 'AF547' } - # Add more schedule items as needed - ], - 'distance' => 2881.617376098415 - } - end - - context 'when the route is created successfully' do - it 'returns the created route' do - post "/api/v1/routes/#{route_id}", params: { route: route_params } - - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(route_params) - - delete "/api/v1/routes/#{route_id}" - end - end - - context 'when the route already exists' do - let(:route_id) { 'route_10209' } - it 'returns a conflict error' do - post "/api/v1/routes/#{route_id}", params: { route: route_params } - - expect(response).to have_http_status(:conflict) - expect(JSON.parse(response.body)).to include({ 'message' => "Route with ID #{route_id} already exists" }) - end - end - end - - describe 'PUT /api/v1/routes/{id}' do - let(:route_id) { 'route_put' } - let(:current_params) do - { - 'airline' => 'AF', - 'airlineid' => 'airline_137', - 'sourceairport' => 'TLV', - 'destinationairport' => 'MRS', - 'stops' => 0, - 'equipment' => '320', - 'schedule' => [ - { 'day' => 0, 'utc' => '10:13:00', 'flight' => 'AF198' }, - { 'day' => 0, 'utc' => '19:14:00', 'flight' => 'AF547' } - # Add more schedule items as needed - ], - 'distance' => 3000 - } - end - let(:updated_params) do - { - 'airline' => 'AF', - 'airlineid' => 'airline_137', - 'sourceairport' => 'TLV', - 'destinationairport' => 'CDG', - 'stops' => 1, - 'equipment' => '321', - 'schedule' => [ - { 'day' => 1, 'utc' => '11:13:00', 'flight' => 'AF199' }, - { 'day' => 1, 'utc' => '20:14:00', 'flight' => 'AF548' } - # Add more schedule items as needed - ], - 'distance' => 3500 - } - end - - context 'when the route is updated successfully' do - it 'returns the updated route' do - put "/api/v1/routes/#{route_id}", params: { route: updated_params } - - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(updated_params) - - delete "/api/v1/routes/#{route_id}" - end - end - - context 'when the route is not updated successfully' do - it 'returns a bad request error' do - post "/api/v1/routes/#{route_id}", params: { route: current_params } - - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json; charset=utf-8') - expect(JSON.parse(response.body)).to include(current_params) - - put "/api/v1/routes/#{route_id}", params: { route: { airline: '' } } - - expect(response).to have_http_status(:bad_request) - expect(JSON.parse(response.body)).to include({ 'error' => 'Invalid request' }) - - delete "/api/v1/routes/#{route_id}" - end - end - end - - describe 'DELETE /api/v1/routes/{id}' do - let(:route_id) { 'route_delete' } - let(:route_params) do - { - 'airline' => 'AF', - 'airlineid' => 'airline_137', - 'sourceairport' => 'TLV', - 'destinationairport' => 'MRS', - 'stops' => 0, - 'equipment' => '320', - 'schedule' => [ - { 'day' => 0, 'utc' => '10:13:00', 'flight' => 'AF198' }, - { 'day' => 0, 'utc' => '19:14:00', 'flight' => 'AF547' } - # Add more schedule items as needed - ], - 'distance' => 2881.617376098415 - } - end - - context 'when the route is deleted successfully' do - it 'returns a success message' do - post "/api/v1/routes/#{route_id}", params: { route: route_params } - - delete "/api/v1/routes/#{route_id}" - - expect(response).to have_http_status(:accepted) - expect(JSON.parse(response.body)).to eq({ 'message' => 'Route deleted successfully' }) - end - end - - context 'when the route does not exist' do - it 'returns a not found error' do - delete "/api/v1/routes/#{route_id}" - - expect(response).to have_http_status(:not_found) - expect(JSON.parse(response.body)).to eq({ 'message' => 'Route with ID route_delete not found' }) - end - end - end -end