cc-codeconductor 0.2.10 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/index.js +1636 -1078
- package/package.json +1 -1
- package/presets/agy/AGENTS.md +46 -2
- package/presets/claude/CLAUDE.md +62 -2
- package/presets/claude/skills/android/SKILL.md +119 -0
- package/presets/claude/skills/laravel-specialist/SKILL.md +264 -0
- package/presets/claude/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/claude/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/claude/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/claude/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/claude/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/claude/skills/php-pro/SKILL.md +208 -0
- package/presets/claude/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/claude/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/claude/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/claude/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/claude/skills/php-pro/references/testing-quality.md +466 -0
- package/presets/codex/AGENTS.md +47 -2
- package/presets/codex/skills/android/SKILL.md +119 -0
- package/presets/codex/skills/laravel-specialist/SKILL.md +264 -0
- package/presets/codex/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/codex/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/codex/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/codex/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/codex/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/codex/skills/php-pro/SKILL.md +208 -0
- package/presets/codex/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/codex/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/codex/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/codex/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/codex/skills/php-pro/references/testing-quality.md +466 -0
- package/presets/opencode/agents/implementer.md +2 -2
- package/presets/opencode/agents/orchestrator.md +132 -5
- package/presets/opencode/agents/reviewer.md +33 -0
- package/presets/opencode/prompts/v0.3.0/architect.md +221 -0
- package/presets/opencode/prompts/v0.3.0/docs.md +189 -0
- package/presets/opencode/prompts/v0.3.0/implementer.md +162 -0
- package/presets/opencode/prompts/v0.3.0/orchestrator.md +360 -0
- package/presets/opencode/prompts/v0.3.0/repo-explorer.md +110 -0
- package/presets/opencode/prompts/v0.3.0/reviewer.md +225 -0
- package/presets/opencode/prompts/v0.3.0/task-coach.md +155 -0
- package/presets/opencode/prompts/v0.3.0/tester.md +251 -0
- package/presets/opencode/skills/android/SKILL.md +119 -0
- package/presets/opencode/skills/laravel-specialist/SKILL.md +264 -0
- package/presets/opencode/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/opencode/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/opencode/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/opencode/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/opencode/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/opencode/skills/php-pro/SKILL.md +208 -0
- package/presets/opencode/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/opencode/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/opencode/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/opencode/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/opencode/skills/php-pro/references/testing-quality.md +466 -0
- package/src/presets/manifests/agy.yml +3 -2
- package/src/presets/manifests/claude.yml +3 -2
- package/src/presets/manifests/codex.yml +3 -2
- package/src/presets/manifests/cursor.yml +3 -2
- package/src/presets/manifests/gemini.yml +3 -2
- package/src/presets/manifests/opencode.yml +3 -2
- package/src/presets/models/opencode.yml +6 -6
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
## Feature Tests
|
|
4
|
+
|
|
5
|
+
```php
|
|
6
|
+
namespace Tests\Feature;
|
|
7
|
+
|
|
8
|
+
use Tests\TestCase;
|
|
9
|
+
use App\Models\{User, Post};
|
|
10
|
+
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
11
|
+
|
|
12
|
+
class PostTest extends TestCase
|
|
13
|
+
{
|
|
14
|
+
use RefreshDatabase;
|
|
15
|
+
|
|
16
|
+
public function test_user_can_create_post(): void
|
|
17
|
+
{
|
|
18
|
+
$user = User::factory()->create();
|
|
19
|
+
|
|
20
|
+
$response = $this->actingAs($user)->post('/api/posts', [
|
|
21
|
+
'title' => 'Test Post',
|
|
22
|
+
'content' => 'This is a test post content.',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
$response->assertStatus(201)
|
|
26
|
+
->assertJson([
|
|
27
|
+
'data' => [
|
|
28
|
+
'title' => 'Test Post',
|
|
29
|
+
],
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
$this->assertDatabaseHas('posts', [
|
|
33
|
+
'title' => 'Test Post',
|
|
34
|
+
'user_id' => $user->id,
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function test_guest_cannot_create_post(): void
|
|
39
|
+
{
|
|
40
|
+
$response = $this->post('/api/posts', [
|
|
41
|
+
'title' => 'Test Post',
|
|
42
|
+
'content' => 'Content',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
$response->assertStatus(401);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public function test_post_requires_valid_data(): void
|
|
49
|
+
{
|
|
50
|
+
$user = User::factory()->create();
|
|
51
|
+
|
|
52
|
+
$response = $this->actingAs($user)->post('/api/posts', [
|
|
53
|
+
'title' => 'AB', // Too short
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
$response->assertStatus(422)
|
|
57
|
+
->assertJsonValidationErrors(['title', 'content']);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public function test_user_can_view_their_posts(): void
|
|
61
|
+
{
|
|
62
|
+
$user = User::factory()->create();
|
|
63
|
+
$posts = Post::factory()->count(3)->create(['user_id' => $user->id]);
|
|
64
|
+
|
|
65
|
+
$response = $this->actingAs($user)->get('/api/posts');
|
|
66
|
+
|
|
67
|
+
$response->assertStatus(200)
|
|
68
|
+
->assertJsonCount(3, 'data')
|
|
69
|
+
->assertJsonStructure([
|
|
70
|
+
'data' => [
|
|
71
|
+
'*' => ['id', 'title', 'content', 'created_at'],
|
|
72
|
+
],
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public function test_user_can_update_own_post(): void
|
|
77
|
+
{
|
|
78
|
+
$user = User::factory()->create();
|
|
79
|
+
$post = Post::factory()->create(['user_id' => $user->id]);
|
|
80
|
+
|
|
81
|
+
$response = $this->actingAs($user)->put("/api/posts/{$post->id}", [
|
|
82
|
+
'title' => 'Updated Title',
|
|
83
|
+
'content' => $post->content,
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
$response->assertStatus(200);
|
|
87
|
+
|
|
88
|
+
$this->assertDatabaseHas('posts', [
|
|
89
|
+
'id' => $post->id,
|
|
90
|
+
'title' => 'Updated Title',
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public function test_user_cannot_update_others_post(): void
|
|
95
|
+
{
|
|
96
|
+
$user = User::factory()->create();
|
|
97
|
+
$otherUser = User::factory()->create();
|
|
98
|
+
$post = Post::factory()->create(['user_id' => $otherUser->id]);
|
|
99
|
+
|
|
100
|
+
$response = $this->actingAs($user)->put("/api/posts/{$post->id}", [
|
|
101
|
+
'title' => 'Updated Title',
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
$response->assertStatus(403);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Unit Tests
|
|
110
|
+
|
|
111
|
+
```php
|
|
112
|
+
namespace Tests\Unit;
|
|
113
|
+
|
|
114
|
+
use Tests\TestCase;
|
|
115
|
+
use App\Models\Post;
|
|
116
|
+
use App\Services\PostService;
|
|
117
|
+
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
118
|
+
|
|
119
|
+
class PostServiceTest extends TestCase
|
|
120
|
+
{
|
|
121
|
+
use RefreshDatabase;
|
|
122
|
+
|
|
123
|
+
public function test_generates_unique_slug(): void
|
|
124
|
+
{
|
|
125
|
+
$service = new PostService();
|
|
126
|
+
|
|
127
|
+
$slug = $service->generateSlug('Test Post');
|
|
128
|
+
|
|
129
|
+
$this->assertEquals('test-post', $slug);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public function test_increments_slug_on_duplicate(): void
|
|
133
|
+
{
|
|
134
|
+
Post::factory()->create(['slug' => 'test-post']);
|
|
135
|
+
|
|
136
|
+
$service = new PostService();
|
|
137
|
+
$slug = $service->generateSlug('Test Post');
|
|
138
|
+
|
|
139
|
+
$this->assertEquals('test-post-1', $slug);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public function test_post_excerpt_returns_limited_content(): void
|
|
143
|
+
{
|
|
144
|
+
$post = new Post(['content' => str_repeat('a', 200)]);
|
|
145
|
+
|
|
146
|
+
$excerpt = $post->excerpt;
|
|
147
|
+
|
|
148
|
+
$this->assertLessThanOrEqual(100, strlen($excerpt));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Pest PHP
|
|
154
|
+
|
|
155
|
+
```php
|
|
156
|
+
<?php
|
|
157
|
+
|
|
158
|
+
use App\Models\{User, Post};
|
|
159
|
+
|
|
160
|
+
it('allows authenticated users to create posts', function () {
|
|
161
|
+
$user = User::factory()->create();
|
|
162
|
+
|
|
163
|
+
$this->actingAs($user)
|
|
164
|
+
->post('/api/posts', [
|
|
165
|
+
'title' => 'Test Post',
|
|
166
|
+
'content' => 'Content',
|
|
167
|
+
])
|
|
168
|
+
->assertStatus(201);
|
|
169
|
+
|
|
170
|
+
expect(Post::count())->toBe(1);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('prevents guests from creating posts', function () {
|
|
174
|
+
$this->post('/api/posts', [
|
|
175
|
+
'title' => 'Test Post',
|
|
176
|
+
'content' => 'Content',
|
|
177
|
+
])->assertStatus(401);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('post requires title and content', function () {
|
|
181
|
+
$user = User::factory()->create();
|
|
182
|
+
|
|
183
|
+
$this->actingAs($user)
|
|
184
|
+
->post('/api/posts', [])
|
|
185
|
+
->assertJsonValidationErrors(['title', 'content']);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Datasets
|
|
189
|
+
it('validates title length', function (string $title, bool $shouldPass) {
|
|
190
|
+
$user = User::factory()->create();
|
|
191
|
+
|
|
192
|
+
$response = $this->actingAs($user)->post('/api/posts', [
|
|
193
|
+
'title' => $title,
|
|
194
|
+
'content' => 'Content',
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
if ($shouldPass) {
|
|
198
|
+
$response->assertStatus(201);
|
|
199
|
+
} else {
|
|
200
|
+
$response->assertJsonValidationErrors(['title']);
|
|
201
|
+
}
|
|
202
|
+
})->with([
|
|
203
|
+
['AB', false], // Too short
|
|
204
|
+
['ABC', true], // Minimum valid
|
|
205
|
+
[str_repeat('A', 255), true], // Maximum valid
|
|
206
|
+
[str_repeat('A', 256), false], // Too long
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
// Hooks
|
|
210
|
+
beforeEach(function () {
|
|
211
|
+
$this->user = User::factory()->create();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
afterEach(function () {
|
|
215
|
+
// Cleanup
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Factories
|
|
220
|
+
|
|
221
|
+
```php
|
|
222
|
+
namespace Database\Factories;
|
|
223
|
+
|
|
224
|
+
use App\Models\{User, Category};
|
|
225
|
+
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
226
|
+
|
|
227
|
+
class PostFactory extends Factory
|
|
228
|
+
{
|
|
229
|
+
public function definition(): array
|
|
230
|
+
{
|
|
231
|
+
return [
|
|
232
|
+
'title' => fake()->sentence(),
|
|
233
|
+
'slug' => fake()->slug(),
|
|
234
|
+
'content' => fake()->paragraphs(3, true),
|
|
235
|
+
'excerpt' => fake()->text(100),
|
|
236
|
+
'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
|
|
237
|
+
'user_id' => User::factory(),
|
|
238
|
+
'category_id' => Category::factory(),
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public function unpublished(): static
|
|
243
|
+
{
|
|
244
|
+
return $this->state(fn (array $attributes) => [
|
|
245
|
+
'published_at' => null,
|
|
246
|
+
]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public function published(): static
|
|
250
|
+
{
|
|
251
|
+
return $this->state(fn (array $attributes) => [
|
|
252
|
+
'published_at' => now(),
|
|
253
|
+
]);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public function forUser(User $user): static
|
|
257
|
+
{
|
|
258
|
+
return $this->state(fn (array $attributes) => [
|
|
259
|
+
'user_id' => $user->id,
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
public function configure(): static
|
|
264
|
+
{
|
|
265
|
+
return $this->afterCreating(function (Post $post) {
|
|
266
|
+
$post->tags()->attach(
|
|
267
|
+
Tag::factory()->count(3)->create()
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Usage
|
|
274
|
+
$post = Post::factory()->create();
|
|
275
|
+
$unpublished = Post::factory()->unpublished()->create();
|
|
276
|
+
$posts = Post::factory()->count(10)->create();
|
|
277
|
+
$userPosts = Post::factory()->forUser($user)->count(5)->create();
|
|
278
|
+
|
|
279
|
+
// With relationships
|
|
280
|
+
$post = Post::factory()
|
|
281
|
+
->has(Comment::factory()->count(3))
|
|
282
|
+
->create();
|
|
283
|
+
|
|
284
|
+
// For relationship
|
|
285
|
+
$posts = Post::factory()
|
|
286
|
+
->count(3)
|
|
287
|
+
->for($user)
|
|
288
|
+
->create();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Mocking
|
|
292
|
+
|
|
293
|
+
```php
|
|
294
|
+
use App\Services\ExternalApiService;
|
|
295
|
+
use Illuminate\Support\Facades\Http;
|
|
296
|
+
|
|
297
|
+
public function test_fetches_data_from_external_api(): void
|
|
298
|
+
{
|
|
299
|
+
Http::fake([
|
|
300
|
+
'api.example.com/*' => Http::response([
|
|
301
|
+
'data' => ['id' => 1, 'name' => 'Test'],
|
|
302
|
+
], 200),
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
$service = new ExternalApiService();
|
|
306
|
+
$result = $service->fetchData();
|
|
307
|
+
|
|
308
|
+
$this->assertEquals('Test', $result['name']);
|
|
309
|
+
|
|
310
|
+
Http::assertSent(function ($request) {
|
|
311
|
+
return $request->url() === 'https://api.example.com/data' &&
|
|
312
|
+
$request->hasHeader('Authorization');
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Mock events
|
|
317
|
+
use Illuminate\Support\Facades\Event;
|
|
318
|
+
|
|
319
|
+
Event::fake([PostCreated::class]);
|
|
320
|
+
|
|
321
|
+
// Test code that dispatches events
|
|
322
|
+
|
|
323
|
+
Event::assertDispatched(PostCreated::class, function ($event) {
|
|
324
|
+
return $event->post->id === 1;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Mock queues
|
|
328
|
+
use Illuminate\Support\Facades\Queue;
|
|
329
|
+
|
|
330
|
+
Queue::fake();
|
|
331
|
+
|
|
332
|
+
// Test code that dispatches jobs
|
|
333
|
+
|
|
334
|
+
Queue::assertPushed(ProcessPost::class);
|
|
335
|
+
Queue::assertPushed(ProcessPost::class, 2);
|
|
336
|
+
Queue::assertPushed(ProcessPost::class, function ($job) {
|
|
337
|
+
return $job->post->id === 1;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Mock notifications
|
|
341
|
+
use Illuminate\Support\Facades\Notification;
|
|
342
|
+
|
|
343
|
+
Notification::fake();
|
|
344
|
+
|
|
345
|
+
// Test code that sends notifications
|
|
346
|
+
|
|
347
|
+
Notification::assertSentTo($user, PostPublished::class);
|
|
348
|
+
|
|
349
|
+
// Mock storage
|
|
350
|
+
use Illuminate\Support\Facades\Storage;
|
|
351
|
+
|
|
352
|
+
Storage::fake('public');
|
|
353
|
+
|
|
354
|
+
// Test file upload
|
|
355
|
+
|
|
356
|
+
Storage::disk('public')->assertExists('file.jpg');
|
|
357
|
+
Storage::disk('public')->assertMissing('missing.jpg');
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Database Testing
|
|
361
|
+
|
|
362
|
+
```php
|
|
363
|
+
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
364
|
+
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
365
|
+
|
|
366
|
+
class PostTest extends TestCase
|
|
367
|
+
{
|
|
368
|
+
use RefreshDatabase; // Migrate database before each test
|
|
369
|
+
|
|
370
|
+
// Or use transactions
|
|
371
|
+
use DatabaseTransactions; // Rollback after each test
|
|
372
|
+
|
|
373
|
+
public function test_database_assertions(): void
|
|
374
|
+
{
|
|
375
|
+
$post = Post::factory()->create([
|
|
376
|
+
'title' => 'Test Post',
|
|
377
|
+
]);
|
|
378
|
+
|
|
379
|
+
$this->assertDatabaseHas('posts', [
|
|
380
|
+
'title' => 'Test Post',
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
$post->delete();
|
|
384
|
+
|
|
385
|
+
$this->assertDatabaseMissing('posts', [
|
|
386
|
+
'id' => $post->id,
|
|
387
|
+
]);
|
|
388
|
+
|
|
389
|
+
$this->assertSoftDeleted('posts', [
|
|
390
|
+
'id' => $post->id,
|
|
391
|
+
]);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
public function test_model_exists(): void
|
|
395
|
+
{
|
|
396
|
+
$post = Post::factory()->create();
|
|
397
|
+
|
|
398
|
+
$this->assertModelExists($post);
|
|
399
|
+
|
|
400
|
+
$post->delete();
|
|
401
|
+
|
|
402
|
+
$this->assertModelMissing($post);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## API Testing
|
|
408
|
+
|
|
409
|
+
```php
|
|
410
|
+
public function test_api_returns_paginated_posts(): void
|
|
411
|
+
{
|
|
412
|
+
Post::factory()->count(30)->create();
|
|
413
|
+
|
|
414
|
+
$response = $this->get('/api/posts');
|
|
415
|
+
|
|
416
|
+
$response->assertStatus(200)
|
|
417
|
+
->assertJsonStructure([
|
|
418
|
+
'data' => [
|
|
419
|
+
'*' => ['id', 'title', 'content'],
|
|
420
|
+
],
|
|
421
|
+
'meta' => ['total', 'current_page', 'last_page'],
|
|
422
|
+
'links' => ['first', 'last', 'prev', 'next'],
|
|
423
|
+
])
|
|
424
|
+
->assertJsonCount(15, 'data'); // Default per page
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
public function test_api_filters_posts_by_category(): void
|
|
428
|
+
{
|
|
429
|
+
$category = Category::factory()->create();
|
|
430
|
+
Post::factory()->count(5)->create(['category_id' => $category->id]);
|
|
431
|
+
Post::factory()->count(5)->create();
|
|
432
|
+
|
|
433
|
+
$response = $this->get("/api/posts?category={$category->id}");
|
|
434
|
+
|
|
435
|
+
$response->assertJsonCount(5, 'data')
|
|
436
|
+
->assertJson([
|
|
437
|
+
'data' => [
|
|
438
|
+
['category_id' => $category->id],
|
|
439
|
+
],
|
|
440
|
+
]);
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Authentication Testing
|
|
445
|
+
|
|
446
|
+
```php
|
|
447
|
+
use Laravel\Sanctum\Sanctum;
|
|
448
|
+
|
|
449
|
+
public function test_authenticated_user_can_access_endpoint(): void
|
|
450
|
+
{
|
|
451
|
+
$user = User::factory()->create();
|
|
452
|
+
|
|
453
|
+
Sanctum::actingAs($user, ['*']);
|
|
454
|
+
|
|
455
|
+
$response = $this->get('/api/user');
|
|
456
|
+
|
|
457
|
+
$response->assertStatus(200)
|
|
458
|
+
->assertJson([
|
|
459
|
+
'data' => [
|
|
460
|
+
'id' => $user->id,
|
|
461
|
+
'email' => $user->email,
|
|
462
|
+
],
|
|
463
|
+
]);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
public function test_user_with_wrong_ability_cannot_access(): void
|
|
467
|
+
{
|
|
468
|
+
$user = User::factory()->create();
|
|
469
|
+
|
|
470
|
+
Sanctum::actingAs($user, ['view-posts']);
|
|
471
|
+
|
|
472
|
+
$response = $this->post('/api/posts', [
|
|
473
|
+
'title' => 'Test',
|
|
474
|
+
'content' => 'Content',
|
|
475
|
+
]);
|
|
476
|
+
|
|
477
|
+
$response->assertStatus(403);
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Running Tests
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
# Run all tests
|
|
485
|
+
php artisan test
|
|
486
|
+
|
|
487
|
+
# Run specific test
|
|
488
|
+
php artisan test --filter=test_user_can_create_post
|
|
489
|
+
|
|
490
|
+
# Run test file
|
|
491
|
+
php artisan test tests/Feature/PostTest.php
|
|
492
|
+
|
|
493
|
+
# Parallel testing
|
|
494
|
+
php artisan test --parallel
|
|
495
|
+
|
|
496
|
+
# With coverage
|
|
497
|
+
php artisan test --coverage
|
|
498
|
+
|
|
499
|
+
# Coverage minimum
|
|
500
|
+
php artisan test --coverage --min=80
|
|
501
|
+
|
|
502
|
+
# Stop on failure
|
|
503
|
+
php artisan test --stop-on-failure
|
|
504
|
+
|
|
505
|
+
# Pest specific
|
|
506
|
+
./vendor/bin/pest
|
|
507
|
+
./vendor/bin/pest --filter=PostTest
|
|
508
|
+
./vendor/bin/pest --coverage
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Best Practices
|
|
512
|
+
|
|
513
|
+
1. **Use RefreshDatabase** - Clean database for each test
|
|
514
|
+
2. **Use factories** - Don't manually create test data
|
|
515
|
+
3. **Test one thing** - Each test should verify one behavior
|
|
516
|
+
4. **Use descriptive names** - test_user_can_create_post
|
|
517
|
+
5. **AAA pattern** - Arrange, Act, Assert
|
|
518
|
+
6. **Mock external services** - Don't make real API calls
|
|
519
|
+
7. **Fake queues and events** - Test async code synchronously
|
|
520
|
+
8. **Test edge cases** - Invalid data, permissions, etc.
|
|
521
|
+
9. **Achieve >85% coverage** - Test critical paths
|
|
522
|
+
10. **Run tests in CI/CD** - Automate test execution
|