diff --git a/.gitignore b/.gitignore index d532d33..ae733c6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ Homestead.json Homestead.yaml .env -tests/_output/* \ No newline at end of file +tests/_output/* +/public/docs/ +/build/ diff --git a/apidoc.json b/apidoc.json new file mode 100755 index 0000000..a78a58d --- /dev/null +++ b/apidoc.json @@ -0,0 +1,8 @@ +{ + "name": "Trip Builder", + "version": "0.1.0", + "description": "API for Trip Builder", + "title": "... API v1", + "url" : "http://localhost:8080", + "sampleUrl" : "http://localhost:8080" +} diff --git a/app/Http/Controllers/AirportController.php b/app/Http/Controllers/AirportController.php index ec4ee02..4bc26d8 100644 --- a/app/Http/Controllers/AirportController.php +++ b/app/Http/Controllers/AirportController.php @@ -4,7 +4,7 @@ namespace App\Http\Controllers; use App\Libraries\IotaCodes\Airport; use App\Libraries\IotaCodes\AirportTransformer; -use App\Libraries\IotaCodes\Client; +use App\Libraries\IotaCodes\Client as IotaClient; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use League\Fractal\Manager; @@ -18,33 +18,27 @@ use League\Fractal\Serializer\JsonApiSerializer; class AirportController extends Controller { + /** - * Create a new controller instance. - * - * @return void - */ - public function __construct() - { - // - } - - /** - * Fetch list of airports - * - * @api {get} /airports + * @api {get} /airports list airports * @apiName List Airports + * @apiGroup Airports + * @apiDescription Fetch list of airports * - * @apiParam (pagination) {number} [page] page number to display - * @apiParam (pagination) {number} [per_page] number of items to display on the page + * @apiParam (query param) {number} [page=1] page number to display + * @apiParam (query param) {number} [per_page=10] number of items to display on the page * - * @apiParam (autocomplete) {string} [autocomplete] string to try and autocomplete + * @apiParam (query param) {string} [autocomplete] string to try and autocomplete + * + * @apiExample {curl} example + * curl -i http://localhost:8080/airports?page=1&per_page=10&autocomplete=aar * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function resourceList(Request $request) { - $client = Client::create(); + $client = IotaClient::create(); $collection = $client->listAirports($request->input('autocomplete')); //add pagination @@ -64,12 +58,15 @@ class AirportController extends Controller } /** - * Get details of an airport by it's code + * @api {get} /airports/:code get airport + * @apiName Get Airport + * @apiGroup Airports + * @apiDescription Get details of an airport by it's code * - * @api {get} /airports/:code - * @apiName Get Airport from code + * @apiParam (query param) {string} code 3 letter airport code * - * @apiParam {string} code 3 letter airport code + * @apiExample {curl} example + * curl -i http://localhost:8080/airports/YUL * * @param \Illuminate\Http\Request $request * @param $code @@ -77,27 +74,15 @@ class AirportController extends Controller */ public function getAirport(Request $request, $code) { - $client = Client::create(); + $client = IotaClient::create(); + + $airport = $client->getAirport($code); + if (is_null($airport)) { + return $this->returnErrorMessage('not a valid airport code', 400); + } - $result = $client->getAirport($code); - $result = new Item($result, new AirportTransformer(), 'airports'); + $result = new Item($airport, new AirportTransformer(), 'airports'); return $this->JsonApiResponse($result, 200); } - - - /** - * Convert the response to Json - * - * @param \League\Fractal\Resource\Item $resource - * @param $statusCode - * @return \Illuminate\Http\JsonResponse - */ - protected function JsonApiResponse(ResourceInterface $resource, $statusCode) - { - $manager = new Manager(); - $manager->setSerializer(new JsonApiSerializer('http://docker.dev:8080')); - - return response()->json($manager->createData($resource)->toArray(), $statusCode); - } } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 0ccb918..6a4e13c 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -3,8 +3,40 @@ namespace App\Http\Controllers; use Laravel\Lumen\Routing\Controller as BaseController; +use League\Fractal\Manager; +use League\Fractal\Resource\ResourceInterface; +use League\Fractal\Serializer\JsonApiSerializer; class Controller extends BaseController { - // + /** + * Convert the response to Json + * + * @param \League\Fractal\Resource\Item $resource + * @param int $statusCode + * @param string $includes + * @return \Illuminate\Http\JsonResponse + */ + protected function JsonApiResponse(ResourceInterface $resource, $statusCode, $includes = '') + { + $manager = new Manager(); + $manager->setSerializer(new JsonApiSerializer('http://docker.dev:8080')); + $manager->parseIncludes($includes); + + return response()->json($manager->createData($resource)->toArray(), $statusCode); + } + + /** + * format an error message + * + * @param string $message + * @param int $statusCode + * @return \Illuminate\Http\JsonResponse + */ + protected function returnErrorMessage($message, $statusCode = 400) + { + return response()->json([ + 'error_message' => $message + ], $statusCode); + } } diff --git a/app/Http/Controllers/FlightController.php b/app/Http/Controllers/FlightController.php index 1d52157..596af7f 100644 --- a/app/Http/Controllers/FlightController.php +++ b/app/Http/Controllers/FlightController.php @@ -7,7 +7,9 @@ use App\Libraries\Trips\Models\Flights; use App\Libraries\Trips\Models\Trips; use App\Libraries\Trips\TripTransformer; use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection; use League\Fractal\Resource\Item; use League\Fractal\Resource\ResourceInterface; @@ -16,62 +18,90 @@ use League\Fractal\Serializer\JsonApiSerializer; class FlightController extends Controller { /** - * Create a new controller instance. + * @api {get} /flights list flights + * @apiName List Flights + * @apiGroup Flights + * @apiDescription List all defined flights * - * @return void + * @apiParam (query params) {number} [page=1] page to fetch + * @apiParam (query param) {number} [per_page=10] number of results to fetch per page + * + * @apiExample {curl} example + * curl -i http://localhost:8080/flights?page=1&per_page=10 + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse */ - public function __construct() - { - // - } - public function listFlights(Request $request) { $flightCollection = Flights::all(); - //TODO: add pagination - + + //add pagination + $per_page = $request->input('per_page', 10); + $page = $request->input('page', 1); + $paginator = new LengthAwarePaginator( + $flightCollection->forPage($page, $per_page), + $flightCollection->count(), + $per_page, + $page + ); + + $result = (new Collection($paginator, new TripTransformer(), 'trips')) + ->setPaginator(new IlluminatePaginatorAdapter($paginator)); + $result = new Collection($flightCollection, new FlightTransform(), 'flights'); - + return $this->JsonApiResponse($result, 200); } + /** + * @api {get} /flights/:id get flight + * @apiName Get Flight + * @apiGroup Flights + * @apiDescription get details of a specific flight + * + * @apiParam (url) {number} id the id of the flight to fetch + * + * @apiExample {curl} example + * curl -i http://localhost:8080/flights/1 + * + * @param \Illuminate\Http\Request $request + * @param $id + * @return \Illuminate\Http\JsonResponse + */ public function getFlight(Request $request, $id) { $flight = Flights::find($id); - $result = new Item($flight, new FlightTransform(), 'flights'); - - return $this->JsonApiResponse($result, 200); - } - - public function removeFlight(Request $request, $id) - { - /** @var \App\Libraries\Trips\Models\Flights $flight */ - $flight = Flights::find($id); - $deleted = $flight->delete(); - -// $result = new Item($flight, [ -// 'id' => $id, -// 'message' => 'flight removed' -// ], 'message'); + if (! $flight) { + return $this->returnErrorMessage("flight id $id not found", 400); + } + $result = new Item($flight, new FlightTransform(), 'flights'); return $this->JsonApiResponse($result, 200); } /** - * Convert the response to Json + * @api {delete} /flights/:id delete a flight + * @apiName Delete a Flight + * @apiGroup Flights + * @apiDescription delete a flight and remove it from any trips * - * @param \League\Fractal\Resource\Item $resource - * @param $statusCode + * @apiParam (url) {number} id the id of the flight to fetch + * + * @param \Illuminate\Http\Request $request + * @param $id * @return \Illuminate\Http\JsonResponse */ - protected function JsonApiResponse(ResourceInterface $resource, $statusCode) + public function removeFlight(Request $request, $id) { - $manager = new Manager(); - $manager->setSerializer(new JsonApiSerializer('http://docker.dev:8080')); - $manager->parseIncludes('flights'); + /** @var \App\Libraries\Trips\Models\Flights $flight */ + $flight = Flights::find($id); + $deleted = $flight->delete(); + + $result = new Item($flight, new FlightTransform(), 'flights'); - return response()->json($manager->createData($resource)->toArray(), $statusCode); + return $this->JsonApiResponse($result, 200); } } diff --git a/app/Http/Controllers/TripController.php b/app/Http/Controllers/TripController.php index 772db70..d565c46 100644 --- a/app/Http/Controllers/TripController.php +++ b/app/Http/Controllers/TripController.php @@ -2,12 +2,14 @@ namespace App\Http\Controllers; +use App\Libraries\IotaCodes\Client as IotaClient; use App\Libraries\Trips\Models\Flights; use App\Libraries\Trips\Models\Trips; use App\Libraries\Trips\TripTransformer; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection; use League\Fractal\Resource\Item; use League\Fractal\Resource\ResourceInterface; @@ -24,7 +26,22 @@ class TripController extends Controller { // } - + + /** + * @api {get} /trips list trips + * @apiName list trips + * @apiGroup Trips + * @apiDescription This api returns a paginated list of trips found + * + * @apiParam (query params) {number} [page=1] page to fetch + * @apiParam (query param) {number} [per_page=10] number of results to fetch per page + * + * @apiExample {curl} example + * curl -i http://localhost:8080/trips?page=1&per_page=10 + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ public function listTrips(Request $request) { $tripsCollection = Trips::all(); @@ -41,10 +58,25 @@ class TripController extends Controller $result = (new Collection($paginator, new TripTransformer(), 'trips')) ->setPaginator(new IlluminatePaginatorAdapter($paginator)); - + return $this->JsonApiResponse($result, 200); } + /** + * @api {get} /trips/:id get trip + * @apiName get trip + * @apiGroup Trips + * @apiDescription Fetch details of a specific trip + * + * @apiParam (url) {number} id the id of the trip to fetch + * + * @apiExample {curl} example + * curl -i http://localhost:8080/trips/1 + * + * @param \Illuminate\Http\Request $request + * @param $id + * @return \Illuminate\Http\JsonResponse + */ public function getTrip(Request $request, $id) { $trip = Trips::find($id); @@ -58,14 +90,36 @@ class TripController extends Controller return $this->JsonApiResponse($result, 200, 'flights'); } + /** + * @api {post} /trips/:id/flights add flight + * @apiName add flight + * @apiGroup Trips + * @apiDescription send a destination to this endpoint to add a flight to the trip + * + * @apiParam (form data) {string} destination the destination of the new flight + * @apiParam (url) {number} id the id of the trip to fetch + * + * @apiExample {curl} example + * curl -i http://localhost:8080/trips/1/flights + * + * @param \Illuminate\Http\Request $request + * @param $id + * @return \Illuminate\Http\JsonResponse + */ public function addFlight(Request $request, $id) { $trip = Trips::find($id); $destination = $request->input('destination'); - + if (! $destination) { return $this->returnErrorMessage('destination not set', 400); } + + $client = IotaClient::create(); + $airport = $client->getAirport($destination); + if (is_null($airport)) { + return $this->returnErrorMessage('destination not a valid airport code', 400); + } $flight = new Flights(['destination' => $destination]); $flight->save(); @@ -74,27 +128,4 @@ class TripController extends Controller $result = new Item($trip, new TripTransformer(), 'trips'); return $this->JsonApiResponse($result, 201, 'flights'); } - - /** - * Convert the response to Json - * - * @param \League\Fractal\Resource\Item $resource - * @param $statusCode - * @return \Illuminate\Http\JsonResponse - */ - protected function JsonApiResponse(ResourceInterface $resource, $statusCode, $includes = '') - { - $manager = new Manager(); - $manager->setSerializer(new JsonApiSerializer('http://docker.dev:8080')); - $manager->parseIncludes($includes); - - return response()->json($manager->createData($resource)->toArray(), $statusCode); - } - - protected function returnErrorMessage($message, $statusCode = 400) - { - return response()->json([ - 'error_message' => $message - ], $statusCode); - } } diff --git a/app/Libraries/IotaCodes/Client.php b/app/Libraries/IotaCodes/Client.php index abbeca6..29bda6a 100644 --- a/app/Libraries/IotaCodes/Client.php +++ b/app/Libraries/IotaCodes/Client.php @@ -63,11 +63,15 @@ class Client return $result; } + /** + * @param $code + * @return Collection + */ public function getAirport($code) { $uri = '/api/v6/airports'; $cacheKey = 'airport.'.$code; - $cacheMinutes = 60; + $cacheMinutes = 1; $response = app('cache')->get($cacheKey, function () use ($cacheKey, $cacheMinutes, $uri, $code) { try { @@ -81,7 +85,7 @@ class Client ]); } catch (\Exception $e) { - //todo handle error + return new Collection(); } $result = $response->getBody()->getContents(); diff --git a/app/Libraries/Trips/FlightTransform.php b/app/Libraries/Trips/FlightTransform.php index 0f091f1..8ab8993 100644 --- a/app/Libraries/Trips/FlightTransform.php +++ b/app/Libraries/Trips/FlightTransform.php @@ -7,6 +7,15 @@ use League\Fractal\TransformerAbstract; class FlightTransform extends TransformerAbstract { + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = [ + 'trips' + ]; + /** * Turn this item object into a generic array * @@ -17,10 +26,23 @@ class FlightTransform extends TransformerAbstract return [ 'id' => $flight->id, 'name' => $flight->destination, + 'trips' => $flight->trips()->get(), 'links' => [ 'rel' => 'self', 'uri' => '/flights/'.$flight->id, ] ]; } + + /** + * Include Trips + * + * @return \League\Fractal\Resource\ResourceAbstract + */ + public function includeTrips(Flights $flight) + { + $flights = $flight->trips()->get(); + + return $this->collection($flights, new TripTransformer()); + } } diff --git a/app/Libraries/Trips/Models/Trips.php b/app/Libraries/Trips/Models/Trips.php index 3e28c99..d665c69 100644 --- a/app/Libraries/Trips/Models/Trips.php +++ b/app/Libraries/Trips/Models/Trips.php @@ -16,4 +16,15 @@ class Trips extends Model { return $this->belongsToMany(Flights::class, 'trip_flights'); } + + /** + * @return array + */ + public function toArray() + { + return [ + 'id' => $this->id, + 'name' => $this->name, + ]; + } } diff --git a/app/Libraries/Trips/TripTransformer.php b/app/Libraries/Trips/TripTransformer.php index d992810..f16d376 100644 --- a/app/Libraries/Trips/TripTransformer.php +++ b/app/Libraries/Trips/TripTransformer.php @@ -36,7 +36,7 @@ class TripTransformer extends TransformerAbstract } /** - * Include Author + * Include Flights * * @return \League\Fractal\Resource\ResourceAbstract */ diff --git a/docker-compose.yml b/docker-compose.yml index 62b2d14..8172ff1 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,7 @@ services: - app links: - app + - apidocs volumes: - ./docker/docker-vhost.conf:/etc/nginx/sites-enabled/vhost.conf @@ -52,3 +53,17 @@ services: entrypoint: - composer - install + + # generate api documentation, then exits + apidocs: + image: apidocs:latest + build: + context: . + dockerfile: ./docker/Dockerfile-apidocs + volumes: + - .:/src + environment: + - DOCS_DIR=public/docs + - APP_DIR=./app + - FILE_FILTER="[Http\/Controllers\/|routes\/].*php" + diff --git a/docker/Dockerfile-apidocs b/docker/Dockerfile-apidocs new file mode 100644 index 0000000..f191e77 --- /dev/null +++ b/docker/Dockerfile-apidocs @@ -0,0 +1,21 @@ +# Using alpine linux for small image +# - http://gliderlabs.viewdocs.io/docker-alpine/ +FROM alpine:3.4 +MAINTAINER Richard Morgan +LABEL version=1.0 \ + "usage"="docker run --rm --name apidocker -v :/src -it apidocker" \ + "env"="DOCS_DIR, APP_DIR, FILE_FILTER" + +ENV DOCS_DIR="build/docs/" \ + APP_DIR="app/" \ + FILE_FILTER=".[php]$" + +ADD ./docker/entrypoint-apidocs.sh /scripts/entrypoint.sh + +# install system packages +RUN apk --no-cache add \ + nodejs \ + && npm install apidoc -g \ + && chmod +x -R /scripts + +ENTRYPOINT /scripts/entrypoint.sh diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 03ab268..86aadcf 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -1,11 +1,5 @@ #!/usr/bin/env bash -# start required services -#service ssh start -#service php5-fpm start - # keep the container running -#tail -f /dev/null -#tail -f /var/www/storage/logs/lumen.log /usr/sbin/php-fpm7.0 -F diff --git a/docker/docker-vhost.conf b/docker/docker-vhost.conf index cf73386..e7acf33 100755 --- a/docker/docker-vhost.conf +++ b/docker/docker-vhost.conf @@ -9,6 +9,10 @@ server { try_files $uri /index.php?$args; } + location /docs { + try_files $uri /docs/index.html; + } + location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; diff --git a/docker/entrypoint-apidocs.sh b/docker/entrypoint-apidocs.sh new file mode 100644 index 0000000..e5d34e3 --- /dev/null +++ b/docker/entrypoint-apidocs.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh + +cd /src/ +echo ${APP_DIR} : ${DOCS_DIR} : ${FILE_FILTER} +rm -rf ${DOCS_DIR} && mkdir -p ${DOCS_DIR} +apidoc -i ${APP_DIR} -o ${DOCS_DIR} -f ${FILE_FILTER} diff --git a/docker/entrypoint-seeder.sh b/docker/entrypoint-seeder.sh new file mode 100644 index 0000000..f1f641a --- /dev/null +++ b/docker/entrypoint-seeder.sh @@ -0,0 +1 @@ +#!/usr/bin/env bash diff --git a/readme.md b/readme.md index 99f228a..0a7b055 100644 --- a/readme.md +++ b/readme.md @@ -1,21 +1,29 @@ -# Lumen PHP Framework +# Trip Builder +- Richard Morgan -[![Build Status](https://travis-ci.org/laravel/lumen-framework.svg)](https://travis-ci.org/laravel/lumen-framework) -[![Total Downloads](https://poser.pugx.org/laravel/lumen-framework/d/total.svg)](https://packagist.org/packages/laravel/lumen-framework) -[![Latest Stable Version](https://poser.pugx.org/laravel/lumen-framework/v/stable.svg)](https://packagist.org/packages/laravel/lumen-framework) -[![Latest Unstable Version](https://poser.pugx.org/laravel/lumen-framework/v/unstable.svg)](https://packagist.org/packages/laravel/lumen-framework) -[![License](https://poser.pugx.org/laravel/lumen-framework/license.svg)](https://packagist.org/packages/laravel/lumen-framework) +Test application based on Laravel Lumen 5.4, running on docker containers. -Laravel Lumen is a stunningly fast PHP micro-framework for building web applications with expressive, elegant syntax. We believe development must be an enjoyable, creative experience to be truly fulfilling. Lumen attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as routing, database abstraction, queueing, and caching. +## Launch Environment +The dev environment is docker based. To run the php app, nginx, postgres database, and redis cache containers, simply run docker-compose up -## Official Documentation -Documentation for the framework can be found on the [Lumen website](http://lumen.laravel.com/docs). +```bash +docker-compose up +``` -## Security Vulnerabilities +This will download the required base images, then build the additional tools into new images, and then launch new containers. +A container also runs briefly to generate the api docs. -If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell at taylor@laravel.com. All security vulnerabilities will be promptly addressed. +## Setup database +Database migrations and seeds are provided for testing the app. These can be run in docker with the following command: -## License +```bash +docker-compose exec app php artisan migrate:refresh --seed +``` -The Lumen framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) + +## Api Documentation +The docker containers expose the api documentation on [http://localhost:8080/docs/](http://localhost:8080/docs/) + + +## diff --git a/tests/acceptance.suite.yml b/tests/acceptance.suite.yml index 2269e0c..a1353b1 100755 --- a/tests/acceptance.suite.yml +++ b/tests/acceptance.suite.yml @@ -8,5 +8,9 @@ class_name: AcceptanceTester modules: enabled: - PhpBrowser: - url: http://localhost/myapp - - \Helper\Acceptance \ No newline at end of file + url: http://localhost + - REST: + depends: PhpBrowser + url: 'http://nginx' + - Asserts + - \Helper\Acceptance diff --git a/tests/acceptance/AirportsCest.php b/tests/acceptance/AirportsCest.php new file mode 100755 index 0000000..dfca4ff --- /dev/null +++ b/tests/acceptance/AirportsCest.php @@ -0,0 +1,18 @@ +sendGET($this->uri, $params); + $r = $I->grabResponse(); + + $I->seeResponseCodeIs(200, $r); + $I->seeResponseIsJson(); + } + + public function listTripReturns1ItemWhenPerPageIs1(AcceptanceTester $I) + { + $params = [ + 'page' => 1, + 'per_page' => 1 + ]; + + $I->sendGET($this->uri, $params); + $r = $I->grabResponse(); + + $I->seeResponseCodeIs(200, $r); + $I->seeResponseIsJson(); + + $data = json_decode($r); + $I->assertCount(1, $data->data, $data); + } + + public function listTripReturns2ItemWhenPerPageIs2(AcceptanceTester $I) + { + $params = [ + 'page' => 1, + 'per_page' => 2 + ]; + + $I->sendGET($this->uri, $params); + $r = $I->grabResponse(); + + $I->seeResponseCodeIs(200, $r); + $I->seeResponseIsJson(); + + $data = json_decode($r); + $I->assertCount(2, $data->data, $data); + } + + public function getTripReturnsTripJson(AcceptanceTester $I) + { + $I->sendGET($this->uri, []); + $r = $I->grabResponse(); + $data = json_decode($r, true); + $tripId = $data['data'][0]['id']; + + $params = []; + $uri = $this->uri . '/' . $tripId; + + $I->sendGET($uri, $params); + $r = $I->grabResponse(); + + $I->seeResponseCodeIs(200, $r); + $I->seeResponseIsJson(); + + $data = json_decode($r, true); + $I->assertEquals('trips', $data['data']['type']); + $I->assertEquals($tripId, $data['data']['id']); + } + + public function getTripReturns400IfTripIdNotFound(AcceptanceTester $I) + { + $params = []; + $uri = $this->uri . '/999999'; + + $I->sendGET($uri, $params); + $r = $I->grabResponse(); + + $I->seeResponseCodeIs(400, $r); + $I->seeResponseIsJson(); + } + + public function addFlightAttachesNewFlightOnTrip(AcceptanceTester $I) + { + $I->sendGET($this->uri, []); + $r = $I->grabResponse(); + $data = json_decode($r, true); + $tripId = $data['data'][0]['id']; + $nbFlights = count($data['data'][0]['attributes']['flights']); + + $params = [ + 'destination' => 'YUL' + ]; + $uri = $this->uri . '/' . $tripId .'/flights'; + + $I->sendPost($uri, $params); + $r = $I->grabResponse(); + + $I->seeResponseCodeIs(201, $r); + $I->seeResponseIsJson(); + + $data = json_decode($r, true); + $I->assertCount($nbFlights+1, $data['data']['attributes']['flights']); + } +}