symfonia-ai-tools 1.0.0

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.
Files changed (74) hide show
  1. package/README.md +489 -0
  2. package/bin/cli.mjs +35 -0
  3. package/lib/installer.mjs +495 -0
  4. package/lib/questions.mjs +332 -0
  5. package/lib/ui.mjs +76 -0
  6. package/lib/utils.mjs +231 -0
  7. package/package.json +26 -0
  8. package/templates/base/CLAUDE.md +34 -0
  9. package/templates/base/_ai/_guidelines_header.md +70 -0
  10. package/templates/base/_ai/context/README.md +20 -0
  11. package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
  12. package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
  13. package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
  14. package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
  15. package/templates/base/_ai/skills/README.md +80 -0
  16. package/templates/base/_ai/skills/TEMPLATE.md +106 -0
  17. package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
  18. package/templates/base/_ai/skills/debug/SKILL.md +93 -0
  19. package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
  20. package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
  21. package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
  22. package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
  23. package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
  24. package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
  25. package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
  26. package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
  27. package/templates/base/_claude/settings.local.json +37 -0
  28. package/templates/base/_cursor/rules/global.mdc +7 -0
  29. package/templates/base/_editorconfig +18 -0
  30. package/templates/base/_gemini/settings.json +3 -0
  31. package/templates/base/_github/copilot-instructions.md +1 -0
  32. package/templates/base/_github/pull_request_template.md +23 -0
  33. package/templates/base/_gitignore +22 -0
  34. package/templates/base/_junie/guidelines.md +1 -0
  35. package/templates/base/commit-instructions.md +92 -0
  36. package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
  37. package/templates/packs/docker/_guidelines.md +10 -0
  38. package/templates/packs/docker/pack.json +8 -0
  39. package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
  40. package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
  41. package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
  42. package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
  43. package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
  44. package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
  45. package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
  46. package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
  47. package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
  48. package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
  49. package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
  50. package/templates/packs/laravel/_guidelines.md +25 -0
  51. package/templates/packs/laravel/pack.json +6 -0
  52. package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
  53. package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
  54. package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
  55. package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
  56. package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
  57. package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
  58. package/templates/packs/playwright/_guidelines.md +12 -0
  59. package/templates/packs/playwright/pack.json +9 -0
  60. package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
  61. package/templates/packs/storybook/pack.json +6 -0
  62. package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
  63. package/templates/packs/vitest/pack.json +6 -0
  64. package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
  65. package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
  66. package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
  67. package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
  68. package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
  69. package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
  70. package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
  71. package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
  72. package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
  73. package/templates/packs/vue3/_guidelines.md +45 -0
  74. package/templates/packs/vue3/pack.json +11 -0
@@ -0,0 +1,248 @@
1
+ # Skill: Queued Job (Laravel)
2
+
3
+ ## Trigger
4
+ Use when creating asynchronous background processing: jobs, events with listeners, scheduled tasks.
5
+
6
+ ## Input
7
+ - What the job does (e.g. "send export email", "sync external API")
8
+ - Trigger: event / manual dispatch / schedule
9
+ - Input data needed
10
+ - Expected output / side effects
11
+
12
+ ## Steps
13
+
14
+ ### 1. Decide architecture
15
+
16
+ - **Simple async task** → Job only
17
+ - **Something happened, react to it** → Event + Listener(s)
18
+ - **Recurring task** → Job + Schedule
19
+ - **Multiple reactions to one trigger** → Event + multiple Listeners
20
+
21
+ ### 2. Create Event (if event-driven)
22
+
23
+ ```bash
24
+ docker exec {{DOCKER_CONTAINER}} php artisan make:event [Entity][Action]Event --path=Modules/[Module]/Domain/Events
25
+ ```
26
+
27
+ ```php
28
+ declare(strict_types=1);
29
+
30
+ namespace Modules\[Module]\Domain\Events;
31
+
32
+ use Illuminate\Foundation\Events\Dispatchable;
33
+ use Illuminate\Queue\SerializesModels;
34
+
35
+ class [Entity][Action]Event
36
+ {
37
+ use Dispatchable;
38
+ use SerializesModels;
39
+
40
+ public function __construct(
41
+ public readonly int $entityId,
42
+ public readonly int $userId,
43
+ // ... minimal data needed by listeners
44
+ ) {}
45
+ }
46
+ ```
47
+
48
+ Rules:
49
+ - Pass IDs, not full models (serialization safety)
50
+ - Keep payload minimal — listeners query fresh data
51
+ - Use `readonly` properties
52
+
53
+ ### 3. Create Job
54
+
55
+ ```bash
56
+ docker exec {{DOCKER_CONTAINER}} php artisan make:job [Action][Entity]Job --path=Modules/[Module]/Domain/Jobs
57
+ ```
58
+
59
+ ```php
60
+ declare(strict_types=1);
61
+
62
+ namespace Modules\[Module]\Domain\Jobs;
63
+
64
+ use Illuminate\Bus\Queueable;
65
+ use Illuminate\Contracts\Queue\ShouldQueue;
66
+ use Illuminate\Foundation\Bus\Dispatchable;
67
+ use Illuminate\Queue\InteractsWithQueue;
68
+ use Illuminate\Queue\SerializesModels;
69
+
70
+ class [Action][Entity]Job implements ShouldQueue
71
+ {
72
+ use Dispatchable;
73
+ use InteractsWithQueue;
74
+ use Queueable;
75
+ use SerializesModels;
76
+
77
+ public int $tries = 3;
78
+ public int $backoff = 60; // seconds between retries
79
+ public int $timeout = 120; // max execution time
80
+
81
+ public function __construct(
82
+ private readonly int $entityId,
83
+ private readonly int $userId,
84
+ ) {}
85
+
86
+ public function handle([Entity]Service $service): void
87
+ {
88
+ // Fetch fresh data
89
+ $entity = $service->findOrFail($this->entityId);
90
+
91
+ // Business logic
92
+ $service->processAction($entity);
93
+ }
94
+
95
+ public function failed(\Throwable $exception): void
96
+ {
97
+ // Log failure, notify admin, update status
98
+ \Log::error("[Action][Entity]Job failed", [
99
+ 'entity_id' => $this->entityId,
100
+ 'error' => $exception->getMessage(),
101
+ ]);
102
+ }
103
+ }
104
+ ```
105
+
106
+ Rules:
107
+ - Always set `$tries`, `$backoff`, `$timeout`
108
+ - Implement `failed()` for error handling
109
+ - Use constructor injection for dependencies in `handle()`
110
+ - Pass IDs in constructor, fetch fresh data in `handle()`
111
+ - Job must be idempotent (safe to retry)
112
+
113
+ ### 4. Create Listener (if event-driven)
114
+
115
+ ```bash
116
+ docker exec {{DOCKER_CONTAINER}} php artisan make:listener [Action]On[Event]Listener --path=Modules/[Module]/Domain/Listeners
117
+ ```
118
+
119
+ ```php
120
+ declare(strict_types=1);
121
+
122
+ namespace Modules\[Module]\Domain\Listeners;
123
+
124
+ use Illuminate\Contracts\Queue\ShouldQueue;
125
+ use Modules\[Module]\Domain\Events\[Entity][Action]Event;
126
+
127
+ class [Action]On[Event]Listener implements ShouldQueue
128
+ {
129
+ public int $tries = 3;
130
+ public int $backoff = 60;
131
+
132
+ public function __construct(
133
+ private readonly [Entity]Service $service,
134
+ ) {}
135
+
136
+ public function handle([Entity][Action]Event $event): void
137
+ {
138
+ $this->service->doAction($event->entityId);
139
+ }
140
+
141
+ public function shouldQueue([Entity][Action]Event $event): bool
142
+ {
143
+ // Optional: conditionally skip processing
144
+ return true;
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### 5. Register Event-Listener mapping
150
+
151
+ In module's `ServiceProvider`:
152
+
153
+ ```php
154
+ protected $listen = [
155
+ [Entity][Action]Event::class => [
156
+ [Action]On[Event]Listener::class,
157
+ ],
158
+ ];
159
+ ```
160
+
161
+ ### 6. Dispatch
162
+
163
+ From service/controller:
164
+
165
+ ```php
166
+ // Direct job dispatch
167
+ [Action][Entity]Job::dispatch($entity->getId(), $user->getId());
168
+
169
+ // With delay
170
+ [Action][Entity]Job::dispatch($entityId, $userId)
171
+ ->delay(now()->addMinutes(5));
172
+
173
+ // Event dispatch
174
+ [Entity][Action]Event::dispatch($entity->getId(), $user->getId());
175
+
176
+ // On specific queue
177
+ [Action][Entity]Job::dispatch($entityId, $userId)
178
+ ->onQueue('exports');
179
+ ```
180
+
181
+ ### 7. Schedule (if recurring)
182
+
183
+ In `Console/Kernel.php` or module's ServiceProvider:
184
+
185
+ ```php
186
+ $schedule->job(new [Action][Entity]Job)->dailyAt('03:00');
187
+ ```
188
+
189
+ ### 8. Write tests
190
+
191
+ ```php
192
+ // Test job dispatched
193
+ public function test_action_dispatches_job(): void
194
+ {
195
+ Queue::fake();
196
+
197
+ $this->service->triggerAction($this->entity);
198
+
199
+ Queue::assertPushed([Action][Entity]Job::class, function ($job) {
200
+ return $job->entityId === $this->entity->getId();
201
+ });
202
+ }
203
+
204
+ // Test job execution
205
+ public function test_job_processes_entity(): void
206
+ {
207
+ $job = new [Action][Entity]Job($this->entity->getId(), $this->user->getId());
208
+ $job->handle(app([Entity]Service::class));
209
+
210
+ $this->entity->refresh();
211
+ $this->assertEquals('processed', $this->entity->getStatus()->value);
212
+ }
213
+
214
+ // Test event triggers listeners
215
+ public function test_event_triggers_listener(): void
216
+ {
217
+ Event::fake();
218
+
219
+ [Entity][Action]Event::dispatch($this->entity->getId(), $this->user->getId());
220
+
221
+ Event::assertDispatched([Entity][Action]Event::class);
222
+ }
223
+
224
+ // Test idempotency
225
+ public function test_job_is_idempotent(): void
226
+ {
227
+ $job = new [Action][Entity]Job($this->entity->getId(), $this->user->getId());
228
+ $service = app([Entity]Service::class);
229
+
230
+ $job->handle($service);
231
+ $job->handle($service); // second run should not break
232
+
233
+ // Assert expected state (same as single run)
234
+ }
235
+ ```
236
+
237
+ ### 9. Verification checklist
238
+
239
+ - [ ] `declare(strict_types=1)` in all files
240
+ - [ ] Job has `$tries`, `$backoff`, `$timeout`
241
+ - [ ] Job implements `failed()` method
242
+ - [ ] Job is idempotent (safe to retry)
243
+ - [ ] Constructor takes IDs, `handle()` fetches fresh data
244
+ - [ ] Event uses `readonly` properties with minimal payload
245
+ - [ ] Listener registered in ServiceProvider
246
+ - [ ] Tests: dispatch, execution, idempotency
247
+ - [ ] `Queue::fake()` / `Event::fake()` in tests
248
+ - [ ] Error handling logs enough context for debugging
@@ -0,0 +1,196 @@
1
+ # Skill: Laravel Feature Tests
2
+
3
+ ## Trigger
4
+ Use when writing feature tests — HTTP controller tests, service integration tests with real database.
5
+
6
+ ## File Location & Namespace
7
+
8
+ - Controllers: `Modules/{Module}/Tests/Feature/Controllers/{ControllerName}Test.php`
9
+ - Services: `Modules/{Module}/Tests/Feature/Services/{ServiceName}Test.php`
10
+ - Namespace mirrors path: `Modules\{Module}\Tests\Feature\Controllers` / `Services`
11
+
12
+ ## Base Class
13
+
14
+ Feature tests extend `Tests\TestCase` which provides:
15
+ - `DatabaseTransactions` trait (auto-rollback, no manual cleanup needed)
16
+ - `$this->user` — a factory-created user
17
+ - `$this->companyId` — the user's company ID
18
+
19
+ **Never** extend `PHPUnit\Framework\TestCase` for feature tests — always use `Tests\TestCase`.
20
+
21
+ ## Controller Test Structure
22
+
23
+ ```php
24
+ <?php
25
+
26
+ declare(strict_types=1);
27
+
28
+ namespace Modules\{Module}\Tests\Feature\Controllers;
29
+
30
+ use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
31
+ use Tests\TestCase;
32
+
33
+ class {Resource}ControllerTest extends TestCase
34
+ {
35
+ protected function setUp(): void
36
+ {
37
+ parent::setUp();
38
+
39
+ $this->actingAs($this->user, 'api-passport');
40
+ $this->withHeader('X-Requested-With', 'XMLHttpRequest');
41
+ }
42
+
43
+ // --- all public test methods first ---
44
+
45
+ public function test_index_returns_ok_with_permission(): void
46
+ {
47
+ $this->grantPermission();
48
+
49
+ $response = $this->getJson('/api/v4/{resource}?page=1&limit=10');
50
+
51
+ $response->assertOk();
52
+ }
53
+
54
+ public function test_index_returns_403_without_permission(): void
55
+ {
56
+ $response = $this->getJson('/api/v4/{resource}?page=1&limit=10');
57
+
58
+ $response->assertForbidden();
59
+ }
60
+
61
+ // --- private helper methods at BOTTOM ---
62
+
63
+ private function grantPermission(): void
64
+ {
65
+ // Grant module permission to test user
66
+ }
67
+ }
68
+ ```
69
+
70
+ ## Service Test Structure
71
+
72
+ ```php
73
+ <?php
74
+
75
+ declare(strict_types=1);
76
+
77
+ namespace Modules\{Module}\Tests\Feature\Services;
78
+
79
+ use Tests\TestCase;
80
+
81
+ class {ServiceName}Test extends TestCase
82
+ {
83
+ public function test_store_creates_entity(): void
84
+ {
85
+ $result = $this->createService()->store($this->companyId, $data);
86
+
87
+ $this->assertDatabaseHas(Entity::TABLE_NAME, [
88
+ Entity::COLUMN_ID => $result->getId(),
89
+ ]);
90
+ }
91
+
92
+ private function createService(): {ServiceName}
93
+ {
94
+ return app({ServiceName}::class);
95
+ }
96
+ }
97
+ ```
98
+
99
+ ## Required Conventions
100
+
101
+ ### Method naming
102
+ - Prefix: `test_` + `snake_case` description
103
+ - No `@test` annotation
104
+ - All methods must have explicit return type (`: void` for tests)
105
+
106
+ ### Class structure order
107
+ 1. Properties (`private`, `protected`)
108
+ 2. `setUp()` / `tearDown()`
109
+ 3. All `public` test methods
110
+ 4. All `private` helper methods — **always at the bottom**
111
+
112
+ ### HTTP status codes
113
+ Always use `Symfony\Component\HttpFoundation\Response as SymfonyResponse` constants or Laravel shortcuts:
114
+ ```php
115
+ $response->assertOk(); // 200
116
+ $response->assertForbidden(); // 403
117
+ $response->assertNotFound(); // 404
118
+ $response->assertUnprocessable(); // 422
119
+ $response->assertNoContent(); // 204
120
+ $response->assertStatus(SymfonyResponse::HTTP_CREATED); // 201
121
+ ```
122
+
123
+ ### Closure type hints
124
+ ```php
125
+ use Mockery\MockInterface;
126
+ $this->mock(Repository::class, function (MockInterface $mock): void { ... });
127
+ ```
128
+
129
+ ### Mocking
130
+ Use Laravel fakes for framework services:
131
+ ```php
132
+ Event::fake();
133
+ Bus::fake();
134
+ Mail::fake();
135
+ Queue::fake();
136
+ ```
137
+
138
+ ## Data Resilience Rules
139
+
140
+ ### Never delete pre-existing data
141
+ `DatabaseTransactions` handles cleanup. Never call `->delete()` or `truncate()` in setUp or tests.
142
+
143
+ ### Before/after delta for counts
144
+ ```php
145
+ // Bad — breaks if pre-existing data exists
146
+ $response->assertJsonCount(0, 'data');
147
+
148
+ // Good — delta pattern
149
+ $beforeTotal = $this->getJson($url)->json('meta.total');
150
+ // ... create 3 items ...
151
+ $afterTotal = $this->getJson($url)->json('meta.total');
152
+ $this->assertSame($beforeTotal + 3, $afterTotal);
153
+ ```
154
+
155
+ ### Search by identifier, not position
156
+ ```php
157
+ // Bad
158
+ $this->assertSame('Expected', $data[0]['name']);
159
+
160
+ // Good
161
+ $found = collect($data)->firstWhere('id', $entity->getId());
162
+ $this->assertNotNull($found);
163
+ $this->assertSame('Expected', $found['name']);
164
+ ```
165
+
166
+ ### Other company IDs
167
+ Use factory-created companies, never arithmetic:
168
+ ```php
169
+ // Bad
170
+ $otherCompanyId = $this->companyId + 999;
171
+
172
+ // Good
173
+ $otherCompanyId = UserModel::factory()->create()->getCompanyId();
174
+ ```
175
+
176
+ ## Controller Test Coverage Checklist
177
+
178
+ | Scenario | Expected |
179
+ |----------|----------|
180
+ | Happy path with permission | 200 / 201 / 204 |
181
+ | Without permission | 403 |
182
+ | Unauthenticated | 401 |
183
+ | Validation errors | 422 |
184
+ | Resource not found | 404 |
185
+ | Different company access | 403 |
186
+ | Response JSON structure | `assertJsonStructure(...)` |
187
+
188
+ ## Running Tests
189
+
190
+ ```bash
191
+ # Single test class
192
+ docker exec {{DOCKER_CONTAINER}} php artisan test --filter='{TestClassName}'
193
+
194
+ # Entire module
195
+ docker exec {{DOCKER_CONTAINER}} php artisan test --filter='Modules\\{Module}\\Tests'
196
+ ```
@@ -0,0 +1,186 @@
1
+ # Skill: Manual HTTP Tests
2
+
3
+ ## Trigger
4
+ Use when creating or modifying API endpoints and manual `.http` test files need to be created or updated.
5
+
6
+ ## Overview
7
+
8
+ Manual HTTP tests use JetBrains HTTP Client (`.http` files) to test API endpoints directly from PhpStorm/IntelliJ. They complement PHPUnit tests by providing quick, interactive verification of endpoints.
9
+
10
+ ## File Location
11
+
12
+ - Place `.http` files in `Modules/{Module}/Tests/Manual/`
13
+ - One file per resource is recommended (e.g., `users.http`)
14
+
15
+ ## Authentication
16
+
17
+ The project uses OAuth 2.0 configured in `http-client.env.json` (copy from `http-client.env.json.example` in project root).
18
+
19
+ - Use in requests: `Authorization: Bearer {{$auth.token("app")}}`
20
+ - Requests testing unauthorized access must **not** include the `Authorization` header
21
+
22
+ ## Request Naming Convention
23
+
24
+ Format: `### {Module} - {Action} {Resource} - {Scenario}`
25
+
26
+ Where:
27
+ - `{Module}` — Laravel module name
28
+ - `{Action}` — HTTP action (Create, Get, List, Count, Update, Delete, Archive, Restore, etc.)
29
+ - `{Resource}` — resource name
30
+ - `{Scenario}` — test scenario (e.g., Valid Request, Missing Name, Without Authorization)
31
+
32
+ Examples:
33
+ - `### Users - Create User - Valid Request`
34
+ - `### Users - Get User - Non-existent`
35
+ - `### Users - Delete User - Without Authorization`
36
+
37
+ ## Required Headers
38
+
39
+ Every request must include:
40
+ ```
41
+ Accept: application/json
42
+ X-Requested-With: XMLHttpRequest
43
+ ```
44
+
45
+ For requests with body, also include:
46
+ ```
47
+ Content-Type: application/json
48
+ ```
49
+
50
+ ## Response Handlers
51
+
52
+ Every request must have a `client.test()` response handler asserting the expected status code:
53
+
54
+ ```
55
+ ### Users - Create User - Valid Request
56
+ POST {{baseUrl}}/api/v4/users
57
+ Authorization: Bearer {{$auth.token("app")}}
58
+ Content-Type: application/json
59
+ Accept: application/json
60
+ X-Requested-With: XMLHttpRequest
61
+
62
+ {
63
+ "name": "John Doe",
64
+ "email": "john@example.com"
65
+ }
66
+
67
+ > {%
68
+ client.test("Returns 201 Created", function () {
69
+ client.assert(response.status === 201, "Expected 201, got " + response.status);
70
+ });
71
+
72
+ client.global.set("userId", response.body.data.id);
73
+ %}
74
+ ```
75
+
76
+ ## Passing Data Between Requests
77
+
78
+ Use `client.global.set()` to save IDs or values from responses:
79
+
80
+ ```javascript
81
+ // In create request handler:
82
+ client.global.set("userId", response.body.data.id);
83
+
84
+ // In subsequent request URL:
85
+ GET {{baseUrl}}/api/v4/users/{{userId}}
86
+ ```
87
+
88
+ ## Test Coverage Checklist
89
+
90
+ For each endpoint, include tests for:
91
+
92
+ | Scenario | Expected Status |
93
+ |----------|----------------|
94
+ | Valid request (happy path) | 200 / 201 / 204 |
95
+ | Validation errors (missing/invalid fields) | 422 |
96
+ | Resource not found | 404 |
97
+ | Without authorization (no `Authorization` header) | 401 |
98
+ | Business logic errors | 400 |
99
+
100
+ ## Things to Avoid
101
+
102
+ - Do **not** use `### ===...` section separators — they render as separate empty requests in PhpStorm's test runner
103
+ - Do **not** hardcode entity IDs — use `client.global.set()` / `client.global.get()` from a create request
104
+ - Do **not** create `http-client.env.json` manually — copy from the example file in project root
105
+
106
+ ## Full Example
107
+
108
+ ```
109
+ ### Users - Create User - Valid Request
110
+ POST {{baseUrl}}/api/v4/users
111
+ Authorization: Bearer {{$auth.token("app")}}
112
+ Content-Type: application/json
113
+ Accept: application/json
114
+ X-Requested-With: XMLHttpRequest
115
+
116
+ {
117
+ "name": "John Doe",
118
+ "email": "john@example.com"
119
+ }
120
+
121
+ > {%
122
+ client.test("Returns 201 Created", function () {
123
+ client.assert(response.status === 201, "Expected 201, got " + response.status);
124
+ });
125
+
126
+ client.global.set("userId", response.body.data.id);
127
+ %}
128
+
129
+ ### Users - Create User - Missing Name
130
+ POST {{baseUrl}}/api/v4/users
131
+ Authorization: Bearer {{$auth.token("app")}}
132
+ Content-Type: application/json
133
+ Accept: application/json
134
+ X-Requested-With: XMLHttpRequest
135
+
136
+ {
137
+ "email": "john@example.com"
138
+ }
139
+
140
+ > {%
141
+ client.test("Returns 422 validation error", function () {
142
+ client.assert(response.status === 422, "Expected 422, got " + response.status);
143
+ });
144
+ %}
145
+
146
+ ### Users - Create User - Without Authorization
147
+ POST {{baseUrl}}/api/v4/users
148
+ Content-Type: application/json
149
+ Accept: application/json
150
+ X-Requested-With: XMLHttpRequest
151
+
152
+ {
153
+ "name": "John Doe",
154
+ "email": "john@example.com"
155
+ }
156
+
157
+ > {%
158
+ client.test("Returns 401 Unauthorized", function () {
159
+ client.assert(response.status === 401, "Expected 401, got " + response.status);
160
+ });
161
+ %}
162
+
163
+ ### Users - Get User - By ID
164
+ GET {{baseUrl}}/api/v4/users/{{userId}}
165
+ Authorization: Bearer {{$auth.token("app")}}
166
+ Accept: application/json
167
+ X-Requested-With: XMLHttpRequest
168
+
169
+ > {%
170
+ client.test("Returns 200 OK", function () {
171
+ client.assert(response.status === 200, "Expected 200, got " + response.status);
172
+ });
173
+ %}
174
+
175
+ ### Users - Delete User - Valid Request
176
+ DELETE {{baseUrl}}/api/v4/users/{{userId}}
177
+ Authorization: Bearer {{$auth.token("app")}}
178
+ Accept: application/json
179
+ X-Requested-With: XMLHttpRequest
180
+
181
+ > {%
182
+ client.test("Returns 200 OK (deleted)", function () {
183
+ client.assert(response.status === 200, "Expected 200, got " + response.status);
184
+ });
185
+ %}
186
+ ```