Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .claude/plans/camel-to-snake-columns/001-core-camel-to-snake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Task 001: Add camelToSnakeCase and Update EntityMetadataFactory

**Status**: completed
**Depends on**: none
**Retry count**: 0

## Description
Add a private `camelToSnakeCase(string $name): string` method to `EntityMetadataFactory` and update the `parse()` method to auto-convert property names to snake_case when no explicit `name:` is provided on the `#[Column]` attribute.

## Context
- Key file: `packages/database/src/Entity/EntityMetadataFactory.php`
- Line 68 is the critical change: `$columnName = $columnAttr->name ?? $propertyName;` becomes `$columnName = $columnAttr->name ?? $this->camelToSnakeCase($propertyName);`
- Test file: `packages/database/tests/Entity/EntityMetadataFactoryTest.php`
- The existing test "uses Column attribute name when specified" (line 172) tests explicit `name:` override — it should still pass
- Single-word properties like `$id`, `$name`, `$email` should be unaffected (already lowercase)

## Requirements (Test Descriptions)
- [ ] `it converts camelCase property names to snake_case column names automatically`
- [ ] `it preserves explicit Column name override when specified`
- [ ] `it handles single-word property names without change`
- [ ] `it handles consecutive uppercase letters correctly (userID becomes user_id)`
- [ ] `it handles leading uppercase sequences correctly (HTMLParser becomes html_parser)`
- [ ] `it updates the existing override test to use a genuinely custom name`

## Acceptance Criteria
- All requirements have passing tests
- `camelToSnakeCase` handles: `postId` -> `post_id`, `createdAt` -> `created_at`, `id` -> `id`, `HTMLParser` -> `html_parser`, `userID` -> `user_id`, `isActive` -> `is_active`
- Explicit `#[Column(name: 'custom')]` still takes priority over auto-conversion
- Existing EntityMetadataFactoryTest tests still pass (some may need column name assertion updates)
- The existing "uses Column attribute name when specified" test (line 172) MUST be updated to use a genuinely custom name that differs from what auto-conversion would produce (e.g., `#[Column(name: 'author')]` on `$userId` instead of `#[Column(name: 'user_id')]`), otherwise the test becomes a no-op that passes whether or not the override works

## Implementation Notes
(Left blank - filled in by programmer during implementation)
33 changes: 33 additions & 0 deletions .claude/plans/camel-to-snake-columns/002-database-unit-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Task 002: Update Database Package Unit Tests

**Status**: completed
**Depends on**: 001
**Retry count**: 0

## Description
Update unit tests in the database package that assert column names from parsed metadata. The core change causes camelCase property names to produce snake_case column names, so assertions checking column names need updating.

## Context
- `packages/database/tests/Entity/EntityMetadataFactoryTest.php` — Some test entities use camelCase properties without explicit names. The "extracts #[Column] attributes" test uses `$title`, `$content` (single-word, no change). The "extracts foreign key reference" test uses `$userId` without explicit name — its column will now be `user_id` instead of `userId`. But this test only checks `references`/`onDelete`/`onUpdate`, not the column name, so it should still pass.
- `packages/database/tests/Entity/SchemaBuilderTest.php` — The "builds ForeignKey objects from column references" test (line 138) expects FK name `fk_posts_userId` and columns `['userId']`. After the change, `$userId` (no explicit name) auto-converts to `user_id`, so expect `fk_posts_user_id` and `['user_id']`. The "preserves foreign key references" test (line 119) doesn't check column name directly — only references/onDelete/onUpdate — so it should pass.
- `packages/database/tests/Entity/EntityMetadataTest.php` — Manually constructs PropertyMetadata/ColumnMetadata, doesn't use the factory. No changes needed.
- `packages/database/tests/Entity/EntityHydratorTest.php` — Manually constructs metadata with snake_case column names. No changes needed.
- `packages/database/tests/Attributes/ColumnAttributeTest.php` — Tests the Column attribute class directly, not the factory. No changes needed.
- `packages/database/tests/Entity/EntityDiscoveryTest.php` — Uses `$id` only. No changes.
- `packages/database/tests/Schema/SchemaRegistryTest.php` — Uses single-word properties only. No changes.

## Requirements (Test Descriptions)
- [ ] `it generates FK name using snake_case column name (fk_posts_user_id)`
- [ ] `it uses snake_case column names in FK column arrays`
- [ ] `it preserves column name assertions for single-word properties`
- [ ] `it passes all existing EntityMetadataFactory tests after column name conversion`

## Acceptance Criteria
- All tests in `packages/database/tests/Entity/` pass
- All tests in `packages/database/tests/Schema/` pass
- All tests in `packages/database/tests/Attributes/` pass
- FK name in SchemaBuilderTest updated from `fk_posts_userId` to `fk_posts_user_id`
- FK columns in SchemaBuilderTest updated from `['userId']` to `['user_id']`

## Implementation Notes
(Left blank - filled in by programmer during implementation)
42 changes: 42 additions & 0 deletions .claude/plans/camel-to-snake-columns/003-database-feature-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Task 003: Update Database Package Feature Tests

**Status**: completed
**Depends on**: 001
**Retry count**: 0

## Description
Update feature tests in the database package that use inline entity classes with camelCase properties. After the core change, these properties produce snake_case column names, breaking assertions that reference the old camelCase column names.

## Context
- `packages/database/tests/Feature/EntityToMigrationWorkflowTest.php`:
- `WorkflowUser` has `$isActive` with bare `#[Column]` — column becomes `is_active` instead of `isActive`
- `WorkflowPost` has `$authorId` with bare `#[Column]` — column becomes `author_id` instead of `authorId`
- Line 230: `->toContain('isActive')` must become `->toContain('is_active')`
- Line 237: `->toContain('authorId')` must become `->toContain('author_id')`
- Line 267: `new SchemaColumn(name: 'isActive', type: 'BOOLEAN')` must become `name: 'is_active'`
- Line 289: `->toContain('isActive')` must become `->toContain('is_active')`
- IMPORTANT: Search the entire file for ALL `SchemaColumn` and `SchemaTable` constructions that represent entity-derived schema. Any manually constructed `SchemaColumn` with a camelCase `name:` that represents what the entity would produce must also be updated (e.g., `name: 'isActive'` -> `name: 'is_active'`, `name: 'authorId'` -> `name: 'author_id'`). The "detects and generates migrations" test constructs expected schema objects that must match the new auto-converted names.

- `packages/database/tests/Feature/RepositoryCrudTest.php`:
- `CrudProduct` has `$isAvailable` with bare `#[Column]` — column becomes `is_available` instead of `isAvailable`
- Line 107: Mock storage uses `'isAvailable'` key — must become `'is_available'`
- Lines 203-205: Mock storage arrays use `'isAvailable'` key — must become `'is_available'`
- Line 228: `str_contains($sql, 'isAvailable = ?')` must become `str_contains($sql, 'is_available = ?')`
- Line 370: Mock storage uses `'isAvailable'` key — must become `'is_available'`

## Requirements (Test Descriptions)
- [ ] `it uses snake_case column names in entity-to-migration workflow assertions`
- [ ] `it uses snake_case column names in SchemaColumn construction for diff tests`
- [ ] `it uses snake_case column names in mock storage arrays for CRUD tests`
- [ ] `it uses snake_case column names in SQL string matching for findBy tests`
- [ ] `it passes all feature tests after column name updates`

## Acceptance Criteria
- All tests in `packages/database/tests/Feature/` pass
- All `isActive` column references updated to `is_active`
- All `authorId` column references updated to `author_id`
- All `isAvailable` column references updated to `is_available`
- Mock connection storage arrays use snake_case keys for column names

## Implementation Notes
(Left blank - filled in by programmer during implementation)
40 changes: 40 additions & 0 deletions .claude/plans/camel-to-snake-columns/004-repository-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Task 004: Update Repository Tests

**Status**: completed
**Depends on**: 001
**Retry count**: 0

## Description
Update Repository unit tests that use entities with camelCase properties. After the core change, the Repository generates SQL using snake_case column names, so mock connections and assertions need updating.

## Context
- `packages/database/tests/Repository/RepositoryTest.php`:
- `RepositoryTestUser` has `$isActive` with bare `#[Column]` — column becomes `is_active` instead of `isActive`
- `$email` with `#[Column('email_address')]` — explicitly named, no change needed
- There are **23 occurrences** of `isActive` in this file that need review. Affected areas:
- Line 188: Comment `'isActive' maps to 'isActive' column` — update comment to reflect snake_case
- Lines 194, 233, 282: Mock connection storage arrays with `'isActive'` key — change to `'is_active'`
- Lines 310, 311, 327, 328, 343: Mock query return arrays with `'isActive'` key — change to `'is_active'`
- Line 334: `findBy(['isActive' => true])` — this uses the PHP property name as the criteria key, which Repository::findBy maps to column name via `$propertyToColumn`. The call stays as `findBy(['isActive' => true])` (property name), but the generated SQL will now contain `is_active = ?` instead of `isActive = ?`, so mock SQL matching must update accordingly.
- Lines 412, 452, 527, 626, 665, 724, 819: Various mock data arrays with `'isActive'` key — change to `'is_active'`
- Line 577: `->not->toContain('isActive')` — this checks SQL SET clause; update to `'is_active'`
- NOTE: Lines that reference `$user->isActive` (PHP property access) do NOT change — only column name string keys in mock data arrays and SQL assertions change

- `packages/database/tests/Repository/RepositoryLifecycleEventTest.php`:
- `LifecycleTestItem` only has `$id` and `$name` (single-word properties) — no changes needed
- Verify test still passes after the core change

## Requirements (Test Descriptions)
- [ ] `it uses snake_case column names in Repository SQL generation assertions`
- [ ] `it uses snake_case column names in mock connection storage arrays`
- [ ] `it preserves explicit Column name override (email_address) in Repository tests`
- [ ] `it passes all Repository tests after column name updates`

## Acceptance Criteria
- All tests in `packages/database/tests/Repository/` pass
- All `isActive` column references in mock code updated to `is_active`
- Explicit `email_address` mapping unchanged
- RepositoryLifecycleEventTest passes without changes

## Implementation Notes
(Left blank - filled in by programmer during implementation)
114 changes: 114 additions & 0 deletions .claude/plans/camel-to-snake-columns/005-production-entity-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Task 005: Remove Redundant Names from Production Entities and Update Entity Tests

**Status**: completed
**Depends on**: 001
**Retry count**: 0

## Description
Remove all explicit `#[Column(name: '...')]` parameters that are now redundant because the auto-conversion produces the same snake_case name. Update entity attribute tests in admin-auth, authentication-token, and media that check Column attribute `name` via reflection.

## Context
All explicit names in production entities match what the auto-conversion would produce. They are now unnecessary boilerplate.

**Entities to clean up:**

- `packages/admin-auth/src/Entity/AdminUser.php`:
- `#[Column('remember_token')]` on `$rememberToken` → `#[Column]`
- `#[Column('is_active', default: '1')]` on `$isActive` → `#[Column(default: '1')]`
- `#[Column('created_at')]` on `$createdAt` → `#[Column]`
- `#[Column('updated_at')]` on `$updatedAt` → `#[Column]`

- `packages/admin-auth/src/Entity/Role.php`:
- `#[Column('is_super_admin', default: '0')]` on `$isSuperAdmin` → `#[Column(default: '0')]`
- `#[Column('created_at')]` on `$createdAt` → `#[Column]`
- `#[Column('updated_at')]` on `$updatedAt` → `#[Column]`

- `packages/admin-auth/src/Entity/Permission.php`:
- `#[Column('created_at')]` on `$createdAt` → `#[Column]`

- `packages/admin-auth/src/Entity/RolePermission.php`:
- `#[Column('role_id', references: 'roles.id', onDelete: 'CASCADE')]` on `$roleId` → `#[Column(references: 'roles.id', onDelete: 'CASCADE')]`
- `#[Column('permission_id', references: 'permissions.id', onDelete: 'CASCADE')]` on `$permissionId` → `#[Column(references: 'permissions.id', onDelete: 'CASCADE')]`

- `packages/media/src/Entity/Media.php`:
- `#[Column('original_filename', length: 255)]` on `$originalFilename` → `#[Column(length: 255)]`
- `#[Column('mime_type', length: 100)]` on `$mimeType` → `#[Column(length: 100)]`
- `#[Column('created_at')]` on `$createdAt` → `#[Column]`
- `#[Column('updated_at')]` on `$updatedAt` → `#[Column]`

- `packages/media/src/Entity/MediaAttachment.php`:
- `#[Column('media_id')]` on `$mediaId` → `#[Column]`
- `#[Column('attachable_type', length: 255)]` on `$attachableType` → `#[Column(length: 255)]`
- `#[Column('attachable_id', length: 255)]` on `$attachableId` → `#[Column(length: 255)]`

- `packages/webhook/src/Entity/WebhookAttempt.php`:
- `#[Column(name: 'status_code')]` on `$statusCode` → `#[Column]`
- `#[Column(name: 'response_body', type: 'TEXT')]` on `$responseBody` → `#[Column(type: 'TEXT')]`
- `#[Column(name: 'error_message', type: 'TEXT')]` on `$errorMessage` → `#[Column(type: 'TEXT')]`
- `#[Column(name: 'attempted_at')]` on `$attemptedAt` → `#[Column]`
- `#[Column(name: 'webhook_url')]` on `$webhookUrl` → `#[Column]`
- `#[Column(name: 'attempt_number')]` on `$attemptNumber` → `#[Column]`

- `packages/authentication-token/src/Entity/PersonalAccessToken.php`:
- `#[Column('tokenable_type')]` on `$tokenableType` → `#[Column]`
- `#[Column('tokenable_id')]` on `$tokenableId` → `#[Column]`
- `#[Column('token_hash', length: 64)]` on `$tokenHash` → `#[Column(length: 64)]`
- `#[Column('last_used_at')]` on `$lastUsedAt` → `#[Column]`
- `#[Column('expires_at')]` on `$expiresAt` → `#[Column]`
- `#[Column('created_at')]` on `$createdAt` → `#[Column]`

- `packages/database/tests/Entity/EntityMetadataFactoryTest.php`:
- Line 175: `#[Column(name: 'user_id')]` on `$userId` — this is now redundant. Remove `name:` parameter.
- NOTE: The "uses Column attribute name when specified" test reworking is handled in task 001.

- `packages/database/tests/Entity/EntityHydratorTest.php`:
- Line 25: `#[Column('email_address')]` on `$email` — NOT redundant (`email` -> `email`, not `email_address`). Keep as-is.

- `packages/database/tests/Repository/RepositoryTest.php`:
- Line 32: `#[Column('email_address')]` on `$email` — NOT redundant. Keep as-is.

**Tests to update (check Column attribute `name` via reflection):**

- `packages/admin-auth/tests/Unit/Entity/AdminUserTest.php`:
- Line 67: `expect($rememberTokenColumn->name)->toBe('remember_token')` — after removal, `$columnAttr->name` will be `null`. Update test to check `null` or remove assertion (the factory handles the conversion, not the attribute).
- Line 72: `expect($isActiveColumn->name)->toBe('is_active')` — same issue. After removal of explicit name from `#[Column(default: '1')]`, `name` is `null`.

- `packages/admin-auth/tests/Unit/Entity/PermissionTest.php`:
- Line 117: `expect($columnAttribute->name)->toBe('created_at')` — same issue.

- `packages/admin-auth/tests/Unit/Entity/RoleTest.php`:
- Lines 151, 165: `expect($columnAttribute->name)->toBe('created_at')` and `'updated_at'` — same issue.

- `packages/authentication-token/tests/Entity/PersonalAccessTokenTest.php`:
- Line 38: `$tokenableTypeColumn->name->toBe('tokenable_type')` — will be `null` after removal. Update to `->toBeNull()` or remove.
- Line 45: `$tokenableIdColumn->name->toBe('tokenable_id')` — same issue.
- Line 57: `$tokenHashColumn->name->toBe('token_hash')` — same issue.
- Line 72: `$lastUsedAtColumn->name->toBe('last_used_at')` — same issue.
- Line 79: `$expiresAtColumn->name->toBe('expires_at')` — same issue.
- Line 86: `$createdAtColumn->name->toBe('created_at')` — same issue.

- `packages/media/tests/Entity/MediaTest.php`:
- Line 45: `$originalFilenameColumn->name->toBe('original_filename')` — will be `null` after removal. Update to `->toBeNull()` or remove.
- Line 53: `$mimeTypeColumn->name->toBe('mime_type')` — same issue.
- Line 88: `$createdAtColumn->name->toBe('created_at')` — same issue.
- Line 96: `$updatedAtColumn->name->toBe('updated_at')` — same issue.

## Requirements (Test Descriptions)
- [ ] `it removes redundant explicit Column name from admin-auth entities`
- [ ] `it removes redundant explicit Column name from media entities`
- [ ] `it removes redundant explicit Column name from webhook entity`
- [ ] `it removes redundant explicit Column name from authentication-token entity`
- [ ] `it updates admin-auth entity tests to not assert explicit Column attribute name`
- [ ] `it updates authentication-token entity test to not assert explicit Column attribute name`
- [ ] `it updates media entity test to not assert explicit Column attribute name`
- [ ] `it passes all tests across affected packages after cleanup`

## Acceptance Criteria
- No redundant explicit `name:` parameters remain in production entity `#[Column]` attributes
- Admin-auth entity tests updated to reflect that Column attribute `name` is now `null` (auto-derived by factory)
- Authentication-token entity test updated to reflect that Column attribute `name` is now `null` (auto-derived by factory)
- Media entity test updated to reflect that Column attribute `name` is now `null` (auto-derived by factory)
- All tests pass across: database, admin-auth, media, webhook, authentication-token, notification-database packages

## Implementation Notes
(Left blank - filled in by programmer during implementation)
Loading