Add groups resource close #371 #378
2 changed files with 99 additions and 29 deletions
|
|
@ -1515,6 +1515,40 @@ mix test test/membership/member_test.exs:42
|
||||||
|
|
||||||
### 4.7 Testing Best Practices
|
### 4.7 Testing Best Practices
|
||||||
|
|
||||||
|
**Testing Philosophy: Focus on Business Logic, Not Framework Functionality**
|
||||||
|
|
||||||
|
We test our business logic and domain-specific behavior, not core framework features. Framework features (Ash validations, Ecto relationships, etc.) are already tested by their respective libraries.
|
||||||
|
|
||||||
|
**What We Test:**
|
||||||
|
- Business rules and validations specific to our domain
|
||||||
|
- Custom business logic (slug generation, calculations, etc.)
|
||||||
|
- Integration between our resources
|
||||||
|
- Database-level constraints (unique constraints, foreign keys, CASCADE)
|
||||||
|
- Query performance (N+1 prevention)
|
||||||
|
|
||||||
|
**What We Don't Test:**
|
||||||
|
- Framework core functionality (Ash validations work, Ecto relationships work, etc.)
|
||||||
|
- Standard CRUD operations without custom logic
|
||||||
|
- Framework-provided features that are already tested upstream
|
||||||
|
- Detailed slug generation edge cases (Umlauts, truncation, etc.) if covered by reusable change tests
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```elixir
|
||||||
|
# ✅ GOOD - Tests our business rule
|
||||||
|
test "slug is immutable (doesn't change when name is updated)" do
|
||||||
|
{:ok, group} = Membership.create_group(%{name: "Original"}, actor: actor)
|
||||||
|
original_slug = group.slug
|
||||||
|
|
||||||
|
{:ok, updated} = Membership.update_group(group, %{name: "New"}, actor: actor)
|
||||||
|
assert updated.slug == original_slug # Business rule: slug doesn't change
|
||||||
|
end
|
||||||
|
|
||||||
|
# ❌ AVOID - Tests framework functionality
|
||||||
|
test "Ash.Changeset validates required fields" do
|
||||||
|
# This is already tested by Ash framework
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
**Descriptive Test Names:**
|
**Descriptive Test Names:**
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
|
|
|
||||||
|
|
@ -944,6 +944,24 @@ Each functional unit can be implemented as a **separate issue**:
|
||||||
|
|
||||||
## Testing Strategy
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Testing Philosophy
|
||||||
|
|
||||||
|
**Focus on Business Logic, Not Framework Functionality**
|
||||||
|
|
||||||
|
We test our business logic and domain-specific behavior, not core framework features. Framework features (Ash validations, Ecto relationships, etc.) are already tested by their respective libraries.
|
||||||
|
|
||||||
|
**What We Test:**
|
||||||
|
- Business rules and validations specific to our domain
|
||||||
|
- Custom business logic (slug generation, calculations, etc.)
|
||||||
|
- Integration between our resources
|
||||||
|
- Database-level constraints (unique constraints, foreign keys, CASCADE)
|
||||||
|
- Query performance (N+1 prevention)
|
||||||
|
|
||||||
|
**What We Don't Test:**
|
||||||
|
- Framework core functionality (Ash validations work, Ecto relationships work, etc.)
|
||||||
|
- Standard CRUD operations without custom logic
|
||||||
|
- Framework-provided features that are already tested upstream
|
||||||
|
|
||||||
### Unit Tests
|
### Unit Tests
|
||||||
|
|
||||||
#### Group Resource Tests
|
#### Group Resource Tests
|
||||||
|
|
@ -952,31 +970,23 @@ Each functional unit can be implemented as a **separate issue**:
|
||||||
|
|
||||||
**Test Cases:**
|
**Test Cases:**
|
||||||
- Create group with valid attributes
|
- Create group with valid attributes
|
||||||
- Return error when name is missing
|
- Return error when name is missing (required validation)
|
||||||
- Return error when name exceeds 100 characters
|
- Return error when name exceeds 100 characters (length validation)
|
||||||
- Return error when name is not unique
|
- Return error when name is not unique (case-insensitive) - application level validation
|
||||||
- Name uniqueness is case-insensitive
|
- Allow description to be nil (optional field)
|
||||||
- Allow description to be nil
|
- Description max length is 500 characters (length validation)
|
||||||
- Trim whitespace from name
|
- Slug is automatically generated from name on create (custom business logic)
|
||||||
- Description max length is 500 characters
|
- Slug is immutable (doesn't change when name is updated) - business rule
|
||||||
- Slug is automatically generated from name on create
|
- Slug is unique (prevents duplicate slugs from different names) - business rule
|
||||||
- Slug is immutable (doesn't change on update)
|
- Slug cannot be empty (rejects name with only special characters) - business rule
|
||||||
- Slug is unique (cannot have duplicates)
|
|
||||||
- Slug handles German umlauts correctly (ä → a, ß → ss)
|
|
||||||
- Slug converts to lowercase
|
|
||||||
- Slug removes special characters
|
|
||||||
- Slug replaces spaces with hyphens
|
|
||||||
- Slug truncates to max 100 characters
|
|
||||||
- Slug cannot be empty (validation fails if slug would be empty)
|
|
||||||
- Read group by slug (via identity lookup)
|
|
||||||
- Update group name and description
|
- Update group name and description
|
||||||
- Prevent duplicate name on update
|
- Prevent duplicate name on update (case-insensitive) - business rule
|
||||||
- Prevent duplicate slug on create (same name → same slug → error)
|
- Delete group and all member associations (cascade behavior)
|
||||||
- Delete group and all member associations
|
- Do not delete members themselves (cascade boundary)
|
||||||
- Do not delete members themselves
|
- Member count calculation returns 0 for empty group (custom calculation)
|
||||||
- Member count calculation returns 0 for empty group
|
- Member count calculation returns correct count when members added/removed (custom calculation)
|
||||||
- Member count calculation returns correct count when members added
|
|
||||||
- Member count updates correctly when members removed
|
**Note:** Detailed slug generation tests (Umlauts, truncation, etc.) are covered by the `GenerateSlug` change tests in `custom_field_slug_test.exs`, which is reused for groups. We don't duplicate these framework-level tests.
|
||||||
|
|
||||||
#### MemberGroup Resource Tests
|
#### MemberGroup Resource Tests
|
||||||
|
|
||||||
|
|
@ -984,9 +994,23 @@ Each functional unit can be implemented as a **separate issue**:
|
||||||
|
|
||||||
**Test Cases:**
|
**Test Cases:**
|
||||||
- Create association between member and group
|
- Create association between member and group
|
||||||
- Prevent duplicate associations
|
- Prevent duplicate associations (unique constraint)
|
||||||
- Cascade delete when member deleted
|
- Cascade delete when member deleted (database constraint)
|
||||||
- Cascade delete when group deleted
|
- Cascade delete when group deleted (database constraint)
|
||||||
|
|
||||||
|
#### Database Constraint Tests
|
||||||
|
|
||||||
|
**File:** `test/membership/group_database_constraints_test.exs`
|
||||||
|
|
||||||
|
**Test Cases:**
|
||||||
|
- Database enforces unique name constraint (case-insensitive via LOWER) - DB level
|
||||||
|
- Database enforces unique slug constraint (case-sensitive) - DB level
|
||||||
|
- Cannot create MemberGroup with non-existent member_id (foreign key constraint)
|
||||||
|
- Cannot create MemberGroup with non-existent group_id (foreign key constraint)
|
||||||
|
- Deleting member cascades to member_groups (verified at DB level)
|
||||||
|
- Deleting group cascades to member_groups (verified at DB level)
|
||||||
|
|
||||||
|
**Note:** These tests verify that constraints are enforced at the database level, not just application level.
|
||||||
|
|
||||||
### Integration Tests
|
### Integration Tests
|
||||||
|
|
||||||
|
|
@ -995,8 +1019,20 @@ Each functional unit can be implemented as a **separate issue**:
|
||||||
**File:** `test/membership/group_integration_test.exs`
|
**File:** `test/membership/group_integration_test.exs`
|
||||||
|
|
||||||
**Test Cases:**
|
**Test Cases:**
|
||||||
- Member can belong to multiple groups
|
- Member can belong to multiple groups (many-to-many relationship)
|
||||||
- Group can contain multiple members
|
- Group can contain multiple members (many-to-many relationship)
|
||||||
|
- Preloading groups with members avoids N+1 queries (performance test with query count verification)
|
||||||
|
|
||||||
|
**File:** `test/membership/member_groups_relationship_test.exs`
|
||||||
|
|
||||||
|
**Test Cases:**
|
||||||
|
- Member has many_to_many groups relationship (load with preloading)
|
||||||
|
- Load multiple members with groups preloaded (N+1 prevention)
|
||||||
|
- Add member to group via Ash API
|
||||||
|
- Remove member from group via Ash API
|
||||||
|
- Add member to multiple groups in single operation
|
||||||
|
- Adding member to same group twice fails (duplicate prevention)
|
||||||
|
- Removing member from group they're not in (idempotent, no error)
|
||||||
|
|
||||||
### UI Tests
|
### UI Tests
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue