diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eafb426..c7e05c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,8 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['7.2', '7.3', '7.4', '8.0'] - # dependencies: ['latest', 'oldest'] # Only latest/upwards dependencies supported currently. + php-versions: ['8.1'] + dependencies: ['latest', 'oldest'] steps: - name: Checkout uses: actions/checkout@v2 @@ -48,18 +48,15 @@ jobs: # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - # - name: Install the lowest dependencies - # run: composer update --with-dependencies --prefer-stable --prefer-dist --prefer-lowest - # if: ${{ matrix.dependencies }} == "latest" + - name: Install dependencies + run: composer update --with-dependencies --prefer-stable --prefer-dist + if: ${{ matrix.dependencies }} == "latest" - name: Install current dependencies from composer.lock run: composer install - continue-on-error: ${{matrix.php-versions == '8.0' }} # Temporal until full support for php8 === [temp-php8] - name: Set up database schema run: mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports['3306'] }} -u${{ env.DB_USERNAME }} -p${{ env.DB_PASSWORD }} ${{ env.DB_DATABASE }} < vendor/phplist/core/resources/Database/Schema.sql - continue-on-error: ${{matrix.php-versions == '8.0' }} # [temp-php8] - name: Validating composer.json run: composer validate --no-check-all --no-check-lock --strict; - continue-on-error: ${{matrix.php-versions == '8.0' }} # [temp-php8] - name: Linting all php files run: find src/ tests/ public/ -name ''*.php'' -print0 | xargs -0 -n 1 -P 4 php -l; php -l; - name: Running unit tests @@ -76,10 +73,7 @@ jobs: continue-on-error: ${{matrix.php-versions == '8.0' }} # [temp-php8] - name: Running static analysis run: vendor/bin/phpstan analyse -l 5 src/ tests/; - continue-on-error: ${{matrix.php-versions == '8.0' }} # [temp-php8] - name: Running PHPMD run: vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml; - continue-on-error: ${{matrix.php-versions == '8.0' }} # [temp-php8] - name: Running PHP_CodeSniffer run: vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/; - continue-on-error: ${{matrix.php-versions == '8.0' }} # [temp-php8] \ No newline at end of file diff --git a/.github/workflows/restapi-docs.yml b/.github/workflows/restapi-docs.yml index 82bcda8..25ab243 100644 --- a/.github/workflows/restapi-docs.yml +++ b/.github/workflows/restapi-docs.yml @@ -1,73 +1,93 @@ -name: Publish REST API Docs -on: [push, pull_request] +name: Publish REST API Docs +on: + push: + branches: + - main + pull_request: + jobs: make-restapi-docs: name: Checkout phpList rest-api and generate docs specification (OpenAPI latest-restapi.json) runs-on: ubuntu-20.04 steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Setup PHP with Composer and Extensions + uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 extensions: mbstring, dom, fileinfo, mysql - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache composer dependencies - uses: actions/cache@v2 + + - name: Cache Composer Dependencies + uses: actions/cache@v3 with: - path: ${{ steps.composer-cache.outputs.dir }} - # Use composer.json for key, if composer.lock is not committed. - # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + path: ~/.composer/cache key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - name: Install current dependencies from composer.lock - run: composer install - - name: Generate OpenAPI Specification JSON for REST API + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install Composer Dependencies + run: composer install --no-interaction --prefer-dist + + - name: Generate OpenAPI Specification JSON run: vendor/bin/openapi -o docs/latest-restapi.json --format json src - - name: Upload REST API(latest-restapi.json) Spec - uses: actions/upload-artifact@v2 + + - name: Upload REST API Specification + uses: actions/upload-artifact@v3 with: name: restapi-json path: docs/latest-restapi.json + deploy-docs: - name: Deploy REST API specification. + name: Deploy REST API Specification runs-on: ubuntu-20.04 needs: make-restapi-docs steps: - - name: Install node - uses: actions/setup-node@v2 - with: - node-version: '14' - - name: Install openapi-checker - run: npm install -g openapi-checker - - name: Checkout phplist/restapi-docs - uses: actions/checkout@v2 - with: - repository: phpList/restapi-docs - fetch-depth: 0 - token: ${{ secrets.PUSH_REST_API_DOCS }} - - name: Restore REST API Spec - uses: actions/download-artifact@v2 - with: - name: restapi-json - - name: Validate latest-restapi.json - run: openapi-checker latest-restapi.json - - name: Get difference between latest-restapi.json and restapi.json - # `|| true` to supress exit code 1 [git diff exits with 1 when there is a difference between the two files and 0 for the reverse. - run: git diff --no-index --output=restapi-diff.txt latest-restapi.json restapi.json || true - - name: Verify difference latest-restapi.json and restapi.json - id: allow-deploy - run: | - if [ -s restapi-diff.txt ]; then echo "Updates made to restapi.json deployment proceeding."; echo '::set-output name=DEPLOY::true'; else echo "No updates made to restapi.json deployment would be skipped."; echo '::set-output name=DEPLOY::false'; fi - - name: Commit and changes and deply - if: ${{ steps.allow-deploy.outputs.DEPLOY == 'true' }} - run: | - mv latest-restapi.json restapi.json - git config user.name "github-actions" - git config user.email "github-actions@restapi-docs.workflow" - git add restapi.json - git commit -s -m "phplist/rest-api docs deployment `date`" - git push + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 14 + + - name: Install openapi-checker + run: npm install -g swagger-cli + + - name: Checkout REST API Docs Repository + uses: actions/checkout@v3 + with: + repository: phpList/restapi-docs + fetch-depth: 0 + token: ${{ secrets.PUSH_REST_API_DOCS }} + + - name: Download Generated REST API Specification + uses: actions/download-artifact@v3 + with: + name: restapi-json + path: docs + + - name: Validate OpenAPI Specification + run: swagger-cli validate docs/latest-restapi.json + + - name: Compare Specifications + run: git diff --no-index --output=restapi-diff.txt docs/latest-restapi.json restapi.json || true + + - name: Check Differences and Decide Deployment + id: allow-deploy + run: | + if [ -s restapi-diff.txt ]; then + echo "Updates detected in the REST API specification. Proceeding with deployment."; + echo 'DEPLOY=true' >> $GITHUB_ENV; + else + echo "No changes detected in the REST API specification. Skipping deployment."; + echo 'DEPLOY=false' >> $GITHUB_ENV; + fi + + - name: Commit and Deploy Updates + if: env.DEPLOY == 'true' + run: | + mv docs/latest-restapi.json docs/restapi.json + git config user.name "github-actions" + git config user.email "github-actions@restapi-docs.workflow" + git add docs/restapi.json + git commit -m "Update REST API documentation `date`" + git push diff --git a/.gitignore b/.gitignore index fbc3bab..2c98e37 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ /public/ /var/ /vendor/ +.phpunit.result.cache diff --git a/CHANGELOG.md b/CHANGELOG.md index f866108..6b79640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +## 5.0.0-alpha1 + +### Changed +- php version 8.1 + ## 4.0.0-alpha2 ### Added diff --git a/composer.json b/composer.json index eb140c1..a613d5c 100644 --- a/composer.json +++ b/composer.json @@ -30,22 +30,22 @@ "source": "https://github.com/phpList/rest-api" }, "require": { - "php": "^7.2|^8.0", - "phplist/core": "^v4.0.0-alpha5", - "friendsofsymfony/rest-bundle": "^2.8.6", - "sensio/framework-extra-bundle": "5.1.0", - "zircote/swagger-php": "^3.1" + "php": "^8.1", + "phplist/core": "dev-ISSUE-337", + "friendsofsymfony/rest-bundle": "*", + "symfony/test-pack": "^1.0", + "symfony/process": "^6.4", + "zircote/swagger-php": "^4.11" }, "require-dev": { - "phpunit/phpunit": "^6.5.14", - "phpunit/phpunit-mock-objects": "^5.0.6", - "phpunit/dbunit": "^3.0.3", - "guzzlehttp/guzzle": "^6.5.5", - "squizlabs/php_codesniffer": "^3.5.8", - "phpstan/phpstan": "^0.7.0", - "nette/caching": "^3.1.0", - "nikic/php-parser": "^3.1.0", - "phpmd/phpmd": "^2.9.1" + "phpunit/phpunit": "^10.0", + "guzzlehttp/guzzle": "^6.3.0", + "squizlabs/php_codesniffer": "^3.2.0", + "phpstan/phpstan": "^1.10", + "nette/caching": "^3.0.0", + "nikic/php-parser": "^4.19.1", + "phpmd/phpmd": "^2.6.0", + "doctrine/instantiator": "^2.0." }, "autoload": { "psr-4": { @@ -86,7 +86,7 @@ }, "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-ISSUE-337": "5.0.x-dev" }, "symfony-app-dir": "bin", "symfony-bin-dir": "bin", @@ -101,44 +101,9 @@ "routes": { "rest-api": { "resource": "@PhpListRestBundle/Controller/", - "type": "rest", + "type": "attribute", "prefix": "/api/v2" } - }, - "configuration": { - "fos_rest": { - "routing_loader": { - "include_format": false - }, - "format_listener": { - "enabled": true, - "rules": [ - { - "path": "^/api/v2/", - "fallback_format": "json" - }, - { - "path": "^/", - "fallback_format": "html" - } - ] - }, - "view": { - "view_response_listener": { - "enabled": true - } - - }, - "exception": { - "enabled": true, - "messages": { - "Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException": true - } - }, - "service": { - "view_handler": "my.secure_view_handler" - } - } } } } diff --git a/config/services.yml b/config/services.yml index a7cb41b..4aede00 100644 --- a/config/services.yml +++ b/config/services.yml @@ -1,14 +1,37 @@ services: + Psr\Container\ContainerInterface: + alias: 'service_container' + PhpList\RestBundle\Controller\: resource: '../src/Controller' public: true autowire: true tags: ['controller.service_arguments'] +# Symfony\Component\Serializer\SerializerInterface: +# autowire: true +# autoconfigure: true + my.secure_handler: - class: \PhpList\RestBundle\ViewHandler\SecuredViewHandler + class: \PhpList\RestBundle\ViewHandler\SecuredViewHandler my.secure_view_handler: - parent: fos_rest.view_handler.default - calls: - - ['registerHandler', [ 'json', ['@my.secure_handler', 'createResponse'] ] ] + parent: fos_rest.view_handler.default + calls: + - ['registerHandler', [ 'json', ['@my.secure_handler', 'createResponse'] ] ] + + PhpList\Core\Security\Authentication: + autowire: true + autoconfigure: true + + PhpList\Core\Domain\Repository\Messaging\SubscriberListRepository: + autowire: true + autoconfigure: true + + PhpList\RestBundle\EventListener\ExceptionListener: + tags: + - { name: kernel.event_listener, event: kernel.exception } + + PhpList\RestBundle\EventListener\ResponseListener: + tags: + - { name: kernel.event_listener, event: kernel.response } diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..b7a3b0e --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,55 @@ +openapi: 3.0.0 +info: + title: 'My API Documentation' + description: 'This is the OpenAPI documentation for My API.' + contact: + email: support@example.com + license: + name: MIT + url: 'https://opensource.org/licenses/MIT' + version: 1.0.0 +servers: + - + url: 'https://api.example.com' + description: 'Production server' + - + url: 'https://staging-api.example.com' + description: 'Staging server' +paths: + /api/v2/lists: + get: + tags: + - lists + summary: 'Gets a list of all subscriber lists.' + description: 'Returns a JSON list of all subscriber lists.' + operationId: 88f205a115c9d929147a83720a247aae + parameters: + - + name: session + in: header + description: 'Session ID obtained from authentication' + required: true + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + properties: { name: { type: string, example: News }, description: { type: string, example: 'News (and some fun stuff)' }, creation_date: { type: string, format: date-time, example: '2016-06-22T15:01:17+00:00' }, list_position: { type: integer, example: 12 }, subject_prefix: { type: string, example: phpList }, public: { type: boolean, example: true }, category: { type: string, example: news }, id: { type: integer, example: 1 } } + type: object + '403': + description: Failure + content: + application/json: + schema: + properties: + message: { type: string, example: 'No valid session key was provided as basic auth password.' } + type: object +tags: + - + name: lists + description: lists diff --git a/src/Controller/ListController.php b/src/Controller/ListController.php index 5d7f68b..ccac0f9 100644 --- a/src/Controller/ListController.php +++ b/src/Controller/ListController.php @@ -1,17 +1,23 @@ * @author Xheni Myrtaj */ -class ListController extends FOSRestController implements ClassResourceInterface +class ListController extends AbstractController { use AuthenticationTrait; - /** - * @var SubscriberListRepository - */ - private $subscriberListRepository = null; + private SubscriberListRepository $subscriberListRepository; + private SubscriberRepository $subscriberRepository; + private SerializerInterface $serializer; - /** - * @param Authentication $authentication - * @param SubscriberListRepository $repository - */ - public function __construct(Authentication $authentication, SubscriberListRepository $repository) - { + public function __construct( + Authentication $authentication, + SubscriberListRepository $repository, + SubscriberRepository $subscriberRepository, + SerializerInterface $serializer + ) { $this->authentication = $authentication; $this->subscriberListRepository = $repository; + $this->subscriberRepository = $subscriberRepository; + $this->serializer = $serializer; } - /** - * Gets a list of all subscriber lists. - * - * @OA\Get( - * path="/api/v2/lists", - * tags={"lists"}, - * summary="Gets a list of all subscriber lists.", - * description="Returns a json list of all subscriber lists.", - * @OA\Parameter( - * name="session", - * in="header", - * description="Session ID obtained from authentication", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Response( - * response=201, - * description="Success", - * @OA\JsonContent( - * type="object", - * example={ - * { - * "name": "News", - * "description": "News (and some fun stuff)", - * "creation_date": "2016-06-22T15:01:17+00:00", - * "list_position": 12, - * "subject_prefix": "phpList", - * "public": true, - * "category": "news", - * "id": 1 - * }, - * { - * "name": "More news", - * "description": "", - * "creation_date": "2016-06-22T15:01:17+00:00", - * "list_position": 12, - * "subject_prefix": "", - * "public": true, - * "category": "", - * "id": 2 - * } - * } - * ) - * ), - * @OA\Response( - * response=403, - * description="Failure", - * @OA\JsonContent( - * @OA\Property( - * property="message", - * type="string", - * example="No valid session key was provided as basic auth password.") - * ) - * ) - * ) - * - * - * - * @param Request $request - * - * @return View - */ - public function cgetAction(Request $request): View + #[Route('/lists', name: 'get_lists', methods: ['GET'])] + #[OA\Get( + path: '/lists', + description: 'Returns a JSON list of all subscriber lists.', + summary: 'Gets a list of all subscriber lists.', + tags: ['lists'], + parameters: [ + new OA\Parameter( + name: 'session', + description: 'Session ID obtained from authentication', + in: 'header', + required: true, + schema: new OA\Schema( + type: 'string' + ) + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + type: 'array', + items: new OA\Items( + properties: [ + new OA\Property(property: 'name', type: 'string', example: 'News'), + new OA\Property( + property: 'description', + type: 'string', + example: 'News (and some fun stuff)' + ), + new OA\Property( + property: 'creation_date', + type: 'string', + format: 'date-time', + example: '2016-06-22T15:01:17+00:00' + ), + new OA\Property(property: 'list_position', type: 'integer', example: 12), + new OA\Property(property: 'subject_prefix', type: 'string', example: 'phpList'), + new OA\Property(property: 'public', type: 'boolean', example: true), + new OA\Property(property: 'category', type: 'string', example: 'news'), + new OA\Property(property: 'id', type: 'integer', example: 1) + ], + type: 'object' + ) + ) + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'No valid session key was provided as basic auth password.' + ) + ], + type: 'object' + ) + ) + ] + )] + public function getLists(Request $request): JsonResponse { $this->requireAuthentication($request); + $data = $this->subscriberListRepository->findAll(); + $json = $this->serializer->serialize($data, 'json', [ + AbstractNormalizer::GROUPS => 'SubscriberList', + ]); - return View::create()->setData($this->subscriberListRepository->findAll()); + return new JsonResponse($json, Response::HTTP_OK, [], true); } - /** - * Gets a subscriber list. - * - * @OA\Get( - * path="/api/v2/lists/{list}", - * tags={"lists"}, - * summary="Gets a subscriber list.", - * description="Returns a single subscriber lists with specified ID", - * @OA\Parameter( - * name="list", - * in="path", - * description="List ID", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Parameter( - * name="session", - * in="header", - * description="Session ID obtained from authentication", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * type="object", - * example={ - * { - * "name": "News", - * "description": "News (and some fun stuff)", - * "creation_date": "2016-06-22T15:01:17+00:00", - * "list_position": 12, - * "subject_prefix": "phpList", - * "public": true, - * "category": "news", - * "id": 1 - * } - * } - * ) - * ), - * @OA\Response( - * response=403, - * description="Failure", - * @OA\JsonContent( - * @OA\Property( - * property="message", - * type="string", - * example="No valid session key was provided as basic auth password." - * ) - * ) - * ), - * @OA\Response( - * response=404, - * description="Failure", - * @OA\JsonContent( - * @OA\Property(property="message", type="string", example="There is no list with that ID.") - * ) - * ) - * ) - * - * @param Request $request - * @param SubscriberList $list - * - * @return View - */ - public function getAction(Request $request, SubscriberList $list): View + #[Route('/lists/{id}', name: 'get_list', methods: ['GET'])] + #[OA\Get( + path: '/lists/{list}', + description: 'Returns a single subscriber list with specified ID.', + summary: 'Gets a subscriber list.', + tags: ['lists'], + parameters: [ + new OA\Parameter( + name: 'list', + description: 'List ID', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string') + ), + new OA\Parameter( + name: 'session', + description: 'Session ID obtained from authentication', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + type: 'object', + example: [ + 'name' => 'News', + 'description' => 'News (and some fun stuff)', + 'creation_date' => '2016-06-22T15:01:17+00:00', + 'list_position' => 12, + 'subject_prefix' => 'phpList', + 'public' => true, + 'category' => 'news', + 'id' => 1 + ] + ) + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'No valid session key was provided as basic auth password.' + ) + ], + type: 'object' + ) + ), + new OA\Response( + response: 404, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'There is no list with that ID.' + ) + ], + type: 'object' + ) + ) + ] + )] + public function getList(Request $request, #[MapEntity(mapping: ['id' => 'id'])] SubscriberList $list): JsonResponse { $this->requireAuthentication($request); + $json = $this->serializer->serialize($list, 'json', [ + AbstractNormalizer::GROUPS => 'SubscriberList', + ]); - return View::create()->setData($list); + return new JsonResponse($json, Response::HTTP_OK, [], true); } - /** - * Deletes a subscriber list. - * - * - * @OA\Delete( - * path="/api/v2/lists/{list}", - * tags={"lists"}, - * summary="Deletes a list.", - * description="Deletes a single subscriber list passed as", - * @OA\Parameter( - * name="session", - * in="header", - * description="Session ID", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Parameter( - * name="list", - * in="path", - * description="List ID", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Response( - * response=200, - * description="Success" - * ), - * @OA\Response( - * response=403, - * description="Failure", - * @OA\JsonContent( - * @OA\Property( - * property="message", - * type="string", - * example="No valid session key was provided as basic auth password or You do not have access to this session." - * ) - * ) - * ), - * @OA\Response( - * response=404, - * description="Failure", - * @OA\JsonContent( - * @OA\Property(property="message", type="string", example="There is no session with that ID.") - * ) - * ) - * ) - * - * - * @param Request $request - * @param SubscriberList $list - * - * @return View - */ - public function deleteAction(Request $request, SubscriberList $list): View - { + #[Route('/lists/{id}', name: 'delete_list', methods: ['DELETE'])] + #[OA\Delete( + path: '/lists/{list}', + description: 'Deletes a single subscriber list.', + summary: 'Deletes a list.', + tags: ['lists'], + parameters: [ + new OA\Parameter( + name: 'session', + description: 'Session ID', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ), + new OA\Parameter( + name: 'list', + description: 'List ID', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success' + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'No valid session key was provided.' + ) + ], + type: 'object' + ) + ), + new OA\Response( + response: 404, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'There is no session with that ID.' + ) + ], + type: 'object' + ) + ) + ] + )] + public function deleteList( + Request $request, + #[MapEntity(mapping: ['id' => 'id'])] SubscriberList $list + ): JsonResponse { $this->requireAuthentication($request); $this->subscriberListRepository->remove($list); - return View::create(); + return new JsonResponse(null, Response::HTTP_NO_CONTENT, [], false); } - /** - * Gets a list of all subscribers (members) of a subscriber list. - * - * - * @OA\Get( - * path="/api/v2/lists/{list}/members", - * tags={"lists"}, - * summary="Gets a list of all subscribers (members) of a subscriber list.", - * description="Returns a json list of all subscriber lists.", - * @OA\Parameter( - * name="session", - * in="header", - * description="Session ID obtained from authentication", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Parameter( - * name="list", - * in="path", - * description="List ID", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * type="object", - * example={ - * { - * "creation_date": "2016-07-22T15:01:17+00:00", - * "email": "oliver@example.com", - * "confirmed": true, - * "blacklisted": true, - * "bounce_count": 17, - * "unique_id": "95feb7fe7e06e6c11ca8d0c48cb46e89", - * "html_email": true, - * "disabled": true, - * "id": 1, - * }, - * { - * "creation_date": "2017-07-22T15:12:17+00:00", - * "email": "sam@example.com", - * "confirmed": true, - * "blacklisted": false, - * "bounce_count": 1, - * "unique_id": "95feb7fe7e06e6c11ca8d0c48cb4616d", - * "html_email": false, - * "disabled": false, - * "id": 2, - * } - * } - * ) - * ), - * @OA\Response( - * response=403, - * description="Failure", - * @OA\JsonContent( - * @OA\Property( - * property="message", - * type="string", - * example="No valid session key was provided as basic auth password.") - * ) - * ) - * ) - * - * - * @param Request $request - * @param SubscriberList $list - * - * @return View - */ - public function getMembersAction(Request $request, SubscriberList $list): View - { + #[Route('/lists/{id}/members', name: 'get_subscriber_from_list', methods: ['GET'])] + #[OA\Get( + path: '/lists/{list}/members', + description: 'Returns a JSON list of all subscribers for a subscriber list.', + summary: 'Gets a list of all subscribers (members) of a subscriber list.', + tags: ['lists'], + parameters: [ + new OA\Parameter( + name: 'session', + description: 'Session ID obtained from authentication', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ), + new OA\Parameter( + name: 'list', + description: 'List ID', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + type: 'array', + items: new OA\Items( + properties: [ + new OA\Property( + property: 'creation_date', + type: 'string', + format: 'date-time', + example: '2016-07-22T15:01:17+00:00' + ), + new OA\Property(property: 'email', type: 'string', example: 'oliver@example.com'), + new OA\Property(property: 'confirmed', type: 'boolean', example: true), + new OA\Property(property: 'blacklisted', type: 'boolean', example: true), + new OA\Property(property: 'bounce_count', type: 'integer', example: 17), + new OA\Property( + property: 'unique_id', + type: 'string', + example: '95feb7fe7e06e6c11ca8d0c48cb46e89' + ), + new OA\Property(property: 'html_email', type: 'boolean', example: true), + new OA\Property(property: 'disabled', type: 'boolean', example: true), + new OA\Property(property: 'id', type: 'integer', example: 1) + ], + type: 'object' + ) + ) + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'No valid session key was provided as basic auth password.' + ) + ], + type: 'object' + ) + ) + ] + )] + public function getListMembers( + Request $request, + #[MapEntity(mapping: ['id' => 'id'])] SubscriberList $list + ): JsonResponse { $this->requireAuthentication($request); - return View::create()->setData($list->getSubscribers()); + $subscribers = $this->subscriberRepository->getSubscribersBySubscribedListId($list->getId()); + + $json = $this->serializer->serialize($subscribers, 'json', [ + AbstractNormalizer::GROUPS => 'SubscriberListMembers', + ]); + + return new JsonResponse($json, Response::HTTP_OK, [], true); } - /** - * Gets the total number of subscribers of a list. - * - * @OA\Get( - * path="/api/v2/lists/{list}/count", - * tags={"lists"}, - * summary="Gets the total number of subscribers of a list", - * description="Returns a count of all subscribers in a given list.", - * @OA\Parameter( - * name="session", - * in="header", - * description="Session ID obtained from authentication", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Parameter( - * name="list", - * in="path", - * description="List ID", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Response( - * response=200, - * description="Success" - * ), - * @OA\Response( - * response=403, - * description="Failure", - * @OA\JsonContent( - * @OA\Property( - * property="message", - * type="string", - * example="No valid session key was provided as basic auth password.") - * ) - * ) - * ) - * - * @param Request $request - * @param SubscriberList $list - * - * @return View - */ - public function getSubscribersCountAction(Request $request, SubscriberList $list): View - { + #[Route('/lists/{id}/subscribers/count', name: 'get_subscribers_count_from_list', methods: ['GET'])] + #[OA\Get( + path: '/lists/{list}/count', + description: 'Returns a count of all subscribers in a given list.', + summary: 'Gets the total number of subscribers of a list', + tags: ['lists'], + parameters: [ + new OA\Parameter( + name: 'session', + description: 'Session ID obtained from authentication', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ), + new OA\Parameter( + name: 'list', + description: 'List ID', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success' + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'No valid session key was provided as basic auth password.' + ) + ], + type: 'object' + ) + ) + ] + )] + public function getSubscribersCount( + Request $request, + #[MapEntity(mapping: ['id' => 'id'])] SubscriberList $list + ): JsonResponse { $this->requireAuthentication($request); + $json = $this->serializer->serialize(count($list->getSubscribers()), 'json'); - return View::create()->setData(count($list->getSubscribers())); + return new JsonResponse($json, Response::HTTP_OK, [], true); } } diff --git a/src/Controller/SessionController.php b/src/Controller/SessionController.php index 63e02cc..1c12dd0 100644 --- a/src/Controller/SessionController.php +++ b/src/Controller/SessionController.php @@ -4,174 +4,183 @@ namespace PhpList\RestBundle\Controller; -use FOS\RestBundle\Controller\FOSRestController; -use FOS\RestBundle\Routing\ClassResourceInterface; -use FOS\RestBundle\View\View; +use Symfony\Bridge\Doctrine\Attribute\MapEntity; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use PhpList\Core\Domain\Model\Identity\Administrator; use PhpList\Core\Domain\Model\Identity\AdministratorToken; use PhpList\Core\Domain\Repository\Identity\AdministratorRepository; use PhpList\Core\Domain\Repository\Identity\AdministratorTokenRepository; use PhpList\Core\Security\Authentication; use PhpList\RestBundle\Controller\Traits\AuthenticationTrait; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; -use OpenApi\Annotations as OA; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Serializer\SerializerInterface; +use OpenApi\Attributes as OA; /** * This controller provides methods to create and destroy REST API sessions. * * @author Oliver Klee */ -class SessionController extends FOSRestController implements ClassResourceInterface +class SessionController extends AbstractController { use AuthenticationTrait; - /** - * @var AdministratorRepository - */ - private $administratorRepository = null; - - /** - * @var AdministratorTokenRepository - */ - private $tokenRepository = null; + private AdministratorRepository $administratorRepository; + private AdministratorTokenRepository $tokenRepository; + private SerializerInterface $serializer; - /** - * @param Authentication $authentication - * @param AdministratorRepository $administratorRepository - * @param AdministratorTokenRepository $tokenRepository - */ public function __construct( Authentication $authentication, AdministratorRepository $administratorRepository, - AdministratorTokenRepository $tokenRepository + AdministratorTokenRepository $tokenRepository, + SerializerInterface $serializer ) { $this->authentication = $authentication; $this->administratorRepository = $administratorRepository; $this->tokenRepository = $tokenRepository; + $this->serializer = $serializer; } /** * Creates a new session (if the provided credentials are valid). * - * @OA\Post( - * path="/api/v2/sessions", - * tags={"sessions"}, - * summary="Log in or create new session.", - * description="Given valid login data, this will generate a login token that will be valid for 1 hour", - * @OA\RequestBody( - * required=true, - * description="Pass session credentials", - * @OA\JsonContent( - * required={"login_name","password"}, - * @OA\Property(property="login_name", type="string", format="string", example="admin"), - * @OA\Property(property="password", type="string", format="password", example="eetIc/Gropvoc1"), - * ), - * ), - * @OA\Response( - * response=201, - * description="Success", - * @OA\JsonContent( - * @OA\Property(property="id", type="integer", example="1234"), - * @OA\Property(property="key", type="string", example="2cfe100561473c6cdd99c9e2f26fa974"), - * @OA\Property(property="expiry", type="string", example="2017-07-20T18:22:48+00:00") - * ) - * ), - * @OA\Response( - * response=400, - * description="Failure", - * @OA\JsonContent( - * @OA\Property(property="message", type="string", example="Empty json, invalid data and or incomplete data") - * ) - * ), - * @OA\Response( - * response="401", - * description="Success", - * @OA\JsonContent( - * @OA\Property(property="message", type="string", example="Not authorized.") - * ) - * ) - * ) - * - * @param Request $request - * - * @return View - * * @throws UnauthorizedHttpException */ - public function postAction(Request $request): View + #[Route('/sessions', name: 'create_session', methods: ['POST'])] + #[OA\Post( + path: '/sessions', + description: 'Given valid login data, this will generate a login token that will be valid for 1 hour.', + summary: 'Log in or create new session.', + requestBody: new OA\RequestBody( + description: 'Pass session credentials', + required: true, + content: new OA\JsonContent( + required: ['login_name', 'password'], + properties: [ + new OA\Property(property: 'login_name', type: 'string', format: 'string', example: 'admin'), + new OA\Property(property: 'password', type: 'string', format: 'password', example: 'eetIc/Gropvoc1') + ] + ) + ), + tags: ['sessions'], + responses: [ + new OA\Response( + response: 201, + description: 'Success', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'id', type: 'integer', example: 1234), + new OA\Property(property: 'key', type: 'string', example: '2cfe100561473c6cdd99c9e2f26fa974'), + new OA\Property(property: 'expiry', type: 'string', example: '2017-07-20T18:22:48+00:00') + ] + ) + ), + new OA\Response( + response: 400, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'Empty json, invalid data and or incomplete data' + ) + ] + ) + ), + new OA\Response( + response: 401, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'message', type: 'string', example: 'Not authorized.') + ] + ) + ) + ] + )] + public function createSession(Request $request): JsonResponse { $this->validateCreateRequest($request); $administrator = $this->administratorRepository->findOneByLoginCredentials( - $request->get('login_name'), - $request->get('password') + $request->getPayload()->get('login_name'), + $request->getPayload()->get('password') ); if ($administrator === null) { throw new UnauthorizedHttpException('', 'Not authorized', null, 1500567098); } $token = $this->createAndPersistToken($administrator); + $json = $this->serializer->serialize($token, 'json'); - return View::create()->setStatusCode(Response::HTTP_CREATED)->setData($token); + return new JsonResponse($json, Response::HTTP_CREATED, [], true); } /** * Deletes a session. * - * @OA\Delete( - * path="/api/v2/sessions/{session}", - * tags={"sessions"}, - * summary="Delete a session.", - * description="Delete the session passed as paramater", - * @OA\Parameter( - * name="session", - * in="path", - * description="Session ID", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Response( - * response=200, - * description="Success" - * ), - * @OA\Response( - * response=403, - * description="Failure", - * @OA\JsonContent( - * @OA\Property( - * property="message", - * type="string", - * example="No valid session key was provided as basic auth password or - * You do not have access to this session." - * ) - * ) - * ), - * @OA\Response( - * response=404, - * description="Failure", - * @OA\JsonContent( - * @OA\Property(property="message", type="string", example="There is no session with that ID.") - * ) - * ) - * ) - * - * * This action may only be called for sessions that are owned by the authenticated administrator. * - * @param Request $request - * @param AdministratorToken $token - * - * @return View - * * @throws AccessDeniedHttpException */ - public function deleteAction(Request $request, AdministratorToken $token): View - { + #[Route('/sessions/{id}', name: 'delete_session', methods: ['DELETE'])] + #[OA\Delete( + path: '/sessions/{session}', + description: 'Delete the session passed as a parameter.', + summary: 'Delete a session.', + tags: ['sessions'], + parameters: [ + new OA\Parameter( + name: 'session', + description: 'Session ID', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success' + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'No valid session key was provided as basic auth password.' + ) + ] + ) + ), + new OA\Response( + response: 404, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'There is no session with that ID.' + ) + ] + ) + ) + ] + )] + public function deleteAction( + Request $request, + #[MapEntity(mapping: ['id' => 'id'])] AdministratorToken $token + ): JsonResponse { $administrator = $this->requireAuthentication($request); if ($token->getAdministrator() !== $administrator) { throw new AccessDeniedHttpException('You do not have access to this session.', null, 1519831644); @@ -179,7 +188,7 @@ public function deleteAction(Request $request, AdministratorToken $token): View $this->tokenRepository->remove($token); - return View::create(); + return new JsonResponse(null, Response::HTTP_NO_CONTENT, [], false); } /** @@ -191,12 +200,12 @@ public function deleteAction(Request $request, AdministratorToken $token): View * * @throws BadRequestHttpException */ - private function validateCreateRequest(Request $request) + private function validateCreateRequest(Request $request): void { if ($request->getContent() === '') { throw new BadRequestHttpException('Empty JSON data', null, 1500559729); } - if (empty($request->get('login_name')) || empty($request->get('password'))) { + if (empty($request->getPayload()->get('login_name')) || empty($request->getPayload()->get('password'))) { throw new BadRequestHttpException('Incomplete credentials', null, 1500562647); } } diff --git a/src/Controller/SubscriberController.php b/src/Controller/SubscriberController.php index 94cc775..3d9087b 100644 --- a/src/Controller/SubscriberController.php +++ b/src/Controller/SubscriberController.php @@ -1,143 +1,161 @@ */ -class SubscriberController extends FOSRestController implements ClassResourceInterface +class SubscriberController extends AbstractController { use AuthenticationTrait; - /** - * @var SubscriberRepository - */ - private $subscriberRepository = null; + private SubscriberRepository $subscriberRepository; - /** - * @param Authentication $authentication - * @param SubscriberRepository $repository - */ - public function __construct(Authentication $authentication, SubscriberRepository $repository) - { + public function __construct( + Authentication $authentication, + SubscriberRepository $repository + ) { $this->authentication = $authentication; $this->subscriberRepository = $repository; } - /** - * Creates a new subscriber (if the provided data is valid and there is no subscriber with the given email - * address yet). - * - * @OA\Post( - * path="/api/v2/subscriber", - * tags={"subscribers"}, - * summary="Create a subscriber list", - * description="Creates a new subscriber (if the provided data is valid and there is no subscriber with - * the given email address yet).", - * @OA\Parameter( - * name="session", - * in="header", - * description="Session ID obtained from authentication", - * required=true, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\RequestBody( - * required=true, - * description="Pass session credentials", - * @OA\JsonContent( - * required={"email"}, - * @OA\Property(property="email", type="string", format="string", example="admin"), - * @OA\Property(property="confirmed", type="string", format="boolean", example="eetIc/Gropvoc1"), - * @OA\Property(property="blacklisted", type="string", format="boolean", example="eetIc/Gropvoc1"), - * @OA\Property(property="html_entail", type="string", format="boolean", example="eetIc/Gropvoc1"), - * @OA\Property(property="disabled", type="string", format="boolean", example="eetIc/Gropvoc1"), - * ), - * ), - * @OA\Response( - * response=201, - * description="Success", - * @OA\JsonContent( - * @OA\Property(property="creation_date", type="integer", example="2017-12-16T18:44:27+00:00"), - * @OA\Property(property="email", type="string", example="subscriber@example.com"), - * @OA\Property(property="confirmed", type="boolean", example="false"), - * @OA\Property(property="blacklisted", type="boolean", example="false"), - * @OA\Property(property="bounced", type="integer", example="0"), - * @OA\Property(property="unique_id", type="string", example="69f4e92cf50eafca9627f35704f030f4"), - * @OA\Property(property="html_entail", type="boolean", example="false"), - * @OA\Property(property="disabled", type="boolean", example="false"), - * @OA\Property(property="id", type="integer", example="1") - * ) - * ), - * @OA\Response( - * response=403, - * description="Failure", - * @OA\JsonContent( - * @OA\Property( - * property="message", - * type="string", - * example="No valid session key was provided as basic auth password.") - * ) - * ), - * @OA\Response( - * response="409", - * description="Failure", - * @OA\JsonContent( - * @OA\Property(property="message", type="string", example="This resource already exists.") - * ) - * ), - * @OA\Response( - * response="422", - * description="Failure", - * @OA\JsonContent( - * @OA\Property(property="message", type="string", example="Some fields invalid: email, confirmed, html_email") - * ) - * ) - * ) - * - * @param Request $request - * - * @return View - * - * @throws ConflictHttpException - */ - public function postAction(Request $request): View + #[Route('/subscribers', name: 'create_subscriber', methods: ['POST'])] + #[OA\Post( + path: '/subscriber', + description: 'Creates a new subscriber (if there is no subscriber with the given email address yet).', + summary: 'Create a subscriber', + requestBody: new OA\RequestBody( + description: 'Pass session credentials', + required: true, + content: new OA\JsonContent( + required: ['email'], + properties: [ + new OA\Property(property: 'email', type: 'string', format: 'string', example: 'admin@example.com'), + new OA\Property(property: 'confirmed', type: 'boolean', example: false), + new OA\Property(property: 'blacklisted', type: 'boolean', example: false), + new OA\Property(property: 'html_email', type: 'boolean', example: false), + new OA\Property(property: 'disabled', type: 'boolean', example: false) + ] + ) + ), + tags: ['subscribers'], + parameters: [ + new OA\Parameter( + name: 'session', + description: 'Session ID obtained from authentication', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 201, + description: 'Success', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'creation_date', + type: 'string', + format: 'date-time', + example: '2017-12-16T18:44:27+00:00' + ), + new OA\Property(property: 'email', type: 'string', example: 'subscriber@example.com'), + new OA\Property(property: 'confirmed', type: 'boolean', example: false), + new OA\Property(property: 'blacklisted', type: 'boolean', example: false), + new OA\Property(property: 'bounced', type: 'integer', example: 0), + new OA\Property( + property: 'unique_id', + type: 'string', + example: '69f4e92cf50eafca9627f35704f030f4' + ), + new OA\Property(property: 'html_email', type: 'boolean', example: false), + new OA\Property(property: 'disabled', type: 'boolean', example: false), + new OA\Property(property: 'id', type: 'integer', example: 1) + ] + ) + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'No valid session key was provided as basic auth password.' + ) + ] + ) + ), + new OA\Response( + response: 409, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'message', type: 'string', example: 'This resource already exists.') + ] + ) + ), + new OA\Response( + response: 422, + description: 'Failure', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'message', + type: 'string', + example: 'Some fields invalid: email, confirmed, html_email' + ) + ] + ) + ) + ] + )] + public function postAction(Request $request, SerializerInterface $serializer): JsonResponse { $this->requireAuthentication($request); - + $data = $request->getPayload(); $this->validateSubscriber($request); - $email = $request->get('email'); + $email = $data->get('email'); if ($this->subscriberRepository->findOneByEmail($email) !== null) { throw new ConflictHttpException('This resource already exists.', null, 1513439108); } - + // @phpstan-ignore-next-line $subscriber = new Subscriber(); $subscriber->setEmail($email); - $subscriber->setConfirmed((bool)$request->get('confirmed')); - $subscriber->setBlacklisted((bool)$request->get('blacklisted')); - $subscriber->setHtmlEmail((bool)$request->get('html_email')); - $subscriber->setDisabled((bool)$request->get('disabled')); + $subscriber->setConfirmed((bool)$data->get('confirmed', false)); + $subscriber->setBlacklisted((bool)$data->get('blacklisted', false)); + $subscriber->setHtmlEmail((bool)$data->get('html_email', true)); + $subscriber->setDisabled((bool)$data->get('disabled', false)); + $this->subscriberRepository->save($subscriber); - return View::create()->setStatusCode(Response::HTTP_CREATED)->setData($subscriber); + return new JsonResponse( + $serializer->serialize($subscriber, 'json'), + Response::HTTP_CREATED, + [], + true + ); } /** @@ -147,17 +165,19 @@ public function postAction(Request $request): View * * @throws UnprocessableEntityHttpException */ - private function validateSubscriber(Request $request) + private function validateSubscriber(Request $request): void { /** @var string[] $invalidFields */ $invalidFields = []; - if (filter_var($request->get('email'), FILTER_VALIDATE_EMAIL) === false) { + if (filter_var($request->getPayload()->get('email'), FILTER_VALIDATE_EMAIL) === false) { $invalidFields[] = 'email'; } $booleanFields = ['confirmed', 'blacklisted', 'html_email', 'disabled']; foreach ($booleanFields as $fieldKey) { - if ($request->get($fieldKey) !== null && !is_bool($request->get($fieldKey))) { + if ($request->getPayload()->get($fieldKey) !== null + && !is_bool($request->getPayload()->get($fieldKey)) + ) { $invalidFields[] = $fieldKey; } } diff --git a/src/DependencyInjection/PhpListRestExtension.php b/src/DependencyInjection/PhpListRestExtension.php index e865fbe..6d54851 100644 --- a/src/DependencyInjection/PhpListRestExtension.php +++ b/src/DependencyInjection/PhpListRestExtension.php @@ -1,8 +1,11 @@ load('services.yml'); } } diff --git a/src/EventListener/ExceptionListener.php b/src/EventListener/ExceptionListener.php new file mode 100644 index 0000000..8cd80b9 --- /dev/null +++ b/src/EventListener/ExceptionListener.php @@ -0,0 +1,39 @@ +getThrowable(); + + if ($exception instanceof AccessDeniedHttpException) { + $response = new JsonResponse([ + 'message' => $exception->getMessage(), + ], 403); + + $event->setResponse($response); + } elseif ($exception instanceof HttpExceptionInterface) { + $response = new JsonResponse([ + 'message' => $exception->getMessage(), + ], $exception->getStatusCode()); + + $event->setResponse($response); + } elseif ($exception instanceof Exception) { + $response = new JsonResponse([ + 'message' => $exception->getMessage(), + ], 500); + + $event->setResponse($response); + } + } +} diff --git a/src/EventListener/ResponseListener.php b/src/EventListener/ResponseListener.php new file mode 100644 index 0000000..2bfb915 --- /dev/null +++ b/src/EventListener/ResponseListener.php @@ -0,0 +1,22 @@ +getResponse(); + + if ($response instanceof JsonResponse) { + $response->headers->set('X-Content-Type-Options', 'nosniff'); + $response->headers->set('Content-Security-Policy', "default-src 'none'"); + $response->headers->set('X-Frame-Options', 'DENY'); + } + } +} diff --git a/src/PhpListRestBundle.php b/src/PhpListRestBundle.php index 79dec0b..5ff7622 100644 --- a/src/PhpListRestBundle.php +++ b/src/PhpListRestBundle.php @@ -1,16 +1,33 @@ */ +#[OA\Info( + version: '1.0.0', + description: 'This is the OpenAPI documentation for My API.', + title: 'My API Documentation', + contact: new OA\Contact( + email: 'support@phplist.com' + ), + license: new OA\License( + name: 'AGPL-3.0-or-later', + url: 'https://www.gnu.org/licenses/agpl.txt' + ) +)] +#[OA\Server( + url: 'https://www.phplist.com/api/v2', + description: 'Production server' +)] class PhpListRestBundle extends Bundle { } diff --git a/src/ViewHandler/SecuredViewHandler.php b/src/ViewHandler/SecuredViewHandler.php index cbcf685..e883092 100644 --- a/src/ViewHandler/SecuredViewHandler.php +++ b/src/ViewHandler/SecuredViewHandler.php @@ -16,7 +16,7 @@ class SecuredViewHandler { /** - * @param ViewHandler $viewHandler + * @param ViewHandler $handler * @param View $view * @param Request $request * @param string $format diff --git a/tests/Integration/Composer/ScriptsTest.php b/tests/Integration/Composer/ScriptsTest.php index bfd6a9c..9a077ac 100644 --- a/tests/Integration/Composer/ScriptsTest.php +++ b/tests/Integration/Composer/ScriptsTest.php @@ -1,4 +1,5 @@ getAbsolutePublicDirectoryPath()); + self::assertDirectoryExists($this->getAbsolutePublicDirectoryPath()); } - /** - * @return string - */ private function getAbsolutePublicDirectoryPath(): string { return dirname(__DIR__, 3) . '/public/'; @@ -31,7 +26,7 @@ private function getAbsolutePublicDirectoryPath(): string /** * @return string[][] */ - public function publicDirectoryFilesDataProvider(): array + public static function publicDirectoryFilesDataProvider(): array { return [ 'production entry point' => ['app.php'], @@ -42,26 +37,18 @@ public function publicDirectoryFilesDataProvider(): array } /** - * @test - * @param string $fileName * @dataProvider publicDirectoryFilesDataProvider */ - public function publicDirectoryFilesExist(string $fileName) + public function testPublicDirectoryFilesExist(string $fileName) { - static::assertFileExists($this->getAbsolutePublicDirectoryPath() . $fileName); + self::assertFileExists($this->getAbsolutePublicDirectoryPath() . $fileName); } - /** - * @test - */ - public function binariesDirectoryHasBeenCreated() + public function testBinariesDirectoryHasBeenCreated() { - static::assertDirectoryExists($this->getAbsoluteBinariesDirectoryPath()); + self::assertDirectoryExists($this->getAbsoluteBinariesDirectoryPath()); } - /** - * @return string - */ private function getAbsoluteBinariesDirectoryPath(): string { return dirname(__DIR__, 3) . '/bin/'; @@ -70,7 +57,7 @@ private function getAbsoluteBinariesDirectoryPath(): string /** * @return string[][] */ - public function binariesDataProvider(): array + public static function binariesDataProvider(): array { return [ 'Symfony console' => ['console'], @@ -78,107 +65,82 @@ public function binariesDataProvider(): array } /** - * @test - * @param string $fileName * @dataProvider binariesDataProvider */ - public function binariesExist(string $fileName) + public function testBinariesExist(string $fileName) { - static::assertFileExists($this->getAbsoluteBinariesDirectoryPath() . $fileName); + self::assertFileExists($this->getAbsoluteBinariesDirectoryPath() . $fileName); } - /** - * @return string - */ private function getBundleConfigurationFilePath(): string { return dirname(__DIR__, 3) . '/config/bundles.yml'; } - /** - * @test - */ - public function bundleConfigurationFileExists() + public function testBundleConfigurationFileExists() { - static::assertFileExists($this->getBundleConfigurationFilePath()); + self::assertFileExists($this->getBundleConfigurationFilePath()); } /** * @return string[][] */ - public function bundleClassNameDataProvider(): array + public static function bundleClassNameDataProvider(): array { return [ - 'framework bundle' => ['Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle'], - 'rest bundle' => ['PhpList\\RestBundle\\PhpListRestBundle'], + 'framework bundle' => ['Symfony\Bundle\FrameworkBundle\FrameworkBundle'], + 'rest bundle' => ['PhpList\RestBundle\PhpListRestBundle'], ]; } /** - * @test - * @param string $bundleClassName * @dataProvider bundleClassNameDataProvider */ - public function bundleConfigurationFileContainsModuleBundles(string $bundleClassName) + public function testBundleConfigurationFileContainsModuleBundles(string $bundleClassName) { $fileContents = file_get_contents($this->getBundleConfigurationFilePath()); - static::assertContains($bundleClassName, $fileContents); + self::assertStringContainsString($bundleClassName, $fileContents); } - /** - * @return string - */ private function getModuleRoutesConfigurationFilePath(): string { return dirname(__DIR__, 3) . '/config/routing_modules.yml'; } - - /** - * @test - */ - public function moduleRoutesConfigurationFileExists() + public function testModuleRoutesConfigurationFileExists() { - static::assertFileExists($this->getModuleRoutesConfigurationFilePath()); + self::assertFileExists($this->getModuleRoutesConfigurationFilePath()); } /** * @return string[][] */ - public function moduleRoutingDataProvider(): array + public static function moduleRoutingDataProvider(): array { return [ 'route name' => ['phplist/rest-api.rest-api'], 'resource' => ["resource: '@PhpListRestBundle/Controller/'"], - 'type' => ['type: annotation'], + 'type' => ['type: attribute'], ]; } /** - * @test - * @param string $routeSearchString * @dataProvider moduleRoutingDataProvider */ - public function moduleRoutesConfigurationFileContainsModuleRoutes(string $routeSearchString) + public function testModuleRoutesConfigurationFileContainsModuleRoutes(string $routeSearchString) { $fileContents = file_get_contents($this->getModuleRoutesConfigurationFilePath()); - static::assertContains($routeSearchString, $fileContents); + self::assertStringContainsString($routeSearchString, $fileContents); } - /** - * @test - */ - public function parametersConfigurationFileExists() + public function testParametersConfigurationFileExists() { - static::assertFileExists(dirname(__DIR__, 3) . '/config/parameters.yml'); + self::assertFileExists(dirname(__DIR__, 3) . '/config/parameters.yml'); } - /** - * @test - */ - public function modulesConfigurationFileExists() + public function testModulesConfigurationFileExists() { - static::assertFileExists(dirname(__DIR__, 3) . '/config/config_modules.yml'); + self::assertFileExists(dirname(__DIR__, 3) . '/config/config_modules.yml'); } } diff --git a/tests/Integration/Controller/AbstractControllerTest.php b/tests/Integration/Controller/AbstractTestController.php similarity index 71% rename from tests/Integration/Controller/AbstractControllerTest.php rename to tests/Integration/Controller/AbstractTestController.php index 169ece2..22dc053 100644 --- a/tests/Integration/Controller/AbstractControllerTest.php +++ b/tests/Integration/Controller/AbstractTestController.php @@ -1,10 +1,14 @@ */ -abstract class AbstractControllerTest extends AbstractWebTest +abstract class AbstractTestController extends WebTestCase { use DatabaseTestTrait; - /** - * @var string - */ - const ADMINISTRATOR_TABLE_NAME = 'phplist_admin'; - - /** - * @var string - */ - const TOKEN_TABLE_NAME = 'phplist_admintoken'; - - protected function setUp() + protected function setUp(): void { + parent::setUp(); + self::createClient(); $this->setUpDatabaseTest(); - $this->setUpWebTest(); + $this->loadSchema(); + } + + protected function tearDown(): void + { + $schemaTool = new SchemaTool($this->entityManager); + $schemaTool->dropDatabase(); + parent::tearDown(); } /** @@ -58,7 +61,14 @@ protected function jsonRequest( $serverWithContentType = $server; $serverWithContentType['CONTENT_TYPE'] = 'application/json'; - return $this->client->request($method, $uri, $parameters, $files, $serverWithContentType, $content); + return self::getClient()->request( + $method, + $uri, + $parameters, + $files, + $serverWithContentType, + $content + ); } /** @@ -81,9 +91,7 @@ protected function authenticatedJsonRequest( array $server = [], string $content = null ): Crawler { - $this->getDataSet()->addTable(static::ADMINISTRATOR_TABLE_NAME, __DIR__ . '/Fixtures/Administrator.csv'); - $this->getDataSet()->addTable(static::TOKEN_TABLE_NAME, __DIR__ . '/Fixtures/AdministratorToken.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class]); $serverWithAuthentication = $server; $serverWithAuthentication['PHP_AUTH_USER'] = 'unused'; @@ -99,7 +107,7 @@ protected function authenticatedJsonRequest( */ protected function getDecodedJsonResponseContent(): array { - return json_decode($this->client->getResponse()->getContent(), true); + return json_decode(self::getClient()->getResponse()->getContent(), true); } /** @@ -109,7 +117,7 @@ protected function getDecodedJsonResponseContent(): array */ protected function getResponseContentAsInt(): int { - return json_decode($this->client->getResponse()->getContent(), true); + return json_decode(self::getClient()->getResponse()->getContent(), true); } /** @@ -119,9 +127,9 @@ protected function getResponseContentAsInt(): int * * @return void */ - protected function assertJsonResponseContentEquals(array $expected) + protected function assertJsonResponseContentEquals(array $expected): void { - static::assertSame($expected, $this->getDecodedJsonResponseContent()); + self::assertSame($expected, $this->getDecodedJsonResponseContent()); } /** @@ -131,12 +139,12 @@ protected function assertJsonResponseContentEquals(array $expected) * * @return void */ - protected function assertHttpStatusWithJsonContentType(int $status) + protected function assertHttpStatusWithJsonContentType(int $status): void { - $response = $this->client->getResponse(); + $response = self::getClient()->getResponse(); - static::assertSame($status, $response->getStatusCode()); - static::assertContains('application/json', (string)$response->headers); + self::assertSame($status, $response->getStatusCode()); + self::assertStringContainsString('application/json', (string)$response->headers); } /** @@ -144,7 +152,7 @@ protected function assertHttpStatusWithJsonContentType(int $status) * * @return void */ - protected function assertHttpOkay() + protected function assertHttpOkay(): void { $this->assertHttpStatusWithJsonContentType(Response::HTTP_OK); } @@ -154,7 +162,7 @@ protected function assertHttpOkay() * * @return void */ - protected function assertHttpCreated() + protected function assertHttpCreated(): void { $this->assertHttpStatusWithJsonContentType(Response::HTTP_CREATED); } @@ -164,11 +172,11 @@ protected function assertHttpCreated() * * @return void */ - protected function assertHttpNoContent() + protected function assertHttpNoContent(): void { - $response = $this->client->getResponse(); + $response = self::getClient()->getResponse(); - static::assertSame(Response::HTTP_NO_CONTENT, $response->getStatusCode()); + self::assertSame(Response::HTTP_NO_CONTENT, $response->getStatusCode()); } /** @@ -176,7 +184,7 @@ protected function assertHttpNoContent() * * @return void */ - protected function assertHttpBadRequest() + protected function assertHttpBadRequest(): void { $this->assertHttpStatusWithJsonContentType(Response::HTTP_BAD_REQUEST); } @@ -186,7 +194,7 @@ protected function assertHttpBadRequest() * * @return void */ - protected function assertHttpUnauthorized() + protected function assertHttpUnauthorized(): void { $this->assertHttpStatusWithJsonContentType(Response::HTTP_UNAUTHORIZED); } @@ -196,7 +204,7 @@ protected function assertHttpUnauthorized() * * @return void */ - protected function assertHttpNotFound() + protected function assertHttpNotFound(): void { $this->assertHttpStatusWithJsonContentType(Response::HTTP_NOT_FOUND); } @@ -206,7 +214,7 @@ protected function assertHttpNotFound() * * @return void */ - protected function assertHttpForbidden() + protected function assertHttpForbidden(): void { $this->assertHttpStatusWithJsonContentType(Response::HTTP_FORBIDDEN); } @@ -216,11 +224,11 @@ protected function assertHttpForbidden() * * @return void */ - protected function assertHttpMethodNotAllowed() + protected function assertHttpMethodNotAllowed(): void { - $response = $this->client->getResponse(); + $response = self::getClient()->getResponse(); - static::assertSame(Response::HTTP_METHOD_NOT_ALLOWED, $response->getStatusCode()); + self::assertSame(Response::HTTP_METHOD_NOT_ALLOWED, $response->getStatusCode()); } /** @@ -229,13 +237,12 @@ protected function assertHttpMethodNotAllowed() * * @return void */ - protected function assertHttpConflict() + protected function assertHttpConflict(): void { $this->assertHttpStatusWithJsonContentType(Response::HTTP_CONFLICT); - static::assertSame( + self::assertSame( [ - 'code' => Response::HTTP_CONFLICT, 'message' => 'This resource already exists.', ], $this->getDecodedJsonResponseContent() @@ -247,7 +254,7 @@ protected function assertHttpConflict() * * @return void */ - protected function assertHttpUnprocessableEntity() + protected function assertHttpUnprocessableEntity(): void { $this->assertHttpStatusWithJsonContentType(Response::HTTP_UNPROCESSABLE_ENTITY); } diff --git a/tests/Integration/Controller/Fixtures/AdministratorFixture.php b/tests/Integration/Controller/Fixtures/AdministratorFixture.php new file mode 100644 index 0000000..09f30d6 --- /dev/null +++ b/tests/Integration/Controller/Fixtures/AdministratorFixture.php @@ -0,0 +1,55 @@ +setSubjectId($admin, (int)$row['id']); + $admin->setLoginName($row['loginname']); + $admin->setEmailAddress($row['email']); + $admin->setPasswordHash($row['password']); + $admin->setDisabled((bool) $row['disabled']); + $admin->setSuperUser((bool) $row['superuser']); + + $manager->persist($admin); + + $this->setSubjectProperty($admin, 'creationDate', new DateTime($row['created'])); + $this->setSubjectProperty($admin, 'passwordChangeDate', new DateTime($row['passwordchanged'])); + } while (true); + + fclose($handle); + } +} diff --git a/tests/Integration/Controller/Fixtures/AdministratorTokenFixture.php b/tests/Integration/Controller/Fixtures/AdministratorTokenFixture.php new file mode 100644 index 0000000..d3d022b --- /dev/null +++ b/tests/Integration/Controller/Fixtures/AdministratorTokenFixture.php @@ -0,0 +1,61 @@ +getRepository(Administrator::class); + + do { + $data = fgetcsv($handle); + if ($data === false) { + break; + } + $row = array_combine($headers, $data); + + $admin = $adminRepository->find($row['adminid']); + if ($admin === null) { + $admin = new Administrator(); + $this->setSubjectId($admin, (int)$row['adminid']); + $admin->setSuperUser(true); + $manager->persist($admin); + } + + $adminToken = new AdministratorToken(); + $this->setSubjectId($adminToken, (int)$row['id']); + $adminToken->setKey($row['value']); + $adminToken->setAdministrator($admin); + $manager->persist($adminToken); + + $this->setSubjectProperty($adminToken, 'expiry', new DateTime($row['expires'])); + $this->setSubjectProperty($adminToken, 'creationDate', (bool) $row['entered']); + } while (true); + + fclose($handle); + } +} diff --git a/tests/Integration/Controller/Fixtures/Subscriber.csv b/tests/Integration/Controller/Fixtures/Subscriber.csv index deaf955..1fb2471 100644 --- a/tests/Integration/Controller/Fixtures/Subscriber.csv +++ b/tests/Integration/Controller/Fixtures/Subscriber.csv @@ -1,2 +1,4 @@ id,entered,modified,email,confirmed,blacklisted,bouncecount,uniqid,htmlemail,disabled 1,"2016-07-22 15:01:17","2016-08-23 19:50:43","oliver@example.com",1,1,17,"95feb7fe7e06e6c11ca8d0c48cb46e89",1,1 +2,"2016-07-22 15:01:17","2016-08-23 19:50:43","oliver1@example.com",1,1,17,"95feb7fe7e06e6c11ca8d0c48cb46e87",1,1 +3,"2016-07-22 15:01:17","2016-08-23 19:50:43","oliver2@example.com",1,1,17,"95feb7fe7e06e6c11ca8d0c48cb46e86",1,1 diff --git a/tests/Integration/Controller/Fixtures/SubscriberFixture.php b/tests/Integration/Controller/Fixtures/SubscriberFixture.php new file mode 100644 index 0000000..9a3b600 --- /dev/null +++ b/tests/Integration/Controller/Fixtures/SubscriberFixture.php @@ -0,0 +1,59 @@ +setSubjectId($subscriber, (int)$row['id']); + + $subscriber->setEmail($row['email']); + $subscriber->setConfirmed((bool) $row['confirmed']); + $subscriber->setBlacklisted((bool) $row['blacklisted']); + $subscriber->setBounceCount((int) $row['bouncecount']); + $subscriber->setHtmlEmail((bool) $row['htmlemail']); + $subscriber->setDisabled((bool) $row['disabled']); + + $manager->persist($subscriber); + // avoid pre-persist + $subscriber->setUniqueId($row['uniqid']); + $this->setSubjectProperty($subscriber, 'creationDate', new DateTime($row['entered'])); + $this->setSubjectProperty($subscriber, 'modificationDate', new DateTime($row['modified'])); + } while (true); + + fclose($handle); + } +} diff --git a/tests/Integration/Controller/Fixtures/SubscriberListFixture.php b/tests/Integration/Controller/Fixtures/SubscriberListFixture.php new file mode 100644 index 0000000..5ef4955 --- /dev/null +++ b/tests/Integration/Controller/Fixtures/SubscriberListFixture.php @@ -0,0 +1,69 @@ +getRepository(Administrator::class); + + do { + $data = fgetcsv($handle); + if ($data === false) { + break; + } + $row = array_combine($headers, $data); + + $admin = $adminRepository->find($row['owner']); + if ($admin === null) { + $admin = new Administrator(); + $this->setSubjectId($admin, (int)$row['owner']); + $admin->setSuperUser(true); + $admin->setDisabled(false); + $manager->persist($admin); + } + + $subscriberList = new SubscriberList(); + $this->setSubjectId($subscriberList, (int)$row['id']); + $subscriberList->setName($row['name']); + $subscriberList->setDescription($row['description']); + $subscriberList->setListPosition((int)$row['listorder']); + $subscriberList->setSubjectPrefix($row['prefix']); + $subscriberList->setPublic((bool) $row['active']); + $subscriberList->setCategory($row['category']); + $subscriberList->setOwner($admin); + + $manager->persist($subscriberList); + + $this->setSubjectProperty($subscriberList, 'creationDate', new DateTime($row['entered'])); + $this->setSubjectProperty($subscriberList, 'modificationDate', new DateTime($row['modified'])); + } while (true); + + fclose($handle); + } +} diff --git a/tests/Integration/Controller/Fixtures/SubscriptionFixture.php b/tests/Integration/Controller/Fixtures/SubscriptionFixture.php new file mode 100644 index 0000000..88be840 --- /dev/null +++ b/tests/Integration/Controller/Fixtures/SubscriptionFixture.php @@ -0,0 +1,60 @@ +getRepository(Subscriber::class); + $subscriberListRepository = $manager->getRepository(SubscriberList::class); + + $headers = fgetcsv($handle); + + do { + $data = fgetcsv($handle); + if ($data === false) { + break; + } + $row = array_combine($headers, $data); + + $subscriber = $subscriberRepository->find((int)$row['userid']); + $subscriberList = $subscriberListRepository->find((int)$row['listid']); + + $subscription = new Subscription(); + $subscriberList->addSubscription($subscription); + $subscriber->addSubscription($subscription); + + $manager->persist($subscription); + + $this->setSubjectProperty($subscription, 'creationDate', new DateTime($row['entered'])); + $this->setSubjectProperty($subscription, 'modificationDate', new DateTime($row['modified'])); + } while (true); + + fclose($handle); + } +} diff --git a/tests/Integration/Controller/ListControllerTest.php b/tests/Integration/Controller/ListControllerTest.php index db5f8e3..e12f4e2 100644 --- a/tests/Integration/Controller/ListControllerTest.php +++ b/tests/Integration/Controller/ListControllerTest.php @@ -1,10 +1,16 @@ * @author Xheni Myrtaj */ -class ListControllerTest extends AbstractControllerTest +class ListControllerTest extends AbstractTestController { - /** - * @var string - */ - const LISTS_TABLE_NAME = 'phplist_list'; - - /** - * @var string - */ - const SUBSCRIBER_TABLE_NAME = 'phplist_user_user'; - - /** - * @var string - */ - const SUBSCRIPTION_TABLE_NAME = 'phplist_listuser'; - - /** - * @test - */ - public function controllerIsAvailableViaContainer() + public function testControllerIsAvailableViaContainer() { - static::assertInstanceOf(ListController::class, $this->client->getContainer()->get(ListController::class)); + self::assertInstanceOf(ListController::class, self::getContainer()->get(ListController::class)); } - /** - * @test - */ - public function getListsWithoutSessionKeyReturnsForbiddenStatus() + public function testGetListsWithoutSessionKeyReturnsForbiddenStatus() { - $this->client->request('get', '/api/v2/lists'); + self::getClient()->request('get', '/api/v2/lists'); $this->assertHttpForbidden(); } - /** - * @test - */ - public function getListsWithExpiredSessionKeyReturnsForbiddenStatus() + public function testGetListsWithExpiredSessionKeyReturnsForbiddenStatus() { - $this->getDataSet()->addTable(static::ADMINISTRATOR_TABLE_NAME, __DIR__ . '/Fixtures/Administrator.csv'); - $this->getDataSet()->addTable(static::TOKEN_TABLE_NAME, __DIR__ . '/Fixtures/AdministratorToken.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class]); - $this->client->request( + self::getClient()->request( 'get', '/api/v2/lists', [], @@ -67,23 +47,16 @@ public function getListsWithExpiredSessionKeyReturnsForbiddenStatus() $this->assertHttpForbidden(); } - /** - * @test - */ - public function getListsWithCurrentSessionKeyReturnsOkayStatus() + public function testGetListsWithCurrentSessionKeyReturnsOkayStatus() { $this->authenticatedJsonRequest('get', '/api/v2/lists'); $this->assertHttpOkay(); } - /** - * @test - */ - public function getListsWithCurrentSessionKeyReturnsListData() + public function testGetListsWithCurrentSessionKeyReturnsListData() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists'); @@ -123,49 +96,34 @@ public function getListsWithCurrentSessionKeyReturnsListData() ); } - /** - * @test - */ - public function getListWithoutSessionKeyForExistingListReturnsForbiddenStatus() + public function testGetListWithoutSessionKeyForExistingListReturnsForbiddenStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); - $this->client->request('get', '/api/v2/lists/1'); + self::getClient()->request('get', '/api/v2/lists/1'); $this->assertHttpForbidden(); } - /** - * @test - */ - public function getListWithCurrentSessionKeyForExistingListReturnsOkayStatus() + public function testGetListWithCurrentSessionKeyForExistingListReturnsOkayStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists/1'); $this->assertHttpOkay(); } - /** - * @test - */ - public function getListWithCurrentSessionKeyForInexistentListReturnsNotFoundStatus() + public function testGetListWithCurrentSessionKeyForInexistentListReturnsNotFoundStatus() { $this->authenticatedJsonRequest('get', '/api/v2/lists/999'); $this->assertHttpNotFound(); } - /** - * @test - */ - public function getListWithCurrentSessionKeyReturnsListData() + public function testGetListWithCurrentSessionKeyReturnsListData() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists/1'); @@ -183,80 +141,59 @@ public function getListWithCurrentSessionKeyReturnsListData() ); } - /** - * @test - */ - public function deleteListWithoutSessionKeyForExistingListReturnsForbiddenStatus() + public function testDeleteListWithoutSessionKeyForExistingListReturnsForbiddenStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); - $this->client->request('delete', '/api/v2/lists/1'); + self::getClient()->request('delete', '/api/v2/lists/1'); $this->assertHttpForbidden(); } - /** - * @test - */ - public function deleteListWithCurrentSessionKeyForExistingListReturnsNoContentStatus() + public function testDeleteListWithCurrentSessionKeyForExistingListReturnsNoContentStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberFixture::class, SubscriberListFixture::class, SubscriptionFixture::class]); $this->authenticatedJsonRequest('delete', '/api/v2/lists/1'); $this->assertHttpNoContent(); } - /** - * @test - */ - public function deleteListWithCurrentSessionKeyForInexistentListReturnsNotFoundStatus() + public function testDeleteListWithCurrentSessionKeyForInexistentListReturnsNotFoundStatus() { $this->authenticatedJsonRequest('delete', '/api/v2/lists/999'); $this->assertHttpNotFound(); } - /** - * @test - */ - public function deleteListWithCurrentSessionKeyDeletesList() + public function testDeleteListWithCurrentSessionKeyDeletesList() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); $this->authenticatedJsonRequest('delete', '/api/v2/lists/1'); - $listRepository = $this->container->get(SubscriberListRepository::class); - static::assertNull($listRepository->find(1)); + $listRepository = self::getContainer()->get(SubscriberListRepository::class); + self::assertNull($listRepository->find(1)); } - /** - * @test - */ - public function getListMembersForExistingListWithoutSessionKeyReturnsForbiddenStatus() + public function testGetListMembersForExistingListWithoutSessionKeyReturnsForbiddenStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); - $this->client->request('get', '/api/v2/lists/1/members'); + self::getClient()->request('get', '/api/v2/lists/1/members'); $this->assertHttpForbidden(); } - /** - * @test - */ - public function getListMembersForExistingListWithExpiredSessionKeyReturnsForbiddenStatus() + public function testGetListMembersForExistingListWithExpiredSessionKeyReturnsForbiddenStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->getDataSet()->addTable(static::ADMINISTRATOR_TABLE_NAME, __DIR__ . '/Fixtures/Administrator.csv'); - $this->getDataSet()->addTable(static::TOKEN_TABLE_NAME, __DIR__ . '/Fixtures/AdministratorToken.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([ + SubscriberListFixture::class, + AdministratorFixture::class, + AdministratorTokenFixture::class, + ]); - $this->client->request( + self::getClient()->request( 'get', '/api/v2/lists/1/members', [], @@ -267,51 +204,34 @@ public function getListMembersForExistingListWithExpiredSessionKeyReturnsForbidd $this->assertHttpForbidden(); } - /** - * @test - */ - public function getListMembersWithCurrentSessionKeyForInexistentListReturnsNotFoundStatus() + public function testGetListMembersWithCurrentSessionKeyForInexistentListReturnsNotFoundStatus() { $this->authenticatedJsonRequest('get', '/api/v2/lists/999/members'); $this->assertHttpNotFound(); } - /** - * @test - */ - public function getListMembersWithCurrentSessionKeyForExistingListReturnsOkayStatus() + public function testGetListMembersWithCurrentSessionKeyForExistingListReturnsOkayStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists/1/members'); $this->assertHttpOkay(); } - /** - * @test - */ - public function getListMembersWithCurrentSessionKeyForExistingListWithoutSubscribersReturnsEmptyArray() + public function testGetListMembersWithCurrentSessionKeyForExistingListWithoutSubscribersReturnsEmptyArray() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists/1/members'); $this->assertJsonResponseContentEquals([]); } - /** - * @test - */ - public function getListMembersWithCurrentSessionKeyForExistingListWithSubscribersReturnsSubscribers() + public function testGetListMembersWithCurrentSessionKeyForExistingListWithSubscribersReturnsSubscribers() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->getDataSet()->addTable(static::SUBSCRIBER_TABLE_NAME, __DIR__ . '/Fixtures/Subscriber.csv'); - $this->getDataSet()->addTable(static::SUBSCRIPTION_TABLE_NAME, __DIR__ . '/Fixtures/Subscription.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class, SubscriberFixture::class, SubscriptionFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists/2/members'); @@ -327,87 +247,75 @@ public function getListMembersWithCurrentSessionKeyForExistingListWithSubscriber 'html_email' => true, 'disabled' => true, 'id' => 1, - ] + ], [ + 'creation_date' => '2016-07-22T15:01:17+00:00', + 'email' => 'oliver1@example.com', + 'confirmed' => true, + 'blacklisted' => true, + 'bounce_count' => 17, + 'unique_id' => '95feb7fe7e06e6c11ca8d0c48cb46e87', + 'html_email' => true, + 'disabled' => true, + 'id' => 2, + ], ] ); } - /** - * @test - */ - public function getListSubscribersCountForExistingListWithoutSessionKeyReturnsForbiddenStatus() + public function testGetListSubscribersCountForExistingListWithoutSessionKeyReturnsForbiddenStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); - $this->client->request('get', '/api/v2/lists/1/subscribers/count'); + self::getClient()->request('get', '/api/v2/lists/1/subscribers/count'); $this->assertHttpForbidden(); } - /** - * @test - */ - public function getListSubscribersCountForExistingListWithExpiredSessionKeyReturnsForbiddenStatus() + public function testGetListSubscribersCountForExistingListWithExpiredSessionKeyReturnsForbiddenStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->getDataSet()->addTable(static::ADMINISTRATOR_TABLE_NAME, __DIR__ . '/Fixtures/Administrator.csv'); - $this->getDataSet()->addTable(static::TOKEN_TABLE_NAME, __DIR__ . '/Fixtures/AdministratorToken.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([ + SubscriberListFixture::class, + AdministratorFixture::class, + AdministratorTokenFixture::class, + ]); - $this->client->request( + self::getClient()->request( 'get', '/api/v2/lists/1/subscribers/count', [], [], - ['PHP_AUTH_USER' => 'unused', 'PHP_AUTH_PW' => 'cfdf64eecbbf336628b0f3071adba763'] + ['PHP_AUTH_USER' => 'unused', 'PHP_AUTH_PW' => 'cfdf64eecbbf336628b0f3071adba764'] ); $this->assertHttpForbidden(); } - /** - * @test - */ - public function getListSubscribersCountWithCurrentSessionKeyForExistingListReturnsOkayStatus() + public function testGetListSubscribersCountWithCurrentSessionKeyForExistingListReturnsOkayStatus() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists/1/subscribers/count'); $this->assertHttpOkay(); } - /** - * @test - */ - public function getListSubscribersCountWithCurrentSessionKeyForExistingListWithNoSubscribersReturnsZero() + public function testGetSubscribersCountForEmptyListWithValidSession() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->getDataSet()->addTable(static::SUBSCRIBER_TABLE_NAME, __DIR__ . '/Fixtures/Subscriber.csv'); - $this->getDataSet()->addTable(static::SUBSCRIPTION_TABLE_NAME, __DIR__ . '/Fixtures/Subscription.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class, SubscriberFixture::class, SubscriptionFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists/3/subscribers/count'); $responseContent = $this->getResponseContentAsInt(); - static::assertSame(0, $responseContent); + self::assertSame(0, $responseContent); } - /** - * @test - */ - public function getListSubscribersCountWithCurrentSessionKeyForExistingListWithSubscribersReturnsSubscribersCount() + public function testGetSubscribersCountForListWithValidSession() { - $this->getDataSet()->addTable(static::LISTS_TABLE_NAME, __DIR__ . '/Fixtures/SubscriberList.csv'); - $this->getDataSet()->addTable(static::SUBSCRIBER_TABLE_NAME, __DIR__ . '/Fixtures/Subscriber.csv'); - $this->getDataSet()->addTable(static::SUBSCRIPTION_TABLE_NAME, __DIR__ . '/Fixtures/Subscription.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberListFixture::class, SubscriberFixture::class, SubscriptionFixture::class]); $this->authenticatedJsonRequest('get', '/api/v2/lists/2/subscribers/count'); $responseContent = $this->getResponseContentAsInt(); - static::assertSame(2, $responseContent); + self::assertSame(2, $responseContent); } } diff --git a/tests/Integration/Controller/SessionControllerTest.php b/tests/Integration/Controller/SessionControllerTest.php index 0e6d296..72f1d58 100644 --- a/tests/Integration/Controller/SessionControllerTest.php +++ b/tests/Integration/Controller/SessionControllerTest.php @@ -1,100 +1,78 @@ */ -class SessionControllerTest extends AbstractControllerTest +class SessionControllerTest extends AbstractTestController { - /** - * @var AdministratorTokenRepository|ObjectRepository - */ - private $administratorTokenRepository = null; + private ?AdministratorTokenRepository $administratorTokenRepository = null; - protected function setUp() + protected function setUp(): void { - $this->setUpDatabaseTest(); - $this->setUpWebTest(); - - $this->administratorTokenRepository = $this->bootstrap->getContainer() - ->get(AdministratorTokenRepository::class); + parent::setUp(); + $this->administratorTokenRepository = self::getContainer()->get(AdministratorTokenRepository::class); } - /** - * @test - */ - public function controllerIsAvailableViaContainer() + public function testControllerIsAvailableViaContainer() { - static::assertInstanceOf( + self::assertInstanceOf( SessionController::class, - $this->client->getContainer()->get(SessionController::class) + self::getContainer()->get(SessionController::class) ); } - /** - * @test - */ - public function getSessionsIsNotAllowed() + public function testGetSessionsIsNotAllowed() { - $this->client->request('get', '/api/v2/sessions'); + self::getClient()->request('get', '/api/v2/sessions'); $this->assertHttpMethodNotAllowed(); } - /** - * @test - */ - public function postSessionsWithNoJsonReturnsError400() + public function testPostSessionsWithNoJsonReturnsError400() { $this->jsonRequest('post', '/api/v2/sessions'); $this->assertHttpBadRequest(); $this->assertJsonResponseContentEquals( [ - 'code' => Response::HTTP_BAD_REQUEST, 'message' => 'Empty JSON data', ] ); } - /** - * @test - */ - public function postSessionsWithInvalidJsonWithJsonContentTypeReturnsError400() + public function testPostSessionsWithInvalidJsonWithJsonContentTypeReturnsError400() { $this->jsonRequest('post', '/api/v2/sessions', [], [], [], 'Here be dragons, but no JSON.'); $this->assertHttpBadRequest(); $this->assertJsonResponseContentEquals( [ - 'code' => Response::HTTP_BAD_REQUEST, - 'message' => 'Invalid json message received' + 'message' => 'Could not decode request body.', ] ); } - /** - * @test - */ - public function postSessionsWithValidEmptyJsonWithOtherTypeReturnsError400() + public function testPostSessionsWithValidEmptyJsonWithOtherTypeReturnsError400() { - $this->client->request('post', '/api/v2/sessions', [], [], ['CONTENT_TYPE' => 'application/xml'], '[]'); + self::getClient()->request('post', '/api/v2/sessions', [], [], ['CONTENT_TYPE' => 'application/xml'], '[]'); $this->assertHttpBadRequest(); $this->assertJsonResponseContentEquals( [ - 'code' => Response::HTTP_BAD_REQUEST, - 'message' => 'Invalid xml message received' + 'message' => 'Incomplete credentials', ] ); } @@ -102,7 +80,7 @@ public function postSessionsWithValidEmptyJsonWithOtherTypeReturnsError400() /** * @return string[][] */ - public function incompleteCredentialsDataProvider(): array + public static function incompleteCredentialsDataProvider(): array { return [ 'neither login_name nor password' => ['{}'], @@ -112,30 +90,23 @@ public function incompleteCredentialsDataProvider(): array } /** - * @test - * @param string $jsonData * @dataProvider incompleteCredentialsDataProvider */ - public function postSessionsWithValidIncompleteJsonReturnsError400(string $jsonData) + public function testPostSessionsWithValidIncompleteJsonReturnsError400(string $jsonData) { $this->jsonRequest('post', '/api/v2/sessions', [], [], [], $jsonData); $this->assertHttpBadRequest(); $this->assertJsonResponseContentEquals( [ - 'code' => Response::HTTP_BAD_REQUEST, 'message' => 'Incomplete credentials', ] ); } - /** - * @test - */ - public function postSessionsWithInvalidCredentialsReturnsNotAuthorized() + public function testPostSessionsWithInvalidCredentialsReturnsNotAuthorized() { - $this->getDataSet()->addTable(static::ADMINISTRATOR_TABLE_NAME, __DIR__ . '/Fixtures/Administrator.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([AdministratorFixture::class]); $loginName = 'john.doe'; $password = 'a sandwich and a cup of coffee'; @@ -146,19 +117,14 @@ public function postSessionsWithInvalidCredentialsReturnsNotAuthorized() $this->assertHttpUnauthorized(); $this->assertJsonResponseContentEquals( [ - 'code' => Response::HTTP_UNAUTHORIZED, 'message' => 'Not authorized', ] ); } - /** - * @test - */ - public function postSessionsActionWithValidCredentialsReturnsCreatedHttpStatus() + public function testPostSessionsActionWithValidCredentialsReturnsCreatedHttpStatus() { - $this->getDataSet()->addTable(static::ADMINISTRATOR_TABLE_NAME, __DIR__ . '/Fixtures/Administrator.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([AdministratorFixture::class]); $loginName = 'john.doe'; $password = 'Bazinga!'; @@ -169,95 +135,71 @@ public function postSessionsActionWithValidCredentialsReturnsCreatedHttpStatus() $this->assertHttpCreated(); } - /** - * @test - */ - public function postSessionsActionWithValidCredentialsCreatesToken() + public function testPostSessionsActionWithValidCredentialsCreatesToken() { $administratorId = 1; - $this->getDataSet()->addTable(static::ADMINISTRATOR_TABLE_NAME, __DIR__ . '/Fixtures/Administrator.csv'); - $this->applyDatabaseChanges(); - - $loginName = 'john.doe'; - $password = 'Bazinga!'; - $jsonData = ['login_name' => $loginName, 'password' => $password]; + $this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class]); + $jsonData = ['login_name' => 'john.doe', 'password' => 'Bazinga!']; $this->jsonRequest('post', '/api/v2/sessions', [], [], [], json_encode($jsonData)); $responseContent = $this->getDecodedJsonResponseContent(); $tokenId = $responseContent['id']; $key = $responseContent['key']; - $expiry = $responseContent['expiry']; + $expiry = $responseContent['expiry_date']; /** @var AdministratorToken $token */ $token = $this->administratorTokenRepository->find($tokenId); - static::assertNotNull($token); - static::assertSame($key, $token->getKey()); - static::assertSame($expiry, $token->getExpiry()->format(\DateTime::ATOM)); - static::assertSame($administratorId, $token->getAdministrator()->getId()); + + self::assertNotNull($token); + self::assertSame($key, $token->getKey()); + self::assertSame($expiry, $token->getExpiry()->format(DateTime::ATOM)); + self::assertSame($administratorId, $token->getAdministrator()->getId()); } - /** - * @test - */ - public function deleteSessionWithoutSessionKeyForExistingSessionReturnsForbiddenStatus() + public function testDeleteSessionWithoutSessionKeyForExistingSessionReturnsForbiddenStatus() { - $this->getDataSet()->addTable(static::ADMINISTRATOR_TABLE_NAME, __DIR__ . '/Fixtures/Administrator.csv'); - $this->getDataSet()->addTable(static::TOKEN_TABLE_NAME, __DIR__ . '/Fixtures/AdministratorToken.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class]); - $this->client->request('delete', '/api/v2/sessions/1'); + self::getClient()->request('delete', '/api/v2/sessions/1'); $this->assertHttpForbidden(); } - /** - * @test - */ - public function deleteSessionWithCurrentSessionKeyForExistingSessionReturnsNoContentStatus() + public function testDeleteSessionWithCurrentSessionKeyForExistingSessionReturnsNoContentStatus() { $this->authenticatedJsonRequest('delete', '/api/v2/sessions/1'); $this->assertHttpNoContent(); } - /** - * @test - */ - public function deleteSessionWithCurrentSessionKeyForInexistentSessionReturnsNotFoundStatus() + public function testDeleteSessionWithCurrentSessionKeyForInexistentSessionReturnsNotFoundStatus() { $this->authenticatedJsonRequest('delete', '/api/v2/sessions/999'); $this->assertHttpNotFound(); } - /** - * @test - */ - public function deleteSessionWithCurrentSessionAndOwnSessionKeyDeletesSession() + public function testDeleteSessionWithCurrentSessionAndOwnSessionKeyDeletesSession() { + $this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class]); $this->authenticatedJsonRequest('delete', '/api/v2/sessions/1'); - static::assertNull($this->administratorTokenRepository->find(1)); + self::assertNull($this->administratorTokenRepository->find(1)); } - /** - * @test - */ - public function deleteSessionWithCurrentSessionAndOwnSessionKeyKeepsReturnsForbiddenStatus() + public function testDeleteSessionWithCurrentSessionAndOwnSessionKeyKeepsReturnsForbiddenStatus() { $this->authenticatedJsonRequest('delete', '/api/v2/sessions/3'); $this->assertHttpForbidden(); } - /** - * @test - */ - public function deleteSessionWithCurrentSessionAndOwnSessionKeyKeepsSession() + public function testDeleteSessionWithCurrentSessionAndOwnSessionKeyKeepsSession() { + $this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class]); $this->authenticatedJsonRequest('delete', '/api/v2/sessions/3'); - static::assertNotNull($this->administratorTokenRepository->find(3)); + self::assertNotNull($this->administratorTokenRepository->find(3)); } } diff --git a/tests/Integration/Controller/SubscriberControllerTest.php b/tests/Integration/Controller/SubscriberControllerTest.php index 6d2b1ff..21a63ea 100644 --- a/tests/Integration/Controller/SubscriberControllerTest.php +++ b/tests/Integration/Controller/SubscriberControllerTest.php @@ -1,4 +1,5 @@ */ -class SubscriberControllerTest extends AbstractControllerTest +class SubscriberControllerTest extends AbstractTestController { - /** - * @var string - */ - const SUBSCRIBER_TABLE_NAME = 'phplist_user_user'; + private ?SubscriberRepository $subscriberRepository = null; - /** - * @var SubscriberRepository - */ - private $subscriberRepository = null; - - protected function setUp() + protected function setUp(): void { - $this->setUpDatabaseTest(); - $this->setUpWebTest(); + parent::setUp(); - $this->subscriberRepository = $this->bootstrap->getContainer() - ->get(SubscriberRepository::class); + $this->subscriberRepository = self::getContainer()->get(SubscriberRepository::class); } - /** - * @test - */ - public function controllerIsAvailableViaContainer() + public function testControllerIsAvailableViaContainer() { - static::assertInstanceOf( + self::assertInstanceOf( SubscriberController::class, - $this->client->getContainer()->get(SubscriberController::class) + self::getContainer()->get(SubscriberController::class) ); } - /** - * @test - */ - public function getSubscribersIsNotAllowed() + public function testGetSubscribersIsNotAllowed() { - $this->client->request('get', '/api/v2/subscribers'); + self::getClient()->request('get', '/api/v2/subscribers'); $this->assertHttpMethodNotAllowed(); } - /** - * @test - */ - public function postSubscribersWithoutSessionKeyReturnsForbiddenStatus() + public function testPostSubscribersWithoutSessionKeyReturnsForbiddenStatus() { $this->jsonRequest('post', '/api/v2/subscribers'); $this->assertHttpForbidden(); } - /** - * @test - */ - public function postSubscribersWithValidSessionKeyAndMinimalValidSubscriberDataCreatesResource() + public function testPostSubscribersWithValidSessionKeyAndMinimalValidSubscriberDataCreatesResource() { - $this->touchDatabaseTable(static::SUBSCRIBER_TABLE_NAME); - $email = 'subscriber@example.com'; $jsonData = ['email' => $email]; @@ -79,13 +57,8 @@ public function postSubscribersWithValidSessionKeyAndMinimalValidSubscriberDataC $this->assertHttpCreated(); } - /** - * @test - */ - public function postSubscribersWithValidSessionKeyAndMinimalValidDataReturnsIdAndUniqueId() + public function testPostSubscribersWithValidSessionKeyAndMinimalValidDataReturnsIdAndUniqueId() { - $this->touchDatabaseTable(static::SUBSCRIBER_TABLE_NAME); - $email = 'subscriber@example.com'; $jsonData = ['email' => $email]; @@ -93,17 +66,12 @@ public function postSubscribersWithValidSessionKeyAndMinimalValidDataReturnsIdAn $responseContent = $this->getDecodedJsonResponseContent(); - static::assertGreaterThan(0, $responseContent['id']); - static::assertRegExp('/^[0-9a-f]{32}$/', $responseContent['unique_id']); + self::assertGreaterThan(0, $responseContent['id']); + self::assertMatchesRegularExpression('/^[0-9a-f]{32}$/', $responseContent['unique_id']); } - /** - * @test - */ - public function postSubscribersWithValidSessionKeyAndValidDataCreatesSubscriber() + public function testPostSubscribersWithValidSessionKeyAndValidDataCreatesSubscriber() { - $this->touchDatabaseTable(static::SUBSCRIBER_TABLE_NAME); - $email = 'subscriber@example.com'; $jsonData = ['email' => $email]; @@ -112,16 +80,12 @@ public function postSubscribersWithValidSessionKeyAndValidDataCreatesSubscriber( $responseContent = $this->getDecodedJsonResponseContent(); $subscriberId = $responseContent['id']; - static::assertInstanceOf(Subscriber::class, $this->subscriberRepository->find($subscriberId)); + self::assertInstanceOf(Subscriber::class, $this->subscriberRepository->find($subscriberId)); } - /** - * @test - */ - public function postSubscribersWithValidSessionKeyAndExistingEmailAddressCreatesConflictStatus() + public function testPostSubscribersWithValidSessionKeyAndExistingEmailAddressCreatesConflictStatus() { - $this->getDataSet()->addTable(static::SUBSCRIBER_TABLE_NAME, __DIR__ . '/Fixtures/Subscriber.csv'); - $this->applyDatabaseChanges(); + $this->loadFixtures([SubscriberFixture::class]); $email = 'oliver@example.com'; $jsonData = ['email' => $email]; @@ -134,7 +98,7 @@ public function postSubscribersWithValidSessionKeyAndExistingEmailAddressCreates /** * @return array[][] */ - public function invalidSubscriberDataProvider(): array + public static function invalidSubscriberDataProvider(): array { return [ 'no data' => [[]], @@ -154,26 +118,18 @@ public function invalidSubscriberDataProvider(): array } /** - * @test * @dataProvider invalidSubscriberDataProvider * @param array[] $jsonData */ - public function postSubscribersWithInvalidDataCreatesUnprocessableEntityStatus(array $jsonData) + public function testPostSubscribersWithInvalidDataCreatesUnprocessableEntityStatus(array $jsonData) { - $this->touchDatabaseTable(static::SUBSCRIBER_TABLE_NAME); - $this->authenticatedJsonRequest('post', '/api/v2/subscribers', [], [], [], json_encode($jsonData)); $this->assertHttpUnprocessableEntity(); } - /** - * @test - */ - public function postSubscribersWithValidSessionKeyAssignsProvidedSubscriberData() + public function testPostSubscribersWithValidSessionKeyAssignsProvidedSubscriberData() { - $this->touchDatabaseTable(static::SUBSCRIBER_TABLE_NAME); - $email = 'subscriber@example.com'; $jsonData = [ 'email' => $email, diff --git a/tests/Integration/Routing/RoutingTest.php b/tests/Integration/Routing/RoutingTest.php index c022c1a..b0731ff 100644 --- a/tests/Integration/Routing/RoutingTest.php +++ b/tests/Integration/Routing/RoutingTest.php @@ -1,26 +1,25 @@ */ -class RoutingTest extends AbstractWebTest +class RoutingTest extends WebTestCase { - /** - * @test - */ - public function rootUrlHasHtmlContentType() + public function testRootUrlHasHtmlContentType() { - $this->client->request('get', '/'); + $client = self::createClient(); + $client->request('get', '/api/v2'); - $response = $this->client->getResponse(); + $response = $client->getResponse(); - static::assertContains('text/html', (string)$response->headers); + self::assertStringContainsString('text/html', (string)$response->headers); } } diff --git a/tests/System/Controller/SecuredViewHandlerTest.php b/tests/System/Controller/SecuredViewHandlerTest.php index 0675241..c823ed4 100644 --- a/tests/System/Controller/SecuredViewHandlerTest.php +++ b/tests/System/Controller/SecuredViewHandlerTest.php @@ -1,61 +1,26 @@ */ -class SecuredViewHandlerTest extends TestCase +class SecuredViewHandlerTest extends AbstractTestController { - use SymfonyServerTrait; - - /** - * @var Client - */ - private $httpClient = null; - - protected function setUp() - { - $this->httpClient = new Client(['http_errors' => false]); - } - - protected function tearDown() + public function testSecurityHeaders() { - $this->stopSymfonyServer(); - } - - /** - * @return string[][] - */ - public function environmentDataProvider(): array - { - return [ - 'test' => ['test'], - 'dev' => ['dev'], - ]; - } - - /** - * @test - * @param string $environment - * @dataProvider environmentDataProvider - */ - public function testSecurityHeaders(string $environment) - { - $this->startSymfonyServer($environment); - - $response = $this->httpClient->get( + self::getClient()->request( + 'GET', '/api/v2/sessions', - ['base_uri' => $this->getBaseUrl()] ); + + $response = self::getClient()->getResponse(); $expectedHeaders = [ 'X-Content-Type-Options' => 'nosniff', 'Content-Security-Policy' => "default-src 'none'", @@ -63,7 +28,7 @@ public function testSecurityHeaders(string $environment) ]; foreach ($expectedHeaders as $key => $value) { - static::assertSame([$value], $response->getHeader($key)); + self::assertSame($value, $response->headers->get($key)); } } } diff --git a/tests/System/Controller/SessionControllerTest.php b/tests/System/Controller/SessionControllerTest.php index 9f4a9c4..651d221 100644 --- a/tests/System/Controller/SessionControllerTest.php +++ b/tests/System/Controller/SessionControllerTest.php @@ -1,11 +1,11 @@ */ -class SessionControllerTest extends TestCase +class SessionControllerTest extends AbstractTestController { use SymfonyServerTrait; - /** - * @var Client - */ - private $httpClient = null; - - protected function setUp() - { - $this->httpClient = new Client(['http_errors' => false]); - } - - protected function tearDown() - { - $this->stopSymfonyServer(); - } - - /** - * @return string[][] - */ - public function environmentDataProvider(): array - { - return [ - 'test' => ['test'], - 'dev' => ['dev'], - ]; - } - - /** - * @test - * @param string $environment - * @dataProvider environmentDataProvider - */ - public function postSessionsWithInvalidCredentialsReturnsNotAuthorized(string $environment) + public function testPostSessionsWithInvalidCredentialsReturnsNotAuthorized() { - $this->startSymfonyServer($environment); - $loginName = 'john.doe'; $password = 'a sandwich and a cup of coffee'; $jsonData = ['login_name' => $loginName, 'password' => $password]; - $response = $this->httpClient->post( + self::getClient()->request( + 'POST', '/api/v2/sessions', - ['base_uri' => $this->getBaseUrl(), 'body' => \json_encode($jsonData)] + [], + [], + [], + json_encode($jsonData) ); - static::assertSame(Response::HTTP_UNAUTHORIZED, $response->getStatusCode()); - static::assertSame( + self::assertSame(Response::HTTP_UNAUTHORIZED, self::getClient()->getResponse()->getStatusCode()); + self::assertSame( [ - 'code' => Response::HTTP_UNAUTHORIZED, 'message' => 'Not authorized', ], - \json_decode($response->getBody()->getContents(), true) + json_decode(self::getClient()->getResponse()->getContent(), true) ); } } diff --git a/tests/Unit/PhpListRestBundleTest.php b/tests/Unit/PhpListRestBundleTest.php index b8577e6..305cc86 100644 --- a/tests/Unit/PhpListRestBundleTest.php +++ b/tests/Unit/PhpListRestBundleTest.php @@ -1,4 +1,5 @@ subject = new PhpListRestBundle(); } - /** - * @test - */ - public function classIsBundle() + public function testClassIsBundle() { static::assertInstanceOf(Bundle::class, $this->subject); }