How can I assert notification for a group of users? - laravel

I went through notification testing and have to check the notification routing but I have problem when it comes to group assertions. For a single receiver, all tests passes with the default Notification::assertSentTo method. But things were different when it goes to group notification. I have tried to swap between 3 approaches;
This approach gave a warning stating that This test did not perform any assertions
$author->company->employees()->role('hrd')->each(function ($hrd) {
Notification::assertSentTo($hrd, LeaveRequestNotification::class);
});
Same warning, but with foreach
foreach($author->company->employees->role('hrd') as $hrd) {
Notification::assertSentTo($hrd, LeaveRequestNotification::class);
}
BadMethodCallException with test going red.
Notification::assertSentTo(
$author->company->employees()->role('hrd'),
LeaveRequestNotification::class,
);
Here is the complete error message occured by the 3rd approach
$ test --testsuite=Unit --filter=LeaveTest::testShouldSendRequestNotificationToHrdsWhenAuthorIsRegularEmployee --stop-on-failure
FAIL Tests\Unit\LeaveTest
⨯ should send request notification to hrds when author is regular employee
---
• Tests\Unit\LeaveTest > should send request notification to hrds when author is regular employee
BadMethodCallException
Call to undefined method Illuminate\Database\Eloquent\Relations\HasMany::getKey()
So, what is the proper way for doing this?

Just found the problem, it was lying here on the assignRole loop, I used the wrong subject
$leave->company->employees
instead of
$author->company->employees
The loop:
foreach ($leave->company->employees as $employee) {
if (!$employee->hasRole('employee')) {
$employee->assignRole('hrd');
}
}
dd($author->company->employees()->role('hrd')->count()); // prints 0
I changed the subject and use the 3rd approach, only this time, I add get() after the role definition. Works fine with no error. Thanks for #mrhn for reminding me about the existence of the loop subject.
foreach ($author->company->employees as $employee) {
if (!$employee->hasRole('employee')) {
$employee->assignRole('hrd');
}
}
LeaveRequest::dispatch($leave);
Notification::assertSentTo(
$author->company->employees()->role('hrd')->get(),
LeaveRequestNotification::class,
);

Related

Intercept observables before subscription callback

I am using the following code to make get request:
makeGetReq$(url:string):Observable{
let getReqObservable;
getReqObservable = this.httpClient.get(url) //code for making get request
return getReqObservable
}
The problem is sometimes my backend might return {error:true, message} with status code 200. (I know thats weird).In that case I want to intecept getReqObservable and not allow its subscription callback to run.
image.component.ts
makeGetReq$(url:string):Observable{
let getReqObservable;
getReqObservable = this.httpClient.get(url)//code for making get request
return getReqObservable
.do((value)=>{
if(value.error){
//do not allow it to propagate further
})
})
You should propagate it further, but as an error rather than an event (i.e. do just like if your backend did the right thing and returned an error response):
makeGetReq$(url: string): Observable<Something> {
return this.httpClient.get<Something>(url).pipe(
mergeMap(value => value.error ? throwError(value) : of(value))
);
}
Otherwise, the calling method has no way to know that an error occurred, and thus can't execute the callbacks it might have registered for errors.
The easiest would probably be filter.
Filter items emitted by the source Observable by only emitting those that satisfy a specified predicate.
It would look like this:
return getReqObservable
.filter(value => !value.error)
It was pointed out, that you lose the notification completely if you just filter out the error case. There is of course the option to create a RxJS error notification with throwError, but it is also possible to just subscribe to the same source observable a second time with a different filter condition.
Be careful however to not call the backend twice, e.g. by using share.

Database notifications don't show up after testing notify

I have a unit test with the following:
use \Illuminate\Notifications\DatabaseNotification;
public function testMailSentAndLogged()
{
Notification::fake();
$user = factory(User::class)->create();
$emailAddress = $user->emailAddress;
$emailAddress->notify(new UserCreated);
Notification::assertSentTo(
$emailAddress,
UserCreated::class
);
error_log('DatabaseNotification '.print_r(DatabaseNotification::get()->toArray(), 1));
$this->assertEquals(1, $emailAddress->notifications->count());
}
My Notification has this for the via():
public final function via($notifiable)
{
// complex logic...
error_log('mail, database');
return ['mail', 'database'];
}
The code fails on the $this->assertEquals code. the error_log produces the following:
[03-Jan-2018 01:23:01 UTC] mail, database
[03-Jan-2018 01:23:01 UTC] DatabaseNotification Array
(
)
WHY don't the $emailAddress->notifications pull up anything? Why doesn't DatabaseNotification::get() pull anything?;
In your test, you are calling the method
Notification::fake();
As stated in Laravel's documentation on Mocking,
You may use the Notification facade's fake method to prevent
notifications from being sent.
Actually, this bit of code is the assertion that the Notification would have been sent, under normal circumstances (ie in prod) :
Notification::assertSentTo();
If you remove the call to Notification::fake(), your notification should appear in your testing database.
So you kinda have two solutions. The first one is to remove the call to fake(), thus really sending the notification, which will appear in the database. the second is not to test if the notification was written successfully in the database : it's Laravel's responsibility, not your application's. I recommand the second solution :)

Respect\Validation\Validator - using an array while catching errors

I am attempting to catch errors utilizing the Respect\Validation\Validator opensource PHP class. I used their example to create an array of checks. Although that seems to work ok, I then attempted to catch any error messages so that I could display it to the user. I saw no method to do so as a full array (check everything, store all messages in an array). So instead, I tried to cycle through using the check method in Validator.
This is inside of a class method, using the F3 (Fat Free) Framework.
I end up with the following error:
Cannot use object of type Respect\Validation\Validator as array
The code is below. What is the proper way to perform this task using arrays here? Thank you for the assistance!
$registerValidator =
Respect\Validation\Validator::attribute('email', Respect\Validation\Validator::email()->length(1,null)->notEmpty())
->attribute('address', Respect\Validation\Validator::stringType()->length(3,null)->notEmpty())
->attribute('city', Respect\Validation\Validator::alpha()->length(2,60)->notEmpty())
->attribute('state', Respect\Validation\Validator::alpha()->length(2,2)->notEmpty())
->attribute('zip', Respect\Validation\Validator::intType()->length(5,5)->notEmpty());
foreach($this->f3->get('POST') as $key => $value){
try{
$registerValidator[$key]->check($value);
} catch (\InvalidArgumentException $e) {
$errors = $e->getMainMessage();
$this->userMessage($errors, 'warning');
$this->f3->reroute('/register');
}
}
I have also tried to use the assert method as found in their docs, but utilizing the below change, I get a different error at a 500 Server Internal Error, instead of seeing my echo:
try{
$registerValidator->assert($this->f3->get('POST'));
} catch (Respect\Validation\Validator\NestedValidationException $e) {
$errors = $e->getMessages();
echo($errors); // I can't even get here.
foreach($errors as $error){
$this->userMessage($error, 'warning');
}
$this->f3->reroute('/register');
}
With this 500 Error, rather than seeing my Echo, so the page stops loading entirely.
All of the required rules must pass for ...
You cannot really use the Validator class as an array like you're doing on $registerValidator[$key]->check($value). The object in $registerValidator variable contain the chain of rules to validate an input.
In your case I believe the input is the array coming from the POST, so first of all you should use the Key validator instead of Attribute.
However the real reason why you cannot catch the errors is because you have a typo on your catch statement, the class name should be Respect\Validation\Exceptions\NestedValidationException like it's stated in the documentation, not Respect\Validation\Validator\NestedValidationException.

laravel 4.2. user permissions and hiding link source

I am a total newbie with Laravel and learning it now for a week. I have some basic questions that I can't find an answer to. Next week I will start with developing CRM system and I need some info from experienced developers who could tell me is the approach I am attending to make a good one.
I will need some authentication system, like 4 groups of users (Admin, Basic, Manager, Office) where Manager and Admin will add the Basic users. There will be few view and features and every groups will have defined access to each view and feature. Since few days I am searching for packages, watching the tutorials and learning. I found an interesting package for which I think it could help me with this user-group-permission things.The package is Sentry. Could this help me with my requirements?
What is the case when for example I have a user in group Basic and he deletes for example some comment with the button. On the left side down in the browser the user can see the link to this comment when he hovers the link. For example www.test.com/comments/345/delete where the id is 345. What if user types that with another id, that means he can delete another comment. I found some suggestions on how to solve this, to make it with jQuery and javascript so the link wouldn't be shown and POST would be made with for example with AJAX. But since I am a newbie, I am thinking how much time would this take and is this a good approach at all? Could package Sentry from 1. question help me with the permission on what route each group can access?
Any help or advice would be appreciated.
Sentry does what you want, yes. Here's a question with some answers explaining the permissions part.
The visible link part can be avoided by doing a POST request instead of a GET request.
When you open your form, you add a method attribute.
Form::open(array('url' => 'foo/bar', 'method' => 'post'))
A GET request will put the parameters in the URL, hence the visible ID. Using a POST request will put the parameters in the headers, thus hiding it from the URL.
An example could be deleting a comment. A GET request could look like this:
http://www.example.com/comments/delete/1
And the parameters would be defined in your method signature:
public function getDelete ($id) {
Comment::find($id)->delete();
}
Where the POST equivalent would be
http://www.example.com/comments/delete
And the parameters would be defined in your Input class, you would get them using the get method
public function postDelete() {
Comment::find(Input::get('id'))->delete();
}
1) The best package to help you with that is Sentry indeed.
2) To make sure an user can delete only his comments you can do something like this (but there are more solutions either you do it with Ajax or not):
public function destroy($id) {
$user = Sentry::getUser();
$comment = Comment::find($id);
if($comment) {
if($comment->user_id != $user->id) {
return Response::back(); // optional message: Permission denied!
}
$comment->delete();
return Response::back(); // optional with message: Deleted!
}
return Response::back(); // optional message: Comment not found!
}
You can use Sentry in this case to get the logged in user and check for user id. I think you should let user delete their own comments always but if you need special roles (Admins for example) to be able to delete any comment, or special permission comments.delete (For some Managers) - you can use Sentry as well:
public function destroy($id) {
$user = Sentry::getUser();
$comment = Comment::find($id);
if($comment) {
if($comment->user_id != $user->id && !$user->hasRole('Admin') && !$user->hasPermission('comments.delete'))) {
return Response::back(); // optional message: Permission denied!
}
$comment->delete();
return Response::back(); // optional with message: Deleted!
}
return Response::back(); // optional message: Comment not found!
}
A nicer way of making the DELETE thru a Form request check this:
Laravel RESTfull deleting

Laravel 4.1 + Push Queues + Error Queues

My goal is to somehow notify me if a push message fails after X attempts.
Iron.io push queues docs describe: Error Queues
http://dev.iron.io/mq/reference/push_queues/#error_queues
Following the docs, I have to define an error_queue option in order to failed messages trigger a message in the specified error_queue option.
How can I define an option if push method in IronQueue.php doesn't support option argument. I see that pushRaw does support option argument.
How can I transform the following push example into a pushRaw
Route::get('someroute', function()
{
Queue::push('SomeClass', array('time' => time()));
});
class SomeClass{
public function fire($job, $data)
{
// do something
$job->delete();
}
}
Other ways of detecting push queues fails are also welcome.
As #cmancre said, you can use HUD to set the error queue or you could use the API to set it: http://dev.iron.io/mq/reference/api/#update_a_message_queue
Iron guys just rolled out an UI allowing us to set a error_error via iron admin panel.
In case your error_queue is already firing, to complete the cycle, you need to know which message failed.
To grab the error message information, in the error_queue route just do:
// First we fetch the Request instance
$request = Request::instance();
// Now we can get the content from it
$content = $request->getContent();
Reference: http://www.codingswag.com/2013/07/get-raw-post-data-in-laravel/

Resources