Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:28:07 +08:00
commit 80987b1934
15 changed files with 8486 additions and 0 deletions

763
commands/ruby-optimize.md Normal file
View File

@@ -0,0 +1,763 @@
---
description: Analyze and optimize Ruby code for performance, memory usage, and idiomatic patterns
---
# Ruby Optimize Command
Analyzes Ruby code and provides optimization recommendations for performance, memory usage, code readability, and idiomatic Ruby patterns.
## Arguments
- **$1: path** (required) - File or directory path to optimize
- **$2: focus** (optional) - Optimization focus: `performance`, `memory`, `readability`, or `all` (default: `all`)
## Usage Examples
```bash
# Analyze and optimize all aspects
/ruby-optimize app/models/user.rb
# Focus on performance only
/ruby-optimize app/services/ performance
# Focus on memory optimization
/ruby-optimize lib/data_processor.rb memory
# Focus on readability and idioms
/ruby-optimize app/ readability
# Optimize entire project
/ruby-optimize . all
```
## Workflow
### Step 1: Profile and Analyze Code
**Discovery Phase:**
1. Parse Ruby files in specified path
2. Identify methods and code patterns
3. Detect performance anti-patterns
4. Analyze memory allocation patterns
5. Check for idiomatic Ruby usage
6. Measure complexity metrics
**Analysis Tools:**
```ruby
# Use Ruby parser
require 'parser/current'
# AST analysis for pattern detection
ast = Parser::CurrentRuby.parse(source_code)
# Complexity analysis
require 'flog'
flog = Flog.new
flog.flog(file_path)
```
### Step 2: Performance Analysis
**Detect Performance Anti-Patterns:**
**1. Inefficient Enumeration:**
```ruby
# ISSUE: Using each when map is appropriate
def process_users
result = []
users.each do |user|
result << user.name.upcase
end
result
end
# OPTIMIZED: Use map
def process_users
users.map { |user| user.name.upcase }
end
# Benchmark improvement: 15-20% faster, less memory
```
**2. Repeated Object Creation:**
```ruby
# ISSUE: Creating regex in loop
def filter_emails(emails)
emails.select { |email| email.match(/@gmail\.com/) }
end
# OPTIMIZED: Create regex once
EMAIL_PATTERN = /@gmail\.com/
def filter_emails(emails)
emails.select { |email| email.match(EMAIL_PATTERN) }
end
# Benchmark improvement: 30-40% faster for large datasets
```
**3. N+1 Query Detection:**
```ruby
# ISSUE: N+1 queries
def user_with_posts
users = User.all
users.map do |user|
{
name: user.name,
posts_count: user.posts.count # Separate query for each user
}
end
end
# OPTIMIZED: Eager load or use counter cache
def user_with_posts
users = User.eager(:posts).all
users.map do |user|
{
name: user.name,
posts_count: user.posts.count
}
end
end
# Or with counter cache
def user_with_posts
users = User.all
users.map do |user|
{
name: user.name,
posts_count: user.posts_count # From counter cache
}
end
end
# Benchmark improvement: 10-100x faster depending on data size
```
**4. Inefficient String Building:**
```ruby
# ISSUE: String concatenation in loop
def build_csv(records)
csv = ""
records.each do |record|
csv += "#{record.id},#{record.name}\n"
end
csv
end
# OPTIMIZED: Use array join or StringIO
def build_csv(records)
records.map { |r| "#{r.id},#{r.name}" }.join("\n")
end
# Or for very large datasets
require 'stringio'
def build_csv(records)
StringIO.new.tap do |io|
records.each do |record|
io.puts "#{record.id},#{record.name}"
end
end.string
end
# Benchmark improvement: 5-10x faster for large datasets
```
**5. Unnecessary Sorting:**
```ruby
# ISSUE: Sorting entire collection when only need max/min
def highest_score(users)
users.sort_by(&:score).last
end
# OPTIMIZED: Use max_by
def highest_score(users)
users.max_by(&:score)
end
# Benchmark improvement: O(n) vs O(n log n)
```
**6. Block Performance:**
```ruby
# ISSUE: Symbol#to_proc with arguments
users.map { |u| u.name.upcase }
# OPTIMIZED: Use method chaining where possible
users.map(&:name).map(&:upcase)
# ISSUE: Creating proc in loop
items.select { |item| item.active? }
# OPTIMIZED: Use symbol to_proc
items.select(&:active?)
# Benchmark improvement: 10-15% faster
```
**7. Hash Access Patterns:**
```ruby
# ISSUE: Checking key and accessing value separately
if hash.key?(:name)
value = hash[:name]
process(value)
end
# OPTIMIZED: Use fetch or safe navigation
if value = hash[:name]
process(value)
end
# Or with default
value = hash.fetch(:name, default_value)
process(value)
# ISSUE: Using Hash#merge in loop
result = {}
items.each do |item|
result = result.merge(item.to_hash)
end
# OPTIMIZED: Use Hash#merge! or each_with_object
result = items.each_with_object({}) do |item, hash|
hash.merge!(item.to_hash)
end
# Benchmark improvement: 2-3x faster
```
### Step 3: Memory Optimization
**Detect Memory Issues:**
**1. String Allocation:**
```ruby
# ISSUE: Creating new strings in loop
1000.times do
hash['key'] = value # Creates new 'key' string each time
end
# OPTIMIZED: Use symbols or frozen strings
1000.times do
hash[:key] = value # Reuses same symbol
end
# Or with frozen string literal
# frozen_string_literal: true
# Memory saved: ~40 bytes per string
```
**2. Array/Hash Allocation:**
```ruby
# ISSUE: Building large array without size hint
data = []
10_000.times do |i|
data << i
end
# OPTIMIZED: Preallocate size
data = Array.new(10_000)
10_000.times do |i|
data[i] = i
end
# Or use a different approach
data = (0...10_000).to_a
# Memory improvement: Fewer reallocations
```
**3. Object Copying:**
```ruby
# ISSUE: Unnecessary duplication
def process(data)
temp = data.dup
temp.map! { |item| item * 2 }
temp
end
# OPTIMIZED: Use map without dup if original not needed
def process(data)
data.map { |item| item * 2 }
end
# Memory saved: Full array copy avoided
```
**4. Lazy Evaluation:**
```ruby
# ISSUE: Loading everything into memory
File.readlines('large_file.txt').each do |line|
process(line)
end
# OPTIMIZED: Process line by line
File.foreach('large_file.txt') do |line|
process(line)
end
# Or use lazy enumeration
File.readlines('large_file.txt').lazy.each do |line|
process(line)
end
# Memory saved: File size - line size
```
**5. Memoization Leaks:**
```ruby
# ISSUE: Unbounded memoization cache
def expensive_calculation(input)
@cache ||= {}
@cache[input] ||= perform_calculation(input)
end
# OPTIMIZED: Use bounded cache (LRU)
require 'lru_redux'
def expensive_calculation(input)
@cache ||= LruRedux::Cache.new(1000)
@cache.getset(input) { perform_calculation(input) }
end
# Memory saved: Prevents cache from growing unbounded
```
### Step 4: Readability and Idiom Analysis
**Detect Non-Idiomatic Code:**
**1. Conditional Assignment:**
```ruby
# NON-IDIOMATIC
if user.name.nil?
user.name = 'Guest'
end
# IDIOMATIC
user.name ||= 'Guest'
# NON-IDIOMATIC
if value == nil
value = default
else
value = value
end
# IDIOMATIC
value ||= default
```
**2. Safe Navigation:**
```ruby
# NON-IDIOMATIC
if user && user.profile && user.profile.avatar
display(user.profile.avatar)
end
# IDIOMATIC
display(user&.profile&.avatar) if user&.profile&.avatar
# or
if avatar = user&.profile&.avatar
display(avatar)
end
```
**3. Enumerable Methods:**
```ruby
# NON-IDIOMATIC
found = nil
users.each do |user|
if user.active?
found = user
break
end
end
# IDIOMATIC
found = users.find(&:active?)
# NON-IDIOMATIC
actives = []
users.each do |user|
actives << user if user.active?
end
# IDIOMATIC
actives = users.select(&:active?)
# NON-IDIOMATIC
total = 0
prices.each { |price| total += price }
# IDIOMATIC
total = prices.sum
# or
total = prices.reduce(:+)
```
**4. Guard Clauses:**
```ruby
# NON-IDIOMATIC
def process(user)
if user
if user.active?
if user.verified?
# Main logic here
perform_action(user)
end
end
end
end
# IDIOMATIC
def process(user)
return unless user
return unless user.active?
return unless user.verified?
perform_action(user)
end
```
**5. Pattern Matching (Ruby 3.0+):**
```ruby
# LESS IDIOMATIC (Ruby 3.0+)
if response.is_a?(Hash) && response[:status] == 'success'
handle_success(response[:data])
elsif response.is_a?(Hash) && response[:status] == 'error'
handle_error(response[:error])
end
# MORE IDIOMATIC (Ruby 3.0+)
case response
in { status: 'success', data: }
handle_success(data)
in { status: 'error', error: }
handle_error(error)
end
```
**6. Block Syntax:**
```ruby
# NON-IDIOMATIC: do/end for single line
users.map do |u| u.name end
# IDIOMATIC: braces for single line
users.map { |u| u.name }
# NON-IDIOMATIC: braces for multi-line
users.select { |u|
u.active? &&
u.verified?
}
# IDIOMATIC: do/end for multi-line
users.select do |u|
u.active? && u.verified?
end
```
**7. String Interpolation:**
```ruby
# NON-IDIOMATIC
"Hello " + user.name + "!"
# IDIOMATIC
"Hello #{user.name}!"
# NON-IDIOMATIC
'Total: ' + total.to_s
# IDIOMATIC
"Total: #{total}"
```
### Step 5: Generate Benchmarks
**Create Benchmark Comparisons:**
```ruby
# Generated benchmark file: benchmarks/optimization_comparison.rb
require 'benchmark'
puts "Performance Comparison"
puts "=" * 50
# Original implementation
def original_method
# Original code
end
# Optimized implementation
def optimized_method
# Optimized code
end
Benchmark.bm(20) do |x|
x.report("Original:") do
10_000.times { original_method }
end
x.report("Optimized:") do
10_000.times { optimized_method }
end
end
# Memory profiling
require 'memory_profiler'
puts "\nMemory Comparison"
puts "=" * 50
report = MemoryProfiler.report do
original_method
end
puts "Original Memory Usage:"
puts " Total allocated: #{report.total_allocated_memsize} bytes"
puts " Total retained: #{report.total_retained_memsize} bytes"
report = MemoryProfiler.report do
optimized_method
end
puts "\nOptimized Memory Usage:"
puts " Total allocated: #{report.total_allocated_memsize} bytes"
puts " Total retained: #{report.total_retained_memsize} bytes"
```
### Step 6: Generate Optimization Report
**Comprehensive Report Structure:**
```
================================================================================
RUBY OPTIMIZATION REPORT
================================================================================
File: app/services/data_processor.rb
Focus: all
Date: 2024-01-15
--------------------------------------------------------------------------------
SUMMARY
--------------------------------------------------------------------------------
Total Issues Found: 18
Performance: 8
Memory: 5
Readability: 5
Potential Improvements:
Estimated Speed Gain: 2.5x faster
Estimated Memory Reduction: 45%
Code Quality: +15 readability score
--------------------------------------------------------------------------------
PERFORMANCE OPTIMIZATIONS
--------------------------------------------------------------------------------
1. Inefficient Enumeration (Line 23)
Severity: Medium
Impact: 20% speed improvement
Current:
result = []
users.each { |u| result << u.name.upcase }
result
Optimized:
users.map { |u| u.name.upcase }
Benchmark:
Before: 1.45ms per 1000 items
After: 1.15ms per 1000 items
Improvement: 20.7% faster
2. N+1 Query Pattern (Line 45)
Severity: High
Impact: 10-100x speed improvement
Current:
users.map { |u| { name: u.name, posts: u.posts.count } }
Optimized:
users.eager(:posts).map { |u| { name: u.name, posts: u.posts.count } }
Benchmark:
Before: 1250ms for 100 users with 10 posts each
After: 25ms for 100 users with 10 posts each
Improvement: 50x faster
[... more performance issues ...]
--------------------------------------------------------------------------------
MEMORY OPTIMIZATIONS
--------------------------------------------------------------------------------
1. String Allocation in Loop (Line 67)
Severity: Medium
Impact: 400 bytes saved per 1000 iterations
Current:
1000.times { hash['key'] = value }
Optimized:
1000.times { hash[:key] = value }
Memory:
Before: 40KB allocated
After: 160 bytes allocated
Savings: 99.6%
[... more memory issues ...]
--------------------------------------------------------------------------------
READABILITY IMPROVEMENTS
--------------------------------------------------------------------------------
1. Non-Idiomatic Conditional (Line 89)
Severity: Low
Impact: Improved code clarity
Current:
if user.name.nil?
user.name = 'Guest'
end
Idiomatic:
user.name ||= 'Guest'
[... more readability issues ...]
--------------------------------------------------------------------------------
COMPLEXITY METRICS
--------------------------------------------------------------------------------
Method Complexity (Flog scores):
process_data: 45.2 (High - consider refactoring)
transform_records: 23.1 (Medium)
validate_input: 8.5 (Low)
Recommendations:
- Extract methods from process_data to reduce complexity
- Consider using service objects for complex operations
--------------------------------------------------------------------------------
BENCHMARKS
--------------------------------------------------------------------------------
File Generated: benchmarks/data_processor_comparison.rb
Run benchmarks:
ruby benchmarks/data_processor_comparison.rb
Expected Results:
Original: 2.450s
Optimized: 0.980s
Speedup: 2.5x
--------------------------------------------------------------------------------
ACTION ITEMS
--------------------------------------------------------------------------------
High Priority:
1. Fix N+1 query in line 45 (50x performance gain)
2. Optimize string building in line 67 (99% memory reduction)
3. Refactor process_data method (complexity: 45.2)
Medium Priority:
4. Use map instead of each+append (20% speed gain)
5. Cache regex patterns (30% speed gain)
6. Implement guard clauses in validate_input
Low Priority:
7. Use idiomatic Ruby patterns throughout
8. Apply consistent block syntax
9. Improve variable naming
--------------------------------------------------------------------------------
AUTOMATIC FIXES
--------------------------------------------------------------------------------
Low-risk changes that can be auto-applied:
- String to symbol conversion (5 occurrences)
- each to map conversion (3 occurrences)
- Conditional to ||= conversion (4 occurrences)
Apply automatic fixes? [y/N]
================================================================================
END REPORT
================================================================================
```
### Step 7: Optional - Apply Automatic Fixes
**Safe Transformations:**
For low-risk, well-defined improvements:
```ruby
# Create optimized version of file
# app/services/data_processor_optimized.rb
# Apply automatic transformations:
# - String literals to symbols
# - each+append to map
# - if/nil? to ||=
# - Block syntax corrections
# Generate diff
# Show side-by-side comparison
# Offer to replace original or keep both
```
## Output Formats
### Console Output
- Colored severity indicators (red/yellow/green)
- Progress indicator during analysis
- Summary statistics
- Top issues highlighted
### Report Files
- Detailed markdown report
- Generated benchmark files
- Optional optimized code files
- Diff files for review
### JSON Output (Optional)
```json
{
"file": "app/services/data_processor.rb",
"summary": {
"total_issues": 18,
"performance": 8,
"memory": 5,
"readability": 5
},
"issues": [
{
"type": "performance",
"severity": "high",
"line": 45,
"description": "N+1 query pattern",
"impact": "50x speed improvement",
"suggestion": "Use eager loading"
}
]
}
```
## Error Handling
- Handle invalid Ruby syntax gracefully
- Skip non-Ruby files
- Report files that cannot be parsed
- Handle missing dependencies
- Warn about risky optimizations
- Preserve backups before modifications

647
commands/sinatra-review.md Normal file
View File

@@ -0,0 +1,647 @@
---
description: Review Sinatra code for security issues, performance problems, route conflicts, and framework best practices
---
# Sinatra Review Command
Performs comprehensive code review of Sinatra applications, identifying security vulnerabilities, performance issues, routing conflicts, and deviations from best practices.
## Arguments
- **$1: path** (optional) - Path to review (defaults to current directory)
## Usage Examples
```bash
# Review current directory
/sinatra-review
# Review specific directory
/sinatra-review /path/to/sinatra-app
# Review specific file
/sinatra-review app/controllers/users_controller.rb
```
## Workflow
### Step 1: Scan and Identify Application Files
**Discovery Phase:**
1. Locate `config.ru` to identify Rack application
2. Find Sinatra application files (controllers, routes)
3. Identify application structure (classic vs modular)
4. Scan for middleware configuration
5. Locate view templates and helpers
6. Find configuration files
7. Identify database and model files
**File Patterns to Search:**
```bash
# Application files
*.rb files inheriting from Sinatra::Base
config.ru
app.rb (classic style)
app/controllers/*.rb
lib/**/*.rb
# View templates
views/**/*.erb
views/**/*.haml
views/**/*.slim
# Configuration
config/*.rb
Gemfile
.env files
```
### Step 2: Analyze Route Definitions
**Route Conflict Detection:**
Check for:
1. **Duplicate routes** with same path and HTTP method
2. **Overlapping routes** where order matters (specific before generic)
3. **Missing route constraints** leading to ambiguous matching
4. **Wildcard route conflicts**
**Examples of Issues:**
```ruby
# ISSUE: Route order conflict
get '/users/new' do
# Never reached because of wildcard below
end
get '/users/:id' do
# This catches /users/new
end
# FIX: Specific routes before wildcards
get '/users/new' do
# Now reached first
end
get '/users/:id' do
# Only catches other IDs
end
# ISSUE: Duplicate routes
get '/api/users' do
# First definition
end
get '/api/users' do
# Overwrites first - only this runs
end
# ISSUE: Missing validation
get '/users/:id' do
user = User.find(params[:id]) # What if id is not numeric?
end
# FIX: Add validation
get '/users/:id', id: /\d+/ do
user = User.find(params[:id])
end
```
**Route Analysis Report:**
```
Route Analysis:
Total routes: 25
GET: 15, POST: 5, PUT: 3, DELETE: 2
⚠ Warnings:
- Route order issue in app/controllers/users_controller.rb:15
GET /users/:id should be after GET /users/new
- Missing parameter validation in app/controllers/posts_controller.rb:32
Route GET /posts/:id should validate :id is numeric
```
### Step 3: Security Analysis
**Security Checklist:**
**1. CSRF Protection:**
```ruby
# CHECK: Is CSRF protection enabled?
use Rack::Protection
# or
use Rack::Protection::AuthenticityToken
# ISSUE: Missing CSRF for POST/PUT/DELETE
post '/users' do
User.create(params[:user]) # Vulnerable to CSRF
end
# FIX: Ensure Rack::Protection is enabled
```
**2. XSS Prevention:**
```ruby
# CHECK: Are templates auto-escaping HTML?
# ERB: Use <%= %> (escapes) not <%== %> (raw)
# ISSUE: Raw user input in template
<div><%== @user.bio %></div>
# FIX: Escape user input
<div><%= @user.bio %></div>
# CHECK: JSON responses properly encoded
# ISSUE: Manual JSON creation
get '/api/users' do
"{ \"name\": \"#{user.name}\" }" # XSS if name contains quotes
end
# FIX: Use JSON library
get '/api/users' do
json({ name: user.name })
end
```
**3. SQL Injection:**
```ruby
# ISSUE: String interpolation in queries
DB["SELECT * FROM users WHERE email = '#{params[:email]}'"]
# FIX: Use parameterized queries
DB["SELECT * FROM users WHERE email = ?", params[:email]]
# ISSUE: Unsafe ActiveRecord
User.where("email = '#{params[:email]}'")
# FIX: Use hash conditions
User.where(email: params[:email])
```
**4. Authentication & Authorization:**
```ruby
# CHECK: Protected routes have authentication
# ISSUE: Admin route without auth check
delete '/users/:id' do
User.find(params[:id]).destroy # No auth check!
end
# FIX: Add authentication
before '/admin/*' do
halt 401 unless current_user&.admin?
end
# CHECK: Session security
# ISSUE: Weak session configuration
use Rack::Session::Cookie, secret: 'easy'
# FIX: Strong secret and secure flags
use Rack::Session::Cookie,
secret: ENV['SESSION_SECRET'], # Long random string
same_site: :strict,
httponly: true,
secure: production?
```
**5. Mass Assignment:**
```ruby
# ISSUE: Accepting all params
User.create(params)
# FIX: Whitelist allowed attributes
def user_params
params.slice(:name, :email, :bio)
end
User.create(user_params)
```
**6. File Upload Security:**
```ruby
# ISSUE: Unrestricted file uploads
post '/upload' do
File.write("uploads/#{params[:file][:filename]}", params[:file][:tempfile].read)
end
# FIX: Validate file type and sanitize filename
post '/upload' do
file = params[:file]
# Validate content type
halt 400 unless ['image/jpeg', 'image/png'].include?(file[:type])
# Sanitize filename
filename = File.basename(file[:filename]).gsub(/[^a-zA-Z0-9\._-]/, '')
# Save with random name
secure_name = "#{SecureRandom.hex}-#{filename}"
File.write("uploads/#{secure_name}", file[:tempfile].read)
end
```
**7. Information Disclosure:**
```ruby
# ISSUE: Detailed error messages in production
configure :production do
set :show_exceptions, true # Exposes stack traces
end
# FIX: Hide errors in production
configure :production do
set :show_exceptions, false
set :dump_errors, false
end
error do
log_error(env['sinatra.error'])
json({ error: 'Internal server error' }, 500)
end
```
**Security Report:**
```
Security Analysis:
✓ CSRF protection enabled (Rack::Protection)
✓ Session configured securely
⚠ Potential Issues:
- SQL injection risk in app/models/user.rb:45
- Raw HTML output in views/profile.erb:12
- Missing authentication check in app/controllers/admin_controller.rb:23
- Weak session secret detected
Critical: 1
High: 2
Medium: 3
Low: 2
```
### Step 4: Review Middleware Configuration
**Middleware Analysis:**
Check for:
1. **Missing essential middleware** (Protection, CommonLogger)
2. **Incorrect ordering** (e.g., session after auth)
3. **Performance issues** (e.g., no compression)
4. **Security middleware** properly configured
**Common Issues:**
```ruby
# ISSUE: Missing compression
# FIX: Add Rack::Deflater
use Rack::Deflater
# ISSUE: Session middleware after authentication
use TokenAuth
use Rack::Session::Cookie # Session needed by auth!
# FIX: Session before authentication
use Rack::Session::Cookie
use TokenAuth
# ISSUE: No security headers
# FIX: Add Rack::Protection
use Rack::Protection, except: [:session_hijacking]
# ISSUE: Static file serving after application
run MyApp
use Rack::Static # Never reached!
# FIX: Static before application
use Rack::Static, urls: ['/css', '/js'], root: 'public'
run MyApp
```
**Middleware Report:**
```
Middleware Configuration:
✓ Rack::CommonLogger (logging)
✓ Rack::Session::Cookie (sessions)
✓ Rack::Protection (security)
⚠ Warnings:
- Missing Rack::Deflater (compression)
- Middleware order issue: Session should be before CustomAuth
- Consider adding Rack::Attack for rate limiting
```
### Step 5: Performance Assessment
**Performance Patterns to Check:**
**1. Database Query Optimization:**
```ruby
# ISSUE: N+1 queries
get '/users' do
users = User.all
users.map { |u| { name: u.name, posts: u.posts.count } }
# Queries DB for each user's posts
end
# FIX: Eager load or use counter cache
get '/users' do
users = User.eager(:posts).all
users.map { |u| { name: u.name, posts: u.posts.count } }
end
# ISSUE: Loading entire collection
get '/users' do
json User.all.map(&:to_hash) # Load all users in memory
end
# FIX: Paginate
get '/users' do
page = params[:page]&.to_i || 1
per_page = 50
users = User.limit(per_page).offset((page - 1) * per_page)
json users.map(&:to_hash)
end
```
**2. Caching Opportunities:**
```ruby
# ISSUE: Expensive operation on every request
get '/stats' do
json calculate_expensive_stats # Takes 2 seconds
end
# FIX: Add caching
get '/stats' do
stats = cache.fetch('stats', expires_in: 300) do
calculate_expensive_stats
end
json stats
end
# ISSUE: No HTTP caching headers
get '/public/data' do
json PublicData.all
end
# FIX: Add cache control
get '/public/data' do
cache_control :public, max_age: 3600
json PublicData.all
end
```
**3. Response Optimization:**
```ruby
# ISSUE: Rendering large response synchronously
get '/large-export' do
csv = generate_large_csv # Blocks for 30 seconds
send_file csv
end
# FIX: Stream or queue as background job
get '/large-export' do
stream do |out|
CSV.generate(out) do |csv|
User.find_each do |user|
csv << user.to_csv_row
end
end
end
end
```
**Performance Report:**
```
Performance Analysis:
⚠ Issues Detected:
- Potential N+1 query in app/controllers/users_controller.rb:42
- Missing pagination in GET /api/posts (returns all records)
- No caching headers on GET /api/public/data
- Expensive operation in GET /stats without caching
Recommendations:
- Add database query optimization (eager loading)
- Implement pagination for collection endpoints
- Add HTTP caching headers for static content
- Consider Redis caching for expensive operations
```
### Step 6: Error Handling Review
**Error Handling Patterns:**
```ruby
# ISSUE: No error handlers defined
get '/users/:id' do
User.find(params[:id]) # Raises if not found, shows stack trace
end
# FIX: Add error handlers
error ActiveRecord::RecordNotFound do
json({ error: 'Not found' }, 404)
end
error 404 do
json({ error: 'Endpoint not found' }, 404)
end
error 500 do
json({ error: 'Internal server error' }, 500)
end
# ISSUE: Not handling exceptions in routes
post '/users' do
User.create!(params) # Raises on validation error
end
# FIX: Handle exceptions
post '/users' do
user = User.create(params)
if user.persisted?
json(user.to_hash, 201)
else
json({ errors: user.errors }, 422)
end
end
```
### Step 7: Testing Coverage
**Test Analysis:**
Check for:
1. Test files exist
2. Route coverage
3. Error case testing
4. Integration vs unit tests
5. Test quality and patterns
**Report:**
```
Testing Analysis:
Framework: RSpec
Total specs: 45
Coverage: 78%
⚠ Missing Tests:
- No tests for POST /api/users
- Error cases not tested in app/controllers/posts_controller.rb
- Missing integration tests for authentication flow
Recommendations:
- Add tests for all POST/PUT/DELETE routes
- Test error scenarios (404, 422, 500)
- Increase coverage to 90%+
```
### Step 8: Generate Comprehensive Report
**Final Report Structure:**
```
================================================================================
SINATRA CODE REVIEW REPORT
================================================================================
Project: my-sinatra-app
Path: /path/to/app
Date: 2024-01-15
Reviewer: Sinatra Review Tool
--------------------------------------------------------------------------------
SUMMARY
--------------------------------------------------------------------------------
Total Issues: 15
Critical: 2
High: 4
Medium: 6
Low: 3
Categories:
Security: 5 issues
Performance: 4 issues
Best Practices: 6 issues
--------------------------------------------------------------------------------
CRITICAL ISSUES
--------------------------------------------------------------------------------
1. SQL Injection Vulnerability
Location: app/models/user.rb:45
Severity: Critical
Issue:
DB["SELECT * FROM users WHERE email = '#{email}'"]
Fix:
DB["SELECT * FROM users WHERE email = ?", email]
Impact: Attacker can execute arbitrary SQL queries
2. Missing Authentication on Admin Route
Location: app/controllers/admin_controller.rb:23
Severity: Critical
Issue:
delete '/users/:id' do
User.find(params[:id]).destroy
end
Fix:
before '/admin/*' do
authenticate_admin!
end
Impact: Unauthorized users can delete records
--------------------------------------------------------------------------------
HIGH PRIORITY ISSUES
--------------------------------------------------------------------------------
[List high priority issues...]
--------------------------------------------------------------------------------
RECOMMENDATIONS
--------------------------------------------------------------------------------
Security:
- Enable Rack::Protection::AuthenticityToken for CSRF
- Rotate session secret to strong random value
- Implement rate limiting with Rack::Attack
- Add Content-Security-Policy headers
Performance:
- Add Rack::Deflater for response compression
- Implement caching strategy (Redis or Memcached)
- Add pagination to collection endpoints
- Optimize database queries (N+1 issues)
Testing:
- Increase test coverage to 90%+
- Add integration tests for critical flows
- Test error scenarios
- Add security-focused tests
Best Practices:
- Extract business logic to service objects
- Use helpers for repeated code
- Implement proper error handling
- Add API documentation
--------------------------------------------------------------------------------
DETAILED FINDINGS
--------------------------------------------------------------------------------
[Full list of all issues with locations, descriptions, and fixes]
================================================================================
END REPORT
================================================================================
```
## Review Categories
### Security
- CSRF protection
- XSS prevention
- SQL injection
- Authentication/Authorization
- Session security
- Mass assignment
- File upload security
- Information disclosure
- Secure headers
### Performance
- Database query optimization
- N+1 queries
- Caching opportunities
- Response optimization
- Static asset handling
- Connection pooling
### Best Practices
- Route organization
- Error handling
- Code organization
- Helper usage
- Configuration management
- Logging
- Documentation
### Testing
- Test coverage
- Test quality
- Missing tests
- Test organization
## Output Format
- Console output with colored severity indicators
- Detailed report with file locations and line numbers
- Suggested fixes with code examples
- Priority-sorted issue list
- Summary statistics
## Error Handling
- Handle non-Sinatra Ruby applications gracefully
- Report when application structure cannot be determined
- Skip non-readable files
- Handle parse errors in Ruby files

View File

@@ -0,0 +1,654 @@
---
description: Scaffold new Sinatra applications with modern structure, best practices, testing setup, and deployment configuration
---
# Sinatra Scaffold Command
Scaffolds a new Sinatra application with modern project structure, testing framework, and deployment configuration.
## Arguments
- **$1: project-name** (required) - Name of the project/application
- **$2: type** (optional) - Application type: `classic`, `modular`, or `api` (default: `modular`)
- **$3: options** (optional) - JSON string with configuration options:
- `testing`: `rspec` or `minitest` (default: `rspec`)
- `database`: `sequel`, `activerecord`, or `none` (default: `sequel`)
- `frontend`: `none`, `erb`, or `haml` (default: `erb`)
## Usage Examples
```bash
# Basic modular app with defaults
/sinatra-scaffold my-app
# Classic app with RSpec and no database
/sinatra-scaffold simple-app classic '{"testing":"rspec","database":"none","frontend":"erb"}'
# API-only app with Minitest and ActiveRecord
/sinatra-scaffold api-service api '{"testing":"minitest","database":"activerecord","frontend":"none"}'
# Full-featured modular app
/sinatra-scaffold webapp modular '{"testing":"rspec","database":"sequel","frontend":"haml"}'
```
## Workflow
### Step 1: Validate and Initialize
**Actions:**
1. Validate project name format (alphanumeric, hyphens, underscores)
2. Check if directory already exists
3. Parse and validate options JSON
4. Create project directory structure
**Validation:**
```bash
# Check project name
if [[ ! "$PROJECT_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Error: Invalid project name. Use alphanumeric characters, hyphens, or underscores."
exit 1
fi
# Check if directory exists
if [ -d "$PROJECT_NAME" ]; then
echo "Error: Directory '$PROJECT_NAME' already exists."
exit 1
fi
```
### Step 2: Create Directory Structure
**Classic Structure:**
```
project-name/
├── app.rb
├── config.ru
├── Gemfile
├── Rakefile
├── config/
│ └── environment.rb
├── public/
│ ├── css/
│ ├── js/
│ └── images/
├── views/
│ ├── layout.erb
│ └── index.erb
├── spec/ or test/
└── README.md
```
**Modular Structure:**
```
project-name/
├── app/
│ ├── controllers/
│ │ ├── application_controller.rb
│ │ └── base_controller.rb
│ ├── models/
│ ├── services/
│ └── helpers/
├── config/
│ ├── environment.rb
│ ├── database.yml (if database selected)
│ └── puma.rb
├── config.ru
├── db/
│ └── migrations/
├── lib/
│ └── tasks/
├── public/
│ ├── css/
│ ├── js/
│ └── images/
├── views/
│ ├── layout.erb
│ └── index.erb
├── spec/ or test/
│ ├── spec_helper.rb
│ └── controllers/
├── Gemfile
├── Rakefile
├── .env.example
├── .gitignore
└── README.md
```
**API Structure:**
```
project-name/
├── app/
│ ├── controllers/
│ │ ├── api_controller.rb
│ │ └── base_controller.rb
│ ├── models/
│ ├── services/
│ └── serializers/
├── config/
│ ├── environment.rb
│ ├── database.yml
│ └── puma.rb
├── config.ru
├── db/
│ └── migrations/
├── lib/
├── spec/ or test/
│ ├── spec_helper.rb
│ ├── requests/
│ └── support/
├── Gemfile
├── Rakefile
├── .env.example
├── .gitignore
└── README.md
```
### Step 3: Generate Gemfile
**Base Dependencies (All Types):**
```ruby
source 'https://rubygems.org'
ruby '~> 3.2'
gem 'sinatra', '~> 3.0'
gem 'sinatra-contrib', '~> 3.0'
gem 'puma', '~> 6.0'
gem 'rake', '~> 13.0'
gem 'dotenv', '~> 2.8'
# Add database gems if selected
# gem 'sequel', '~> 5.0' or gem 'activerecord', '~> 7.0'
# gem 'pg', '~> 1.5' # PostgreSQL
# Add frontend gems if not API
# gem 'haml', '~> 6.0' if haml selected
group :development, :test do
gem 'rspec', '~> 3.12' # or minitest
gem 'rack-test', '~> 2.0'
gem 'rerun', '~> 0.14'
end
group :development do
gem 'pry', '~> 0.14'
end
group :test do
gem 'simplecov', '~> 0.22', require: false
gem 'database_cleaner-sequel', '~> 2.0' # if using Sequel
end
```
**Additional Dependencies by Type:**
For modular/API:
```ruby
gem 'rack-cors', '~> 2.0' # For API
gem 'multi_json', '~> 1.15'
```
For database options:
```ruby
# Sequel
gem 'sequel', '~> 5.0'
gem 'pg', '~> 1.5'
# ActiveRecord
gem 'activerecord', '~> 7.0'
gem 'pg', '~> 1.5'
gem 'sinatra-activerecord', '~> 2.0'
```
### Step 4: Generate Application Files
**Classic App (app.rb):**
```ruby
require 'sinatra'
require 'sinatra/reloader' if development?
require_relative 'config/environment'
get '/' do
erb :index
end
```
**Modular Base Controller (app/controllers/base_controller.rb):**
```ruby
require 'sinatra/base'
require 'sinatra/json'
class BaseController < Sinatra::Base
configure do
set :root, File.expand_path('../..', __dir__)
set :views, Proc.new { File.join(root, 'views') }
set :public_folder, Proc.new { File.join(root, 'public') }
set :show_exceptions, false
set :raise_errors, false
end
configure :development do
require 'sinatra/reloader'
register Sinatra::Reloader
end
helpers do
def json_response(data, status = 200)
halt status, { 'Content-Type' => 'application/json' }, data.to_json
end
end
error do
error = env['sinatra.error']
status 500
json_response({ error: error.message })
end
not_found do
json_response({ error: 'Not found' }, 404)
end
end
```
**Application Controller (app/controllers/application_controller.rb):**
```ruby
require_relative 'base_controller'
class ApplicationController < BaseController
get '/' do
erb :index
end
get '/health' do
json_response({ status: 'ok', timestamp: Time.now.to_i })
end
end
```
**API Controller (for API type):**
```ruby
require_relative 'base_controller'
class ApiController < BaseController
before do
content_type :json
end
# CORS for development
configure :development do
before do
headers 'Access-Control-Allow-Origin' => '*'
end
options '*' do
headers 'Access-Control-Allow-Methods' => 'GET, POST, PUT, PATCH, DELETE, OPTIONS'
headers 'Access-Control-Allow-Headers' => 'Content-Type, Authorization'
200
end
end
get '/' do
json_response({
name: 'API',
version: '1.0',
endpoints: [
{ path: '/health', method: 'GET' }
]
})
end
get '/health' do
json_response({ status: 'healthy', timestamp: Time.now.to_i })
end
end
```
### Step 5: Create Configuration Files
**config.ru:**
```ruby
require_relative 'config/environment'
# Modular
map '/' do
run ApplicationController
end
# API
# map '/api/v1' do
# run ApiController
# end
```
**config/environment.rb:**
```ruby
ENV['RACK_ENV'] ||= 'development'
require 'bundler'
Bundler.require(:default, ENV['RACK_ENV'])
# Load environment variables
require 'dotenv'
Dotenv.load(".env.#{ENV['RACK_ENV']}", '.env')
# Database setup (if selected)
# require_relative 'database'
# Load application files
Dir[File.join(__dir__, '../app/**/*.rb')].sort.each { |file| require file }
```
**config/database.yml (if database selected):**
```yaml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("DB_POOL", 5) %>
host: <%= ENV.fetch("DB_HOST", "localhost") %>
development:
<<: *default
database: <%= ENV.fetch("PROJECT_NAME") %>_development
test:
<<: *default
database: <%= ENV.fetch("PROJECT_NAME") %>_test
production:
<<: *default
database: <%= ENV.fetch("DB_NAME") %>
username: <%= ENV.fetch("DB_USER") %>
password: <%= ENV.fetch("DB_PASSWORD") %>
```
**config/puma.rb:**
```ruby
workers ENV.fetch('WEB_CONCURRENCY', 2)
threads_count = ENV.fetch('MAX_THREADS', 5)
threads threads_count, threads_count
preload_app!
port ENV.fetch('PORT', 3000)
environment ENV.fetch('RACK_ENV', 'development')
on_worker_boot do
# Database reconnection if using ActiveRecord
# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
```
### Step 6: Set Up Testing Framework
**RSpec spec/spec_helper.rb:**
```ruby
ENV['RACK_ENV'] = 'test'
require 'simplecov'
SimpleCov.start
require_relative '../config/environment'
require 'rack/test'
require 'rspec'
# Database cleaner setup (if database)
# require 'database_cleaner/sequel'
RSpec.configure do |config|
config.include Rack::Test::Methods
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
# Database cleaner (if database)
# config.before(:suite) do
# DatabaseCleaner.strategy = :transaction
# DatabaseCleaner.clean_with(:truncation)
# end
#
# config.around(:each) do |example|
# DatabaseCleaner.cleaning do
# example.run
# end
# end
end
```
**Example spec/controllers/application_controller_spec.rb:**
```ruby
require_relative '../spec_helper'
RSpec.describe ApplicationController do
def app
ApplicationController
end
describe 'GET /' do
it 'returns success' do
get '/'
expect(last_response).to be_ok
end
end
describe 'GET /health' do
it 'returns health status' do
get '/health'
expect(last_response).to be_ok
json = JSON.parse(last_response.body)
expect(json['status']).to eq('ok')
end
end
end
```
### Step 7: Create Supporting Files
**.env.example:**
```bash
RACK_ENV=development
PORT=3000
# Database (if selected)
DB_HOST=localhost
DB_NAME=project_name_development
DB_USER=postgres
DB_PASSWORD=
# Session
SESSION_SECRET=your-secret-key-here
# External services
# API_KEY=
```
**.gitignore:**
```
*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/
# Environment files
.env
.env.local
# Database
*.sqlite3
*.db
# Logs
*.log
# Editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
```
**Rakefile:**
```ruby
require_relative 'config/environment'
# Database tasks (if using Sequel)
if defined?(Sequel)
require 'sequel/core'
namespace :db do
desc 'Run migrations'
task :migrate, [:version] do |t, args|
Sequel.extension :migration
db = Sequel.connect(ENV['DATABASE_URL'])
if args[:version]
puts "Migrating to version #{args[:version]}"
Sequel::Migrator.run(db, 'db/migrations', target: args[:version].to_i)
else
puts 'Migrating to latest'
Sequel::Migrator.run(db, 'db/migrations')
end
puts 'Migration complete'
end
end
end
# Testing tasks
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
```
**README.md:**
```markdown
# [Project Name]
[Brief description of the project]
## Setup
1. Install dependencies:
```bash
bundle install
```
2. Set up environment variables:
```bash
cp .env.example .env
# Edit .env with your configuration
```
3. Set up database (if applicable):
```bash
rake db:migrate
```
## Development
Run the application:
```bash
bundle exec rerun 'rackup -p 3000'
```
Or with Puma:
```bash
bundle exec puma -C config/puma.rb
```
## Testing
Run tests:
```bash
bundle exec rspec
```
## Deployment
[Add deployment instructions]
## API Documentation
[Add API documentation if applicable]
```
### Step 8: Initialize Git Repository
**Actions:**
```bash
cd project-name
git init
git add .
git commit -m "Initial commit: Sinatra application scaffold"
```
### Step 9: Install Dependencies
**Actions:**
```bash
bundle install
```
**Verification:**
- Confirm all gems installed successfully
- Check for any dependency conflicts
- Display next steps to user
## Expected Output
```
Creating Sinatra application: my-app
Type: modular
Options: {"testing":"rspec","database":"sequel","frontend":"erb"}
✓ Created directory structure
✓ Generated Gemfile
✓ Created application files
✓ Set up configuration files
✓ Configured RSpec testing
✓ Created supporting files
✓ Initialized git repository
✓ Installed dependencies
Application created successfully!
Next steps:
cd my-app
bundle exec rerun 'rackup -p 3000'
Visit: http://localhost:3000
Tests: bundle exec rspec
```
## Error Handling
- Invalid project name format
- Directory already exists
- Invalid JSON options
- Bundle install failures
- File creation permission errors
## Notes
- All generated code follows Ruby and Sinatra best practices
- Testing framework is fully configured and ready to use
- Development tools (rerun, pry) included for better DX
- Production-ready configuration provided
- Database migrations directory created if database selected
- CORS configured for API applications

860
commands/sinatra-test.md Normal file
View File

@@ -0,0 +1,860 @@
---
description: Generate comprehensive tests for Sinatra routes, middleware, and helpers using RSpec or Minitest
---
# Sinatra Test Command
Generates comprehensive test suites for Sinatra applications including route tests, middleware tests, helper tests, and integration tests using RSpec or Minitest.
## Arguments
- **$1: test-type** (optional) - Type of tests to generate: `routes`, `middleware`, `helpers`, or `all` (default: `all`)
- **$2: framework** (optional) - Testing framework: `rspec` or `minitest` (default: `rspec`)
## Usage Examples
```bash
# Generate all tests using RSpec
/sinatra-test
# Generate only route tests with RSpec
/sinatra-test routes
# Generate all tests using Minitest
/sinatra-test all minitest
# Generate middleware tests with Minitest
/sinatra-test middleware minitest
# Generate helper tests with RSpec
/sinatra-test helpers rspec
```
## Workflow
### Step 1: Analyze Application Structure
**Discovery Phase:**
1. Identify application type (classic vs modular)
2. Locate controller files
3. Extract route definitions
4. Find middleware stack
5. Identify helper methods
6. Check existing test structure
7. Detect testing framework if already configured
**Files to Analyze:**
```ruby
# Controllers
app/controllers/**/*.rb
app.rb (classic style)
# Middleware
config.ru
config/**/*.rb
# Helpers
app/helpers/**/*.rb
helpers/ directory
# Existing tests
spec/**/*_spec.rb
test/**/*_test.rb
```
**Route Extraction:**
```ruby
# Parse routes from controller files
# Identify: HTTP method, path, parameters, conditions
# Example routes to extract:
get '/users' do
# Handler
end
get '/users/:id', :id => /\d+/ do
# Handler with constraint
end
post '/users', :provides => [:json] do
# Handler with content negotiation
end
```
### Step 2: Generate Test Structure (RSpec)
**Create spec_helper.rb if missing:**
```ruby
# spec/spec_helper.rb
ENV['RACK_ENV'] = 'test'
require 'simplecov'
SimpleCov.start do
add_filter '/spec/'
add_filter '/config/'
end
require_relative '../config/environment'
require 'rack/test'
require 'rspec'
require 'json'
# Database setup (if applicable)
if defined?(Sequel)
require 'database_cleaner/sequel'
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
end
end
RSpec.configure do |config|
config.include Rack::Test::Methods
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.filter_run_when_matching :focus
config.example_status_persistence_file_path = 'spec/examples.txt'
config.disable_monkey_patching!
config.warnings = true
config.order = :random
Kernel.srand config.seed
end
# Helper methods for all specs
module SpecHelpers
def json_response
JSON.parse(last_response.body)
end
def auth_header(token)
{ 'HTTP_AUTHORIZATION' => "Bearer #{token}" }
end
end
RSpec.configure do |config|
config.include SpecHelpers
end
```
**Create support files:**
```ruby
# spec/support/factory_helper.rb (if using factories)
require 'factory_bot'
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
# spec/support/shared_examples.rb
RSpec.shared_examples 'authenticated endpoint' do
it 'returns 401 without authentication' do
send(http_method, path)
expect(last_response.status).to eq(401)
end
end
RSpec.shared_examples 'json endpoint' do
it 'returns JSON content type' do
send(http_method, path, valid_params)
expect(last_response.content_type).to include('application/json')
end
end
```
### Step 3: Generate Route Tests
**For each route, generate comprehensive tests:**
```ruby
# spec/controllers/users_controller_spec.rb
require_relative '../spec_helper'
RSpec.describe UsersController do
def app
UsersController
end
describe 'GET /users' do
context 'with no users' do
it 'returns empty array' do
get '/users'
expect(last_response).to be_ok
expect(json_response).to eq([])
end
end
context 'with existing users' do
let!(:users) { create_list(:user, 3) }
it 'returns all users' do
get '/users'
expect(last_response).to be_ok
expect(json_response.length).to eq(3)
end
it 'includes user attributes' do
get '/users'
user_data = json_response.first
expect(user_data).to have_key('id')
expect(user_data).to have_key('name')
expect(user_data).to have_key('email')
end
end
context 'with pagination' do
let!(:users) { create_list(:user, 25) }
it 'respects page parameter' do
get '/users?page=2&per_page=10'
expect(json_response.length).to eq(10)
end
it 'includes pagination metadata' do
get '/users?page=1&per_page=10'
expect(json_response['meta']).to include(
'total' => 25,
'page' => 1,
'per_page' => 10
)
end
end
context 'with filtering' do
let!(:active_user) { create(:user, active: true) }
let!(:inactive_user) { create(:user, active: false) }
it 'filters by active status' do
get '/users?active=true'
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(active_user.id)
end
end
end
describe 'GET /users/:id' do
let(:user) { create(:user) }
context 'when user exists' do
it 'returns user details' do
get "/users/#{user.id}"
expect(last_response).to be_ok
expect(json_response['id']).to eq(user.id)
end
it 'includes all user attributes' do
get "/users/#{user.id}"
expect(json_response).to include(
'id' => user.id,
'name' => user.name,
'email' => user.email
)
end
end
context 'when user does not exist' do
it 'returns 404' do
get '/users/99999'
expect(last_response.status).to eq(404)
end
it 'returns error message' do
get '/users/99999'
expect(json_response).to include('error')
end
end
context 'with invalid id format' do
it 'returns 404' do
get '/users/invalid'
expect(last_response.status).to eq(404)
end
end
end
describe 'POST /users' do
let(:valid_attributes) do
{
name: 'John Doe',
email: 'john@example.com',
password: 'SecurePass123'
}
end
context 'with valid attributes' do
it 'creates a new user' do
expect {
post '/users', valid_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
}.to change(User, :count).by(1)
end
it 'returns 201 status' do
post '/users', valid_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
end
it 'returns created user' do
post '/users', valid_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
expect(json_response).to include(
'name' => 'John Doe',
'email' => 'john@example.com'
)
end
it 'does not return password' do
post '/users', valid_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
expect(json_response).not_to have_key('password')
end
end
context 'with invalid attributes' do
it 'returns 422 status' do
post '/users', { name: '' }.to_json,
'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(422)
end
it 'returns validation errors' do
post '/users', { name: '' }.to_json,
'CONTENT_TYPE' => 'application/json'
expect(json_response).to have_key('errors')
end
it 'does not create user' do
expect {
post '/users', { name: '' }.to_json,
'CONTENT_TYPE' => 'application/json'
}.not_to change(User, :count)
end
end
context 'with duplicate email' do
let!(:existing_user) { create(:user, email: 'john@example.com') }
it 'returns 422 status' do
post '/users', valid_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(422)
end
it 'returns uniqueness error' do
post '/users', valid_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
expect(json_response['errors']).to include('email')
end
end
end
describe 'PUT /users/:id' do
let(:user) { create(:user) }
let(:update_attributes) { { name: 'Updated Name' } }
context 'when user exists' do
it 'updates user attributes' do
put "/users/#{user.id}", update_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
user.reload
expect(user.name).to eq('Updated Name')
end
it 'returns 200 status' do
put "/users/#{user.id}", update_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
expect(last_response).to be_ok
end
it 'returns updated user' do
put "/users/#{user.id}", update_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
expect(json_response['name']).to eq('Updated Name')
end
end
context 'with invalid attributes' do
it 'returns 422 status' do
put "/users/#{user.id}", { email: 'invalid' }.to_json,
'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(422)
end
it 'does not update user' do
original_email = user.email
put "/users/#{user.id}", { email: 'invalid' }.to_json,
'CONTENT_TYPE' => 'application/json'
user.reload
expect(user.email).to eq(original_email)
end
end
context 'when user does not exist' do
it 'returns 404' do
put '/users/99999', update_attributes.to_json,
'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(404)
end
end
end
describe 'DELETE /users/:id' do
let!(:user) { create(:user) }
context 'when user exists' do
it 'deletes the user' do
expect {
delete "/users/#{user.id}"
}.to change(User, :count).by(-1)
end
it 'returns 204 status' do
delete "/users/#{user.id}"
expect(last_response.status).to eq(204)
end
it 'returns empty body' do
delete "/users/#{user.id}"
expect(last_response.body).to be_empty
end
end
context 'when user does not exist' do
it 'returns 404' do
delete '/users/99999'
expect(last_response.status).to eq(404)
end
end
end
# Authentication tests
describe 'authentication' do
let(:protected_path) { '/users' }
let(:http_method) { :get }
let(:path) { protected_path }
it_behaves_like 'authenticated endpoint'
end
# Content negotiation tests
describe 'content negotiation' do
let(:user) { create(:user) }
context 'with Accept: application/json' do
it 'returns JSON' do
get "/users/#{user.id}", {}, { 'HTTP_ACCEPT' => 'application/json' }
expect(last_response.content_type).to include('application/json')
end
end
context 'with Accept: application/xml' do
it 'returns XML' do
get "/users/#{user.id}", {}, { 'HTTP_ACCEPT' => 'application/xml' }
expect(last_response.content_type).to include('application/xml')
end
end
end
end
```
### Step 4: Generate Middleware Tests
```ruby
# spec/middleware/custom_middleware_spec.rb
require_relative '../spec_helper'
RSpec.describe CustomMiddleware do
let(:app) { ->(env) { [200, {}, ['OK']] } }
let(:middleware) { CustomMiddleware.new(app) }
let(:request) { Rack::MockRequest.new(middleware) }
describe 'request processing' do
it 'passes request to next middleware' do
response = request.get('/')
expect(response.status).to eq(200)
end
it 'adds custom header to response' do
response = request.get('/')
expect(response.headers['X-Custom-Header']).to eq('value')
end
it 'modifies request environment' do
env = {}
middleware.call(env)
expect(env['custom.key']).to be_present
end
end
describe 'error handling' do
let(:app) { ->(env) { raise StandardError, 'Error' } }
it 'catches errors from downstream' do
response = request.get('/')
expect(response.status).to eq(500)
end
it 'logs error' do
expect { request.get('/') }.to change { error_log.size }.by(1)
end
end
describe 'configuration' do
let(:middleware) { CustomMiddleware.new(app, option: 'value') }
it 'accepts configuration options' do
expect(middleware.options[:option]).to eq('value')
end
it 'applies configuration to behavior' do
response = request.get('/')
expect(response.headers['X-Option']).to eq('value')
end
end
end
```
### Step 5: Generate Helper Tests
```ruby
# spec/helpers/application_helpers_spec.rb
require_relative '../spec_helper'
RSpec.describe ApplicationHelpers do
let(:dummy_class) do
Class.new do
include ApplicationHelpers
# Mock request/session for helper context
def request
@request ||= Struct.new(:path_info).new('/test')
end
def session
@session ||= {}
end
end
end
let(:helpers) { dummy_class.new }
describe '#current_user' do
context 'when user is logged in' do
before do
helpers.session[:user_id] = 1
allow(User).to receive(:find).with(1).and_return(
double('User', id: 1, name: 'John')
)
end
it 'returns current user' do
expect(helpers.current_user).to be_present
expect(helpers.current_user.id).to eq(1)
end
it 'memoizes user' do
expect(User).to receive(:find).once
helpers.current_user
helpers.current_user
end
end
context 'when user is not logged in' do
it 'returns nil' do
expect(helpers.current_user).to be_nil
end
end
end
describe '#logged_in?' do
it 'returns true when current_user exists' do
allow(helpers).to receive(:current_user).and_return(double('User'))
expect(helpers.logged_in?).to be true
end
it 'returns false when current_user is nil' do
allow(helpers).to receive(:current_user).and_return(nil)
expect(helpers.logged_in?).to be false
end
end
describe '#format_date' do
let(:date) { Time.new(2024, 1, 15, 10, 30, 0) }
it 'formats date with default format' do
expect(helpers.format_date(date)).to eq('2024-01-15')
end
it 'accepts custom format' do
expect(helpers.format_date(date, '%m/%d/%Y')).to eq('01/15/2024')
end
it 'handles nil date' do
expect(helpers.format_date(nil)).to eq('')
end
end
describe '#truncate' do
let(:long_text) { 'This is a very long text that should be truncated' }
it 'truncates text to specified length' do
expect(helpers.truncate(long_text, 20)).to eq('This is a very long...')
end
it 'does not truncate short text' do
short_text = 'Short'
expect(helpers.truncate(short_text, 20)).to eq('Short')
end
it 'accepts custom omission' do
expect(helpers.truncate(long_text, 20, omission: '…')).to include('…')
end
end
end
```
### Step 6: Generate Minitest Tests (Alternative)
**If framework is Minitest:**
```ruby
# test/test_helper.rb
ENV['RACK_ENV'] = 'test'
require 'simplecov'
SimpleCov.start
require_relative '../config/environment'
require 'minitest/autorun'
require 'minitest/spec'
require 'rack/test'
class Minitest::Spec
include Rack::Test::Methods
def json_response
JSON.parse(last_response.body)
end
end
# test/controllers/users_controller_test.rb
require_relative '../test_helper'
describe UsersController do
def app
UsersController
end
describe 'GET /users' do
it 'returns success' do
get '/users'
assert last_response.ok?
end
it 'returns JSON' do
get '/users'
assert_includes last_response.content_type, 'application/json'
end
describe 'with existing users' do
before do
@users = 3.times.map { User.create(name: 'Test') }
end
it 'returns all users' do
get '/users'
assert_equal 3, json_response.length
end
end
end
describe 'POST /users' do
let(:valid_params) { { name: 'John', email: 'john@example.com' } }
it 'creates user' do
assert_difference 'User.count', 1 do
post '/users', valid_params.to_json,
'CONTENT_TYPE' => 'application/json'
end
end
it 'returns 201' do
post '/users', valid_params.to_json,
'CONTENT_TYPE' => 'application/json'
assert_equal 201, last_response.status
end
end
end
```
### Step 7: Generate Integration Tests
```ruby
# spec/integration/user_registration_spec.rb
require_relative '../spec_helper'
RSpec.describe 'User Registration Flow' do
def app
Sinatra::Application
end
describe 'complete registration process' do
let(:user_params) do
{
name: 'John Doe',
email: 'john@example.com',
password: 'SecurePass123'
}
end
it 'allows new user to register and log in' do
# Step 1: Register
post '/register', user_params.to_json,
'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
user_id = json_response['id']
# Step 2: Verify email confirmation sent
expect(EmailService.last_email[:to]).to eq('john@example.com')
# Step 3: Confirm email
token = EmailService.last_email[:token]
get "/confirm/#{token}"
expect(last_response.status).to eq(200)
# Step 4: Log in
post '/login', { email: 'john@example.com', password: 'SecurePass123' }.to_json,
'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(200)
expect(json_response).to have_key('token')
# Step 5: Access protected resource
token = json_response['token']
get '/profile', {}, auth_header(token)
expect(last_response).to be_ok
expect(json_response['id']).to eq(user_id)
end
end
end
```
### Step 8: Create Test Documentation
**Generate test README:**
```markdown
# Test Suite Documentation
## Running Tests
### All Tests
```bash
bundle exec rspec
```
### Specific Test File
```bash
bundle exec rspec spec/controllers/users_controller_spec.rb
```
### By Tag
```bash
bundle exec rspec --tag focus
```
## Test Structure
- `spec/controllers/` - Route and controller tests
- `spec/middleware/` - Middleware tests
- `spec/helpers/` - Helper method tests
- `spec/models/` - Model tests (if applicable)
- `spec/integration/` - End-to-end integration tests
- `spec/support/` - Shared examples and helpers
## Coverage
Run tests with coverage report:
```bash
COVERAGE=true bundle exec rspec
```
View coverage report:
```bash
open coverage/index.html
```
## Testing Patterns
### Route Testing
- Test successful responses
- Test error cases (404, 422, 500)
- Test authentication/authorization
- Test parameter validation
- Test content negotiation
### Helper Testing
- Test with various inputs
- Test edge cases
- Test nil handling
- Mock dependencies
### Integration Testing
- Test complete user flows
- Test interactions between components
- Test external service integration
```
## Output
**Generated files report:**
```
Test Generation Complete!
Framework: RSpec
Test Type: all
Generated Files:
✓ spec/spec_helper.rb
✓ spec/support/factory_helper.rb
✓ spec/support/shared_examples.rb
✓ spec/controllers/users_controller_spec.rb (45 examples)
✓ spec/controllers/posts_controller_spec.rb (38 examples)
✓ spec/middleware/custom_middleware_spec.rb (12 examples)
✓ spec/helpers/application_helpers_spec.rb (15 examples)
✓ spec/integration/user_registration_spec.rb (5 examples)
✓ TEST_README.md
Total Examples: 115
Coverage Target: 90%
Run tests: bundle exec rspec
```
## Error Handling
- Handle applications without routes gracefully
- Skip already existing test files (or offer to overwrite)
- Detect testing framework from Gemfile
- Warn if test dependencies missing
- Handle parse errors in application files