Skip to content

Commit 080ae7d

Browse files
author
G
committed
Unit Tests
1 parent e785e90 commit 080ae7d

30 files changed

+1727
-465
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ yarn-error.log
1414
/public/info.php
1515
/public/googlef006135870f7b2b7.html
1616
tests/CodeCoverage
17+
.vscode/settings.json

app/Entity/IpAddress.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace App\Entity;
4+
5+
use InvalidArgumentException;
6+
7+
class IpAddress
8+
{
9+
private $ip;
10+
11+
public function __construct(string $ipAddress)
12+
{
13+
if(filter_var($ipAddress, \FILTER_VALIDATE_IP) === false){
14+
throw new InvalidArgumentException('Invalid IP address used: '.$ipAddress);
15+
}
16+
17+
$this->ip = $ipAddress;
18+
}
19+
20+
public function get(): string
21+
{
22+
return $this->ip;
23+
}
24+
}

app/Http/Controllers/ProgrammerExperienceController.php

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,80 @@
11
<?php
2+
23
namespace App\Http\Controllers;
34

45
use App\Http\Controllers\Controller;
56
use App\Model\ProgrammingExperienceFormOptions as FormOptions;
67
use App\Repository\PersonExperience;
78
use App\Http\Requests\ProgrammingExperienceSave;
9+
use GBrabyn\LaravelFormHelpers\AttributeTemplate;
810

911
/**
1012
*
1113
* @author G Brabyn
1214
*/
13-
class ProgrammerExperienceController extends Controller
15+
class ProgrammerExperienceController extends Controller
1416
{
15-
17+
1618
public function index()
1719
{
1820
return view('programmer/list', [
19-
'people' => PersonExperience::where(['sessionId'=>session()->getId()])->orderBy('fullName', 'asc')->get(),
21+
'people' => PersonExperience::where(['sessionToken' => session('sessionToken')])
22+
->orderBy('fullName', 'asc')->get(),
2023
]);
2124
}
22-
23-
public function add(FormOptions $formOptions)
25+
26+
public function add(FormOptions $formOptions, AttributeTemplate $attrTemp)
2427
{
2528
return view('programmer/edit', [
26-
'countries' => $formOptions->getCountries('en_US'),
29+
'countries' => $formOptions->getCountries(request('locale', 'en_US')),
2730
'languages' => $formOptions->getProgrammingLanguages(),
2831
'workTypes' => $formOptions->getWorkTypeOptions(),
32+
'attributeTemplate' => $attrTemp,
2933
]);
3034
}
31-
35+
3236
public function store(ProgrammingExperienceSave $request)
3337
{
3438
$validatedData = $this->savePrepared($request->validated());
35-
$save = array_merge($validatedData, ['sessionId'=>session()->getId()]);
39+
$save = array_merge($validatedData, ['sessionToken' => session('sessionToken')]);
3640
PersonExperience::create($save)->save();
3741

3842
return redirect()->route('programmer.list');
3943
}
4044

41-
private function savePrepared(array $data) : array
45+
private function savePrepared(array $data): array
4246
{
4347
unset($data['g-recaptcha-response']);
4448
$data['lastEdit'] = date('Y-m-d H:i:s');
4549
$data['additionalLanguages'] = array_values($data['additionalLanguages']);
4650
$data['experience'] = array_values($data['experience']);
47-
48-
foreach($data['experience'] AS &$experience){
51+
52+
foreach ($data['experience'] as &$experience) {
4953
$experience['additionalLanguagesUsed'] = array_values($experience['additionalLanguagesUsed']);
5054
}
51-
55+
5256
return $data;
5357
}
54-
58+
5559
public function edit(FormOptions $formOptions)
5660
{
5761
// Customize to your purposes
58-
$personExp = PersonExperience::where(['sessionId'=>session()->getId(), 'id'=>request('id')])->firstOrFail();
62+
$personExp = PersonExperience::where([
63+
'sessionToken' => session('sessionToken'),
64+
'id' => request('id')
65+
])->firstOrFail();
5966

6067
return view('programmer/edit', [
6168
'personExperience' => $personExp,
62-
'countries' => $formOptions->getCountries('en_US'),
69+
'countries' => $formOptions->getCountries(request('locale', 'en_US')),
6370
'languages' => $formOptions->getProgrammingLanguages(),
6471
'workTypes' => $formOptions->getWorkTypeOptions(),
65-
]);
72+
]);
6673
}
67-
74+
6875
public function update(ProgrammingExperienceSave $request)
6976
{
70-
$personExp = PersonExperience::where(['sessionId'=>session()->getId(), 'id'=>request('id')])->firstOrFail(); // Customize to your needs
77+
$personExp = PersonExperience::where(['sessionToken' => session('sessionToken'), 'id' => request('id')])->firstOrFail(); // Customize to your needs
7178
$validatedData = $this->savePrepared($request->validated());
7279
$personExp->update($validatedData);
7380

app/Http/Kernel.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Kernel extends HttpKernel
3838
\App\Http\Middleware\VerifyCsrfToken::class,
3939
\Illuminate\Routing\Middleware\SubstituteBindings::class,
4040
\App\Http\Middleware\ContentSecurityPolicy::class,
41+
\App\Http\Middleware\SessionToken::class,
4142
],
4243

4344
'api' => [

app/Http/Middleware/SessionToken.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
8+
/** This middleware is purely for the sack of feature testing. would use session ID
9+
* directly otherwise.
10+
*/
11+
class SessionToken
12+
{
13+
/**
14+
* Handle an incoming request.
15+
*
16+
* @param \Illuminate\Http\Request $request
17+
* @param \Closure $next
18+
* @return mixed
19+
*/
20+
public function handle(Request $request, Closure $next)
21+
{
22+
if ($request->session()->has('sessionToken') === false) {
23+
$request->session()->put('sessionToken', $request->session()->getId());
24+
}
25+
26+
return $next($request);
27+
}
28+
}
Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
namespace App\Http\Requests;
34

45
use Illuminate\Foundation\Http\FormRequest;
@@ -23,38 +24,40 @@ public function authorize()
2324
protected function prepareForValidation()
2425
{
2526
/**
26-
* The array fields could be not set if no checkbox is checked or JS is used
27+
* The array fields could be not set if no checkbox is checked or JS is used
2728
* to take all the array fields away. Here we make sure all array fields have a
28-
* default value of [] "empty array" if they are not submitted or have a
29+
* default value of [] "empty array" if they are not submitted or have a
2930
* non-array value
3031
*/
31-
32+
3233
$additionalLanguages = is_array($this->post('additionalLanguages')) ? $this->post('additionalLanguages') : [];
3334
$experience = is_array($this->post('experience')) ? $this->post('experience') : [];
34-
35+
3536
$fixedExperience = [];
36-
foreach($experience as $k => $job){
37+
foreach ($experience as $k => $job) {
3738
$additionalLanguagesUsed = is_array($job['additionalLanguagesUsed'] ?? null)
38-
? $job['additionalLanguagesUsed'] : [];
39+
? $job['additionalLanguagesUsed']
40+
: [];
3941

4042
$fixedJob = [
41-
'languagesUsed' => is_array($job['languagesUsed'] ?? null)
42-
? $job['languagesUsed'] : [],
43+
'languagesUsed' => is_array($job['languagesUsed'] ?? null)
44+
? $job['languagesUsed']
45+
: [],
4346

4447
'additionalLanguagesUsed'
45-
=> array_filter($additionalLanguagesUsed, function($value){
46-
return !empty($value);
47-
}), // remove empty additionalLanguagesUsed[] fields
48+
=> array_filter($additionalLanguagesUsed, function ($value) {
49+
return !empty($value);
50+
}), // remove empty additionalLanguagesUsed[] fields
4851
];
4952
$fixedExperience[$k] = array_replace($job, $fixedJob);
50-
}
53+
}
5154

5255
$this->merge([
5356
'languages' => is_array($this->post('languages')) ? $this->post('languages') : [],
54-
'additionalLanguages'
55-
=> array_filter($additionalLanguages, function($value){ // remove empty additionalLanguages[] fields
56-
return !empty($value);
57-
}),
57+
'additionalLanguages'
58+
=> array_filter($additionalLanguages, function ($value) { // remove empty additionalLanguages[] fields
59+
return !empty($value);
60+
}),
5861
'experience' => $fixedExperience,
5962
]);
6063
}
@@ -64,22 +67,22 @@ protected function prepareForValidation()
6467
*
6568
* @return array
6669
*/
67-
public function rules()
70+
public function rules(ReCaptchaV3 $captcha)
6871
{
6972
$rules = [
7073
'fullName' => ['bail', 'required', 'max:255', $this->uniqueFullName()],
7174
'email' => ['bail', 'required', 'max:255', 'email'],
7275
'address' => ['nullable', 'string'],
7376
'countryId' => ['bail', 'required', Rule::in(array_keys(FormOptions::getCountries('en_US'))),],
74-
'languages'=> [
75-
'array',
76-
function($attribute, $value, $fail){ // Validate that either "Programming Languages" or "Additional Languages" has entries
77-
if(empty($value) && empty($this->post('additionalLanguages'))){
77+
'languages' => [
78+
'array',
79+
function ($attribute, $value, $fail) { // Validate that either "Programming Languages" or "Additional Languages" has entries
80+
if (empty($value) && empty($this->post('additionalLanguages'))) {
7881
$fail(trans('messages.languagesRequired'));
7982
}
8083
},
8184
],
82-
'languages.*'=> ['string', 'distinct', Rule::in(FormOptions::getProgrammingLanguages())],
85+
'languages.*' => ['string', 'distinct', Rule::in(FormOptions::getProgrammingLanguages())],
8386
'additionalLanguages' => ['array'],
8487
'additionalLanguages.*' => ['string', 'distinct'],
8588
'experience' => ['array'],
@@ -90,56 +93,57 @@ function($attribute, $value, $fail){ // Validate that either "Programming Lan
9093
'experience.*.finishDate' => ['required', 'date_format:Y-m-d', 'after_or_equal:experience.*.startDate'],
9194
'experience.*.type' => ['required', Rule::in(array_keys(FormOptions::getWorkTypeOptions()))],
9295
'g-recaptcha-response' => [
93-
function($attribute, $value, $fail){ // reCAPTCHA validation
94-
$validator = new ReCaptchaV3(env('RECAPTCHA_SECRET'));
95-
$validator->setValue($value);
96-
97-
if($validator->isValid() === false){
96+
'bail',
97+
'required',
98+
function ($attribute, $value, $fail) use ($captcha) { // reCAPTCHA validation
99+
$captcha->setValue($value);
100+
101+
if ($captcha->isValid() === false) {
98102
$fail(trans('messages.inputReCaptchaFail'));
99103
}
100104
},
101105
],
102106
];
103-
107+
104108
$experience = is_array($this->post('experience')) ? $this->post('experience') : [];
105-
foreach(array_keys($experience) as $k){
106-
$rules['experience.'.$k.'.languagesUsed'] = [
109+
foreach (array_keys($experience) as $k) {
110+
$rules['experience.' . $k . '.languagesUsed'] = [
107111
'array',
108-
function($attribute, $value, $fail) use($k) { // Validate that either "Languages Used" or "Additional Languages Used" has entries
109-
if(empty($value) && empty($this->input('experience.'.$k.'.additionalLanguagesUsed'))){
112+
function ($attribute, $value, $fail) use ($k) { // Validate that either "Languages Used" or "Additional Languages Used" has entries
113+
if (empty($value) && empty($this->input('experience.' . $k . '.additionalLanguagesUsed'))) {
110114
$fail(trans('messages.languagesUsedRequired'));
111115
}
112116
},
113117
];
114-
115-
$rules['experience.'.$k.'.languagesUsed.*'] = [
116-
'string',
117-
'distinct',
118+
119+
$rules['experience.' . $k . '.languagesUsed.*'] = [
120+
'string',
121+
'distinct',
118122
Rule::in(array_keys(FormOptions::getProgrammingLanguages())),
119123
];
120-
121-
$rules['experience.'.$k.'.additionalLanguagesUsed'] = ['array'];
122-
$rules['experience.'.$k.'.additionalLanguagesUsed.*'] = ['string', 'distinct'];
123-
}
124-
124+
125+
$rules['experience.' . $k . '.additionalLanguagesUsed'] = ['array'];
126+
$rules['experience.' . $k . '.additionalLanguagesUsed.*'] = ['string', 'distinct'];
127+
}
128+
125129
return $rules;
126130
}
127-
128-
private function uniqueFullName() : Rules\Unique
131+
132+
private function uniqueFullName(): Rules\Unique
129133
{
130134
$rule = Rule::unique(PersonExperience::class, 'fullName')
131-
->where('sessionId', session()->getId());
135+
->where('sessionToken', session('sessionToken'));
132136

133-
if(request()->id){
137+
if (request()->id) {
134138
$rule->ignore(request()->id, 'id');
135139
}
136-
140+
137141
return $rule;
138142
}
139-
143+
140144
/**
141145
* Customizing some of the error messages
142-
*
146+
*
143147
* @return array
144148
*/
145149
public function messages()
@@ -155,6 +159,7 @@ public function messages()
155159
'experience.*.finishDate.after_or_equal' => trans('messages.formAfterStartDate'),
156160
'experience.*.type.required' => trans('messages.inputRequired'),
157161
'experience.*.additionalLanguagesUsed.*.distinct' => trans('messages.inputDuplicated'),
162+
'g-recaptcha-response.required' => trans('messages.inputReCaptchaFail'),
158163
];
159164
}
160165
}

0 commit comments

Comments
 (0)