-
Notifications
You must be signed in to change notification settings - Fork 12
[IP-44]: batch of trivial fixes (#342, #606, #402, #44) #685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
1170e42
7de31de
1454e5d
8a015d7
451512e
7194eb8
26b3850
645cb1a
7f9bf22
1ee4e85
b3bc789
5a8f003
8d17a54
f344991
6a0f1aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| --- | ||
| name: test-gaps | ||
| description: Flags security- or correctness-critical logic (auth checks, guards, validation) added or changed without a test proving both its allow and its deny path | ||
| --- | ||
|
|
||
| # Purpose | ||
|
|
||
| Catches the specific failure mode where a real behavior change ships with no test proving it | ||
| works: code added to prevent something bad, with nothing that proves the bad thing is actually | ||
| prevented. Triggered by this incident: an `abort_unless`/authorization guard was added to | ||
| `MyCompanies::switch` with zero test coverage — it could have been silently deleted or inverted | ||
| in a later change and nothing would fail. | ||
|
|
||
| This is narrower than `security-review` (which finds *missing* guards in code) and unrelated to | ||
| `test-honesty` (which is about schema/factory/seeder alignment). This skill assumes the guard | ||
| already exists and asks: is there a test that would fail if the guard were removed? | ||
|
|
||
| --- | ||
|
|
||
| # 1. Trigger Conditions | ||
|
|
||
| Apply this check whenever a diff adds or modifies any of: | ||
|
|
||
| - an authorization/ownership check (`abort_if`/`abort_unless`, `Gate::`, `->can()`, a Policy | ||
| method, a custom `assertBelongsTo*`/`assertOwns*`-style guard) | ||
| - input validation added specifically to reject a class of bad input (not just Filament's | ||
| built-in `->required()`/`->rule()` form validation, which already has its own test convention) | ||
| - a permission/role check gating an action, route, or Livewire method | ||
|
|
||
| --- | ||
|
|
||
| # 2. Coverage Rule | ||
|
|
||
| Every guard covered by Rule 1 needs **two** tests, not one: | ||
|
|
||
| - **Allow path**: the legitimate case still succeeds through the guard. | ||
| - **Deny path**: the guard actually blocks the illegitimate case — asserts the specific | ||
| exception/response the guard produces, not just "doesn't crash." | ||
|
|
||
| A guard with only an allow-path test (or no test) is a gap: nothing would catch the guard being | ||
| weakened, removed, or silently made a no-op in a later refactor. | ||
|
|
||
| --- | ||
|
|
||
| # 3. Test Placement Rule | ||
|
|
||
| If the guard lives inline inside a Filament/Livewire action closure, page method, or controller, | ||
| and testing it directly would require going through framework machinery that doesn't reliably | ||
| reach the unauthorized case (e.g. a table's own query already scopes out records the user | ||
| couldn't select in the first place, so a Feature test via `callTableAction()` never actually | ||
| exercises the deny path), that's a signal the check belongs in an extracted, directly-testable | ||
| method — a service method, a Policy, a dedicated class — not a reason to skip the deny-path test. | ||
|
|
||
| --- | ||
|
|
||
| # 4. What This Skill Does NOT Do | ||
|
|
||
| - Does not invent new authorization requirements — only checks that guards which already exist | ||
| in the diff are proven by tests. | ||
| - Does not replace `security-review`'s job of spotting where a guard is *missing* entirely. | ||
| - Does not apply to routine Filament form validation (`->required()`, `->rule()`, etc.) — that | ||
| has its own established test conventions in this codebase and isn't the failure mode this | ||
| skill targets. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,13 @@ | |
| ## InvoicePlane v2 — Development Makefile | ||
| ## ────────────────────────────────────────────────────────────────────────────── | ||
| ## | ||
| ## NOTE: `vendor/bin/phpunit` (used by the targets below) and `php artisan | ||
| ## test` (make artisan-test) have been observed to behave differently for | ||
| ## this app — a raw phpunit run has silently dropped submitted field values | ||
| ## in Livewire form tests in some environments. If a target below reports a | ||
| ## failure that `make artisan-filter FILTER="..."` doesn't reproduce, prefer | ||
| ## the artisan-test variant; it matches what CI runs. | ||
|
Comment on lines
+5
to
+10
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Standardize on Artisan as the supported test runner. Raw PHPUnit is explicitly documented as unreliable for Livewire form tests, but it remains both the default Make target and the newly advertised database-init command.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| ## | ||
| ## QUICK START | ||
| ## make test Run the full PHPUnit suite (all tests) | ||
| ## make smoke Run only @group smoke tests (fast sanity check) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| <?php | ||
|
|
||
| namespace Modules\Clients\Filament\Company\Resources\Relations\RelationManagers; | ||
|
|
||
| use Filament\Actions\DeleteAction; | ||
| use Filament\Actions\EditAction; | ||
| use Filament\Forms\Components\Textarea; | ||
| use Filament\Resources\RelationManagers\RelationManager; | ||
| use Filament\Schemas\Schema; | ||
| use Filament\Tables\Columns\TextColumn; | ||
| use Filament\Tables\Table; | ||
| use Modules\Core\Enums\UserRole; | ||
|
|
||
| class NotesRelationManager extends RelationManager | ||
| { | ||
| protected static string $relationship = 'notes'; | ||
|
|
||
| public function form(Schema $schema): Schema | ||
| { | ||
| return $schema->components([ | ||
| Textarea::make('content') | ||
| ->required() | ||
| ->columnSpanFull(), | ||
| ]); | ||
| } | ||
|
|
||
| public function table(Table $table): Table | ||
| { | ||
| return $table | ||
| ->columns([ | ||
| TextColumn::make('content')->wrap(), | ||
| TextColumn::make('noted_at')->dateTime(), | ||
| ]) | ||
| ->recordActions([ | ||
| EditAction::make()->authorize(fn (): bool => $this->canManageNotes()), | ||
| DeleteAction::make()->authorize(fn (): bool => $this->canManageNotes()), | ||
| ]); | ||
| } | ||
|
|
||
| protected function canManageNotes(): bool | ||
| { | ||
| return auth()->user()?->hasAnyRole([ | ||
| UserRole::CUSTOMER_ADMIN->value, | ||
| ...UserRole::elevated(), | ||
| ]) ?? false; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| <?php | ||
|
|
||
| namespace Modules\Clients\Tests\Feature; | ||
|
|
||
| use Livewire\Livewire; | ||
| use Modules\Clients\Filament\Company\Resources\Relations\Pages\ViewRelation; | ||
| use Modules\Clients\Filament\Company\Resources\Relations\RelationManagers\NotesRelationManager; | ||
| use Modules\Clients\Models\Relation; | ||
| use Modules\Core\Tests\AbstractCompanyPanelTestCase; | ||
| use PHPUnit\Framework\Attributes\Test; | ||
|
|
||
| class NotesRelationManagerTest extends AbstractCompanyPanelTestCase | ||
| { | ||
| #[Test] | ||
| public function it_edits_a_client_note(): void | ||
|
Comment on lines
+14
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Add PHPUnit group attributes. Both tests use Proposed change+use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
#[Test]
+#[Group('crud')]
public function it_edits_a_client_note(): void
#[Test]
+#[Group('crud')]
public function it_deletes_a_client_note(): voidAs per coding guidelines, tests must use Also applies to: 41-42 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| { | ||
| /* Arrange */ | ||
| $client = Relation::factory()->for($this->company)->customer()->create(); | ||
| $note = $client->notes()->create([ | ||
| 'company_id' => $this->company->id, | ||
| 'user_id' => $this->user->id, | ||
| 'noted_at' => now(), | ||
| 'is_private' => false, | ||
| 'title' => 'Client note', | ||
| 'content' => 'Original note', | ||
| ]); | ||
|
|
||
| /* Act */ | ||
| $component = Livewire::actingAs($this->user) | ||
| ->test(NotesRelationManager::class, [ | ||
| 'ownerRecord' => $client, | ||
| 'pageClass' => ViewRelation::class, | ||
| ]) | ||
| ->callTableAction('edit', $note, data: ['content' => 'Updated note']); | ||
|
|
||
| /* Assert */ | ||
| $component->assertHasNoTableActionErrors(); | ||
| $this->assertDatabaseHas('notes', ['id' => $note->id, 'content' => 'Updated note']); | ||
| } | ||
|
|
||
| #[Test] | ||
| public function it_deletes_a_client_note(): void | ||
| { | ||
| /* Arrange */ | ||
| $client = Relation::factory()->for($this->company)->customer()->create(); | ||
| $note = $client->notes()->create([ | ||
| 'company_id' => $this->company->id, | ||
| 'user_id' => $this->user->id, | ||
| 'noted_at' => now(), | ||
| 'is_private' => false, | ||
| 'title' => 'Client note', | ||
| 'content' => 'Delete me', | ||
| ]); | ||
|
|
||
| /* Act */ | ||
| $component = Livewire::actingAs($this->user) | ||
| ->test(NotesRelationManager::class, [ | ||
| 'ownerRecord' => $client, | ||
| 'pageClass' => ViewRelation::class, | ||
| ]) | ||
| ->callTableAction('delete', $note); | ||
|
|
||
| /* Assert */ | ||
| $component->assertHasNoTableActionErrors(); | ||
| $this->assertDatabaseMissing('notes', ['id' => $note->id]); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Specify the fenced-block language.
Add
bashto the opening fence so markdownlint passes.🧰 Tools
🪛 markdownlint-cli2 (0.23.0)
[warning] 202-202: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Source: Linters/SAST tools