Password recovery rebuild, correctly translated Mails and some minor fixes #658

This commit is contained in:
msquare 2019-10-13 13:43:08 +02:00
commit c0e97bfe75
49 changed files with 1061 additions and 310 deletions

View File

@ -13,6 +13,12 @@ $route->get('/login', 'AuthController@login');
$route->post('/login', 'AuthController@postLogin');
$route->get('/logout', 'AuthController@logout');
// Password recovery
$route->get('/password/reset', 'PasswordResetController@reset');
$route->post('/password/reset', 'PasswordResetController@postReset');
$route->get('/password/reset/{token:.+}', 'PasswordResetController@resetPassword');
$route->post('/password/reset/{token:.+}', 'PasswordResetController@postResetPassword');
// Stats
$route->get('/metrics', 'Metrics\\Controller@metrics');
$route->get('/stats', 'Metrics\\Controller@stats');

View File

@ -1,7 +1,6 @@
<?php
use Engelsystem\Database\DB;
use Engelsystem\Models\User\PasswordReset;
use Engelsystem\Models\User\State;
use Engelsystem\Models\User\User;
use Engelsystem\ShiftCalendarRenderer;
@ -311,120 +310,6 @@ function users_list_controller()
];
}
/**
* Second step of password recovery: set a new password using the token link from email
*
* @return string
*/
function user_password_recovery_set_new_controller()
{
$request = request();
$passwordReset = PasswordReset::whereToken($request->input('token'))->first();
if (!$passwordReset) {
error(__('Token is not correct.'));
redirect(page_link_to('login'));
}
if ($request->hasPostData('submit')) {
$valid = true;
if (
$request->has('password')
&& strlen($request->postData('password')) >= config('min_password_length')
) {
if ($request->postData('password') != $request->postData('password2')) {
$valid = false;
error(__('Your passwords don\'t match.'));
}
} else {
$valid = false;
error(__('Your password is to short (please use at least 6 characters).'));
}
if ($valid) {
auth()->setPassword($passwordReset->user, $request->postData('password'));
success(__('Password saved.'));
$passwordReset->delete();
redirect(page_link_to('login'));
}
}
return User_password_set_view();
}
/**
* First step of password recovery: display a form that asks for your email and send email with recovery link
*
* @return string
*/
function user_password_recovery_start_controller()
{
$request = request();
if ($request->hasPostData('submit')) {
$valid = true;
$user_source = null;
if ($request->has('email') && strlen(strip_request_item('email')) > 0) {
$email = strip_request_item('email');
if (check_email($email)) {
/** @var User $user_source */
$user_source = User::whereEmail($email)->first();
if (!$user_source) {
$valid = false;
error(__('E-mail address is not correct.'));
}
} else {
$valid = false;
error(__('E-mail address is not correct.'));
}
} else {
$valid = false;
error(__('Please enter your e-mail.'));
}
if ($valid) {
$token = User_generate_password_recovery_token($user_source);
engelsystem_email_to_user(
$user_source,
__('Password recovery'),
sprintf(
__('Please visit %s to recover your password.'),
page_link_to('user_password_recovery', ['token' => $token])
)
);
success(__('We sent an email containing your password recovery link.'));
redirect(page_link_to('login'));
}
}
return User_password_recovery_view();
}
/**
* User password recovery in 2 steps.
* (By email)
*
* @return string
*/
function user_password_recovery_controller()
{
if (request()->has('token')) {
return user_password_recovery_set_new_controller();
}
return user_password_recovery_start_controller();
}
/**
* Menu title for password recovery.
*
* @return string
*/
function user_password_recovery_title()
{
return __('Password recovery');
}
/**
* Loads a user from param user_id.
*

View File

@ -2,7 +2,6 @@
use Carbon\Carbon;
use Engelsystem\Database\DB;
use Engelsystem\Models\User\PasswordReset;
use Engelsystem\Models\User\User;
use Engelsystem\ValidationResult;
use Illuminate\Database\Query\JoinClause;
@ -227,24 +226,6 @@ function User_reset_api_key($user, $log = true)
}
}
/**
* Generates a new password recovery token for given user.
*
* @param User $user
* @return string
*/
function User_generate_password_recovery_token($user)
{
$reset = PasswordReset::findOrNew($user->id);
$reset->user_id = $user->id;
$reset->token = md5($user->name . time() . rand());
$reset->save();
engelsystem_log('Password recovery for ' . User_Nick_render($user, true) . ' started.');
return $reset->token;
}
/**
* @param User $user
* @return float

View File

@ -242,9 +242,9 @@ function guest_register()
redirect(page_link_to('register'));
}
// If a welcome message is present, display registration success page.
// If a welcome message is present, display it on the next page
if ($message = $config->get('welcome_msg')) {
return User_registration_success_view($message);
info((new Parsedown())->text($message));
}
redirect(page_link_to('/'));

View File

@ -107,46 +107,6 @@ function User_settings_view(
]);
}
/**
* Displays the welcome message to the user and shows a login form.
*
* @param string $event_welcome_message
* @return string
*/
function User_registration_success_view($event_welcome_message)
{
$parsedown = new Parsedown();
$event_welcome_message = $parsedown->text($event_welcome_message);
return page_with_title(__('Registration successful'), [
msg(),
div('row', [
div('col-md-4', [
$event_welcome_message
]),
div('col-md-4', [
'<h2>' . __('Login') . '</h2>',
form([
form_text('login', __('Nick'), ''),
form_password('password', __('Password')),
form_submit('submit', __('Login')),
buttons([
button(page_link_to('user_password_recovery'), __('I forgot my password'))
]),
info(__('Please note: You have to activate cookies!'), true)
], page_link_to('login'))
]),
div('col-md-4', [
'<h2>' . __('What can I do?') . '</h2>',
'<p>' . __('Please read about the jobs you can do to help us.') . '</p>',
buttons([
button(page_link_to('angeltypes', ['action' => 'about']), __('Teams/Job description') . ' &raquo;')
])
])
])
]);
}
/**
* Gui for deleting user with password field.
*
@ -257,11 +217,11 @@ function Users_view(
$user_table_headers = [
'name' => Users_table_header_link('name', __('Nick'), $order_by)
];
if(config('enable_user_name')) {
if (config('enable_user_name')) {
$user_table_headers['first_name'] = Users_table_header_link('first_name', __('Prename'), $order_by);
$user_table_headers['last_name'] = Users_table_header_link('last_name', __('Name'), $order_by);
}
if(config('enable_dect')) {
if (config('enable_dect')) {
$user_table_headers['dect'] = Users_table_header_link('dect', __('DECT'), $order_by);
}
$user_table_headers['arrived'] = Users_table_header_link('arrived', __('Arrived'), $order_by);
@ -271,8 +231,16 @@ function Users_view(
$user_table_headers['force_active'] = Users_table_header_link('force_active', __('Forced'), $order_by);
$user_table_headers['got_shirt'] = Users_table_header_link('got_shirt', __('T-Shirt'), $order_by);
$user_table_headers['shirt_size'] = Users_table_header_link('shirt_size', __('Size'), $order_by);
$user_table_headers['arrival_date'] = Users_table_header_link('planned_arrival_date', __('Planned arrival'), $order_by);
$user_table_headers['departure_date'] = Users_table_header_link('planned_departure_date', __('Planned departure'), $order_by);
$user_table_headers['arrival_date'] = Users_table_header_link(
'planned_arrival_date',
__('Planned arrival'),
$order_by
);
$user_table_headers['departure_date'] = Users_table_header_link(
'planned_departure_date',
__('Planned departure'),
$order_by
);
$user_table_headers['last_login_at'] = Users_table_header_link('last_login_at', __('Last login'), $order_by);
$user_table_headers['actions'] = '';
@ -791,41 +759,6 @@ function User_view_state_admin($freeloader, $user_source)
return $state;
}
/**
* View for password recovery step 1: E-Mail
*
* @return string
*/
function User_password_recovery_view()
{
return page_with_title(user_password_recovery_title(), [
msg(),
__('We will send you an e-mail with a password recovery link. Please use the email address you used for registration.'),
form([
form_text('email', __('E-Mail'), ''),
form_submit('submit', __('Recover'))
])
]);
}
/**
* View for password recovery step 2: New password
*
* @return string
*/
function User_password_set_view()
{
return page_with_title(user_password_recovery_title(), [
msg(),
__('Please enter a new password.'),
form([
form_password('password', __('Password')),
form_password('password2', __('Confirm password')),
form_submit('submit', __('Save'))
])
]);
}
/**
* @param array[] $user_angeltypes
* @return string

View File

@ -619,9 +619,9 @@ msgid "Please visit %s to recover your password."
msgstr "Bitte besuche %s, um Dein Passwort zurückzusetzen"
#: includes/controller/users_controller.php:394
msgid "We sent an email containing your password recovery link."
msgid "We sent you an email containing your password recovery link."
msgstr ""
"Wir haben eine eMail mit einem Link zum Passwort-zurücksetzen geschickt."
"Wir haben dir eine eMail mit einem Link zum Passwort-zurücksetzen geschickt."
#: includes/helper/email_helper.php:41
#, php-format
@ -2769,3 +2769,21 @@ msgstr "Bitte gib ein Passwort an."
msgid "validation.login.required"
msgstr "Bitte gib einen Loginnamen an."
msgid "form.submit"
msgstr "Absenden"
msgid "validation.email.required"
msgstr "Bitte gib eine E-Mail-Adresse an."
msgid "validation.email.email"
msgstr "Die E-Mail-Adresse ist nicht gültig."
msgid "validation.password.min"
msgstr "Dein angegebenes Passwort ist zu kurz."
msgid "validation.password.confirmed"
msgstr "Deine Passwörter stimmen nicht überein."
msgid "validation.password_confirmation.required"
msgstr "Du musst dein Passwort bestätigen."

View File

@ -30,3 +30,21 @@ msgstr "The password is required."
msgid "validation.login.required"
msgstr "The login name is required."
msgid "form.submit"
msgstr "Submit"
msgid "validation.email.required"
msgstr "The email address is required."
msgid "validation.email.email"
msgstr "This email address is not valid."
msgid "validation.password.min"
msgstr "Your password is too short."
msgid "validation.password.confirmed"
msgstr "Your passwords are not equal."
msgid "validation.password_confirmation.required"
msgstr "You have to confirm your password."

View File

@ -551,7 +551,7 @@ msgid "Please visit %s to recover your password."
msgstr "Por favor visite %s para recuperar sua senha"
#: includes/controller/users_controller.php:271
msgid "We sent an email containing your password recovery link."
msgid "We sent you an email containing your password recovery link."
msgstr "Nós enviamos um email com o link para recuperação da sua senha."
#: includes/helper/email_helper.php:12

View File

@ -1,6 +1,6 @@
{{ __('Hi %s,', [username]) }}
{% block title %}{{ __('Hi %s,', [username]) }}{% endblock %}
{{ __('here is a message for you from the %s:', [config('app_name')]) }}
{{ message|raw }}
{% block introduction %}{{ __('here is a message for you from the %s:', [config('app_name')]) }}{% endblock %}
{% block message %}{{ message|raw }}{% endblock %}
{{ __('This email is autogenerated and has not been signed. You got this email because you are registered in the %s.', [config('app_name')]) }}
{% block footer %}{{ __('This email is autogenerated and has not been signed. You got this email because you are registered in the %s.', [config('app_name')]) }}{% endblock %}

View File

@ -0,0 +1,3 @@
{% extends "emails/mail.twig" %}
{% block message %}{{ __('Please visit %s to recover your password.', [url('/password/reset/') ~ reset.token]) }}{% endblock %}

View File

@ -0,0 +1,18 @@
{% macro input(name, label, type, required) %}
<div class="form-group">
{% if label %}
<label for="{{ name }}">{{ label }}</label>
{% endif %}
<input type="{{ type|default('text') }}" class="form-control" id="{{ name }}" name="{{ name }}"
{%- if required|default(false) %} required="required"{% endif -%}
>
</div>
{% endmacro %}
{% macro hidden(name, value) %}
<input type="hidden" id="{{ name }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
{% macro submit(label) %}
<button type="submit" class="btn btn-default">{{ label|default(__('form.submit')) }}</button>
{% endmacro %}

View File

@ -32,6 +32,7 @@
<div class="col-sm-6 col-sm-offset-3 col-md-4 col-md-offset-4">
<div class="panel panel-primary first">
<div class="panel-body">
{{ msg() }}
{% for message in errors|default([]) %}
{{ m.alert(__(message), 'danger') }}
{% endfor %}
@ -61,7 +62,7 @@
</div>
<div class="text-center">
<a href="{{ url('user-password-recovery') }}" class="">
<a href="{{ url('/password/reset') }}" class="">
{{ __('I forgot my password') }}
</a>
</div>

View File

@ -0,0 +1,18 @@
{% extends "pages/password/reset.twig" %}
{% import 'macros/base.twig' as m %}
{% import 'macros/form.twig' as f %}
{% block row_content %}
<div class="col-md-8">
<form action="" enctype="multipart/form-data" method="post">
{{ csrf() }}
{{ f.input('password', __('Password'), 'password', true) }}
{{ f.input('password_confirmation', __('Confirm password'), 'password', true) }}
<div class="form-group">
{{ f.submit(__('Save')) }}
</div>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends "pages/password/reset.twig" %}
{% import 'macros/base.twig' as m %}
{% block row_content %}
<div class="col-md-12">
{% if type == 'email' %}
{{ m.alert(__('We sent you an email containing your password recovery link.'), 'info') }}
{% elseif type == 'reset' %}
{{ m.alert(__('Password saved.'), 'success') }}
{% endif %}
</div>
{% endblock %}

View File

@ -0,0 +1,32 @@
{% extends 'layouts/app.twig' %}
{% import 'macros/base.twig' as m %}
{% import 'macros/form.twig' as f %}
{% block title %}{{ __('Password recovery') }}{% endblock %}
{% block content %}
<div class="container">
<h1>{{ __('Password recovery') }}</h1>
{% for message in errors|default([]) %}
{{ m.alert(__(message), 'danger') }}
{% endfor %}
<div class="row">
{% block row_content %}
<div class="col-md-8">
<form action="" enctype="multipart/form-data" method="post">
{{ csrf() }}
{{ __('We will send you an e-mail with a password recovery link. Please use the email address you used for registration.') }}
{{ f.input('email', __('E-Mail'), 'email', true) }}
<div class="form-group">
{{ f.submit(__('Recover')) }}
</div>
</form>
</div>
{% endblock %}
</div>
</div>
{% endblock %}

View File

@ -88,7 +88,7 @@ class AuthController extends BaseController
$user = $this->auth->authenticate($data['login'], $data['password']);
if (!$user instanceof User) {
$this->session->set('errors', $this->session->get('errors', []) + ['auth.not-found']);
$this->session->set('errors', array_merge($this->session->get('errors', []), ['auth.not-found']));
return $this->showLogin();
}

View File

@ -0,0 +1,167 @@
<?php
namespace Engelsystem\Controllers;
use Engelsystem\Http\Exceptions\HttpNotFound;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Mail\EngelsystemMailer;
use Engelsystem\Models\User\PasswordReset;
use Engelsystem\Models\User\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class PasswordResetController extends BaseController
{
/** @var LoggerInterface */
protected $log;
/** @var EngelsystemMailer */
protected $mail;
/** @var Response */
protected $response;
/** @var SessionInterface */
protected $session;
/** @var array */
protected $permissions = [
'reset' => 'login',
'postReset' => 'login',
'resetPassword' => 'login',
'postResetPassword' => 'login',
];
/**
* @param Response $response
* @param SessionInterface $session
* @param EngelsystemMailer $mail
* @param LoggerInterface $log
*/
public function __construct(
Response $response,
SessionInterface $session,
EngelsystemMailer $mail,
LoggerInterface $log
) {
$this->log = $log;
$this->mail = $mail;
$this->response = $response;
$this->session = $session;
}
/**
* @return Response
*/
public function reset(): Response
{
return $this->showView('pages/password/reset');
}
/**
* @param Request $request
* @return Response
*/
public function postReset(Request $request): Response
{
$data = $this->validate($request, [
'email' => 'required|email',
]);
/** @var User $user */
$user = User::whereEmail($data['email'])->first();
if ($user) {
$reset = PasswordReset::findOrNew($user->id);
$reset->user_id = $user->id;
$reset->token = md5(random_bytes(64));
$reset->save();
$this->log->info(
sprintf('Password recovery for %s (%u)', $user->name, $user->id),
['user' => $user->toJson()]
);
$this->mail->sendViewTranslated(
$user,
'Password recovery',
'emails/password-reset',
['username' => $user->name, 'reset' => $reset]
);
}
return $this->showView('pages/password/reset-success', ['type' => 'email']);
}
/**
* @param Request $request
* @return Response
*/
public function resetPassword(Request $request): Response
{
$this->requireToken($request);
return $this->showView('pages/password/reset-form');
}
/**
* @param Request $request
* @return Response
*/
public function postResetPassword(Request $request): Response
{
$reset = $this->requireToken($request);
$data = $this->validate($request, [
'password' => 'required|min:' . config('min_password_length'),
'password_confirmation' => 'required',
]);
if ($data['password'] !== $data['password_confirmation']) {
$this->session->set('errors',
array_merge($this->session->get('errors', []), ['validation.password.confirmed']));
return $this->showView('pages/password/reset-form');
}
auth()->setPassword($reset->user, $data['password']);
$reset->delete();
return $this->showView('pages/password/reset-success', ['type' => 'reset']);
}
/**
* @param string $view
* @param array $data
* @return Response
*/
protected function showView($view = 'pages/password/reset', $data = []): Response
{
$errors = Collection::make(Arr::flatten($this->session->get('errors', [])));
$this->session->remove('errors');
return $this->response->withView(
$view,
array_merge_recursive(['errors' => $errors], $data)
);
}
/**
* @param Request $request
* @return PasswordReset
*/
protected function requireToken(Request $request): PasswordReset
{
$token = $request->getAttribute('token');
/** @var PasswordReset|null $reset */
$reset = PasswordReset::whereToken($token)->first();
if (!$reset) {
throw new HttpNotFound();
}
return $reset;
}
}

View File

@ -41,8 +41,10 @@ class TranslationServiceProvider extends ServiceProvider
'localeChangeCallback' => [$this, 'setLocale'],
]
);
$this->app->instance(Translator::class, $translator);
$this->app->instance('translator', $translator);
$this->app->singleton(Translator::class, function () use ($translator) {
return $translator;
});
$this->app->alias(Translator::class, 'translator');
}
/**

View File

@ -0,0 +1,23 @@
<?php
namespace Engelsystem\Http\Exceptions;
use Throwable;
class HttpNotFound extends HttpException
{
/**
* @param string $message
* @param array $headers
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(
string $message = '',
array $headers = [],
int $code = 0,
Throwable $previous = null
) {
parent::__construct(404, $message, $headers, $code, $previous);
}
}

View File

@ -3,6 +3,7 @@
namespace Engelsystem\Http;
use Engelsystem\Renderer\Renderer;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
@ -11,21 +12,21 @@ class Response extends SymfonyResponse implements ResponseInterface
use MessageTrait;
/** @var Renderer */
protected $view;
protected $renderer;
/**
* @param string $content
* @param int $status
* @param array $headers
* @param Renderer $view
* @param Renderer $renderer
*/
public function __construct(
$content = '',
int $status = 200,
array $headers = [],
Renderer $view = null
Renderer $renderer = null
) {
$this->view = $view;
$this->renderer = $renderer;
parent::__construct($content, $status, $headers);
}
@ -47,7 +48,7 @@ class Response extends SymfonyResponse implements ResponseInterface
* provided status code; if none is provided, implementations MAY
* use the defaults as suggested in the HTTP specification.
* @return static
* @throws \InvalidArgumentException For invalid status code arguments.
* @throws InvalidArgumentException For invalid status code arguments.
*/
public function withStatus($code, $reasonPhrase = '')
{
@ -107,12 +108,12 @@ class Response extends SymfonyResponse implements ResponseInterface
*/
public function withView($view, $data = [], $status = 200, $headers = [])
{
if (!$this->view instanceof Renderer) {
throw new \InvalidArgumentException('Renderer not defined');
if (!$this->renderer instanceof Renderer) {
throw new InvalidArgumentException('Renderer not defined');
}
$new = clone $this;
$new->setContent($this->view->render($view, $data));
$new->setContent($this->renderer->render($view, $data));
$new->setStatusCode($status, ($status == $this->getStatusCode() ? $this->statusText : null));
foreach ($headers as $key => $values) {
@ -144,4 +145,14 @@ class Response extends SymfonyResponse implements ResponseInterface
return $response;
}
/**
* Set the renderer to use
*
* @param Renderer $renderer
*/
public function setRenderer(Renderer $renderer)
{
$this->renderer = $renderer;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Engelsystem\Http\Validation\Rules;
use Respect\Validation\Rules\Between as RespectBetween;
class Between extends RespectBetween
{
use StringInputLength;
}

View File

@ -0,0 +1,10 @@
<?php
namespace Engelsystem\Http\Validation\Rules;
use Respect\Validation\Rules\Max as RespectMax;
class Max extends RespectMax
{
use StringInputLength;
}

View File

@ -0,0 +1,10 @@
<?php
namespace Engelsystem\Http\Validation\Rules;
use Respect\Validation\Rules\Min as RespectMin;
class Min extends RespectMin
{
use StringInputLength;
}

View File

@ -0,0 +1,44 @@
<?php
namespace Engelsystem\Http\Validation\Rules;
use DateTime;
use Illuminate\Support\Str;
use Throwable;
trait StringInputLength
{
/**
* Use the input length of a string
*
* @param mixed $input
* @return bool
*/
public function validate($input): bool
{
if (
is_string($input)
&& !is_numeric($input)
&& !$this->isDateTime($input)
) {
$input = Str::length($input);
}
return parent::validate($input);
}
/**
* @param mixed $input
* @return bool
*/
protected function isDateTime($input): bool
{
try {
new DateTime($input);
} catch (Throwable $e) {
return false;
}
return true;
}
}

View File

@ -2,6 +2,8 @@
namespace Engelsystem\Mail;
use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Models\User\User;
use Engelsystem\Renderer\Renderer;
use Swift_Mailer as SwiftMailer;
@ -10,30 +12,75 @@ class EngelsystemMailer extends Mailer
/** @var Renderer|null */
protected $view;
/** @var Translator|null */
protected $translation;
/** @var string */
protected $subjectPrefix = null;
/**
* @param SwiftMailer $mailer
* @param Renderer $view
* @param Translator $translation
*/
public function __construct(SwiftMailer $mailer, Renderer $view = null)
public function __construct(SwiftMailer $mailer, Renderer $view = null, Translator $translation = null)
{
parent::__construct($mailer);
$this->translation = $translation;
$this->view = $view;
}
/**
* @param string|string[]|User $to
* @param string $subject
* @param string $template
* @param array $data
* @param string|null $locale
* @return int
*/
public function sendViewTranslated(
$to,
string $subject,
string $template,
array $data = [],
?string $locale = null
): int {
if ($to instanceof User) {
$locale = $locale ?: $to->settings->language;
$to = $to->contact->email ? $to->contact->email : $to->email;
}
$activeLocale = null;
if (
$locale
&& $this->translation
&& isset($this->translation->getLocales()[$locale])
) {
$activeLocale = $this->translation->getLocale();
$this->translation->setLocale($locale);
}
$subject = $this->translation ? $this->translation->translate($subject) : $subject;
$sentMails = $this->sendView($to, $subject, $template, $data);
if ($activeLocale) {
$this->translation->setLocale($activeLocale);
}
return $sentMails;
}
/**
* Send a template
*
* @param string $to
* @param string|string[] $to
* @param string $subject
* @param string $template
* @param array $data
* @return int
*/
public function sendView($to, $subject, $template, $data = []): int
public function sendView($to, string $subject, string $template, array $data = []): int
{
$body = $this->view->render($template, $data);

View File

@ -26,7 +26,6 @@ class LegacyMiddleware implements MiddlewareInterface
'shifts_json_export',
'users',
'user_driver_licenses',
'user_password_recovery',
'user_worklog',
];
@ -112,11 +111,6 @@ class LegacyMiddleware implements MiddlewareInterface
case 'shifts_json_export':
require_once realpath(__DIR__ . '/../../includes/controller/shifts_controller.php');
shifts_json_export_controller();
case 'user_password_recovery':
require_once realpath(__DIR__ . '/../../includes/controller/users_controller.php');
$title = user_password_recovery_title();
$content = user_password_recovery_controller();
return [$title, $content];
case 'public_dashboard':
return public_dashboard_controller();
case 'angeltypes':

View File

@ -32,6 +32,7 @@ class Legacy extends TwigExtension
new TwigFunction('menuUserHints', 'header_render_hints', $isSafeHtml),
new TwigFunction('menuUserSubmenu', 'make_user_submenu', $isSafeHtml),
new TwigFunction('page', [$this, 'getPage']),
new TwigFunction('msg', 'msg', $isSafeHtml),
];
}

View File

@ -12,9 +12,8 @@ use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase;
use Illuminate\Support\Collection;
use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
@ -66,6 +65,7 @@ class AuthControllerTest extends TestCase
$session = new Session(new MockArraySessionStorage());
/** @var Validator|MockObject $validator */
$validator = new Validator();
$session->set('errors', [['bar' => 'some.bar.error']]);
$user = new User([
'name' => 'foo',
@ -89,7 +89,7 @@ class AuthControllerTest extends TestCase
$response->expects($this->once())
->method('withView')
->with('pages/login', ['errors' => Collection::make(['auth.not-found'])])
->with('pages/login', ['errors' => collect(['some.bar.error', 'auth.not-found'])])
->willReturn($response);
$response->expects($this->once())
->method('redirectTo')

View File

@ -155,8 +155,8 @@ class StatsTest extends TestCase
$this->initDatabase();
$this->addUsers();
(new PasswordReset(['use_id' => 1, 'token' => 'loremIpsum123']))->save();
(new PasswordReset(['use_id' => 3, 'token' => '5omeR4nd0mTok3N']))->save();
(new PasswordReset(['user_id' => 1, 'token' => 'loremIpsum123']))->save();
(new PasswordReset(['user_id' => 3, 'token' => '5omeR4nd0mTok3N']))->save();
$stats = new Stats($this->database);
$this->assertEquals(2, $stats->passwordResets());

View File

@ -0,0 +1,266 @@
<?php
namespace Engelsystem\Test\Unit\Controllers;
use Engelsystem\Config\Config;
use Engelsystem\Controllers\PasswordResetController;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\HttpNotFound;
use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Mail\EngelsystemMailer;
use Engelsystem\Models\User\PasswordReset;
use Engelsystem\Models\User\User;
use Engelsystem\Renderer\Renderer;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\Test\TestLogger;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class PasswordResetControllerTest extends TestCase
{
use HasDatabase;
/** @var array */
protected $args = [];
/**
* @covers \Engelsystem\Controllers\PasswordResetController::reset
* @covers \Engelsystem\Controllers\PasswordResetController::__construct
*/
public function testReset(): void
{
$controller = $this->getController('pages/password/reset');
$response = $controller->reset();
$this->assertEquals(200, $response->getStatusCode());
}
/**
* @covers \Engelsystem\Controllers\PasswordResetController::postReset
*/
public function testPostReset(): void
{
$this->initDatabase();
$request = new Request([], ['email' => 'foo@bar.batz']);
$user = $this->createUser();
$controller = $this->getController(
'pages/password/reset-success',
['type' => 'email', 'errors' => collect()]
);
/** @var TestLogger $log */
$log = $this->args['log'];
/** @var EngelsystemMailer|MockObject $mailer */
$mailer = $this->args['mailer'];
$this->setExpects($mailer, 'sendViewTranslated');
$controller->postReset($request);
$this->assertNotEmpty(PasswordReset::find($user->id)->first());
$this->assertTrue($log->hasInfoThatContains($user->name));
}
/**
* @covers \Engelsystem\Controllers\PasswordResetController::postReset
*/
public function testPostResetInvalidRequest(): void
{
$request = new Request();
$controller = $this->getController();
$this->expectException(ValidationException::class);
$controller->postReset($request);
}
/**
* @covers \Engelsystem\Controllers\PasswordResetController::postReset
*/
public function testPostResetNoUser(): void
{
$this->initDatabase();
$request = new Request([], ['email' => 'foo@bar.batz']);
$controller = $this->getController(
'pages/password/reset-success',
['type' => 'email', 'errors' => collect()]
);
$controller->postReset($request);
}
/**
* @covers \Engelsystem\Controllers\PasswordResetController::resetPassword
* @covers \Engelsystem\Controllers\PasswordResetController::requireToken
*/
public function testResetPassword(): void
{
$this->initDatabase();
$user = $this->createUser();
$token = $this->createToken($user);
$request = new Request([], [], ['token' => $token->token]);
$controller = $this->getController('pages/password/reset-form');
$controller->resetPassword($request);
}
/**
* @covers \Engelsystem\Controllers\PasswordResetController::resetPassword
* @covers \Engelsystem\Controllers\PasswordResetController::requireToken
*/
public function testResetPasswordNoToken(): void
{
$this->initDatabase();
$controller = $this->getController();
$this->expectException(HttpNotFound::class);
$controller->resetPassword(new Request());
}
/**
* @covers \Engelsystem\Controllers\PasswordResetController::postResetPassword
*/
public function testPostResetPassword(): void
{
$this->initDatabase();
$this->app->instance('config', new Config(['min_password_length' => 3]));
$user = $this->createUser();
$token = $this->createToken($user);
$password = 'SomeRandomPasswordForAmazingSecurity';
$request = new Request(
[],
['password' => $password, 'password_confirmation' => $password],
['token' => $token->token]
);
$controller = $this->getController(
'pages/password/reset-success',
['type' => 'reset', 'errors' => collect()]
);
$auth = new Authenticator($request, $this->args['session'], $user);
$this->app->instance('authenticator', $auth);
$response = $controller->postResetPassword($request);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEmpty(PasswordReset::find($user->id));
$this->assertNotNull(auth()->authenticate($user->name, $password));
}
/**
* @covers \Engelsystem\Controllers\PasswordResetController::postResetPassword
* @covers \Engelsystem\Controllers\PasswordResetController::showView
*/
public function testPostResetPasswordNotMatching(): void
{
$this->initDatabase();
$this->app->instance('config', new Config(['min_password_length' => 3]));
$user = $this->createUser();
$token = $this->createToken($user);
$password = 'SomeRandomPasswordForAmazingSecurity';
$request = new Request(
[],
['password' => $password, 'password_confirmation' => $password . 'OrNot'],
['token' => $token->token]
);
$controller = $this->getController(
'pages/password/reset-form',
['errors' => collect(['some.other.error', 'validation.password.confirmed'])]
);
/** @var Session $session */
$session = $this->args['session'];
$session->set('errors', ['foo' => ['bar' => 'some.other.error']]);
$controller->postResetPassword($request);
$this->assertEmpty($session->get('errors'));
}
/**
* @return array
*/
protected function getControllerArgs(): array
{
$response = new Response();
$session = new Session(new MockArraySessionStorage());
/** @var EngelsystemMailer|MockObject $mailer */
$mailer = $this->createMock(EngelsystemMailer::class);
$log = new TestLogger();
$renderer = $this->createMock(Renderer::class);
$response->setRenderer($renderer);
return $this->args = [
'response' => $response,
'session' => $session,
'mailer' => $mailer,
'log' => $log,
'renderer' => $renderer
];
}
/**
* @param string $view
* @param array $data
* @return PasswordResetController
*/
protected function getController(?string $view = null, ?array $data = null): PasswordResetController
{
/** @var Response $response */
/** @var Session $session */
/** @var EngelsystemMailer|MockObject $mailer */
/** @var TestLogger $log */
/** @var Renderer|MockObject $renderer */
list($response, $session, $mailer, $log, $renderer) = array_values($this->getControllerArgs());
$controller = new PasswordResetController($response, $session, $mailer, $log);
$controller->setValidator(new Validator());
if ($view) {
$args = [$view];
if ($data) {
$args[] = $data;
}
$this->setExpects($renderer, 'render', $args, 'Foo');
}
return $controller;
}
/**
* @return User
*/
protected function createUser(): User
{
$user = new User([
'name' => 'foo',
'password' => '',
'email' => 'foo@bar.batz',
'api_key' => '',
]);
$user->save();
return $user;
}
/**
* @param User $user
* @return PasswordReset
*/
protected function createToken(User $user): PasswordReset
{
$reset = new PasswordReset(['user_id' => $user->id, 'token' => 'SomeTestToken123']);
$reset->save();
return $reset;
}
}

View File

@ -2,7 +2,6 @@
namespace Engelsystem\Test\Unit;
use Engelsystem\Application;
use Engelsystem\Database\Database;
use Engelsystem\Database\Migration\Migrate;
use Engelsystem\Database\Migration\MigrationServiceProvider;
@ -27,12 +26,11 @@ trait HasDatabase
$connection->getPdo()->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->database = new Database($connection);
$app = new Application();
$app->instance(Database::class, $this->database);
$app->register(MigrationServiceProvider::class);
$this->app->instance(Database::class, $this->database);
$this->app->register(MigrationServiceProvider::class);
/** @var Migrate $migration */
$migration = $app->get('db.migration');
$migration = $this->app->get('db.migration');
$migration->initMigration();
$this->database

View File

@ -21,7 +21,7 @@ class TranslationServiceProviderTest extends ServiceProviderTest
$locales = ['fo_OO' => 'Foo', 'fo_OO.BAR' => 'Foo (Bar)', 'te_ST.WTF-9' => 'WTF\'s Testing?'];
$config = new Config(['locales' => $locales, 'default_locale' => $defaultLocale]);
$app = $this->getApp(['make', 'instance', 'get']);
$app = $this->getApp(['make', 'singleton', 'alias', 'get']);
/** @var Session|MockObject $session */
$session = $this->createMock(Session::class);
/** @var Translator|MockObject $translator */
@ -60,12 +60,16 @@ class TranslationServiceProviderTest extends ServiceProviderTest
)
->willReturn($translator);
$app->expects($this->exactly(2))
->method('instance')
->withConsecutive(
[Translator::class, $translator],
['translator', $translator]
);
$app->expects($this->once())
->method('singleton')
->willReturnCallback(function (string $abstract, callable $callback) use ($translator) {
$this->assertEquals(Translator::class, $abstract);
$this->assertEquals($translator, $callback());
});
$app->expects($this->once())
->method('alias')
->with(Translator::class, 'translator');
$serviceProvider->register();
}

View File

@ -0,0 +1,22 @@
<?php
namespace Engelsystem\Test\Unit\Http\Exceptions;
use Engelsystem\Http\Exceptions\HttpNotFound;
use PHPUnit\Framework\TestCase;
class HttpNotFoundTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Exceptions\HttpNotFound::__construct
*/
public function testConstruct()
{
$exception = new HttpNotFound();
$this->assertEquals(404, $exception->getStatusCode());
$this->assertEquals('', $exception->getMessage());
$exception = new HttpNotFound('Nothing to see here!');
$this->assertEquals('Nothing to see here!', $exception->getMessage());
}
}

View File

@ -55,6 +55,7 @@ class ResponseTest extends TestCase
/**
* @covers \Engelsystem\Http\Response::withView
* @covers \Engelsystem\Http\Response::setRenderer
*/
public function testWithView()
{
@ -73,6 +74,17 @@ class ResponseTest extends TestCase
$this->assertEquals('Foo ipsum!', $newResponse->getContent());
$this->assertEquals(505, $newResponse->getStatusCode());
$this->assertArraySubset(['test' => ['er']], $newResponse->getHeaders());
/** @var REnderer|MockObject $renderer */
$anotherRenderer = $this->createMock(Renderer::class);
$anotherRenderer->expects($this->once())
->method('render')
->with('bar')
->willReturn('Stuff');
$response->setRenderer($anotherRenderer);
$response = $response->withView('bar');
$this->assertEquals('Stuff', $response->getContent());
}
/**

View File

@ -4,7 +4,7 @@ namespace Engelsystem\Test\Unit\Http\SessionHandlers;
use Engelsystem\Http\SessionHandlers\DatabaseHandler;
use Engelsystem\Test\Unit\HasDatabase;
use PHPUnit\Framework\TestCase;
use Engelsystem\Test\Unit\TestCase;
class DatabaseHandlerTest extends TestCase
{
@ -90,6 +90,7 @@ class DatabaseHandlerTest extends TestCase
*/
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Engelsystem\Test\Unit\Http\Validation\Rules;
use Engelsystem\Http\Validation\Rules\Between;
use Engelsystem\Test\Unit\TestCase;
class BetweenTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Validation\Rules\Between
*/
public function testValidate()
{
$rule = new Between(3, 10);
$this->assertFalse($rule->validate(1));
$this->assertFalse($rule->validate('11'));
$this->assertTrue($rule->validate(5));
$this->assertFalse($rule->validate('AS'));
$this->assertFalse($rule->validate('TestContentThatCounts'));
$this->assertTrue($rule->validate('TESTING'));
$rule = new Between('2042-01-01', '2042-10-10');
$this->assertFalse($rule->validate('2000-01-01'));
$this->assertFalse($rule->validate('3000-01-01'));
$this->assertTrue($rule->validate('2042-05-11'));
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Engelsystem\Test\Unit\Http\Validation\Rules;
use Engelsystem\Http\Validation\Rules\Max;
use Engelsystem\Test\Unit\TestCase;
class MaxTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Validation\Rules\Max
*/
public function testValidate()
{
$rule = new Max(3);
$this->assertFalse($rule->validate(10));
$this->assertFalse($rule->validate('22'));
$this->assertTrue($rule->validate(3));
$this->assertFalse($rule->validate('TEST'));
$this->assertTrue($rule->validate('AS'));
$rule = new Max('2042-01-01');
$this->assertFalse($rule->validate('2100-01-01'));
$this->assertTrue($rule->validate('2000-01-01'));
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Engelsystem\Test\Unit\Http\Validation\Rules;
use Engelsystem\Http\Validation\Rules\Min;
use Engelsystem\Test\Unit\TestCase;
class MinTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Validation\Rules\Min
*/
public function testValidate()
{
$rule = new Min(3);
$this->assertFalse($rule->validate(1));
$this->assertFalse($rule->validate('2'));
$this->assertTrue($rule->validate(3));
$this->assertFalse($rule->validate('AS'));
$this->assertTrue($rule->validate('TEST'));
$rule = new Min('2042-01-01');
$this->assertFalse($rule->validate('2000-01-01'));
$this->assertTrue($rule->validate('2345-01-01'));
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Engelsystem\Test\Unit\Http\Validation\Rules;
use Engelsystem\Test\Unit\Http\Validation\Rules\Stub\UsesStringInputLength;
use Engelsystem\Test\Unit\TestCase;
class StringInputLengthTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Validation\Rules\StringInputLength::validate
* @covers \Engelsystem\Http\Validation\Rules\StringInputLength::isDateTime
* @dataProvider validateProvider
* @param mixed $input
* @param mixed $expectedInput
*/
public function testValidate($input, $expectedInput)
{
$rule = new UsesStringInputLength();
$rule->validate($input);
$this->assertEquals($expectedInput, $rule->lastInput);
}
/**
* @return array[]
*/
public function validateProvider()
{
return [
['TEST', 4],
['?', 1],
['2042-01-01 00:00', '2042-01-01 00:00'],
['3', '3'],
];
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Engelsystem\Test\Unit\Http\Validation\Rules\Stub;
class ParentClassImplementation
{
/** @var bool */
public $validateResult = true;
/** @var mixed */
public $lastInput;
/**
* @param mixed $input
* @return bool
*/
public function validate($input): bool
{
$this->lastInput = $input;
return $this->validateResult;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Engelsystem\Test\Unit\Http\Validation\Rules\Stub;
use Engelsystem\Http\Validation\Rules\StringInputLength;
class UsesStringInputLength extends ParentClassImplementation
{
use StringInputLength;
}

View File

@ -50,9 +50,10 @@ class ValidatorTest extends TestCase
));
$this->assertFalse($val->validate(
['lorem' => 2],
['lorem' => 'required|min:3|max:10']
['lorem' => 'OMG'],
['lorem' => 'required|min:4|max:10']
));
$this->assertEquals(['lorem' => ['validation.lorem.min']], $val->getErrors());
$this->assertFalse($val->validate(
['lorem' => 42],
['lorem' => 'required|min:3|max:10']

View File

@ -2,15 +2,22 @@
namespace Engelsystem\Test\Unit\Mail;
use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Mail\EngelsystemMailer;
use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\User;
use Engelsystem\Renderer\Renderer;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Swift_Mailer as SwiftMailer;
use Swift_Message as SwiftMessage;
class EngelsystemMailerTest extends TestCase
{
use HasDatabase;
/**
* @covers \Engelsystem\Mail\EngelsystemMailer::__construct
* @covers \Engelsystem\Mail\EngelsystemMailer::sendView
@ -24,21 +31,69 @@ class EngelsystemMailerTest extends TestCase
/** @var EngelsystemMailer|MockObject $mailer */
$mailer = $this->getMockBuilder(EngelsystemMailer::class)
->setConstructorArgs(['mailer' => $swiftMailer, 'view' => $view])
->setMethods(['send'])
->onlyMethods(['send'])
->getMock();
$mailer->expects($this->once())
->method('send')
->with('foo@bar.baz', 'Lorem dolor', 'Rendered Stuff!')
->willReturn(1);
$view->expects($this->once())
->method('render')
->with('test/template.tpl', ['dev' => true])
->willReturn('Rendered Stuff!');
$this->setExpects($mailer, 'send', ['foo@bar.baz', 'Lorem dolor', 'Rendered Stuff!'], 1);
$this->setExpects($view, 'render', ['test/template.tpl', ['dev' => true]], 'Rendered Stuff!');
$return = $mailer->sendView('foo@bar.baz', 'Lorem dolor', 'test/template.tpl', ['dev' => true]);
$this->equalTo(1, $return);
}
/**
* @covers \Engelsystem\Mail\EngelsystemMailer::sendViewTranslated
*/
public function testSendViewTranslated()
{
$this->initDatabase();
$settings = new Settings([
'language' => 'de_DE',
'theme' => '',
]);
$contact = new Contact(['email' => null]);
$user = new User([
'id' => 42,
'name' => 'username',
'email' => 'foo@bar.baz',
'password' => '',
'api_key' => '',
]);
$user->save();
$settings->user()->associate($user)->save();
$contact->user()->associate($user)->save();
/** @var Renderer|MockObject $view */
$view = $this->createMock(Renderer::class);
/** @var SwiftMailer|MockObject $swiftMailer */
$swiftMailer = $this->createMock(SwiftMailer::class);
/** @var Translator|MockObject $translator */
$translator = $this->createMock(Translator::class);
/** @var EngelsystemMailer|MockObject $mailer */
$mailer = $this->getMockBuilder(EngelsystemMailer::class)
->setConstructorArgs(['mailer' => $swiftMailer, 'view' => $view, 'translation' => $translator])
->onlyMethods(['sendView'])
->getMock();
$this->setExpects($mailer, 'sendView', ['foo@bar.baz', 'Lorem dolor', 'test/template.tpl', ['dev' => true]], 1);
$this->setExpects($translator, 'getLocales', null, ['de_DE' => 'de_DE', 'en_US' => 'en_US']);
$this->setExpects($translator, 'getLocale', null, 'en_US');
$this->setExpects($translator, 'translate', ['translatable.text'], 'Lorem dolor');
$translator->expects($this->exactly(2))
->method('setLocale')
->withConsecutive(['de_DE'], ['en_US']);
$return = $mailer->sendViewTranslated(
$user,
'translatable.text',
'test/template.tpl',
['dev' => true],
'de_DE'
);
$this->equalTo(1, $return);
}
/**
* @covers \Engelsystem\Mail\EngelsystemMailer::getSubjectPrefix
* @covers \Engelsystem\Mail\EngelsystemMailer::send
@ -50,32 +105,12 @@ class EngelsystemMailerTest extends TestCase
$message = $this->createMock(SwiftMessage::class);
/** @var SwiftMailer|MockObject $swiftMailer */
$swiftMailer = $this->createMock(SwiftMailer::class);
$swiftMailer->expects($this->once())
->method('createMessage')
->willReturn($message);
$swiftMailer->expects($this->once())
->method('send')
->willReturn(1);
$message->expects($this->once())
->method('setTo')
->with(['to@xam.pel'])
->willReturn($message);
$message->expects($this->once())
->method('setFrom')
->with('foo@bar.baz', 'Lorem Ipsum')
->willReturn($message);
$message->expects($this->once())
->method('setSubject')
->with('[Mail test] Foo Bar')
->willReturn($message);
$message->expects($this->once())
->method('setBody')
->with('Lorem Ipsum!')
->willReturn($message);
$this->setExpects($swiftMailer, 'createMessage', null, $message);
$this->setExpects($swiftMailer, 'send', null, 1);
$this->setExpects($message, 'setTo', [['to@xam.pel']], $message);
$this->setExpects($message, 'setFrom', ['foo@bar.baz', 'Lorem Ipsum'], $message);
$this->setExpects($message, 'setSubject', ['[Mail test] Foo Bar'], $message);
$this->setExpects($message, 'setBody', ['Lorem Ipsum!'], $message);
$mailer = new EngelsystemMailer($swiftMailer);
$mailer->setFromAddress('foo@bar.baz');

View File

@ -5,7 +5,7 @@ namespace Engelsystem\Test\Unit\Models;
use Carbon\Carbon;
use Engelsystem\Models\EventConfig;
use Engelsystem\Test\Unit\HasDatabase;
use PHPUnit\Framework\TestCase;
use Engelsystem\Test\Unit\TestCase;
class EventConfigTest extends TestCase
{
@ -102,7 +102,8 @@ class EventConfigTest extends TestCase
*/
protected function getEventConfig()
{
return new class extends EventConfig {
return new class extends EventConfig
{
/**
* @param string $value
* @param string $type
@ -122,6 +123,7 @@ class EventConfigTest extends TestCase
*/
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
}
}

View File

@ -4,7 +4,7 @@ namespace Engelsystem\Test\Unit\Models;
use Engelsystem\Models\LogEntry;
use Engelsystem\Test\Unit\HasDatabase;
use PHPUnit\Framework\TestCase;
use Engelsystem\Test\Unit\TestCase;
use Psr\Log\LogLevel;
class LogEntryTest extends TestCase
@ -38,6 +38,7 @@ class LogEntryTest extends TestCase
*/
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
}
}

View File

@ -5,8 +5,8 @@ namespace Engelsystem\Test\Unit\Models;
use Engelsystem\Models\User\HasUserModel;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\Models\User\Stub\HasUserModelImplementation;
use Engelsystem\Test\Unit\TestCase;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use PHPUnit\Framework\TestCase;
class HasUserModelTest extends TestCase
{
@ -28,6 +28,7 @@ class HasUserModelTest extends TestCase
*/
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
}
}

View File

@ -10,7 +10,7 @@ use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\State;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase;
use PHPUnit\Framework\TestCase;
use Engelsystem\Test\Unit\TestCase;
class UserTest extends TestCase
{
@ -95,6 +95,7 @@ class UserTest extends TestCase
*/
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
}
}

View File

@ -26,6 +26,7 @@ class LegacyTest extends ExtensionTest
$this->assertExtensionExists('menuUserHints', 'header_render_hints', $functions, $isSafeHtml);
$this->assertExtensionExists('menuUserSubmenu', 'make_user_submenu', $functions, $isSafeHtml);
$this->assertExtensionExists('page', [$extension, 'getPage'], $functions);
$this->assertExtensionExists('msg', 'msg', $functions, $isSafeHtml);
}
/**

View File

@ -2,18 +2,22 @@
namespace Engelsystem\Test\Unit;
use PHPUnit\Framework\MockObject\Matcher\InvokedRecorder;
use Engelsystem\Application;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
abstract class TestCase extends PHPUnitTestCase
{
/** @var Application */
protected $app;
/**
* @param MockObject $object
* @param string $method
* @param array $arguments
* @param mixed $return
* @param InvokedRecorder $times
* @param InvocationOrder $times
*/
protected function setExpects($object, $method, $arguments = null, $return = null, $times = null)
{
@ -34,4 +38,12 @@ abstract class TestCase extends PHPUnitTestCase
$invocation->willReturn($return);
}
}
/**
* Called before each test run
*/
protected function setUp(): void
{
$this->app = new Application(__DIR__ . '/../../');
}
}