pg_multitenant_schemas 0.1.3 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.actrc +17 -0
- data/.env.local.example +21 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +86 -0
- data/LOCAL_TESTING_SUMMARY.md +141 -0
- data/README.md +269 -16
- data/TESTING_LOCALLY.md +208 -0
- data/docs/README.md +81 -0
- data/docs/configuration.md +340 -0
- data/docs/context.md +292 -0
- data/docs/errors.md +498 -0
- data/docs/github_actions_permissions_fix.md +136 -0
- data/docs/github_actions_setup.md +181 -0
- data/docs/integration_testing.md +454 -0
- data/docs/local_workflow_testing.md +314 -0
- data/docs/migrator.md +291 -0
- data/docs/rails_integration.md +468 -0
- data/docs/schema_switcher.md +182 -0
- data/docs/tenant_resolver.md +394 -0
- data/docs/testing.md +358 -0
- data/examples/context_management.rb +198 -0
- data/examples/migration_workflow.rb +50 -0
- data/examples/rails_integration/controller_examples.rb +368 -0
- data/examples/schema_operations.rb +124 -0
- data/lib/pg_multitenant_schemas/configuration.rb +4 -4
- data/lib/pg_multitenant_schemas/migration_display_reporter.rb +30 -0
- data/lib/pg_multitenant_schemas/migration_executor.rb +81 -0
- data/lib/pg_multitenant_schemas/migration_schema_operations.rb +54 -0
- data/lib/pg_multitenant_schemas/migration_status_reporter.rb +65 -0
- data/lib/pg_multitenant_schemas/migrator.rb +89 -0
- data/lib/pg_multitenant_schemas/schema_switcher.rb +40 -66
- data/lib/pg_multitenant_schemas/tasks/advanced_tasks.rake +21 -0
- data/lib/pg_multitenant_schemas/tasks/basic_tasks.rake +20 -0
- data/lib/pg_multitenant_schemas/tasks/pg_multitenant_schemas.rake +53 -143
- data/lib/pg_multitenant_schemas/tasks/tenant_tasks.rake +65 -0
- data/lib/pg_multitenant_schemas/tenant_task_helpers.rb +102 -0
- data/lib/pg_multitenant_schemas/version.rb +1 -1
- data/lib/pg_multitenant_schemas.rb +10 -5
- data/pg_multitenant_schemas.gemspec +10 -9
- data/pre-push-check.sh +95 -0
- data/rails_integration/app/controllers/application_controller.rb +6 -0
- data/rails_integration/app/models/tenant.rb +6 -0
- data/test-github-setup.sh +85 -0
- data/validate-github-commands.sh +47 -0
- metadata +49 -17
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# GitHub Actions Setup Guide
|
|
2
|
+
|
|
3
|
+
This document explains how to set up GitHub Actions for automated testing and releases.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The repository uses two main workflows:
|
|
8
|
+
|
|
9
|
+
1. **CI Workflow** (`.github/workflows/main.yml`) - Runs tests on every push and PR
|
|
10
|
+
2. **Release Workflow** (`.github/workflows/release.yml`) - Automatically releases to RubyGems when version changes
|
|
11
|
+
|
|
12
|
+
## Setting Up RubyGems API Key
|
|
13
|
+
|
|
14
|
+
To enable automatic releases to RubyGems, you need to set up a GitHub secret with your RubyGems API key.
|
|
15
|
+
|
|
16
|
+
### Step 1: Get Your RubyGems API Key
|
|
17
|
+
|
|
18
|
+
1. Go to [RubyGems.org](https://rubygems.org/)
|
|
19
|
+
2. Sign in to your account
|
|
20
|
+
3. Go to your profile settings
|
|
21
|
+
4. Navigate to "API Keys" section
|
|
22
|
+
5. Create a new API key or use an existing one
|
|
23
|
+
6. Copy the API key (it should start with `rubygems_`)
|
|
24
|
+
|
|
25
|
+
### Step 2: Add GitHub Secret
|
|
26
|
+
|
|
27
|
+
1. Go to your GitHub repository
|
|
28
|
+
2. Click on "Settings" tab
|
|
29
|
+
3. In the left sidebar, click "Secrets and variables" → "Actions"
|
|
30
|
+
4. Click "New repository secret"
|
|
31
|
+
5. Name: `RUBYGEMS_API_KEY`
|
|
32
|
+
6. Value: Paste your RubyGems API key
|
|
33
|
+
7. Click "Add secret"
|
|
34
|
+
|
|
35
|
+
### 3. Configure Repository Permissions
|
|
36
|
+
|
|
37
|
+
**Important:** Configure GitHub Actions permissions to allow the release workflow to create tags and releases:
|
|
38
|
+
|
|
39
|
+
1. Go to your repository Settings
|
|
40
|
+
2. Navigate to **Actions > General**
|
|
41
|
+
3. Under "Workflow permissions":
|
|
42
|
+
- Select **"Read and write permissions"**
|
|
43
|
+
- Check **"Allow GitHub Actions to create and approve pull requests"**
|
|
44
|
+
4. Click **Save**
|
|
45
|
+
|
|
46
|
+
Without these permissions, you'll get errors like:
|
|
47
|
+
```
|
|
48
|
+
remote: Permission to username/repo.git denied to github-actions[bot].
|
|
49
|
+
fatal: unable to access 'https://github.com/username/repo/': The requested URL returned error: 403
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## How the Workflows Work
|
|
53
|
+
|
|
54
|
+
### CI Workflow
|
|
55
|
+
|
|
56
|
+
**Triggers:**
|
|
57
|
+
- Every push to `main` branch
|
|
58
|
+
- Every pull request to `main` branch
|
|
59
|
+
|
|
60
|
+
**What it does:**
|
|
61
|
+
- Tests on Ruby 3.2, 3.3, and 3.4
|
|
62
|
+
- Runs RuboCop for code quality
|
|
63
|
+
- Runs unit tests (excluding integration tests)
|
|
64
|
+
- Runs integration tests (with PostgreSQL database)
|
|
65
|
+
- Runs security audit with bundle-audit
|
|
66
|
+
|
|
67
|
+
### Release Workflow
|
|
68
|
+
|
|
69
|
+
**Triggers:**
|
|
70
|
+
- Push to `main` branch that changes `lib/pg_multitenant_schemas/version.rb`
|
|
71
|
+
|
|
72
|
+
**What it does:**
|
|
73
|
+
- Checks if the version in `version.rb` has changed
|
|
74
|
+
- If version changed:
|
|
75
|
+
1. Builds the gem
|
|
76
|
+
2. Creates a Git tag (e.g., `v0.2.1`)
|
|
77
|
+
3. Creates a GitHub release with changelog notes
|
|
78
|
+
4. Publishes the gem to RubyGems
|
|
79
|
+
|
|
80
|
+
## Release Process
|
|
81
|
+
|
|
82
|
+
To release a new version:
|
|
83
|
+
|
|
84
|
+
1. **Update the version** in `lib/pg_multitenant_schemas/version.rb`:
|
|
85
|
+
```ruby
|
|
86
|
+
module PgMultitenantSchemas
|
|
87
|
+
VERSION = "0.2.1" # Increment this
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
2. **Update the changelog** in `CHANGELOG.md`:
|
|
92
|
+
```markdown
|
|
93
|
+
## [0.2.1] - 2025-09-07
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
- New feature description
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
- Bug fix description
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
3. **Commit and push** to main branch:
|
|
103
|
+
```bash
|
|
104
|
+
git add lib/pg_multitenant_schemas/version.rb CHANGELOG.md
|
|
105
|
+
git commit -m "Bump version to 0.2.1"
|
|
106
|
+
git push origin main
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
4. **Automatic release** will trigger:
|
|
110
|
+
- GitHub Actions will detect the version change
|
|
111
|
+
- Create a Git tag and GitHub release
|
|
112
|
+
- Publish to RubyGems automatically
|
|
113
|
+
|
|
114
|
+
## Manual Release (Alternative)
|
|
115
|
+
|
|
116
|
+
If you prefer manual releases or need to troubleshoot:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Build the gem
|
|
120
|
+
gem build pg_multitenant_schemas.gemspec
|
|
121
|
+
|
|
122
|
+
# Push to RubyGems (requires authentication)
|
|
123
|
+
gem push pg_multitenant_schemas-0.2.1.gem
|
|
124
|
+
|
|
125
|
+
# Create Git tag
|
|
126
|
+
git tag v0.2.1
|
|
127
|
+
git push origin v0.2.1
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Workflow Status
|
|
131
|
+
|
|
132
|
+
You can monitor workflow runs:
|
|
133
|
+
|
|
134
|
+
1. Go to your GitHub repository
|
|
135
|
+
2. Click the "Actions" tab
|
|
136
|
+
3. View running and completed workflows
|
|
137
|
+
4. Click on individual runs to see detailed logs
|
|
138
|
+
|
|
139
|
+
## Security Considerations
|
|
140
|
+
|
|
141
|
+
- **API Keys**: Never commit API keys to the repository
|
|
142
|
+
- **Secrets**: Use GitHub Secrets for sensitive information
|
|
143
|
+
- **Permissions**: The `GITHUB_TOKEN` has limited permissions for creating releases
|
|
144
|
+
- **Audit**: The security workflow checks for vulnerable dependencies
|
|
145
|
+
|
|
146
|
+
## Troubleshooting
|
|
147
|
+
|
|
148
|
+
### Common Issues
|
|
149
|
+
|
|
150
|
+
1. **RubyGems authentication failed**
|
|
151
|
+
- Check that `RUBYGEMS_API_KEY` secret is set correctly
|
|
152
|
+
- Ensure the API key has publishing permissions
|
|
153
|
+
|
|
154
|
+
2. **Git tag already exists**
|
|
155
|
+
- The workflow checks for existing tags
|
|
156
|
+
- If tag exists, release is skipped
|
|
157
|
+
|
|
158
|
+
3. **Tests failing**
|
|
159
|
+
- CI must pass before release workflow runs
|
|
160
|
+
- Check test logs in the Actions tab
|
|
161
|
+
|
|
162
|
+
4. **PostgreSQL connection issues**
|
|
163
|
+
- Integration tests require PostgreSQL service
|
|
164
|
+
- Check service configuration in workflow
|
|
165
|
+
|
|
166
|
+
### Debug Steps
|
|
167
|
+
|
|
168
|
+
1. Check workflow logs in GitHub Actions
|
|
169
|
+
2. Verify secrets are set correctly
|
|
170
|
+
3. Test locally with same Ruby versions
|
|
171
|
+
4. Check RubyGems.org for published gems
|
|
172
|
+
|
|
173
|
+
## Best Practices
|
|
174
|
+
|
|
175
|
+
1. **Version Bumping**: Use semantic versioning (MAJOR.MINOR.PATCH)
|
|
176
|
+
2. **Changelog**: Always update changelog before releasing
|
|
177
|
+
3. **Testing**: Ensure all tests pass locally before pushing
|
|
178
|
+
4. **Security**: Regularly update dependencies and run security audits
|
|
179
|
+
5. **Documentation**: Update documentation for breaking changes
|
|
180
|
+
|
|
181
|
+
This automated setup ensures consistent, reliable releases while maintaining code quality through comprehensive testing.
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# Integration Testing Guide - PostgreSQL Multi-Schema Operations
|
|
2
|
+
|
|
3
|
+
## 🎯 **Integration Test Overview**
|
|
4
|
+
|
|
5
|
+
Integration tests validate real PostgreSQL multi-schema operations, ensuring the gem works correctly with actual database instances. These tests are tagged with `:integration` and require a running PostgreSQL server.
|
|
6
|
+
|
|
7
|
+
## 🏗️ **Integration Test Architecture**
|
|
8
|
+
|
|
9
|
+
### **Test Categories**
|
|
10
|
+
|
|
11
|
+
#### **1. PostgreSQL Integration Tests**
|
|
12
|
+
- **File**: `spec/postgresql_integration_spec.rb`
|
|
13
|
+
- **Purpose**: Basic PostgreSQL schema operations
|
|
14
|
+
- **Examples**: 5 tests
|
|
15
|
+
- **Focus**: Core schema creation, switching, and deletion
|
|
16
|
+
|
|
17
|
+
#### **2. Multiple Schemas Integration Tests**
|
|
18
|
+
- **File**: `spec/multiple_schemas_integration_spec.rb`
|
|
19
|
+
- **Purpose**: Complex multi-tenant scenarios
|
|
20
|
+
- **Examples**: 8 tests
|
|
21
|
+
- **Focus**: Schema isolation, concurrent access, complex scenarios
|
|
22
|
+
|
|
23
|
+
#### **3. Multiple Schema Database Operations**
|
|
24
|
+
- **File**: `spec/multiple_schemas_database_spec.rb`
|
|
25
|
+
- **Purpose**: Bulk operations and data management
|
|
26
|
+
- **Examples**: 4 tests
|
|
27
|
+
- **Focus**: Cross-schema queries, bulk operations, dependency management
|
|
28
|
+
|
|
29
|
+
#### **4. Multiple Tenant Context Tests**
|
|
30
|
+
- **File**: `spec/multiple_tenants_context_spec.rb`
|
|
31
|
+
- **Purpose**: Context switching between tenants
|
|
32
|
+
- **Examples**: 4 tests
|
|
33
|
+
- **Focus**: Thread safety, context isolation
|
|
34
|
+
|
|
35
|
+
## 🔧 **Database Configuration**
|
|
36
|
+
|
|
37
|
+
### **Environment Setup**
|
|
38
|
+
|
|
39
|
+
Integration tests use environment variables for PostgreSQL connection:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Required PostgreSQL settings
|
|
43
|
+
export PG_HOST=localhost # Database host
|
|
44
|
+
export PG_PORT=5432 # Database port
|
|
45
|
+
export PG_TEST_DATABASE=pg_multitenant_test # Test database
|
|
46
|
+
export PG_USER=postgres # Database user
|
|
47
|
+
export PG_PASSWORD= # Database password (if needed)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### **Database Preparation**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Create test database
|
|
54
|
+
createdb pg_multitenant_test
|
|
55
|
+
|
|
56
|
+
# Alternative: Using psql
|
|
57
|
+
psql -c "CREATE DATABASE pg_multitenant_test;"
|
|
58
|
+
|
|
59
|
+
# Grant permissions (if needed)
|
|
60
|
+
psql -d pg_multitenant_test -c "GRANT ALL PRIVILEGES ON SCHEMA public TO postgres;"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### **Connection Configuration**
|
|
64
|
+
|
|
65
|
+
Each integration test establishes its own database connection:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
let(:db_config) do
|
|
69
|
+
{
|
|
70
|
+
host: ENV["PG_HOST"] || "localhost",
|
|
71
|
+
port: ENV["PG_PORT"] || 5432,
|
|
72
|
+
dbname: ENV["PG_TEST_DATABASE"] || "pg_multitenant_test",
|
|
73
|
+
user: ENV["PG_USER"] || "postgres",
|
|
74
|
+
password: ENV["PG_PASSWORD"] || ""
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
let(:conn) do
|
|
79
|
+
PG.connect(db_config)
|
|
80
|
+
rescue PG::ConnectionBad => e
|
|
81
|
+
skip "PostgreSQL not available: #{e.message}"
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 🧪 **Test Execution**
|
|
86
|
+
|
|
87
|
+
### **Running Integration Tests**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Run all integration tests
|
|
91
|
+
bundle exec rspec --tag integration
|
|
92
|
+
|
|
93
|
+
# Run with documentation format
|
|
94
|
+
bundle exec rspec --tag integration --format documentation
|
|
95
|
+
|
|
96
|
+
# Run specific integration test file
|
|
97
|
+
bundle exec rspec spec/postgresql_integration_spec.rb
|
|
98
|
+
|
|
99
|
+
# Run specific test example
|
|
100
|
+
bundle exec rspec ./spec/multiple_schemas_integration_spec.rb:90
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### **Expected Output**
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
Multiple Schema Database Operations
|
|
107
|
+
multiple schema creation and management
|
|
108
|
+
✓ creates and manages multiple tenant schemas
|
|
109
|
+
✓ handles cross-schema queries safely
|
|
110
|
+
✓ performs bulk operations across multiple schemas
|
|
111
|
+
schema cleanup with dependencies
|
|
112
|
+
✓ cleans up multiple schemas with foreign key relationships
|
|
113
|
+
|
|
114
|
+
Multiple Schemas Integration Tests
|
|
115
|
+
multiple tenant schemas
|
|
116
|
+
✓ creates multiple schemas successfully
|
|
117
|
+
✓ switches between multiple schemas
|
|
118
|
+
✓ maintains data isolation between schemas
|
|
119
|
+
✓ handles concurrent schema access safely
|
|
120
|
+
✓ drops multiple schemas cleanly
|
|
121
|
+
schema search path with multiple schemas
|
|
122
|
+
✓ handles complex search paths
|
|
123
|
+
schema migration scenarios
|
|
124
|
+
✓ handles schema creation with existing objects
|
|
125
|
+
error scenarios with multiple schemas
|
|
126
|
+
✓ handles dropping non-existent schemas gracefully
|
|
127
|
+
✓ handles schema dependencies correctly
|
|
128
|
+
|
|
129
|
+
Finished in 0.67914 seconds
|
|
130
|
+
21 examples, 0 failures
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 📋 **Detailed Test Scenarios**
|
|
134
|
+
|
|
135
|
+
### **PostgreSQL Integration Tests**
|
|
136
|
+
|
|
137
|
+
#### **Schema Creation Tests**
|
|
138
|
+
```ruby
|
|
139
|
+
describe "schema creation" do
|
|
140
|
+
it "actually creates a schema in PostgreSQL" do
|
|
141
|
+
schema_name = "test_schema"
|
|
142
|
+
|
|
143
|
+
# Create schema using gem
|
|
144
|
+
PgMultitenantSchemas::SchemaSwitcher.create_schema(schema_name)
|
|
145
|
+
|
|
146
|
+
# Verify schema exists in PostgreSQL
|
|
147
|
+
result = conn.exec("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '#{schema_name}';")
|
|
148
|
+
expect(result.ntuples).to eq(1)
|
|
149
|
+
expect(result.getvalue(0, 0)).to eq(schema_name)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### **Schema Switching Tests**
|
|
155
|
+
```ruby
|
|
156
|
+
describe "schema switching" do
|
|
157
|
+
it "actually switches the search_path" do
|
|
158
|
+
schema_name = "test_schema"
|
|
159
|
+
PgMultitenantSchemas::SchemaSwitcher.create_schema(schema_name)
|
|
160
|
+
|
|
161
|
+
# Switch to schema
|
|
162
|
+
PgMultitenantSchemas::SchemaSwitcher.switch_schema(schema_name)
|
|
163
|
+
|
|
164
|
+
# Verify search_path changed
|
|
165
|
+
result = conn.exec("SELECT current_schema();")
|
|
166
|
+
expect(result.getvalue(0, 0)).to eq(schema_name)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### **Multiple Schemas Integration Tests**
|
|
172
|
+
|
|
173
|
+
#### **Data Isolation Tests**
|
|
174
|
+
```ruby
|
|
175
|
+
describe "multiple tenant schemas" do
|
|
176
|
+
it "maintains data isolation between schemas" do
|
|
177
|
+
tenants = ["tenant_a", "tenant_b", "tenant_c"]
|
|
178
|
+
|
|
179
|
+
# Create schemas and insert tenant-specific data
|
|
180
|
+
tenants.each do |tenant|
|
|
181
|
+
PgMultitenantSchemas::SchemaSwitcher.create_schema(tenant)
|
|
182
|
+
PgMultitenantSchemas::SchemaSwitcher.switch_schema(tenant)
|
|
183
|
+
|
|
184
|
+
conn.exec("CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(50));")
|
|
185
|
+
conn.exec("INSERT INTO users (name) VALUES ('User from #{tenant}');")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Verify each schema contains only its own data
|
|
189
|
+
tenants.each do |tenant|
|
|
190
|
+
PgMultitenantSchemas::SchemaSwitcher.switch_schema(tenant)
|
|
191
|
+
result = conn.exec("SELECT name FROM users;")
|
|
192
|
+
expect(result.getvalue(0, 0)).to eq("User from #{tenant}")
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### **Concurrent Access Tests**
|
|
199
|
+
```ruby
|
|
200
|
+
describe "concurrent schema access" do
|
|
201
|
+
it "handles concurrent schema access safely" do
|
|
202
|
+
tenants = ["tenant_a", "tenant_b", "tenant_c"]
|
|
203
|
+
|
|
204
|
+
# Create multiple database connections
|
|
205
|
+
connections = tenants.map { |_| PG.connect(db_config) }
|
|
206
|
+
|
|
207
|
+
begin
|
|
208
|
+
# Each connection works in different schema
|
|
209
|
+
connections.each_with_index do |connection, index|
|
|
210
|
+
tenant = tenants[index]
|
|
211
|
+
|
|
212
|
+
# Set search path for this specific connection
|
|
213
|
+
connection.exec("SET search_path TO #{connection.escape_identifier(tenant)}")
|
|
214
|
+
|
|
215
|
+
# Clean up from previous runs
|
|
216
|
+
connection.exec("DROP TABLE IF EXISTS orders CASCADE")
|
|
217
|
+
|
|
218
|
+
# Create table and insert data
|
|
219
|
+
connection.exec("CREATE TABLE orders (id SERIAL PRIMARY KEY, tenant_name VARCHAR(50));")
|
|
220
|
+
connection.exec("INSERT INTO orders (tenant_name) VALUES ('#{tenant}');")
|
|
221
|
+
|
|
222
|
+
# Verify isolation
|
|
223
|
+
result = connection.exec("SELECT tenant_name FROM orders;")
|
|
224
|
+
expect(result.getvalue(0, 0)).to eq(tenant)
|
|
225
|
+
end
|
|
226
|
+
ensure
|
|
227
|
+
connections.each(&:close)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### **Database Operations Tests**
|
|
234
|
+
|
|
235
|
+
#### **Cross-Schema Query Tests**
|
|
236
|
+
```ruby
|
|
237
|
+
describe "cross-schema queries" do
|
|
238
|
+
it "handles cross-schema queries safely" do
|
|
239
|
+
schemas = ["schema_with_users", "schema_with_products"]
|
|
240
|
+
|
|
241
|
+
schemas.each do |schema|
|
|
242
|
+
PgMultitenantSchemas::SchemaSwitcher.create_schema(schema)
|
|
243
|
+
PgMultitenantSchemas::SchemaSwitcher.switch_schema(schema)
|
|
244
|
+
|
|
245
|
+
if schema.include?("users")
|
|
246
|
+
conn.exec("CREATE TABLE customers (id SERIAL PRIMARY KEY, name VARCHAR(50));")
|
|
247
|
+
conn.exec("INSERT INTO customers (name) VALUES ('John Doe');")
|
|
248
|
+
else
|
|
249
|
+
conn.exec("CREATE TABLE customers (id SERIAL PRIMARY KEY, product VARCHAR(50));")
|
|
250
|
+
conn.exec("INSERT INTO customers (product) VALUES ('Widget');")
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Verify each schema maintains its own table structure
|
|
255
|
+
PgMultitenantSchemas::SchemaSwitcher.switch_schema("schema_with_users")
|
|
256
|
+
result = conn.exec("SELECT column_name FROM information_schema.columns WHERE table_name = 'customers' AND table_schema = 'schema_with_users' ORDER BY ordinal_position;")
|
|
257
|
+
user_columns = result.map { |row| row["column_name"] }
|
|
258
|
+
expect(user_columns).to include("name")
|
|
259
|
+
expect(user_columns).not_to include("product")
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### **Bulk Operations Tests**
|
|
265
|
+
```ruby
|
|
266
|
+
describe "bulk operations" do
|
|
267
|
+
it "performs bulk operations across multiple schemas" do
|
|
268
|
+
schemas = ["company_a", "company_b"]
|
|
269
|
+
|
|
270
|
+
# Set up multiple schemas with data
|
|
271
|
+
schemas.each do |schema|
|
|
272
|
+
PgMultitenantSchemas::SchemaSwitcher.create_schema(schema)
|
|
273
|
+
PgMultitenantSchemas::SchemaSwitcher.switch_schema(schema)
|
|
274
|
+
|
|
275
|
+
conn.exec("CREATE TABLE analytics (id SERIAL PRIMARY KEY, metric_value INTEGER);")
|
|
276
|
+
conn.exec("INSERT INTO analytics (metric_value) VALUES (100), (200), (300);")
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Perform bulk analysis across schemas
|
|
280
|
+
total_metrics = 0
|
|
281
|
+
schemas.each do |schema|
|
|
282
|
+
PgMultitenantSchemas::SchemaSwitcher.switch_schema(schema)
|
|
283
|
+
result = conn.exec("SELECT SUM(metric_value) as total FROM analytics;")
|
|
284
|
+
total_metrics += result.getvalue(0, 0).to_i
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
expect(total_metrics).to eq(1200) # (100+200+300) * 2 schemas
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## 🧹 **Test Cleanup and Safety**
|
|
293
|
+
|
|
294
|
+
### **Automatic Cleanup**
|
|
295
|
+
|
|
296
|
+
Every integration test includes comprehensive cleanup:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
after do
|
|
300
|
+
# Clean up test schemas created during tests
|
|
301
|
+
["tenant_a", "tenant_b", "tenant_c", "public_test", "test_schema",
|
|
302
|
+
"another_test_schema", "company_a", "company_b", "company_c",
|
|
303
|
+
"schema_with_users", "schema_with_products", "sub_tenant",
|
|
304
|
+
"dependency_test"].each do |schema|
|
|
305
|
+
begin
|
|
306
|
+
PgMultitenantSchemas::SchemaSwitcher.drop_schema(schema, cascade: true)
|
|
307
|
+
rescue => e
|
|
308
|
+
# Log but don't fail tests on cleanup errors
|
|
309
|
+
puts "Cleanup warning: #{e.message}" if ENV['DEBUG_TESTS']
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Always reset to public schema
|
|
314
|
+
PgMultitenantSchemas::SchemaSwitcher.reset_to_public_schema
|
|
315
|
+
|
|
316
|
+
# Close connections
|
|
317
|
+
conn&.close
|
|
318
|
+
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### **Schema Naming Strategy**
|
|
322
|
+
|
|
323
|
+
Integration tests use predictable schema names for easy cleanup:
|
|
324
|
+
|
|
325
|
+
- **Tenant Schemas**: `tenant_a`, `tenant_b`, `tenant_c`
|
|
326
|
+
- **Company Schemas**: `company_a`, `company_b`, `company_c`
|
|
327
|
+
- **Feature Schemas**: `schema_with_users`, `schema_with_products`
|
|
328
|
+
- **Test Schemas**: `test_schema`, `another_test_schema`
|
|
329
|
+
- **Special Cases**: `public_test`, `sub_tenant`, `dependency_test`
|
|
330
|
+
|
|
331
|
+
### **Cascade Deletion**
|
|
332
|
+
|
|
333
|
+
Tests use `CASCADE` option to handle schema dependencies:
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
# Drop schema with all dependent objects
|
|
337
|
+
PgMultitenantSchemas::SchemaSwitcher.drop_schema(schema_name, cascade: true)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## 🚨 **Troubleshooting Integration Tests**
|
|
341
|
+
|
|
342
|
+
### **Common Issues and Solutions**
|
|
343
|
+
|
|
344
|
+
#### **PostgreSQL Connection Failures**
|
|
345
|
+
```bash
|
|
346
|
+
# Error: PostgreSQL not available
|
|
347
|
+
# Solution: Check PostgreSQL service
|
|
348
|
+
brew services start postgresql # macOS
|
|
349
|
+
sudo service postgresql start # Linux
|
|
350
|
+
|
|
351
|
+
# Verify connection
|
|
352
|
+
pg_isready -h localhost -p 5432
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### **Permission Errors**
|
|
356
|
+
```bash
|
|
357
|
+
# Error: permission denied to create schema
|
|
358
|
+
# Solution: Grant schema creation permissions
|
|
359
|
+
psql -d pg_multitenant_test -c "GRANT CREATE ON DATABASE pg_multitenant_test TO postgres;"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### **Schema Already Exists**
|
|
363
|
+
```bash
|
|
364
|
+
# Error: schema "tenant_a" already exists
|
|
365
|
+
# Solution: Manual cleanup
|
|
366
|
+
psql -d pg_multitenant_test -c "DROP SCHEMA IF EXISTS tenant_a CASCADE;"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### **Table Already Exists**
|
|
370
|
+
```ruby
|
|
371
|
+
# Error: relation "orders" already exists
|
|
372
|
+
# Fix: Add proper cleanup in test
|
|
373
|
+
before do
|
|
374
|
+
conn.exec("DROP TABLE IF EXISTS orders CASCADE")
|
|
375
|
+
end
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### **Debug Mode**
|
|
379
|
+
|
|
380
|
+
Enable debug output for troubleshooting:
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
# Run with debug information
|
|
384
|
+
DEBUG_TESTS=1 bundle exec rspec --tag integration
|
|
385
|
+
|
|
386
|
+
# Run specific failing test with verbose output
|
|
387
|
+
bundle exec rspec ./spec/multiple_schemas_integration_spec.rb:90 --format documentation --backtrace
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### **Manual Database Inspection**
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# Connect to test database
|
|
394
|
+
psql -d pg_multitenant_test
|
|
395
|
+
|
|
396
|
+
# List all schemas
|
|
397
|
+
\dn
|
|
398
|
+
|
|
399
|
+
# Check current schema
|
|
400
|
+
SELECT current_schema();
|
|
401
|
+
|
|
402
|
+
# List tables in specific schema
|
|
403
|
+
\dt tenant_a.*
|
|
404
|
+
|
|
405
|
+
# Clean up manually if needed
|
|
406
|
+
DROP SCHEMA IF EXISTS tenant_a CASCADE;
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## 📊 **Integration Test Metrics**
|
|
410
|
+
|
|
411
|
+
### **Performance Benchmarks**
|
|
412
|
+
|
|
413
|
+
- **Total Integration Tests**: 21 examples
|
|
414
|
+
- **Average Execution Time**: ~0.67 seconds
|
|
415
|
+
- **Database Operations**: ~50+ schema create/drop operations
|
|
416
|
+
- **Concurrent Connections**: Up to 3 simultaneous connections
|
|
417
|
+
- **Data Isolation**: 100% between tenant schemas
|
|
418
|
+
|
|
419
|
+
### **Coverage Areas**
|
|
420
|
+
|
|
421
|
+
✅ **Schema Lifecycle**: Create, switch, drop operations
|
|
422
|
+
✅ **Data Isolation**: Complete tenant separation
|
|
423
|
+
✅ **Concurrent Access**: Multi-connection safety
|
|
424
|
+
✅ **Error Handling**: Graceful failure scenarios
|
|
425
|
+
✅ **Complex Queries**: Cross-schema operations
|
|
426
|
+
✅ **Bulk Operations**: Multi-tenant data processing
|
|
427
|
+
✅ **Dependencies**: Foreign key and cascade handling
|
|
428
|
+
✅ **Search Path**: PostgreSQL search_path management
|
|
429
|
+
|
|
430
|
+
## 🎯 **Best Practices for Integration Testing**
|
|
431
|
+
|
|
432
|
+
### **Test Development Guidelines**
|
|
433
|
+
|
|
434
|
+
1. **Real Database Operations**: Always test against actual PostgreSQL
|
|
435
|
+
2. **Clean Isolation**: Each test creates and destroys its own schemas
|
|
436
|
+
3. **Comprehensive Cleanup**: Always clean up after tests, even on failure
|
|
437
|
+
4. **Connection Management**: Properly close database connections
|
|
438
|
+
5. **Error Scenarios**: Test both success and failure paths
|
|
439
|
+
6. **Performance Awareness**: Monitor test execution time
|
|
440
|
+
7. **Thread Safety**: Test concurrent access patterns
|
|
441
|
+
8. **Documentation**: Document complex test scenarios
|
|
442
|
+
|
|
443
|
+
### **Integration Test Checklist**
|
|
444
|
+
|
|
445
|
+
- [ ] Database connection established
|
|
446
|
+
- [ ] Schemas created with unique names
|
|
447
|
+
- [ ] Operations tested with real PostgreSQL
|
|
448
|
+
- [ ] Data isolation verified
|
|
449
|
+
- [ ] Cleanup performed in after hooks
|
|
450
|
+
- [ ] Error scenarios tested
|
|
451
|
+
- [ ] Thread safety validated
|
|
452
|
+
- [ ] Performance characteristics measured
|
|
453
|
+
|
|
454
|
+
This comprehensive integration testing approach ensures the PgMultitenantSchemas gem works reliably in real-world PostgreSQL environments with multiple tenants and complex schema operations.
|