Files

281 lines
9.4 KiB
Markdown
Raw Permalink Normal View History

# member-crud Specification
## Purpose
TBD - created by archiving change add-graphql-member-api. Update Purpose after archive.
## Requirements
### Requirement: Create member with validation
The system MUST allow creating new members with only firstName required, optionally validating email and phone format when provided.
**Validation rules:**
- firstName: Required field, must be non-empty string
- Email (when provided): Must match regex pattern `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
- Phone (when provided): Must match E.164 format `^\+?[1-9]\d{1,14}$`
- All other fields (lastName, address fields, email, phone) are optional
- Email and phone do NOT require uniqueness checking
- Format validation only applies when field is provided (not null/empty)
#### Scenario: Create member with valid data
**Given** client provides valid member data:
```python
{
"first_name": "John",
"last_name": "Doe",
"street": "123 Main Street",
"apartment_number": "Apt 4B",
"zip": "12345",
"city": "Springfield",
"country": "USA",
"email": "john.doe@example.com",
"phone": "+15551234567"
}
```
**When** createMember mutation executes
**Then** new Member record is created in database
**And** member.id is auto-generated
**And** member.created_at is set to current timestamp
**And** member.updated_at is set to current timestamp
**And** member object is returned to client
#### Scenario: Create member with invalid email format
**Given** client provides email "invalid-email"
**When** createMember mutation executes
**Then** validation error is raised
**And** error message is "Invalid email format: invalid-email"
**And** no database record is created
#### Scenario: Create member with invalid phone format
**Given** client provides phone "123-456"
**When** createMember mutation executes
**Then** validation error is raised
**And** error message is "Invalid phone format: 123-456"
**And** no database record is created
#### Scenario: Create member with duplicate email (allowed)
**Given** member exists with email "john@example.com"
**When** client creates another member with email "john@example.com"
**Then** new member is created successfully
**And** both members coexist in database
**And** no uniqueness constraint error occurs
#### Scenario: Create member without apartment number
**Given** client omits apartment_number field
**When** createMember mutation executes
**Then** member is created with apartment_number=None
**And** all other fields are populated correctly
#### Scenario: Create member with only firstName (minimal data)
**Given** client provides only first_name="Jane"
**When** createMember mutation executes
**Then** new Member record is created in database
**And** member.first_name is "Jane"
**And** member.last_name is None
**And** member.email is None
**And** member.phone is None
**And** all address fields (street, zip, city, country, apartment_number) are None
**And** created_at and updated_at are populated
#### Scenario: Create member with optional email (valid format)
**Given** client provides first_name="John" and email="john@example.com"
**When** createMember mutation executes
**Then** email format is validated
**And** member is created with validated email
**And** other optional fields are None
#### Scenario: Create member with optional email (invalid format)
**Given** client provides first_name="John" and email="invalid"
**When** createMember mutation executes
**Then** validation error is raised
**And** error message is "Invalid email format: invalid"
**And** no database record is created
### Requirement: Read member by ID
The system MUST allow fetching a single member by unique identifier with all fields populated.
#### Scenario: Get existing member by ID
**Given** member exists with id=1
**When** member(id=1) query executes
**Then** Member object is returned
**And** all fields match database record
**And** timestamps are in ISO 8601 format
#### Scenario: Get member with non-existent ID
**Given** no member exists with id=999
**When** member(id=999) query executes
**Then** None is returned
**And** no exception is raised
### Requirement: List all members with sorting
The system MUST allow fetching all members sorted alphabetically by last name (with nulls last), then first name.
#### Scenario: List members in sorted order
**Given** members exist:
- id=1: firstName="Alice", lastName="Smith"
- id=2: firstName="Bob", lastName="Jones"
- id=3: firstName="Charlie", lastName="Smith"
- id=4: firstName="Diana", lastName=None
**When** members query executes
**Then** members are returned in order: Jones, Smith (Alice), Smith (Charlie), Diana (null lastName)
**And** sorting is case-insensitive
**And** members with null lastName appear at the end
#### Scenario: List members when database is empty
**Given** no members exist in database
**When** members query executes
**Then** empty list is returned
**And** no error is raised
#### Scenario: List members returns all fields
**Given** members exist in database
**When** members query requests all fields
**Then** each member includes id, name, address, contact, timestamps
**And** no fields are null except apartmentNumber (if not provided)
### Requirement: Update member with partial field changes
The system MUST allow updating specific member fields while preserving unchanged fields and refreshing the updated_at timestamp.
#### Scenario: Update member email
**Given** member exists with id=1, email="old@example.com"
**When** updateMember mutation executes with:
```python
{"id": 1, "email": "new@example.com"}
```
**Then** member.email is updated to "new@example.com"
**And** member.updated_at is refreshed to current timestamp
**And** member.created_at remains unchanged
**And** all other fields remain unchanged
#### Scenario: Update multiple fields simultaneously
**Given** member exists with id=1
**When** updateMember mutation provides phone, street, and city
**Then** all three fields are updated
**And** other fields remain unchanged
**And** updated_at is refreshed
#### Scenario: Update member with invalid email
**Given** member exists with id=1
**When** updateMember mutation provides email="invalid"
**Then** validation error is raised
**And** database record is not modified
**And** error message indicates invalid email format
#### Scenario: Update non-existent member
**Given** no member exists with id=999
**When** updateMember(id=999) mutation executes
**Then** error is raised
**And** error message is "Member with ID 999 not found"
#### Scenario: Update with no field changes
**Given** member exists with id=1
**When** updateMember mutation provides only id (no other fields)
**Then** no fields are modified
**And** updated_at is NOT refreshed (no actual changes)
### Requirement: Delete member from system
The system MUST allow permanent deletion of member records by ID.
#### Scenario: Delete existing member
**Given** member exists with id=1
**When** deleteMember(id=1) mutation executes
**Then** member is removed from database
**And** mutation returns true
**When** subsequent query for id=1 executes
**Then** None is returned
#### Scenario: Delete non-existent member
**Given** no member exists with id=999
**When** deleteMember(id=999) mutation executes
**Then** error is raised
**And** error message is "Member with ID 999 not found"
**And** mutation returns false or raises exception
### Requirement: Seed database with sample member
The system MUST provide an idempotent script to populate database with one sample member for development and testing.
**Sample member data:**
- first_name: "Jane"
- last_name: "Doe"
- street: None (demonstrates optional address fields)
- apartment_number: None
- zip: None
- city: None
- country: None
- email: "jane.doe@example.com"
- phone: None (demonstrates optional phone)
#### Scenario: Seed script creates sample member
**Given** database schema exists (migrations applied)
**And** no member exists with email "jane.doe@example.com"
**When** seed script executes
**Then** sample member is created in database
**And** script prints "Sample member created successfully"
**And** script exits with code 0
#### Scenario: Seed script is idempotent
**Given** sample member already exists with email "jane.doe@example.com"
**When** seed script executes again
**Then** no new member is created
**And** existing member is not modified
**And** script prints "Sample member already exists"
**And** script exits with code 0
#### Scenario: Seed script verifies sample member
**Given** seed script has completed
**When** query executes for email "jane.doe@example.com"
**Then** member exists with all sample data fields populated
**And** created_at and updated_at timestamps are valid
### Requirement: Input validation with clear error messages
The system MUST validate all inputs and provide actionable error messages identifying the validation failure.
#### Scenario: Empty required field error
**Given** createMember mutation has firstName=""
**When** mutation executes
**Then** validation error is raised
**And** error message is "firstName cannot be empty"
#### Scenario: Field length exceeds maximum
**Given** createMember mutation has firstName with 150 characters
**When** mutation executes
**Then** validation error is raised
**And** error message indicates maximum length of 100 characters
#### Scenario: Multiple validation errors
**Given** createMember mutation has invalid email AND invalid phone
**When** mutation executes
**Then** validation errors for both fields are returned
**And** error messages clearly identify each invalid field