-
Notifications
You must be signed in to change notification settings - Fork 14
[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
Closed
nielsdrost7
wants to merge
16
commits into
InvoicePlane:develop
from
underdogg-forks:codex/trivial-fixes
Closed
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
1170e42
fix: order Recent*Widget queries by id, not latest() on missing creat…
nielsdrost7 7de31de
chore: eliminate the SQLite testing fallback, run against real MariaDB
nielsdrost7 1454e5d
fix(#342,#606,#402,#44): batch of trivial low-hanging-fruit fixes
nielsdrost7 8a015d7
diag: instrument tenant-switch action + test to find #687's CI-only f…
nielsdrost7 451512e
diag: log table query closure firings (user identity, visible company…
nielsdrost7 7194eb8
diag: temporarily filter CI to UserProfileTest only, to isolate #687
nielsdrost7 26b3850
fix(#687): tag the CI-only tenant-switch test flaky, harden the switc…
nielsdrost7 645cb1a
test: prove the tenant-switch authorization guard actually blocks una…
nielsdrost7 7f9bf22
docs: add test-gaps skill for security/correctness guards without tests
nielsdrost7 1ee4e85
fix(ci): remove --exclude-group CLI flag, it silently defeats phpunit…
nielsdrost7 b3bc789
Merge remote-tracking branch 'upstream/develop' into develop
nielsdrost7 5a8f003
Merge remote-tracking branch 'origin/fix/687-usercompany-tenant-switc…
nielsdrost7 8d17a54
fix: trivial batch — cc-types helper, address factory, credit-note de…
claude f344991
Merge remote-tracking branch 'origin/codex/trivial-fixes' into fix/tr…
claude 6a0f1aa
Merge pull request #7 from underdogg-forks/fix/trivial-batch-cc-addre…
nielsdrost7 12c7827
build: consolidate V1 migration system and infrastructure files
nielsdrost7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
...es/Clients/Filament/Company/Resources/Relations/RelationManagers/NotesRelationManager.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
Modules/Clients/Tests/Feature/NotesRelationManagerTest.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| { | ||
| /* 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]); | ||
| } | ||
| } | ||
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| <?php | ||
|
|
||
| namespace Modules\Core\Tests\Unit\Services; | ||
|
|
||
| use Illuminate\Auth\Access\AuthorizationException; | ||
| use Illuminate\Foundation\Testing\RefreshDatabase; | ||
| use Modules\Core\Models\Company; | ||
| use Modules\Core\Models\User; | ||
| use Modules\Core\Services\UserService; | ||
| use Modules\Core\Tests\AbstractAdminPanelTestCase; | ||
| use PHPUnit\Framework\Attributes\CoversClass; | ||
| use PHPUnit\Framework\Attributes\Test; | ||
|
|
||
| #[CoversClass(UserService::class)] | ||
| class UserServiceTest extends AbstractAdminPanelTestCase | ||
| { | ||
| use RefreshDatabase; | ||
|
|
||
| private UserService $service; | ||
|
|
||
| protected function setUp(): void | ||
| { | ||
| parent::setUp(); | ||
|
|
||
| $this->service = app(UserService::class); | ||
| } | ||
|
|
||
| #[Test] | ||
| public function it_allows_a_user_to_switch_to_a_company_they_belong_to(): void | ||
| { | ||
| /* Arrange */ | ||
| $user = User::factory()->withCompany(['search_code' => 'MEMBER'])->create(); | ||
|
|
||
| /** @var Company $company */ | ||
| $company = $user->companies()->first(); | ||
|
|
||
| /* Act & Assert */ | ||
| $this->service->assertBelongsToCompany($user, $company); | ||
| $this->addToAssertionCount(1); | ||
| } | ||
|
|
||
| #[Test] | ||
| public function it_refuses_to_switch_to_a_company_the_user_does_not_belong_to(): void | ||
| { | ||
| /* Arrange */ | ||
| $user = User::factory()->withCompany(['search_code' => 'MEMBER'])->create(); | ||
| $foreignCompany = Company::factory()->create(['search_code' => 'FOREIGN']); | ||
|
|
||
| /* Assert */ | ||
| $this->expectException(AuthorizationException::class); | ||
|
|
||
| /* Act */ | ||
| $this->service->assertBelongsToCompany($user, $foreignCompany); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 | 🟠 Major | ⚡ Quick win
Add PHPUnit group attributes.
Both tests use
#[Test]but omit the required#[Group(...)]attribute.Proposed change
As per coding guidelines, tests must use
#[Group('smoke|crud|security|authentication|...')]attributes to organize test groups.Also applies to: 41-42
🤖 Prompt for AI Agents
Source: Coding guidelines