patch_retention 0.1.5 → 0.2.0

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.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Patch Retention API Wrapper
2
2
 
3
+ [![CI](https://github.com/[USERNAME]/patch_retention/actions/workflows/ci.yml/badge.svg)](https://github.com/[USERNAME]/patch_retention/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/patch_retention.svg)](https://badge.fury.io/rb/patch_retention)
5
+
3
6
  ## Installation
4
7
 
5
8
  Install the gem and add to the application's Gemfile by executing:
@@ -18,7 +21,30 @@ $ gem install patch_retention
18
21
 
19
22
  ### Configuration
20
23
 
21
- For local environment, you can set the following environment variables:
24
+ #### Using Environment Files (Recommended for Development)
25
+
26
+ This gem uses dotenv for managing environment variables in development and test environments. Copy the example file and add your credentials:
27
+
28
+ ```bash
29
+ # For development
30
+ cp .env.example .env.development.local
31
+ # Edit .env.development.local with your credentials
32
+
33
+ # For running tests
34
+ cp .env.test.example .env.test
35
+ # Edit .env.test with your test credentials
36
+ ```
37
+
38
+ The following files are used:
39
+ - `.env.test` - Test environment (gitignored - copy from `.env.test.example`)
40
+ - `.env.test.example` - Test environment template (committed)
41
+ - `.env.development` - Development defaults (committed)
42
+ - `.env.development.local` - Your local development credentials (gitignored)
43
+ - `.env.example` - Example configuration (committed)
44
+
45
+ #### Using Environment Variables
46
+
47
+ For production or CI environments, you can set the following environment variables:
22
48
 
23
49
  ```bash
24
50
  PATCH_RETENTION_API_URL='your_custom_api_url' # Optional, by default is set to: https://api.patchretention.com/v2
@@ -38,9 +64,52 @@ PatchRetention.configure do |config|
38
64
  end
39
65
  ```
40
66
 
67
+ ### Using a Custom Configuration per Call
68
+
69
+ While you can configure the gem globally, there might be scenarios where you need to use a different set of credentials or API endpoint for a specific API call. All API call methods (e.g., `PatchRetention::Products.create`, `PatchRetention::Memberships.create`, `PatchRetention::Events.create`, `PatchRetention::Contacts.find_or_create_by`, etc.) accept an optional `config:` parameter.
70
+
71
+ This parameter expects an instance of `PatchRetention::Configuration`.
72
+
73
+ **Example: Creating a call with a custom `PatchRetention::Configuration` Object:**
74
+
75
+ If you want to use different configuration values for a specific call, you can create a new `PatchRetention::Configuration` object, populate it, and pass it to the desired method.
76
+
77
+ ```ruby
78
+ # Create a custom configuration instance
79
+ custom_config = PatchRetention::Configuration.new
80
+ custom_config.client_id = "your_other_client_id"
81
+ custom_config.client_secret = "your_other_client_secret"
82
+ custom_config.api_url = "https://specific-instance.patchretention.com/v2"
83
+ # custom_config.proxy_url = "http://your.other.proxy.com:8080" # Optional
84
+
85
+ # Use this custom configuration for an API call
86
+ begin
87
+ product_details = {
88
+ name: "Special Product",
89
+ price: 1500,
90
+ status: "PUBLISHED"
91
+ }
92
+ new_product = PatchRetention::Products.create(**product_details, config: custom_config)
93
+ puts "Created product with custom config: #{new_product}"
94
+
95
+ membership_payload = {
96
+ contact_id: "ct_somecontact",
97
+ product_id: new_product["id"], # Assuming new_product is a hash with an "id" key
98
+ start_at: Time.now.xmlschema
99
+ }
100
+ new_membership = PatchRetention::Memberships.create(**membership_payload, config: custom_config)
101
+ puts "Created membership with custom config: #{new_membership}"
102
+
103
+ rescue PatchRetention::Error => e
104
+ puts "Error with custom config: #{e.message}"
105
+ end
106
+ ```
107
+
108
+ This approach allows you to maintain multiple configurations if needed, for example, when interacting with different Patch Retention accounts or environments within the same application. If you need to use a highly customized Faraday connection (e.g., with special middleware), you would need to ensure the `PatchRetention.connection` method can accommodate this through the `Configuration` object, potentially by enhancing the `Configuration` class or the `PatchRetention.connection` method itself to interpret more advanced settings from the `Configuration` object.
109
+
41
110
  ### Usage
42
111
 
43
- First, you need to configure the gem with your API credentials.
112
+ First, you need to configure the gem with your API credentials globally (if not providing custom configurations per call).
44
113
 
45
114
  Then, you can use the gem's methods to interact with the API.
46
115
 
@@ -148,9 +217,126 @@ event = PatchRetention::Events.create(
148
217
  )
149
218
  ```
150
219
 
220
+ **Products**
221
+
222
+ To create a product:
223
+
224
+ ```ruby
225
+ product = PatchRetention::Products.create(
226
+ name: "Test Product",
227
+ price: 999, # Price in Cents
228
+ status: "PUBLISHED", # or "UNPUBLISHED"
229
+ description: "This is a product for testing purposes.", # Optional
230
+ membership: true, # Optional, boolean
231
+ tags: ["test", "new_product"], # Optional, array of strings
232
+ external_id: "SKU12345", # Optional
233
+ external_data: { "custom_key": "custom_value" } # Optional, hash
234
+ )
235
+ # => {"id"=>"prod_xxxxxxxxxxxxxx", "name"=>"Test Product", ...}
236
+ ```
237
+
238
+ To update a product:
239
+
240
+ ```ruby
241
+ product = PatchRetention::Products.update(
242
+ product_id: "prod_xxxxxxxxxxxxxx",
243
+ name: "Updated Product Name",
244
+ price: 1299,
245
+ status: "UNPUBLISHED"
246
+ )
247
+ ```
248
+
249
+ To find a product:
250
+
251
+ ```ruby
252
+ product = PatchRetention::Products.find(product_id: "prod_xxxxxxxxxxxxxx")
253
+ ```
254
+
255
+ **Memberships**
256
+
257
+ To create a membership:
258
+
259
+ ```ruby
260
+ require 'time'
261
+
262
+ membership = PatchRetention::Memberships.create(
263
+ contact_id: "68273ecd9xxxxxxxxxxxx", # Required, ID of the contact
264
+ product_id: "65de5xxxxxxxxxxxxx", # Required, ID of the product (e.g., from product creation)
265
+ start_at: Time.now.xmlschema, # Optional, ISO8601 timestamp
266
+ end_at: (Time.now + 30*24*60*60).xmlschema, # Optional, ISO8601 timestamp (e.g., 30 days from now)
267
+ external_id: "MEM123", # Optional
268
+ external_data: { "notes": "Trial membership" }, # Optional, hash
269
+ tags: ["trial", "api_created"] # Optional, array of strings
270
+ )
271
+ # => {"id"=>"mem_xxxxxxxxxxxxxx", "contact_id"=>"ct_xxxxxxxxxxxxxx", ...}
272
+ ```
273
+
274
+ To update a membership:
275
+
276
+ ```ruby
277
+ membership = PatchRetention::Memberships.update(
278
+ membership_id: "mem_xxxxxxxxxxxxxx",
279
+ end_at: (Time.now + 60*24*60*60).xmlschema, # Extend to 60 days
280
+ tags: ["extended", "premium"]
281
+ )
282
+ ```
283
+
284
+ To find a membership:
285
+
286
+ ```ruby
287
+ membership = PatchRetention::Memberships.find(membership_id: "mem_xxxxxxxxxxxxxx")
288
+ ```
289
+
151
290
  ## Development
152
291
 
153
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
292
+ After checking out the repo, run `bin/setup` to install dependencies.
293
+
294
+ ### Setting up Test Environment
295
+
296
+ To run tests, you'll need to set up test credentials. The test credentials should NOT be committed to the repository.
297
+
298
+ 1. **Using environment variables (recommended):**
299
+ ```bash
300
+ export PATCH_RETENTION_TEST_CLIENT_ID=your_test_client_id
301
+ export PATCH_RETENTION_TEST_CLIENT_SECRET=your_test_client_secret
302
+ ```
303
+
304
+ 2. **Using the setup script:**
305
+ ```bash
306
+ # This will create .env.test from your environment variables
307
+ ./script/setup_test_env
308
+ ```
309
+
310
+ 3. **Manual setup:**
311
+ Create a `.env.test` file with:
312
+ ```
313
+ PATCH_RETENTION_CLIENT_ID=your_test_client_id
314
+ PATCH_RETENTION_CLIENT_SECRET=your_test_client_secret
315
+ PATCH_RETENTION_API_URL=https://api.patchretention.com/v2
316
+ ```
317
+
318
+ **Note:** The `.env.test` file is gitignored and should never be committed.
319
+
320
+ ### Running Tests
321
+
322
+ Once your test environment is set up:
323
+ ```bash
324
+ bundle exec rake spec
325
+ ```
326
+
327
+ #### VCR Recording Modes
328
+
329
+ By default, VCR runs in `:once` mode, which only records HTTP interactions once. To re-record cassettes:
330
+
331
+ ```bash
332
+ # Re-record all cassettes
333
+ VCR_RECORD_MODE=new_episodes bundle exec rake spec
334
+
335
+ # Never make real HTTP calls (useful for CI)
336
+ VCR_RECORD_MODE=none bundle exec rake spec
337
+ ```
338
+
339
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
154
340
 
155
341
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
156
342
 
data/Rakefile CHANGED
@@ -9,4 +9,4 @@ require "rubocop/rake_task"
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
- task default: %i[spec rubocop]
12
+ task default: [:spec, :rubocop]
@@ -0,0 +1,66 @@
1
+ # API Implementation Decisions
2
+
3
+ ## Date: 2025-05-26
4
+
5
+ ### Context
6
+ During the implementation of Products and Memberships endpoints for the Patch Retention API v2, several important decisions were made based on API behavior discoveries.
7
+
8
+ ### Key Decisions
9
+
10
+ #### 1. ID Format Handling
11
+ - **Decision**: Use flexible ID validation instead of expecting specific prefixes
12
+ - **Rationale**: The API returns IDs in MongoDB format (e.g., `6833ef45d8ebcbe4774efb98`) rather than prefixed formats like `prod_xxx` or `mem_xxx`
13
+ - **Implementation**: Changed tests from `expect(result["id"]).to match(/^prod_/)` to `expect(result["id"]).to be_a(String)`
14
+
15
+ #### 2. Contact ID Field Naming
16
+ - **Decision**: Handle both `_id` and `id` fields for contacts
17
+ - **Rationale**: Contacts API returns `_id` as the primary identifier, while other endpoints use `id`
18
+ - **Implementation**: Use `contact["_id"] || contact["id"]` pattern throughout tests
19
+
20
+ #### 3. External Data Handling
21
+ - **Decision**: Don't expect `external_data` to be returned in responses
22
+ - **Rationale**: The API accepts `external_data` but doesn't always return it in the response
23
+ - **Implementation**: Removed assertions for `external_data` in test expectations
24
+
25
+ #### 4. Tag Capitalization
26
+ - **Decision**: Expect tags to be capitalized by the API
27
+ - **Rationale**: The API automatically capitalizes tags (e.g., "test" becomes "Test")
28
+ - **Implementation**: Updated test data to use capitalized tags
29
+
30
+ #### 5. Membership Status Updates
31
+ - **Decision**: Remove `cancel` and `expire` convenience methods
32
+ - **Rationale**: Based on Excel documentation and API testing, only create and update operations are supported
33
+ - **Implementation**: Removed these methods and updated documentation accordingly
34
+
35
+ ### API Response Patterns Discovered
36
+
37
+ 1. **Products Response Structure**:
38
+ ```json
39
+ {
40
+ "account_id": 654173,
41
+ "name": "Test Widget",
42
+ "price": 799,
43
+ "status": "PUBLISHED",
44
+ "_id": "6833ef45...",
45
+ "id": "6833ef45...",
46
+ "created_at": "2025-05-26T04:34:13.503Z",
47
+ "updated_at": "2025-05-26T04:34:13.503Z"
48
+ }
49
+ ```
50
+
51
+ 2. **Memberships Response Structure**:
52
+ ```json
53
+ {
54
+ "account_id": 654173,
55
+ "contact_id": "6833ef45...",
56
+ "product_id": "6833ef45...",
57
+ "status": "PENDING",
58
+ "_id": "6833ef45...",
59
+ "id": "6833ef45..."
60
+ }
61
+ ```
62
+
63
+ ### Future Considerations
64
+ - Monitor if API adds support for prefixed IDs
65
+ - Watch for changes in external_data handling
66
+ - Consider adding response object wrappers to normalize field names
@@ -0,0 +1,152 @@
1
+ # Testing Patterns for Patch Retention Gem
2
+
3
+ ## VCR Configuration
4
+
5
+ ### Setup
6
+ ```ruby
7
+ VCR.configure do |config|
8
+ config.cassette_library_dir = "spec/fixtures/vcr_cassettes"
9
+ config.hook_into :webmock
10
+ config.configure_rspec_metadata!
11
+ config.default_cassette_options = {
12
+ record: :new_episodes,
13
+ match_requests_on: [:method, :uri, :body]
14
+ }
15
+
16
+ # Filter sensitive data
17
+ config.filter_sensitive_data('<CLIENT_ID>') { '654173' }
18
+ config.filter_sensitive_data('<CLIENT_SECRET>') { 'secret_...' }
19
+ end
20
+ ```
21
+
22
+ ### Usage in Specs
23
+ Tag specs with `:vcr` to automatically record/replay HTTP interactions:
24
+ ```ruby
25
+ describe ".create", :vcr do
26
+ # test implementation
27
+ end
28
+ ```
29
+
30
+ ## Debugging Failed Specs
31
+
32
+ ### Using Debug Output
33
+ When specs fail, add debug output to understand API responses:
34
+
35
+ ```ruby
36
+ it "creates a product successfully" do
37
+ result = create_product
38
+ puts "DEBUG: Product creation result: #{result.inspect}"
39
+
40
+ # assertions...
41
+ end
42
+ ```
43
+
44
+ ### Common Debugging Patterns
45
+
46
+ 1. **Inspect Full Response**:
47
+ ```ruby
48
+ puts "DEBUG: API Response: #{result.inspect}"
49
+ ```
50
+
51
+ 2. **Check Specific Fields**:
52
+ ```ruby
53
+ puts "DEBUG: Contact ID used: #{contact['_id'] || contact['id']}"
54
+ puts "DEBUG: Product ID used: #{product['id']}"
55
+ ```
56
+
57
+ 3. **Conditional Debugging**:
58
+ ```ruby
59
+ if result["error"]
60
+ puts "DEBUG: Error response: #{result.inspect}"
61
+ end
62
+ ```
63
+
64
+ ## Test Data Patterns
65
+
66
+ ### Creating Test Objects
67
+ Always create fresh test data for each test to avoid dependencies:
68
+
69
+ ```ruby
70
+ let(:contact) do
71
+ PatchRetention::Contacts.find_or_create_by(
72
+ contact_params: {
73
+ first_name: "Test",
74
+ last_name: "User",
75
+ email: "test.user@example.com",
76
+ phone: "1234567890"
77
+ },
78
+ query_params: {
79
+ email: "test.user@example.com"
80
+ }
81
+ )
82
+ end
83
+ ```
84
+
85
+ ### Handling API Quirks
86
+
87
+ 1. **Tag Capitalization**:
88
+ ```ruby
89
+ let(:tags) { ["Test", "Widget", "Api_created"] } # API capitalizes tags
90
+ ```
91
+
92
+ 2. **Optional Field Validation**:
93
+ ```ruby
94
+ # Only check if field exists in response
95
+ if result["tags"]
96
+ expect(result["tags"]).to eq(expected_tags)
97
+ end
98
+ ```
99
+
100
+ 3. **Flexible ID Validation**:
101
+ ```ruby
102
+ expect(result["id"]).to be_a(String)
103
+ expect(result["id"].length).to be > 0
104
+ ```
105
+
106
+ ## Custom Configuration Testing
107
+
108
+ Always test that methods accept custom configuration:
109
+
110
+ ```ruby
111
+ context "with custom configuration" do
112
+ let(:custom_config) do
113
+ PatchRetention::Configuration.new.tap do |config|
114
+ config.client_id = '654173'
115
+ config.client_secret = 'secret_...'
116
+ end
117
+ end
118
+
119
+ it "uses the custom configuration" do
120
+ result = described_class.create(
121
+ # params...
122
+ config: custom_config
123
+ )
124
+
125
+ expect(result).to be_a(Hash)
126
+ end
127
+ end
128
+ ```
129
+
130
+ ## Error Testing Patterns
131
+
132
+ ### Required Parameter Validation
133
+ ```ruby
134
+ context "missing required fields" do
135
+ it "raises an error when name is missing" do
136
+ expect {
137
+ described_class.create(price: 999, status: "PUBLISHED")
138
+ }.to raise_error(ArgumentError, /missing keyword: :?name/)
139
+ end
140
+ end
141
+ ```
142
+
143
+ ### API Error Handling
144
+ ```ruby
145
+ context "with invalid data" do
146
+ it "raises PatchRetention::Error" do
147
+ expect {
148
+ described_class.update(membership_id: "invalid_id")
149
+ }.to raise_error(PatchRetention::Error, /Failed to update membership/)
150
+ end
151
+ end
152
+ ```
@@ -0,0 +1,100 @@
1
+ # GitHub Actions CI/CD Setup
2
+
3
+ ## Overview
4
+ This gem uses GitHub Actions for continuous integration and deployment. The workflows ensure code quality, test coverage, and security.
5
+
6
+ ## Workflows
7
+
8
+ ### 1. CI Workflow (`.github/workflows/ci.yml`)
9
+ **Triggers**: Push to main/master, Pull requests
10
+
11
+ **Jobs**:
12
+ - **Test Matrix**: Runs tests on Ruby 3.1, 3.2, and 3.3
13
+ - **Minimum Version Test**: Ensures compatibility with Ruby 3.1
14
+ - **Security Audit**: Checks for known vulnerabilities using bundler-audit
15
+
16
+ **Steps**:
17
+ 1. Run RSpec tests
18
+ 2. Run RuboCop for code style
19
+ 3. Build the gem
20
+ 4. Security audit
21
+
22
+ ### 2. PR Test Workflow (`.github/workflows/pr-test.yml`)
23
+ **Triggers**: Pull request events
24
+
25
+ **Features**:
26
+ - Runs tests with VCR in playback-only mode
27
+ - Checks for sensitive data in VCR cassettes
28
+ - Uses dummy credentials for testing
29
+
30
+ **Security Check**:
31
+ ```bash
32
+ grep -r "654173\|secret_A654173" spec/fixtures/vcr_cassettes/
33
+ ```
34
+
35
+ ### 3. Release Workflow (`.github/workflows/release.yml`)
36
+ **Triggers**: Push of version tags (v*)
37
+
38
+ **Process**:
39
+ 1. Run tests and linting
40
+ 2. Build gem
41
+ 3. Create GitHub release
42
+ 4. Upload gem artifact
43
+ 5. (Optional) Publish to RubyGems
44
+
45
+ ## Local Testing
46
+
47
+ Run the test script to validate CI locally:
48
+ ```bash
49
+ ./.github/test-workflow.sh
50
+ ```
51
+
52
+ This script runs:
53
+ - All tests
54
+ - RuboCop
55
+ - Gem build
56
+ - Sensitive data check
57
+ - Security audit
58
+
59
+ ## Maintaining CI
60
+
61
+ ### Adding Ruby Versions
62
+ Edit `.github/workflows/ci.yml`:
63
+ ```yaml
64
+ strategy:
65
+ matrix:
66
+ ruby-version: ['3.1', '3.2', '3.3']
67
+ ```
68
+
69
+ ### Updating Dependencies
70
+ The workflows use `bundler-cache: true` which automatically:
71
+ - Runs `bundle install`
72
+ - Caches dependencies
73
+ - Restores cache on subsequent runs
74
+
75
+ ### Security Considerations
76
+ 1. **Never commit real API credentials**
77
+ 2. **VCR cassettes are checked for sensitive data**
78
+ 3. **Use environment variables for secrets**
79
+ 4. **Regular security audits via bundler-audit**
80
+
81
+ ### Troubleshooting
82
+
83
+ **Tests fail in CI but pass locally**:
84
+ - Check Ruby version differences
85
+ - Ensure VCR cassettes are committed
86
+ - Verify environment variables
87
+
88
+ **RuboCop failures**:
89
+ - Run `bundle exec rubocop -A` locally
90
+ - Commit the fixes
91
+
92
+ **Security audit failures**:
93
+ - Update affected gems: `bundle update [gem_name]`
94
+ - Check advisory details in output
95
+
96
+ ## Badge
97
+ Add to README:
98
+ ```markdown
99
+ [![CI](https://github.com/[USERNAME]/patch_retention/actions/workflows/ci.yml/badge.svg)](https://github.com/[USERNAME]/patch_retention/actions/workflows/ci.yml)
100
+ ```
@@ -0,0 +1,122 @@
1
+ # Troubleshooting Guide
2
+
3
+ ## Common Issues and Solutions
4
+
5
+ ### 1. Contact ID Not Found
6
+ **Problem**: Membership creation fails with "Invalid contact_id specified"
7
+
8
+ **Cause**: Contacts API returns `_id` field, not `id`
9
+
10
+ **Solution**:
11
+ ```ruby
12
+ contact_id = contact["_id"] || contact["id"]
13
+ ```
14
+
15
+ ### 2. Tags Don't Match Expected Values
16
+ **Problem**: Test assertions fail because tags don't match
17
+
18
+ **Cause**: API automatically capitalizes all tags
19
+
20
+ **Solution**:
21
+ ```ruby
22
+ # Use capitalized tags in tests
23
+ let(:tags) { ["Test", "Widget", "Premium"] }
24
+ ```
25
+
26
+ ### 3. External Data Not Returned
27
+ **Problem**: `external_data` is nil in API response
28
+
29
+ **Cause**: API accepts but doesn't always return external_data
30
+
31
+ **Solution**:
32
+ ```ruby
33
+ # Don't assert on external_data in responses
34
+ # Or make it conditional:
35
+ expect(result["external_data"]).to eq(data) if result["external_data"]
36
+ ```
37
+
38
+ ### 4. JSON Parse Error on Update
39
+ **Problem**: `JSON::ParserError: unexpected token at 'Not Found'`
40
+
41
+ **Cause**: Invalid ID format or non-existent resource
42
+
43
+ **Solution**:
44
+ ```ruby
45
+ # Ensure you're using the correct ID from creation
46
+ membership_id = membership["id"] # not "_id" for memberships
47
+ ```
48
+
49
+ ### 5. VCR Cassette Mismatch
50
+ **Problem**: Tests fail with VCR errors about request not matching
51
+
52
+ **Cause**: Request body or headers changed
53
+
54
+ **Solution**:
55
+ ```bash
56
+ # Delete the cassette and re-record
57
+ rm spec/fixtures/vcr_cassettes/[cassette_name].yml
58
+ bundle exec rspec [spec_file]
59
+ ```
60
+
61
+ ### 6. Debugging API Responses
62
+
63
+ **Add debug output to specs**:
64
+ ```ruby
65
+ it "creates a resource" do
66
+ result = create_resource
67
+ puts "DEBUG: API Response: #{result.inspect}"
68
+
69
+ # This helps identify:
70
+ # - Actual field names returned
71
+ # - Data transformations by API
72
+ # - Error messages
73
+ end
74
+ ```
75
+
76
+ ### 7. Phone Number Formatting
77
+ **Problem**: Phone numbers have unexpected format
78
+
79
+ **Cause**: API may prepend country code (e.g., "1234567890" becomes "11234567890")
80
+
81
+ **Solution**: Be flexible with phone number assertions or normalize before comparison
82
+
83
+ ### 8. Timestamp Precision
84
+ **Problem**: Time comparisons fail by small amounts
85
+
86
+ **Solution**:
87
+ ```ruby
88
+ expect(Time.parse(result["start_at"])).to be_within(60).of(Time.parse(expected_time))
89
+ ```
90
+
91
+ ### 9. Status Field Values
92
+ **Note**: Memberships have a `status` field that defaults to "PENDING"
93
+
94
+ ### 10. Missing Methods After Updates
95
+ **Problem**: NoMethodError for cancel/expire methods
96
+
97
+ **Cause**: These methods were removed as they're not supported by the API
98
+
99
+ **Solution**: Use the `update` method with status parameter if needed
100
+
101
+ ## Debug Checklist
102
+
103
+ When a test fails:
104
+ 1. ✓ Add debug output to see actual API response
105
+ 2. ✓ Check if field names match (id vs _id)
106
+ 3. ✓ Verify data transformations (capitalization, formatting)
107
+ 4. ✓ Ensure test data is valid for the API
108
+ 5. ✓ Check VCR cassettes are up to date
109
+ 6. ✓ Verify API credentials are correct
110
+
111
+ ## Useful Debug Commands
112
+
113
+ ```bash
114
+ # Run specific test with output
115
+ bundle exec rspec spec/path/to/spec.rb:line_number --format documentation
116
+
117
+ # Clear VCR cassettes
118
+ rm -rf spec/fixtures/vcr_cassettes/
119
+
120
+ # Run tests without VCR (live API calls)
121
+ VCR=off bundle exec rspec
122
+ ```