Skip to content

Commit def3b93

Browse files
author
Julien Neuhart
committed
adding unit test + improving code
1 parent be59a5f commit def3b93

20 files changed

+249
-27
lines changed

docker-compose.yml

+10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ services:
3838
- mysql_data:/var/lib/mysql
3939
- ./services/mysql/utf8mb4.cnf:/etc/mysql/conf.d/utf8mb4.cnf:ro
4040

41+
mysql_tests:
42+
image: mysql:5.7
43+
environment:
44+
MYSQL_ROOT_PASSWORD: "admin"
45+
MYSQL_DATABASE: "tests"
46+
MYSQL_USER: "foo"
47+
MYSQL_PASSWORD: "bar"
48+
volumes:
49+
- ./services/mysql/utf8mb4.cnf:/etc/mysql/conf.d/utf8mb4.cnf:ro
50+
4151
phpmyadmin:
4252
image: phpmyadmin/phpmyadmin:4.7
4353
labels:

sources/app/assets/vue/api/post.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import axios from "axios";
22

33
export default {
44
create(message) {
5-
return axios.post("/api/post/create", {
5+
return axios.post("/api/posts", {
66
message: message
77
});
88
},
9-
posts() {
9+
findAll() {
1010
return axios.get("/api/posts");
1111
}
1212
};

sources/app/assets/vue/components/ErrorMessage.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default {
1212
name: "ErrorMessage",
1313
props: {
1414
error: {
15-
type: Object,
15+
type: Error,
1616
required: true
1717
}
1818
},

sources/app/assets/vue/store/post.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ export default {
7474
return null;
7575
}
7676
},
77-
async posts({ commit }) {
77+
async findAll({ commit }) {
7878
commit(FETCHING_POSTS);
7979
try {
80-
let response = await PostAPI.posts();
80+
let response = await PostAPI.findAll();
8181
commit(FETCHING_POSTS_SUCCESS, response.data);
8282
return response.data;
8383
} catch (error) {

sources/app/assets/vue/views/Posts.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export default {
9999
}
100100
},
101101
created() {
102-
this.$store.dispatch("post/posts");
102+
this.$store.dispatch("post/findAll");
103103
},
104104
methods: {
105105
async createPost() {

sources/app/composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"squizlabs/php_codesniffer": "^3.4",
4141
"symfony/debug-pack": "*",
4242
"symfony/maker-bundle": "^1.0",
43+
"symfony/phpunit-bridge": "^4.3",
4344
"symfony/profiler-pack": "*",
4445
"symfony/test-pack": "*",
4546
"symfony/web-server-bundle": "4.3.*",
@@ -73,7 +74,7 @@
7374
"scripts": {
7475
"csfix": "phpcbf --ignore=src/Migrations/**,src/Kernel.php",
7576
"cscheck": "phpcs --ignore=src/Migrations/**,src/Kernel.php",
76-
"phpstan": "phpstan analyse src/ tests/ -c phpstan.neon --level=7 --no-progress -vvv --memory-limit=1024M",
77+
"phpstan": "phpstan analyse src/ -c phpstan.neon --level=7 --no-progress -vvv --memory-limit=1024M",
7778
"auto-scripts": {
7879
"cache:clear": "symfony-cmd",
7980
"assets:install %PUBLIC_DIR%": "symfony-cmd"

sources/app/composer.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sources/app/config/packages/security.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ security:
22
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
33
encoders:
44
App\Entity\User:
5-
algorithm: bcrypt
5+
algorithm: auto
66
providers:
77
in_memory: { memory: ~ }
88
pdo:

sources/app/config/services.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ services:
2626

2727
# add more service definitions when explicit configuration is needed
2828
# please note that last definitions always *replace* previous ones
29+
App\Exception\HTTPExceptionListener:
30+
tags:
31+
- { name: kernel.event_listener, event: kernel.exception }
32+
2933
app.security.hash.password.listener:
3034
class: App\Security\HashPasswordListener
3135
tags:

sources/app/phpcs.xml.dist

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<!-- Directories to be checked -->
1212
<file>src</file>
13-
<!-- <file>tests</file> -->
13+
<file>tests</file>
1414

1515
<exclude-pattern>tests/dependencies/*</exclude-pattern>
1616

sources/app/phpunit.xml.dist

+7-5
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.5/phpunit.xsd"
66
backupGlobals="false"
77
colors="true"
8-
bootstrap="config/bootstrap.php"
8+
bootstrap="tests/bootstrap.php"
99
>
1010
<php>
1111
<ini name="error_reporting" value="-1" />
12-
<server name="APP_ENV" value="test" force="true" />
13-
<server name="SHELL_VERBOSITY" value="-1" />
14-
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
15-
<server name="SYMFONY_PHPUNIT_VERSION" value="6.5" />
12+
<env name="APP_ENV" value="test" force="true" />
13+
<env name="SHELL_VERBOSITY" value="-1" />
14+
<env name="SYMFONY_PHPUNIT_REMOVE" value="" />
15+
<env name="SYMFONY_PHPUNIT_VERSION" value="6.5" />
16+
<env name="KERNEL_CLASS" value="App\Kernel" />
17+
<env name="DATABASE_URL" value="mysql://foo:bar@mysql_tests/tests" force="true" />
1618
</php>
1719

1820
<testsuites>

sources/app/src/Controller/PostController.php

+13-5
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1212
use Symfony\Component\HttpFoundation\JsonResponse;
1313
use Symfony\Component\HttpFoundation\Request;
14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
1416
use Symfony\Component\Serializer\Encoder\JsonEncoder;
1517
use Symfony\Component\Serializer\SerializerInterface;
1618

1719
/**
20+
* @Rest\Route("/api")
1821
* @IsGranted("IS_AUTHENTICATED_FULLY")
1922
*/
2023
final class PostController extends AbstractController
@@ -32,29 +35,34 @@ public function __construct(EntityManagerInterface $em, SerializerInterface $ser
3235
}
3336

3437
/**
35-
* @Rest\Post("/api/post/create", name="createPost")
38+
* @throws BadRequestHttpException
39+
*
40+
* @Rest\Post("/posts", name="createPost")
3641
* @IsGranted("ROLE_FOO")
3742
*/
3843
public function createAction(Request $request): JsonResponse
3944
{
4045
$message = $request->request->get('message');
46+
if (empty($message)) {
47+
throw new BadRequestHttpException('message cannot be empty');
48+
}
4149
$post = new Post();
4250
$post->setMessage($message);
4351
$this->em->persist($post);
4452
$this->em->flush();
4553
$data = $this->serializer->serialize($post, JsonEncoder::FORMAT);
4654

47-
return new JsonResponse($data, 200, [], true);
55+
return new JsonResponse($data, Response::HTTP_CREATED, [], true);
4856
}
4957

5058
/**
51-
* @Rest\Get("/api/posts", name="posts")
59+
* @Rest\Get("/posts", name="findAllPosts")
5260
*/
53-
public function postsAction(): JsonResponse
61+
public function findAllAction(): JsonResponse
5462
{
5563
$posts = $this->em->getRepository(Post::class)->findBy([], ['id' => 'DESC']);
5664
$data = $this->serializer->serialize($posts, JsonEncoder::FORMAT);
5765

58-
return new JsonResponse($data, 200, [], true);
66+
return new JsonResponse($data, Response::HTTP_OK, [], true);
5967
}
6068
}

sources/app/src/Controller/SecurityController.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
use RuntimeException;
99
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1010
use Symfony\Component\HttpFoundation\JsonResponse;
11+
use Symfony\Component\HttpFoundation\Response;
1112
use Symfony\Component\Routing\Annotation\Route;
1213
use Symfony\Component\Serializer\Encoder\JsonEncoder;
1314
use Symfony\Component\Serializer\SerializerInterface;
1415

16+
/**
17+
* @Route("/api")
18+
*/
1519
final class SecurityController extends AbstractController
1620
{
1721
/** @var SerializerInterface */
@@ -23,7 +27,7 @@ public function __construct(SerializerInterface $serializer)
2327
}
2428

2529
/**
26-
* @Route("/api/security/login", name="login")
30+
* @Route("/security/login", name="login")
2731
*/
2832
public function loginAction(): JsonResponse
2933
{
@@ -33,13 +37,13 @@ public function loginAction(): JsonResponse
3337
$userClone->setPassword('');
3438
$data = $this->serializer->serialize($userClone, JsonEncoder::FORMAT);
3539

36-
return new JsonResponse($data, 200, [], true);
40+
return new JsonResponse($data, Response::HTTP_OK, [], true);
3741
}
3842

3943
/**
4044
* @throws RuntimeException
4145
*
42-
* @Route("/api/security/logout", name="logout")
46+
* @Route("/security/logout", name="logout")
4347
*/
4448
public function logoutAction(): void
4549
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\DataFixtures;
6+
7+
use Doctrine\Bundle\FixturesBundle\Fixture;
8+
use Doctrine\Common\Persistence\ObjectManager;
9+
10+
final class AppFixtures extends Fixture
11+
{
12+
public function load(ObjectManager $manager): void
13+
{
14+
$manager->flush();
15+
}
16+
}

sources/app/src/DataFixtures/UserFixtures.php

+20-3
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,29 @@
1010

1111
final class UserFixtures extends Fixture
1212
{
13+
public const DEFAULT_USER_LOGIN = 'foo';
14+
15+
public const DEFAULT_USER_PASSWORD = 'bar';
16+
17+
public const USER_LOGIN_ROLE_BAR = 'bar';
18+
19+
public const USER_PASSWORD_ROLE_BAR = 'foo';
20+
1321
public function load(ObjectManager $manager): void
22+
{
23+
$this->createUser($manager, self::DEFAULT_USER_LOGIN, self::DEFAULT_USER_PASSWORD, ['ROLE_FOO']);
24+
$this->createUser($manager, self::USER_LOGIN_ROLE_BAR, self::USER_PASSWORD_ROLE_BAR, ['ROLE_BAR']);
25+
}
26+
27+
/**
28+
* @param string[] $roles
29+
*/
30+
private function createUser(ObjectManager $manager, string $login, string $password, array $roles): void
1431
{
1532
$userEntity = new User();
16-
$userEntity->setLogin('foo');
17-
$userEntity->setPlainPassword('bar');
18-
$userEntity->setRoles(['ROLE_FOO']);
33+
$userEntity->setLogin($login);
34+
$userEntity->setPlainPassword($password);
35+
$userEntity->setRoles($roles);
1936
$manager->persist($userEntity);
2037
$manager->flush();
2138
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Exception;
6+
7+
use Symfony\Component\HttpFoundation\JsonResponse;
8+
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
9+
use Symfony\Component\HttpKernel\Exception\HttpException;
10+
use function strpos;
11+
12+
final class HTTPExceptionListener
13+
{
14+
public function onKernelException(ExceptionEvent $event): void
15+
{
16+
$exception = $event->getException();
17+
if (! ($exception instanceof HttpException) || strpos($event->getRequest()->getRequestUri(), '/api/') === false) {
18+
return;
19+
}
20+
21+
$response = new JsonResponse(['error' => $exception->getMessage()]);
22+
$response->setStatusCode($exception->getStatusCode());
23+
$event->setResponse($response);
24+
}
25+
}

sources/app/src/Migrations/Version20190618152017.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function up(Schema $schema) : void
2222
// this up() migration is auto-generated, please modify it to your needs
2323
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
2424

25-
$this->addSql('CREATE TABLE users (id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', login VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:simple_array)\', created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D649AA08CB10 (login), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
25+
$this->addSql('CREATE TABLE users (id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', login VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:simple_array)\', created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_1483A5E9AA08CB10 (login), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
2626
}
2727

2828
public function down(Schema $schema) : void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Tests\Controller;
6+
7+
use App\DataFixtures\UserFixtures;
8+
use Safe\Exceptions\JsonException;
9+
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
10+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
11+
use Symfony\Component\HttpFoundation\Request;
12+
use Symfony\Component\HttpFoundation\Response;
13+
use function Safe\json_decode;
14+
use function Safe\json_encode;
15+
16+
abstract class AbstractControllerWebTestCase extends WebTestCase
17+
{
18+
/** @var KernelBrowser */
19+
protected $client;
20+
21+
protected function setUp(): void
22+
{
23+
self::bootKernel();
24+
$this->client = static::createClient();
25+
}
26+
27+
/**
28+
* @param mixed[] $data
29+
*
30+
* @throws JsonException
31+
*/
32+
protected function JSONRequest(string $method, string $uri, array $data = []): void
33+
{
34+
$this->client->request($method, $uri, [], [], ['CONTENT_TYPE' => 'application/json'], json_encode($data));
35+
}
36+
37+
/**
38+
* @return mixed
39+
*
40+
* @throws JsonException
41+
*/
42+
protected function assertJSONResponse(Response $response, int $expectedStatusCode)
43+
{
44+
$this->assertEquals($expectedStatusCode, $response->getStatusCode());
45+
$this->assertTrue($response->headers->contains('Content-Type', 'application/json'));
46+
$this->assertJson($response->getContent());
47+
48+
return json_decode($response->getContent(), true);
49+
}
50+
51+
/**
52+
* @throws JsonException
53+
*/
54+
protected function login(string $username = UserFixtures::DEFAULT_USER_LOGIN, string $password = UserFixtures::DEFAULT_USER_PASSWORD): void
55+
{
56+
$this->client->request(Request::METHOD_POST, '/api/security/login', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode(['username' => $username, 'password' => $password]));
57+
$this->assertEquals(Response::HTTP_OK, $this->client->getResponse()->getStatusCode());
58+
}
59+
}

0 commit comments

Comments
 (0)