Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Test Patterns
rhino-project/rhino-laravel
cursor
testing
policies
authorization
roles
sanctum
api-endpoints
query-scopes

Testing conventions for the laravel-global-controller package

Install
php artisan boost:add-skill rhino-project/rhino-laravel

Save this content to: .cursor/rules/test-patterns.mdc

# Test Patterns

## What to Test

For each model, create tests covering:

1. **Policy tests** — Test each role can/cannot perform each CRUD action
2. **Scope tests** — Test each role sees the correct records
3. **Hidden column tests** — Test each role sees the correct columns in the JSON response
4. **Middleware tests** — Test middleware executes correctly (headers, logs, etc.)
5. **CRUD integration tests** — Test full endpoint lifecycle with authentication

## Role Testing

MUST test every role defined in the app separately. Test both allow and deny:

```php
public function test_admin_can_delete_post()
{
    $user = $this->createUserWithRole('admin');
    $post = Post::factory()->create();

$this->actingAs($user, 'sanctum')
        ->deleteJson("/api/{$org->slug}/posts/{$post->id}")
        ->assertStatus(204);
}

public function test_viewer_cannot_delete_post()
{
    $user = $this->createUserWithRole('viewer');
    $post = Post::factory()->create();

    $this->actingAs($user, 'sanctum')
        ->deleteJson("/api/{$org->slug}/posts/{$post->id}")
        ->assertStatus(403);
}
```

## Column Visibility Testing

Assert that hidden columns are NOT present in the response:

```php
public function test_viewer_cannot_see_cost_price()
{
    $user = $this->createUserWithRole('viewer');

    $response = $this->actingAs($user, 'sanctum')
        ->getJson("/api/{$org->slug}/products");

    $response->assertJsonMissing(['cost_price']);
}

public function test_admin_can_see_cost_price()
{
    $user = $this->createUserWithRole('admin');

    $response = $this->actingAs($user, 'sanctum')
        ->getJson("/api/{$org->slug}/products");

    $response->assertJsonFragment(['cost_price' => $product->cost_price]);
}
```

## Scope Testing

Verify data filtering per role:

```php
public function test_editor_sees_published_and_own_drafts()
{
    $editor = $this->createUserWithRole('editor');
    $publishedPost = Post::factory()->create(['is_published' => true]);
    $editorDraft = Post::factory()->create(['author_id' => $editor->id, 'is_published' => false]);
    $otherDraft = Post::factory()->create(['is_published' => false]);

    $response = $this->actingAs($editor, 'sanctum')
        ->getJson("/api/{$org->slug}/posts");

$ids = collect($response->json())->pluck('id');
    $this->assertTrue($ids->contains($publishedPost->id));
    $this->assertTrue($ids->contains($editorDraft->id));
    $this->assertFalse($ids->contains($otherDraft->id));
}
```

## DON'Ts

- Don't skip testing policy denials (test that restricted roles get 403)
- Don't skip testing unauthenticated access (test that 401 is returned)
- Don't test only the happy path — test each role's boundaries

Test Patterns

What to Test

For each model, create tests covering:

  1. Policy tests — Test each role can/cannot perform each CRUD action
  2. Scope tests — Test each role sees the correct records
  3. Hidden column tests — Test each role sees the correct columns in the JSON response
  4. Middleware tests — Test middleware executes correctly (headers, logs, etc.)
  5. CRUD integration tests — Test full endpoint lifecycle with authentication

Role Testing

MUST test every role defined in the app separately. Test both allow and deny:

public function test_admin_can_delete_post()
{
    $user = $this->createUserWithRole('admin');
    $post = Post::factory()->create();

$this->actingAs($user, 'sanctum')
        ->deleteJson("/api/{$org->slug}/posts/{$post->id}")
        ->assertStatus(204);
}

public function test_viewer_cannot_delete_post()
{
    $user = $this->createUserWithRole('viewer');
    $post = Post::factory()->create();

    $this->actingAs($user, 'sanctum')
        ->deleteJson("/api/{$org->slug}/posts/{$post->id}")
        ->assertStatus(403);
}

Column Visibility Testing

Assert that hidden columns are NOT present in the response:

public function test_viewer_cannot_see_cost_price()
{
    $user = $this->createUserWithRole('viewer');

    $response = $this->actingAs($user, 'sanctum')
        ->getJson("/api/{$org->slug}/products");

    $response->assertJsonMissing(['cost_price']);
}

public function test_admin_can_see_cost_price()
{
    $user = $this->createUserWithRole('admin');

    $response = $this->actingAs($user, 'sanctum')
        ->getJson("/api/{$org->slug}/products");

    $response->assertJsonFragment(['cost_price' => $product->cost_price]);
}

Scope Testing

Verify data filtering per role:

public function test_editor_sees_published_and_own_drafts()
{
    $editor = $this->createUserWithRole('editor');
    $publishedPost = Post::factory()->create(['is_published' => true]);
    $editorDraft = Post::factory()->create(['author_id' => $editor->id, 'is_published' => false]);
    $otherDraft = Post::factory()->create(['is_published' => false]);

    $response = $this->actingAs($editor, 'sanctum')
        ->getJson("/api/{$org->slug}/posts");

$ids = collect($response->json())->pluck('id');
    $this->assertTrue($ids->contains($publishedPost->id));
    $this->assertTrue($ids->contains($editorDraft->id));
    $this->assertFalse($ids->contains($otherDraft->id));
}

DON'Ts

  • Don't skip testing policy denials (test that restricted roles get 403)
  • Don't skip testing unauthenticated access (test that 401 is returned)
  • Don't test only the happy path — test each role's boundaries
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource