From 2ec1e010cd21f35dcd76ef9b816bde46e455d4f0 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 11:50:18 -0700 Subject: [PATCH 01/15] 4763: Add github ci yml and ensure rubygems will read from github releases --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++++++++++ data_nexus.gemspec | 1 + 2 files changed, 44 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9dcdacd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + ruby-version: ["2.7", "3.0", "3.1", "3.2", "3.4", "4.0"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Run tests + run: bundle exec rspec + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.2" + bundler-cache: true + + - name: Run RuboCop + run: bundle exec rubocop diff --git a/data_nexus.gemspec b/data_nexus.gemspec index 4d2070c..5511539 100644 --- a/data_nexus.gemspec +++ b/data_nexus.gemspec @@ -16,6 +16,7 @@ Gem::Specification.new do |spec| spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage + spec.metadata['changelog_uri'] = "#{spec.homepage}/releases" spec.metadata['rubygems_mfa_required'] = 'true' # Specify which files should be added to the gem when it is released. From 6497b9c846f4a74a8320029cb14dc75270f1872f Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 11:53:26 -0700 Subject: [PATCH 02/15] 4763: Remove bundled with --- Gemfile.lock | 3 --- 1 file changed, 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ae682df..0f0b6ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -121,6 +121,3 @@ DEPENDENCIES rubocop-rspec (~> 3.0) vcr (~> 6.0) webmock (~> 3.0) - -BUNDLED WITH - 2.7.1 From e8e25f5a46d86907417fa82dab048bf3d0ebc485 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 12:11:18 -0700 Subject: [PATCH 03/15] 4763: Tweak cassettes --- spec/cassettes/member_consents/create.yml | 12 ++++++------ spec/cassettes/member_consents/delete.yml | 8 ++++---- spec/cassettes/member_consents/find.yml | 12 ++++++------ spec/cassettes/member_consents/update.yml | 12 ++++++------ spec/integration/member_consents_spec.rb | 2 +- spec/support/vcr.rb | 1 + 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/spec/cassettes/member_consents/create.yml b/spec/cassettes/member_consents/create.yml index 2e9a86e..36d3522 100644 --- a/spec/cassettes/member_consents/create.yml +++ b/spec/cassettes/member_consents/create.yml @@ -25,19 +25,19 @@ http_interactions: Cache-Control: - max-age=0, private, must-revalidate Content-Length: - - '203' + - "03" Content-Type: - application/json; charset=utf-8 Date: - - Mon, 12 Jan 2026 17:28:27 GMT + - Mon, 1 Jan 06 19:09:17 GMT Location: - - "/api/members//consents/5" + - "/api/members//consents/7" Server: - Cowboy X-Request-Id: - - GIoL6w5qVbe2LjcAAAoB + - GIoRa4qd7Ymp018AAAYk body: encoding: UTF-8 - string: '{"data":{"id":5,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""}}' - recorded_at: Mon, 12 Jan 2026 17:28:28 GMT + string: '{"data":{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""}}' + recorded_at: Mon, 12 Jan 2026 19:09:17 GMT recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_consents/delete.yml b/spec/cassettes/member_consents/delete.yml index fa8c093..3d0bd5d 100644 --- a/spec/cassettes/member_consents/delete.yml +++ b/spec/cassettes/member_consents/delete.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: delete - uri: https://localhost:4000/api/members//consents/3 + uri: https://localhost:4000/api/members//consents/ body: encoding: US-ASCII string: '' @@ -25,13 +25,13 @@ http_interactions: Cache-Control: - max-age=0, private, must-revalidate Date: - - Mon, 12 Jan 2026 17:28:28 GMT + - Mon, 1 Jan 06 19:09:18 GMT Server: - Cowboy X-Request-Id: - - GIoL60_MDbCwDsAAAAsh + - GIoRa81lhLq5b_QAAAKB body: encoding: UTF-8 string: '' - recorded_at: Mon, 12 Jan 2026 17:28:29 GMT + recorded_at: Mon, 12 Jan 2026 19:09:18 GMT recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_consents/find.yml b/spec/cassettes/member_consents/find.yml index 1a64bc6..fe2041d 100644 --- a/spec/cassettes/member_consents/find.yml +++ b/spec/cassettes/member_consents/find.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://localhost:4000/api/members//consents/3 + uri: https://localhost:4000/api/members//consents/ body: encoding: US-ASCII string: '' @@ -25,17 +25,17 @@ http_interactions: Cache-Control: - max-age=0, private, must-revalidate Content-Length: - - '192' + - '188' Content-Type: - application/json; charset=utf-8 Date: - - Mon, 12 Jan 2026 17:28:28 GMT + - Mon, 1 Jan 06 19:09:17 GMT Server: - Cowboy X-Request-Id: - - GIoL6ysyeKPSkJMAAAri + - GIoRa6oneiY6_4UAAAFB body: encoding: UTF-8 - string: '{"data":{"id":3,"category":"privacy","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}}' - recorded_at: Mon, 12 Jan 2026 17:28:28 GMT + string: '{"data":{"id":,"category":"tos","consent_details":{},"member_response":true,"consented_at":"05-10-08T0:18:09Z","program_id":"","member_id":""}}' + recorded_at: Mon, 12 Jan 2026 19:09:18 GMT recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_consents/update.yml b/spec/cassettes/member_consents/update.yml index 7fab306..7721360 100644 --- a/spec/cassettes/member_consents/update.yml +++ b/spec/cassettes/member_consents/update.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: patch - uri: https://localhost:4000/api/members//consents/3 + uri: https://localhost:4000/api/members//consents/ body: encoding: UTF-8 string: '{"consent":{"member_response":false}}' @@ -25,17 +25,17 @@ http_interactions: Cache-Control: - max-age=0, private, must-revalidate Content-Length: - - '193' + - '189' Content-Type: - application/json; charset=utf-8 Date: - - Mon, 12 Jan 2026 17:28:28 GMT + - Mon, 1 Jan 06 19:09:17 GMT Server: - Cowboy X-Request-Id: - - GIoL6z3Ao6-Odi4AAAsB + - GIoRa7wA5nO5Vm0AAAIh body: encoding: UTF-8 - string: '{"data":{"id":3,"category":"privacy","consent_details":{},"member_response":false,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}}' - recorded_at: Mon, 12 Jan 2026 17:28:28 GMT + string: '{"data":{"id":,"category":"tos","consent_details":{},"member_response":false,"consented_at":"05-10-08T0:18:09Z","program_id":"","member_id":""}}' + recorded_at: Mon, 12 Jan 2026 19:09:18 GMT recorded_with: VCR 6.4.0 diff --git a/spec/integration/member_consents_spec.rb b/spec/integration/member_consents_spec.rb index 7a81ea5..6757653 100644 --- a/spec/integration/member_consents_spec.rb +++ b/spec/integration/member_consents_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe 'Member Consents', :vcr do +RSpec.describe 'Member Consents', :vcr, order: :defined do let(:api_key) { ENV.fetch('DATANEXUS_API_KEY', 'test-api-key') } let(:base_url) { ENV.fetch('DATANEXUS_BASE_URL', 'http://localhost:4000') } let(:ssl_verify) { ENV.fetch('DATANEXUS_SSL_VERIFY', 'true') == 'true' } diff --git a/spec/support/vcr.rb b/spec/support/vcr.rb index 7ef317f..8a90d59 100644 --- a/spec/support/vcr.rb +++ b/spec/support/vcr.rb @@ -12,6 +12,7 @@ config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_API_KEY', 'test-api-key') } config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_PROGRAM_ID', 'test-program-id') } config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } + config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_CONSENT_ID', 'test-consent-id') } config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_BORN_ON', '1970-01-01') } config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_EMPLOYEE_ID', 'test-employee-id') } From 547fc3fc6e54be2e4f015baf4f6bbd28104506d0 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 12:57:17 -0700 Subject: [PATCH 04/15] 4763: Pin public_suffix to older version --- Gemfile | 3 +++ Gemfile.lock | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 168fe3e..1625c79 100644 --- a/Gemfile +++ b/Gemfile @@ -16,3 +16,6 @@ gem 'rubocop-rspec', '~> 3.0' gem 'vcr', '~> 6.0' gem 'webmock', '~> 3.0' + +# Pin public_suffix for Ruby 2.7+ compatibility (7.0+ requires Ruby >= 3.2) +gem 'public_suffix', '< 7.0' diff --git a/Gemfile.lock b/Gemfile.lock index 0f0b6ef..aeb9928 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -49,7 +49,7 @@ GEM psych (5.3.1) date stringio - public_suffix (7.0.2) + public_suffix (6.0.2) racc (1.8.1) rainbow (3.1.1) rake (13.3.1) @@ -114,6 +114,7 @@ PLATFORMS DEPENDENCIES data_nexus! irb + public_suffix (< 7.0) rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.0) @@ -121,3 +122,6 @@ DEPENDENCIES rubocop-rspec (~> 3.0) vcr (~> 6.0) webmock (~> 3.0) + +BUNDLED WITH + 2.7.1 From ef2a7ba601627cbf9b0f93ed84bf552eaacf81a7 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 13:02:25 -0700 Subject: [PATCH 05/15] 4763: Further tweak consents cassettes --- spec/cassettes/member_consents/create.yml | 80 +++++++++++------------ spec/cassettes/member_consents/delete.yml | 68 +++++++++---------- spec/cassettes/member_consents/find.yml | 76 ++++++++++----------- spec/cassettes/member_consents/update.yml | 76 ++++++++++----------- spec/integration/member_consents_spec.rb | 6 +- spec/support/vcr.rb | 2 +- 6 files changed, 154 insertions(+), 154 deletions(-) diff --git a/spec/cassettes/member_consents/create.yml b/spec/cassettes/member_consents/create.yml index 36d3522..34afd94 100644 --- a/spec/cassettes/member_consents/create.yml +++ b/spec/cassettes/member_consents/create.yml @@ -1,43 +1,43 @@ --- http_interactions: -- request: - method: post - uri: https://localhost:4000/api/members//consents - body: - encoding: UTF-8 - string: '{"consent":{"category":"sms","member_response":true,"consent_details":{"sms_phone_number":"+15558675309"},"program_id":""}}' - headers: - Authorization: - - apikey - Content-Type: - - application/json - Accept: - - application/json - User-Agent: - - data-nexus-ruby/0.1.0 - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - response: - status: - code: 201 - message: Created - headers: - Cache-Control: - - max-age=0, private, must-revalidate - Content-Length: - - "03" - Content-Type: - - application/json; charset=utf-8 - Date: - - Mon, 1 Jan 06 19:09:17 GMT - Location: - - "/api/members//consents/7" - Server: - - Cowboy - X-Request-Id: - - GIoRa4qd7Ymp018AAAYk - body: - encoding: UTF-8 - string: '{"data":{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""}}' - recorded_at: Mon, 12 Jan 2026 19:09:17 GMT + - request: + method: post + uri: https://localhost:4000/api/members//consents + body: + encoding: UTF-8 + string: '{"consent":{"category":"sms","member_response":true,"consent_details":{"sms_phone_number":"+15558675309"},"program_id":""}}' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 201 + message: Created + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - "203" + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 12 Jan 2026 19:09:17 GMT + Location: + - "/api/members//consents/7" + Server: + - Cowboy + X-Request-Id: + - GIoRa4qd7Ymp018AAAYk + body: + encoding: UTF-8 + string: '{"data":{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""}}' + recorded_at: Mon, 12 Jan 2026 19:09:17 GMT recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_consents/delete.yml b/spec/cassettes/member_consents/delete.yml index 3d0bd5d..6d1d724 100644 --- a/spec/cassettes/member_consents/delete.yml +++ b/spec/cassettes/member_consents/delete.yml @@ -1,37 +1,37 @@ --- http_interactions: -- request: - method: delete - uri: https://localhost:4000/api/members//consents/ - body: - encoding: US-ASCII - string: '' - headers: - Authorization: - - apikey - Content-Type: - - application/json - Accept: - - application/json - User-Agent: - - data-nexus-ruby/0.1.0 - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - response: - status: - code: 204 - message: No Content - headers: - Cache-Control: - - max-age=0, private, must-revalidate - Date: - - Mon, 1 Jan 06 19:09:18 GMT - Server: - - Cowboy - X-Request-Id: - - GIoRa81lhLq5b_QAAAKB - body: - encoding: UTF-8 - string: '' - recorded_at: Mon, 12 Jan 2026 19:09:18 GMT + - request: + method: delete + uri: https://localhost:4000/api/members//consents/2 + body: + encoding: US-ASCII + string: "" + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 204 + message: No Content + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Date: + - Mon, 12 Jan 2026 19:09:18 GMT + Server: + - Cowboy + X-Request-Id: + - GIoRa81lhLq5b_QAAAKB + body: + encoding: UTF-8 + string: "" + recorded_at: Mon, 12 Jan 2026 19:09:18 GMT recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_consents/find.yml b/spec/cassettes/member_consents/find.yml index fe2041d..d0d39d4 100644 --- a/spec/cassettes/member_consents/find.yml +++ b/spec/cassettes/member_consents/find.yml @@ -1,41 +1,41 @@ --- http_interactions: -- request: - method: get - uri: https://localhost:4000/api/members//consents/ - body: - encoding: US-ASCII - string: '' - headers: - Authorization: - - apikey - Content-Type: - - application/json - Accept: - - application/json - User-Agent: - - data-nexus-ruby/0.1.0 - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - response: - status: - code: 200 - message: OK - headers: - Cache-Control: - - max-age=0, private, must-revalidate - Content-Length: - - '188' - Content-Type: - - application/json; charset=utf-8 - Date: - - Mon, 1 Jan 06 19:09:17 GMT - Server: - - Cowboy - X-Request-Id: - - GIoRa6oneiY6_4UAAAFB - body: - encoding: UTF-8 - string: '{"data":{"id":,"category":"tos","consent_details":{},"member_response":true,"consented_at":"05-10-08T0:18:09Z","program_id":"","member_id":""}}' - recorded_at: Mon, 12 Jan 2026 19:09:18 GMT + - request: + method: get + uri: https://localhost:4000/api/members//consents/2 + body: + encoding: US-ASCII + string: "" + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - "188" + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 12 Jan 2026 19:09:17 GMT + Server: + - Cowboy + X-Request-Id: + - GIoRa6oneiY6_4UAAAFB + body: + encoding: UTF-8 + string: '{"data":{"id":2,"category":"tos","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:09Z","program_id":"","member_id":""}}' + recorded_at: Mon, 12 Jan 2026 19:09:18 GMT recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_consents/update.yml b/spec/cassettes/member_consents/update.yml index 7721360..7321585 100644 --- a/spec/cassettes/member_consents/update.yml +++ b/spec/cassettes/member_consents/update.yml @@ -1,41 +1,41 @@ --- http_interactions: -- request: - method: patch - uri: https://localhost:4000/api/members//consents/ - body: - encoding: UTF-8 - string: '{"consent":{"member_response":false}}' - headers: - Authorization: - - apikey - Content-Type: - - application/json - Accept: - - application/json - User-Agent: - - data-nexus-ruby/0.1.0 - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - response: - status: - code: 200 - message: OK - headers: - Cache-Control: - - max-age=0, private, must-revalidate - Content-Length: - - '189' - Content-Type: - - application/json; charset=utf-8 - Date: - - Mon, 1 Jan 06 19:09:17 GMT - Server: - - Cowboy - X-Request-Id: - - GIoRa7wA5nO5Vm0AAAIh - body: - encoding: UTF-8 - string: '{"data":{"id":,"category":"tos","consent_details":{},"member_response":false,"consented_at":"05-10-08T0:18:09Z","program_id":"","member_id":""}}' - recorded_at: Mon, 12 Jan 2026 19:09:18 GMT + - request: + method: patch + uri: https://localhost:4000/api/members//consents/2 + body: + encoding: UTF-8 + string: '{"consent":{"member_response":false}}' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - "189" + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 12 Jan 2026 19:09:17 GMT + Server: + - Cowboy + X-Request-Id: + - GIoRa7wA5nO5Vm0AAAIh + body: + encoding: UTF-8 + string: '{"data":{"id":2,"category":"tos","consent_details":{},"member_response":false,"consented_at":"2025-10-08T20:18:09Z","program_id":"","member_id":""}}' + recorded_at: Mon, 12 Jan 2026 19:09:18 GMT recorded_with: VCR 6.4.0 diff --git a/spec/integration/member_consents_spec.rb b/spec/integration/member_consents_spec.rb index 6757653..db6fd55 100644 --- a/spec/integration/member_consents_spec.rb +++ b/spec/integration/member_consents_spec.rb @@ -38,7 +38,7 @@ end describe 'finding a consent' do - let(:consent_id) { ENV.fetch('DATANEXUS_TEST_CONSENT_ID', 'test-consent-id') } + let(:consent_id) { 2 } it 'returns the consent data', vcr: { cassette_name: 'member_consents/find' } do consent = client.programs(program_id).members.consents(member_id).find(consent_id) @@ -52,7 +52,7 @@ end describe 'updating a consent' do - let(:consent_id) { ENV.fetch('DATANEXUS_TEST_CONSENT_ID', 'test-consent-id') } + let(:consent_id) { 2 } it 'returns the updated consent', vcr: { cassette_name: 'member_consents/update' } do response = client.programs(program_id).members.consents(member_id).update( @@ -66,7 +66,7 @@ end describe 'deleting a consent' do - let(:consent_id) { ENV.fetch('DATANEXUS_TEST_CONSENT_ID', 'test-consent-id') } + let(:consent_id) { 2 } it 'returns an empty response', vcr: { cassette_name: 'member_consents/delete' } do response = client.programs(program_id).members.consents(member_id).delete(consent_id) diff --git a/spec/support/vcr.rb b/spec/support/vcr.rb index 8a90d59..804d7d3 100644 --- a/spec/support/vcr.rb +++ b/spec/support/vcr.rb @@ -12,7 +12,7 @@ config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_API_KEY', 'test-api-key') } config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_PROGRAM_ID', 'test-program-id') } config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } - config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_CONSENT_ID', 'test-consent-id') } + config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_BORN_ON', '1970-01-01') } config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_EMPLOYEE_ID', 'test-employee-id') } From 04638a2bfc0191b20c5c73716e8486762ff6b17d Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 13:04:34 -0700 Subject: [PATCH 06/15] 4763: Remove bundled with --- Gemfile.lock | 3 --- 1 file changed, 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index aeb9928..4c8f0b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,6 +122,3 @@ DEPENDENCIES rubocop-rspec (~> 3.0) vcr (~> 6.0) webmock (~> 3.0) - -BUNDLED WITH - 2.7.1 From c113276bc298180729adff2338ec9075771f3599 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 13:07:10 -0700 Subject: [PATCH 07/15] 4763: pin erb --- Gemfile | 3 +++ Gemfile.lock | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 1625c79..fc7935f 100644 --- a/Gemfile +++ b/Gemfile @@ -19,3 +19,6 @@ gem 'webmock', '~> 3.0' # Pin public_suffix for Ruby 2.7+ compatibility (7.0+ requires Ruby >= 3.2) gem 'public_suffix', '< 7.0' + +# Pin erb for Ruby 2.7+ compatibility (6.0+ requires Ruby >= 3.2) +gem 'erb', '< 6.0' diff --git a/Gemfile.lock b/Gemfile.lock index 4c8f0b8..d9453a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,7 +17,7 @@ GEM rexml date (3.5.1) diff-lcs (1.6.2) - erb (6.0.1) + erb (5.1.3) faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json @@ -113,6 +113,7 @@ PLATFORMS DEPENDENCIES data_nexus! + erb (< 6.0) irb public_suffix (< 7.0) rake (~> 13.0) From 69fe39097a2bb070090035c66ec8ee598cabad49 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 13:09:44 -0700 Subject: [PATCH 08/15] 4763: pin erb again --- Gemfile | 4 ++-- Gemfile.lock | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index fc7935f..a7010f3 100644 --- a/Gemfile +++ b/Gemfile @@ -20,5 +20,5 @@ gem 'webmock', '~> 3.0' # Pin public_suffix for Ruby 2.7+ compatibility (7.0+ requires Ruby >= 3.2) gem 'public_suffix', '< 7.0' -# Pin erb for Ruby 2.7+ compatibility (6.0+ requires Ruby >= 3.2) -gem 'erb', '< 6.0' +# Pin erb for Ruby 2.7+ compatibility (5.0+ requires Ruby >= 3.2) +gem 'erb', '~> 4.0' diff --git a/Gemfile.lock b/Gemfile.lock index d9453a1..524077c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,12 +12,14 @@ GEM public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) bigdecimal (4.0.1) + cgi (0.5.1) crack (1.0.1) bigdecimal rexml date (3.5.1) diff-lcs (1.6.2) - erb (5.1.3) + erb (4.0.4) + cgi (>= 0.3.3) faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json @@ -113,7 +115,7 @@ PLATFORMS DEPENDENCIES data_nexus! - erb (< 6.0) + erb (~> 4.0) irb public_suffix (< 7.0) rake (~> 13.0) From bb4c43265fc339fa15c3a07e7c160a62084eaaff Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 13:15:05 -0700 Subject: [PATCH 09/15] 4763: Repin suffix --- Gemfile | 4 ++-- Gemfile.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index a7010f3..d570b77 100644 --- a/Gemfile +++ b/Gemfile @@ -17,8 +17,8 @@ gem 'rubocop-rspec', '~> 3.0' gem 'vcr', '~> 6.0' gem 'webmock', '~> 3.0' -# Pin public_suffix for Ruby 2.7+ compatibility (7.0+ requires Ruby >= 3.2) -gem 'public_suffix', '< 7.0' +# Pin public_suffix for Ruby 2.7+ compatibility (6.0+ requires Ruby >= 3.0) +gem 'public_suffix', '~> 5.0' # Pin erb for Ruby 2.7+ compatibility (5.0+ requires Ruby >= 3.2) gem 'erb', '~> 4.0' diff --git a/Gemfile.lock b/Gemfile.lock index 524077c..047e71f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,7 +51,7 @@ GEM psych (5.3.1) date stringio - public_suffix (6.0.2) + public_suffix (5.1.1) racc (1.8.1) rainbow (3.1.1) rake (13.3.1) @@ -117,7 +117,7 @@ DEPENDENCIES data_nexus! erb (~> 4.0) irb - public_suffix (< 7.0) + public_suffix (~> 5.0) rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.0) From 6e4efe60c018a45dd76c1865e8b770cdf663caf8 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 13:17:41 -0700 Subject: [PATCH 10/15] 4764: Who needs ruby 2? --- .github/workflows/ci.yml | 2 +- Gemfile | 6 ------ Gemfile.lock | 8 ++------ data_nexus.gemspec | 2 +- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9dcdacd..bb640c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ["2.7", "3.0", "3.1", "3.2", "3.4", "4.0"] + ruby-version: ["3.0", "3.1", "3.2", "3.4", "4.0"] steps: - uses: actions/checkout@v4 diff --git a/Gemfile b/Gemfile index d570b77..168fe3e 100644 --- a/Gemfile +++ b/Gemfile @@ -16,9 +16,3 @@ gem 'rubocop-rspec', '~> 3.0' gem 'vcr', '~> 6.0' gem 'webmock', '~> 3.0' - -# Pin public_suffix for Ruby 2.7+ compatibility (6.0+ requires Ruby >= 3.0) -gem 'public_suffix', '~> 5.0' - -# Pin erb for Ruby 2.7+ compatibility (5.0+ requires Ruby >= 3.2) -gem 'erb', '~> 4.0' diff --git a/Gemfile.lock b/Gemfile.lock index 047e71f..0f0b6ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,14 +12,12 @@ GEM public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) bigdecimal (4.0.1) - cgi (0.5.1) crack (1.0.1) bigdecimal rexml date (3.5.1) diff-lcs (1.6.2) - erb (4.0.4) - cgi (>= 0.3.3) + erb (6.0.1) faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json @@ -51,7 +49,7 @@ GEM psych (5.3.1) date stringio - public_suffix (5.1.1) + public_suffix (7.0.2) racc (1.8.1) rainbow (3.1.1) rake (13.3.1) @@ -115,9 +113,7 @@ PLATFORMS DEPENDENCIES data_nexus! - erb (~> 4.0) irb - public_suffix (~> 5.0) rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.0) diff --git a/data_nexus.gemspec b/data_nexus.gemspec index 5511539..4be29fa 100644 --- a/data_nexus.gemspec +++ b/data_nexus.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |spec| spec.description = "A Ruby gem for interacting with the Dart Health's DataNexus API." spec.homepage = 'https://github.com/DartHealth/datanexus-ruby' spec.license = 'MIT' - spec.required_ruby_version = '>= 2.7.0' + spec.required_ruby_version = '>= 3.0.0' spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage From 5dd888e77465d9000a18f270e0da113324749f41 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 13:18:31 -0700 Subject: [PATCH 11/15] 4763: Okay, for real, this is the last pin --- Gemfile | 6 ++++++ Gemfile.lock | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 168fe3e..6952f87 100644 --- a/Gemfile +++ b/Gemfile @@ -16,3 +16,9 @@ gem 'rubocop-rspec', '~> 3.0' gem 'vcr', '~> 6.0' gem 'webmock', '~> 3.0' + +# Pin public_suffix for Ruby 3.0/3.1 compatibility (7.0+ requires Ruby >= 3.2) +gem 'public_suffix', '< 7.0' + +# Pin erb for Ruby 3.0/3.1 compatibility (6.0+ requires Ruby >= 3.2) +gem 'erb', '< 6.0' diff --git a/Gemfile.lock b/Gemfile.lock index 0f0b6ef..d9453a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,7 +17,7 @@ GEM rexml date (3.5.1) diff-lcs (1.6.2) - erb (6.0.1) + erb (5.1.3) faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json @@ -49,7 +49,7 @@ GEM psych (5.3.1) date stringio - public_suffix (7.0.2) + public_suffix (6.0.2) racc (1.8.1) rainbow (3.1.1) rake (13.3.1) @@ -113,7 +113,9 @@ PLATFORMS DEPENDENCIES data_nexus! + erb (< 6.0) irb + public_suffix (< 7.0) rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.0) From 2db1cb3f046713a67bf9fb4a5945bcec61b1829f Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Mon, 12 Jan 2026 13:19:55 -0700 Subject: [PATCH 12/15] 4763: Final pin v2 final final --- .rubocop.yml | 2 +- Gemfile | 4 ++-- Gemfile.lock | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ca7d881..47c05d2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,7 @@ plugins: - rubocop-rspec AllCops: - TargetRubyVersion: 2.7 + TargetRubyVersion: 3.0 NewCops: enable # Integration tests have different expectations diff --git a/Gemfile b/Gemfile index 6952f87..d68dd90 100644 --- a/Gemfile +++ b/Gemfile @@ -20,5 +20,5 @@ gem 'webmock', '~> 3.0' # Pin public_suffix for Ruby 3.0/3.1 compatibility (7.0+ requires Ruby >= 3.2) gem 'public_suffix', '< 7.0' -# Pin erb for Ruby 3.0/3.1 compatibility (6.0+ requires Ruby >= 3.2) -gem 'erb', '< 6.0' +# Pin erb for Ruby 3.0/3.1 compatibility (5.0+ requires Ruby >= 3.2) +gem 'erb', '~> 4.0' diff --git a/Gemfile.lock b/Gemfile.lock index d9453a1..524077c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,12 +12,14 @@ GEM public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) bigdecimal (4.0.1) + cgi (0.5.1) crack (1.0.1) bigdecimal rexml date (3.5.1) diff-lcs (1.6.2) - erb (5.1.3) + erb (4.0.4) + cgi (>= 0.3.3) faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json @@ -113,7 +115,7 @@ PLATFORMS DEPENDENCIES data_nexus! - erb (< 6.0) + erb (~> 4.0) irb public_suffix (< 7.0) rake (~> 13.0) From 796920dbb5b37d6443fed6a9c1a28e10263f2a10 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Tue, 13 Jan 2026 17:44:16 -0700 Subject: [PATCH 13/15] 4763: Add root level members and enrollments --- .mise.local.toml.example | 4 +- lib/data_nexus/client.rb | 14 +- lib/data_nexus/resources/member_consents.rb | 16 +-- .../resources/member_enrollments.rb | 122 ++++++++++++++++ lib/data_nexus/resources/members.rb | 117 +++++++++++++++ lib/data_nexus/resources/program_member.rb | 134 ++++++++++++++++++ lib/data_nexus/resources/program_members.rb | 66 +-------- lib/data_nexus/resources/programs.rb | 38 +++-- spec/cassettes/member_enrollments/create.yml | 43 ++++++ spec/cassettes/member_enrollments/delete.yml | 37 +++++ spec/cassettes/member_enrollments/find.yml | 41 ++++++ spec/cassettes/member_enrollments/update.yml | 41 ++++++ spec/cassettes/members/find.yml | 44 ++++++ spec/cassettes/members/list.yml | 96 +++++++++++++ spec/cassettes/members/list_by_born_on.yml | 56 ++++++++ spec/cassettes/members/list_by_name.yml | 41 ++++++ spec/cassettes/members/list_by_program.yml | 96 +++++++++++++ .../members/list_by_updated_since.yml | 96 +++++++++++++ spec/cassettes/members/list_paginated.yml | 66 +++++++++ spec/cassettes/members/update.yml | 44 ++++++ spec/integration/member_consents_spec.rb | 10 +- spec/integration/member_enrollments_spec.rb | 72 ++++++++++ spec/integration/members_spec.rb | 92 ++++++++++++ spec/integration/program_members_spec.rb | 7 +- spec/support/vcr.rb | 1 + 25 files changed, 1301 insertions(+), 93 deletions(-) create mode 100644 lib/data_nexus/resources/member_enrollments.rb create mode 100644 lib/data_nexus/resources/members.rb create mode 100644 lib/data_nexus/resources/program_member.rb create mode 100644 spec/cassettes/member_enrollments/create.yml create mode 100644 spec/cassettes/member_enrollments/delete.yml create mode 100644 spec/cassettes/member_enrollments/find.yml create mode 100644 spec/cassettes/member_enrollments/update.yml create mode 100644 spec/cassettes/members/find.yml create mode 100644 spec/cassettes/members/list.yml create mode 100644 spec/cassettes/members/list_by_born_on.yml create mode 100644 spec/cassettes/members/list_by_name.yml create mode 100644 spec/cassettes/members/list_by_program.yml create mode 100644 spec/cassettes/members/list_by_updated_since.yml create mode 100644 spec/cassettes/members/list_paginated.yml create mode 100644 spec/cassettes/members/update.yml create mode 100644 spec/integration/member_enrollments_spec.rb create mode 100644 spec/integration/members_spec.rb diff --git a/.mise.local.toml.example b/.mise.local.toml.example index 30dcc79..f19a5c4 100644 --- a/.mise.local.toml.example +++ b/.mise.local.toml.example @@ -2,13 +2,13 @@ # This file is gitignored and should contain your local dev/test credentials [env] +# The vast majority of these are for testing against a local DataNexus instance and recording VCR cassettes. DATANEXUS_API_KEY = "your-real-api-key" DATANEXUS_BASE_URL = "https://localhost:4000" DATANEXUS_SSL_VERIFY = "false" DATANEXUS_TEST_PROGRAM_ID = "your-real-program-id" DATANEXUS_TEST_MEMBER_ID = "your-real-member-id" DATANEXUS_TEST_CONSENT_ID = "your-real-consent-id" - -# Required filters for testing (the API requires at least one filter combination) +DATANEXUS_TEST_ENROLLMENT_ID = "your-real-enrollment-id" DATANEXUS_TEST_BORN_ON = "1980-01-15" DATANEXUS_TEST_EMPLOYEE_ID = "EMP123" diff --git a/lib/data_nexus/client.rb b/lib/data_nexus/client.rb index 9f9f274..5caeff8 100644 --- a/lib/data_nexus/client.rb +++ b/lib/data_nexus/client.rb @@ -2,6 +2,7 @@ require_relative 'configuration' require_relative 'connection' +require_relative 'resources/members' require_relative 'resources/programs' module DataNexus @@ -16,7 +17,7 @@ module DataNexus # # @example Access program members # client.programs("program-uuid").members.list - # client.programs("program-uuid").members.find("member-id") + # client.programs("program-uuid").members("member-id").find # class Client # @return [Configuration] The client configuration @@ -48,6 +49,17 @@ def initialize(api_key: nil, base_url: nil, timeout: nil, open_timeout: nil, ssl @connection = Connection.new(@config) end + # Access top-level member resources + # + # @return [Resources::Members] A members resource + # + # @example + # client.members.list + # client.members.find("member-id") + def members + Resources::Members.new(connection) + end + # Access program-scoped resources # # @param program_id [String] The program UUID diff --git a/lib/data_nexus/resources/member_consents.rb b/lib/data_nexus/resources/member_consents.rb index 77f56de..9ac63f1 100644 --- a/lib/data_nexus/resources/member_consents.rb +++ b/lib/data_nexus/resources/member_consents.rb @@ -8,7 +8,7 @@ module Resources # consents for a specific member. # # @example Create a consent - # client.programs("uuid").members.consents("member-id").create( + # client.programs("uuid").members("member-id").consents.create( # consent: { # category: "sms", # member_response: true, @@ -17,15 +17,15 @@ module Resources # ) # # @example Find a consent - # consent = client.programs("uuid").members.consents("member-id").find(123) + # consent = client.programs("uuid").members("member-id").consents.find(123) # # @example Update a consent - # client.programs("uuid").members.consents("member-id").update(123, + # client.programs("uuid").members("member-id").consents.update(123, # consent: { member_response: false } # ) # # @example Delete a consent - # client.programs("uuid").members.consents("member-id").delete(123) + # client.programs("uuid").members("member-id").consents.delete(123) # class MemberConsents # @return [Connection] The HTTP connection @@ -58,7 +58,7 @@ def initialize(connection, member_id, program_id) # @return [Hash] Response containing :data with the created consent # # @example - # response = client.programs("uuid").members.consents("member-id").create( + # response = client.programs("uuid").members("member-id").consents.create( # consent: { # category: "sms", # member_response: true, @@ -78,7 +78,7 @@ def create(consent:) # @return [Hash] The consent data # # @example - # consent = client.programs("uuid").members.consents("member-id").find(123) + # consent = client.programs("uuid").members("member-id").consents.find(123) # puts consent[:category] def find(consent_id) response = connection.get("#{base_path}/#{consent_id}") @@ -92,7 +92,7 @@ def find(consent_id) # @return [Hash] Response containing :data with the updated consent # # @example - # response = client.programs("uuid").members.consents("member-id").update(123, + # response = client.programs("uuid").members("member-id").consents.update(123, # consent: { member_response: false } # ) # updated_consent = response[:data] @@ -107,7 +107,7 @@ def update(consent_id, consent:) # @return [Hash] Empty hash on success (204 No Content) # # @example - # client.programs("uuid").members.consents("member-id").delete(123) + # client.programs("uuid").members("member-id").consents.delete(123) def delete(consent_id) connection.delete("#{base_path}/#{consent_id}") end diff --git a/lib/data_nexus/resources/member_enrollments.rb b/lib/data_nexus/resources/member_enrollments.rb new file mode 100644 index 0000000..9cc32e6 --- /dev/null +++ b/lib/data_nexus/resources/member_enrollments.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module DataNexus + module Resources + # Resource for managing member enrollments + # + # Provides methods for creating, finding, updating, and deleting + # enrollments for a specific member. + # + # @example Create an enrollment + # client.programs("program-uuid").members("member-id").enrollments.create( + # enrollment: { + # enrolled_at: "2024-01-01T00:00:00Z", + # expires_at: "2025-01-01T00:00:00Z" + # } + # ) + # + # @example Find an enrollment + # enrollment = client.programs("program-uuid").members("member-id").enrollments.find(123) + # + # @example Update an enrollment + # client.programs("program-uuid").members("member-id").enrollments.update(123, + # enrollment: { expires_at: "2026-01-01T00:00:00Z" } + # ) + # + # @example Delete an enrollment + # client.programs("program-uuid").members("member-id").enrollments.delete(123) + # + class MemberEnrollments + # @return [Connection] The HTTP connection + attr_reader :connection + + # @return [String] The member ID + attr_reader :member_id + + # @return [String] The program UUID + attr_reader :program_id + + # Initialize a new MemberEnrollments resource + # + # @param connection [Connection] The HTTP connection + # @param member_id [String] The member ID + # @param program_id [String] The program UUID + def initialize(connection, member_id, program_id) + @connection = connection + @member_id = member_id + @program_id = program_id + end + + # Create a new enrollment for the member + # + # @param enrollment [Hash] The enrollment attributes + # @option enrollment [String] :enrolled_at The datetime of enrollment (required) + # @option enrollment [String] :expires_at The datetime when enrollment expires (optional) + # @return [Hash] Response containing :data with the created enrollment + # + # @note The program_id is automatically injected from the resource chain + # + # @example + # response = client.programs("program-uuid").members("member-id").enrollments.create( + # enrollment: { + # enrolled_at: "2024-01-01T00:00:00Z" + # } + # ) + # created_enrollment = response[:data] + def create(enrollment:) + enrollment_with_program = enrollment.merge(program_id: program_id) + body = { enrollment: enrollment_with_program } + connection.post(base_path, body) + end + + # Find a specific enrollment by ID + # + # @param enrollment_id [Integer, String] The enrollment ID + # @return [Hash] The enrollment data + # + # @example + # enrollment = client.programs("program-uuid").members("member-id").enrollments.find(123) + # puts enrollment[:program_id] + def find(enrollment_id) + response = connection.get("#{base_path}/#{enrollment_id}") + response[:data] + end + + # Update an enrollment's attributes + # + # @param enrollment_id [Integer, String] The enrollment ID + # @param enrollment [Hash] The enrollment attributes to update + # @return [Hash] Response containing :data with the updated enrollment + # + # @example + # response = client.programs("program-uuid").members("member-id").enrollments.update(123, + # enrollment: { expires_at: "2026-01-01T00:00:00Z" } + # ) + # updated_enrollment = response[:data] + def update(enrollment_id, enrollment:) + body = { enrollment: enrollment } + connection.patch("#{base_path}/#{enrollment_id}", body) + end + + # Delete an enrollment + # + # @param enrollment_id [Integer, String] The enrollment ID + # @return [Hash] Empty hash on success (204 No Content) + # + # @example + # client.programs("program-uuid").members("member-id").enrollments.delete(123) + def delete(enrollment_id) + connection.delete("#{base_path}/#{enrollment_id}") + end + + private + + # Base path for member enrollments endpoints + # + # @return [String] + def base_path + "/api/members/#{member_id}/enrollments" + end + end + end +end diff --git a/lib/data_nexus/resources/members.rb b/lib/data_nexus/resources/members.rb new file mode 100644 index 0000000..fbea6d1 --- /dev/null +++ b/lib/data_nexus/resources/members.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +module DataNexus + module Resources + # Resource for managing members at the top level + # + # Provides methods for listing, finding, and updating members + # without requiring a program scope. + # + # @example List members with filters + # client.members.list( + # first_name: "george", + # born_on: "1976-07-04" + # ) + # + # @example Paginate through members + # collection = client.members.list(first: 50) + # collection.each_page { |page| process(page.data) } + # + # @example Find a specific member + # member = client.members.find("member-id") + # + class Members + # @return [Connection] The HTTP connection + attr_reader :connection + + # Initialize a new Members resource + # + # @param connection [Connection] The HTTP connection + def initialize(connection) + @connection = connection + end + + # List members with optional filters + # + # @param after [String, nil] Cursor for next group of records + # @param before [String, nil] Cursor for previous group of records + # @param first [Integer, nil] Number of records to fetch after cursor + # @param last [Integer, nil] Number of records to fetch before cursor + # @param born_on [String, nil] Filter by date of birth (YYYY-MM-DD) + # @param first_name [String, nil] Filter by first name + # @param last_name [String, nil] Filter by last name + # @param program_id [String, nil] Filter by program UUID (members eligible for program) + # @param updated_since [String, nil] Filter by update time (ISO 8601 datetime) + # + # @return [Collection] Paginated collection of members + # + # @example Basic listing + # collection = client.members.list + # collection.data.each { |m| puts m[:first_name] } + # + # @example With filters + # collection = client.members.list( + # born_on: "1976-07-04", + # first_name: "george" + # ) + # + # @example With pagination + # collection = client.members.list(first: 50, after: "cursor") + # + # @example Filter by program eligibility + # collection = client.members.list(program_id: "uuid") + # + # @example Filter by update time + # collection = client.members.list(updated_since: "2024-01-01T00:00:00Z") + def list(**params) + allowed_params = %i[ + after before first last + born_on first_name last_name + program_id updated_since + ] + + query_params = params.slice(*allowed_params).compact + response = connection.get(base_path, query_params) + Collection.new(response, resource: self, params: query_params) + end + + # Find a specific member by ID + # + # @param member_id [String] The member ID + # @return [Hash] The member data + # + # @example + # member = client.members.find("member-id") + # puts member[:first_name] + def find(member_id) + response = connection.get("#{base_path}/#{member_id}") + response[:data] + end + + # Update a member's attributes + # + # @param member_id [String] The member ID + # @param member [Hash] The member attributes to update + # @return [Hash] Response containing :data with the updated member + # + # @example + # response = client.members.update("member-id", + # member: { phone_number: "+15551234567" } + # ) + # updated_member = response[:data] + def update(member_id, member:) + body = { member: member } + connection.patch("#{base_path}/#{member_id}", body) + end + + private + + # Base path for members endpoints + # + # @return [String] + def base_path + '/api/members' + end + end + end +end diff --git a/lib/data_nexus/resources/program_member.rb b/lib/data_nexus/resources/program_member.rb new file mode 100644 index 0000000..705dc0f --- /dev/null +++ b/lib/data_nexus/resources/program_member.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require_relative 'member_consents' +require_relative 'member_enrollments' + +module DataNexus + module Resources + # Resource for managing a specific member within a program context + # + # Provides methods for finding, updating, and accessing related resources + # for a specific member. + # + # @example Find a member + # member = client.programs("program-uuid").members("member-id").find + # + # @example Update a member + # client.programs("program-uuid").members("member-id").update( + # member: { phone_number: "+15551234567" } + # ) + # + # @example Get household members + # household = client.programs("program-uuid").members("member-id").household + # + # @example Access consents + # client.programs("program-uuid").members("member-id").consents.create( + # consent: { category: "sms", member_response: true, consent_details: {} } + # ) + # + # @example Access enrollments + # client.programs("program-uuid").members("member-id").enrollments.create( + # enrollment: { enrolled_at: "2024-01-01T00:00:00Z" } + # ) + # + class ProgramMember + # @return [Connection] The HTTP connection + attr_reader :connection + + # @return [String] The program UUID + attr_reader :program_id + + # @return [String] The member ID + attr_reader :member_id + + # Initialize a new ProgramMember resource + # + # @param connection [Connection] The HTTP connection + # @param program_id [String] The program UUID + # @param member_id [String] The member ID + def initialize(connection, program_id, member_id) + @connection = connection + @program_id = program_id + @member_id = member_id + end + + # Find this member + # + # @return [Hash] The member data + # + # @example + # member = client.programs("uuid").members("member-id").find + # puts member[:first_name] + def find + response = connection.get("#{base_path}/#{member_id}") + response[:data] + end + + # Update this member's attributes + # + # @param member [Hash] The member attributes to update + # @return [Hash] Response containing :data with the updated member + # + # @example + # response = client.programs("uuid").members("member-id").update( + # member: { phone_number: "+15551234567" } + # ) + # updated_member = response[:data] + def update(member:) + body = { member: member } + connection.patch("#{base_path}/#{member_id}", body) + end + + # Get household members for this member + # + # @return [Collection] Collection of household members + # + # @example + # household = client.programs("uuid").members("member-id").household + # household.data.each { |m| puts "#{m[:first_name]} - #{m[:relationship_type]}" } + def household + response = connection.get("#{base_path}/#{member_id}/household") + Collection.new(response, resource: self, params: {}) + end + + # Access consents for this member + # + # @return [MemberConsents] The member consents resource + # + # @example Create a consent + # client.programs("uuid").members("member-id").consents.create( + # consent: { category: "sms", member_response: true, consent_details: {} } + # ) + # + # @example Find a consent + # client.programs("uuid").members("member-id").consents.find(123) + def consents + MemberConsents.new(connection, member_id, program_id) + end + + # Access enrollments for this member + # + # @return [MemberEnrollments] The member enrollments resource + # + # @example Create an enrollment + # client.programs("uuid").members("member-id").enrollments.create( + # enrollment: { enrolled_at: "2024-01-01T00:00:00Z" } + # ) + # + # @example Find an enrollment + # client.programs("uuid").members("member-id").enrollments.find(123) + def enrollments + MemberEnrollments.new(connection, member_id, program_id) + end + + private + + # Base path for program members endpoints + # + # @return [String] + def base_path + "/api/programs/#{program_id}/members" + end + end + end +end diff --git a/lib/data_nexus/resources/program_members.rb b/lib/data_nexus/resources/program_members.rb index d73dd05..48ec8a2 100644 --- a/lib/data_nexus/resources/program_members.rb +++ b/lib/data_nexus/resources/program_members.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true -require_relative 'member_consents' - module DataNexus module Resources - # Resource for managing program members + # Resource for listing program members # - # Provides methods for listing, finding, updating, and retrieving - # household information for members within a specific program. + # Provides methods for listing and searching members within a specific program. + # For operations on a specific member, use `programs("uuid").members("member-id")`. # # @example List members with filters # client.programs("uuid").members.list( @@ -67,64 +65,6 @@ def list(**params) Collection.new(response, resource: self, params: query_params) end - # Find a specific member by ID - # - # @param member_id [String] The member ID - # @return [Hash] The member data - # - # @example - # member = client.programs("uuid").members.find("member-id") - # puts member[:first_name] - def find(member_id) - response = connection.get("#{base_path}/#{member_id}") - response[:data] - end - - # Update a member's attributes - # - # @param member_id [String] The member ID - # @param member [Hash] The member attributes to update - # @return [Hash] Response containing :data with the updated member - # - # @example - # response = client.programs("uuid").members.update("member-id", - # member: { first_name: "George", last_name: "Washington" } - # ) - # updated_member = response[:data] - def update(member_id, member:) - body = { member: member } - connection.patch("#{base_path}/#{member_id}", body) - end - - # Get household members for a specific member - # - # @param member_id [String] The member ID - # @return [Collection] Paginated collection of household members - # - # @example - # household = client.programs("uuid").members.household("member-id") - # household.data.each { |m| puts "#{m[:first_name]} - #{m[:relationship_type]}" } - def household(member_id) - response = connection.get("#{base_path}/#{member_id}/household") - Collection.new(response, resource: self, params: { member_id: member_id }) - end - - # Access consents for a specific member - # - # @param member_id [String] The member ID - # @return [MemberConsents] The member consents resource - # - # @example Create a consent - # client.programs("uuid").members.consents("member-id").create( - # consent: { category: "sms", member_response: true, consent_details: {} } - # ) - # - # @example Find a consent - # client.programs("uuid").members.consents("member-id").find(123) - def consents(member_id) - MemberConsents.new(connection, member_id, program_id) - end - private # Base path for program members endpoints diff --git a/lib/data_nexus/resources/programs.rb b/lib/data_nexus/resources/programs.rb index b8a8c6a..995f19b 100644 --- a/lib/data_nexus/resources/programs.rb +++ b/lib/data_nexus/resources/programs.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require_relative 'program_member' require_relative 'program_members' module DataNexus @@ -9,9 +10,14 @@ module Resources # This class provides access to resources that are scoped to a specific program, # such as members, consents, and enrollments. # - # @example Access program members + # @example List program members # client.programs("program-uuid").members.list - # client.programs("program-uuid").members.find("member-id") + # + # @example Access a specific member + # client.programs("program-uuid").members("member-id").find + # client.programs("program-uuid").members("member-id").update(member: { phone_number: "..." }) + # client.programs("program-uuid").members("member-id").consents.create(...) + # client.programs("program-uuid").members("member-id").enrollments.create(...) # class Programs # @return [Connection] The HTTP connection @@ -31,21 +37,33 @@ def initialize(connection, program_id) # Access program members # - # @return [ProgramMembers] The program members resource + # When called without an argument, returns a resource for listing members. + # When called with a member_id, returns a resource for that specific member. + # + # @param member_id [String, nil] The member ID (optional) + # @return [ProgramMembers] When no member_id provided - for listing members + # @return [ProgramMember] When member_id provided - for member-specific operations # # @example List members # client.programs("uuid").members.list # - # @example Find a member - # client.programs("uuid").members.find("member-id") + # @example Access a specific member + # client.programs("uuid").members("member-id").find # # @example Update a member - # client.programs("uuid").members.update("member-id", member: { first_name: "George" }) + # client.programs("uuid").members("member-id").update(member: { first_name: "George" }) + # + # @example Access member consents + # client.programs("uuid").members("member-id").consents.create(...) # - # @example Get household members - # client.programs("uuid").members.household("member-id") - def members - @members ||= ProgramMembers.new(connection, program_id) + # @example Access member enrollments + # client.programs("uuid").members("member-id").enrollments.create(...) + def members(member_id = nil) + if member_id + ProgramMember.new(connection, program_id, member_id) + else + ProgramMembers.new(connection, program_id) + end end end end diff --git a/spec/cassettes/member_enrollments/create.yml b/spec/cassettes/member_enrollments/create.yml new file mode 100644 index 0000000..7dad6f0 --- /dev/null +++ b/spec/cassettes/member_enrollments/create.yml @@ -0,0 +1,43 @@ +--- +http_interactions: +- request: + method: post + uri: https://localhost:4000/api/members//enrollments + body: + encoding: UTF-8 + string: '{"enrollment":{"enrolled_at":"2024-01-01T00:00:00Z","expires_at":"2025-01-01T00:00:00Z","program_id":""}}' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 201 + message: Created + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '169' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 14 Jan 2026 00:36:01 GMT + Location: + - "/api/members//enrollments/2" + Server: + - Cowboy + X-Request-Id: + - GIpx1Lbr-TKCdcIAAAFi + body: + encoding: UTF-8 + string: '{"data":{"id":2,"expires_at":"2025-01-01T00:00:00Z","enrolled_at":"2024-01-01T00:00:00.000000Z","program_id":"","member_id":""}}' + recorded_at: Wed, 14 Jan 2026 00:36:02 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_enrollments/delete.yml b/spec/cassettes/member_enrollments/delete.yml new file mode 100644 index 0000000..d866150 --- /dev/null +++ b/spec/cassettes/member_enrollments/delete.yml @@ -0,0 +1,37 @@ +--- +http_interactions: + - request: + method: delete + uri: https://localhost:4000/api/members//enrollments/ + body: + encoding: US-ASCII + string: "" + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 204 + message: No Content + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Date: + - Wed, 14 Jan 2026 00:36:02 GMT + Server: + - Cowboy + X-Request-Id: + - GIpx1O7PjJwQ-akAAALi + body: + encoding: UTF-8 + string: "" + recorded_at: Wed, 14 Jan 2026 00:36:03 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_enrollments/find.yml b/spec/cassettes/member_enrollments/find.yml new file mode 100644 index 0000000..d917122 --- /dev/null +++ b/spec/cassettes/member_enrollments/find.yml @@ -0,0 +1,41 @@ +--- +http_interactions: + - request: + method: get + uri: https://localhost:4000/api/members//enrollments/ + body: + encoding: US-ASCII + string: "" + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - "174" + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 14 Jan 2026 00:36:02 GMT + Server: + - Cowboy + X-Request-Id: + - GIpx1MtOUeFDYVsAAAJi + body: + encoding: UTF-8 + string: '{"data":{"id":"","expires_at":null,"enrolled_at":"2025-08-28T15:09:28.840837Z","enrollment_events":[],"program_id":"","member_id":""}}' + recorded_at: Wed, 14 Jan 2026 00:36:02 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/member_enrollments/update.yml b/spec/cassettes/member_enrollments/update.yml new file mode 100644 index 0000000..376c6a6 --- /dev/null +++ b/spec/cassettes/member_enrollments/update.yml @@ -0,0 +1,41 @@ +--- +http_interactions: + - request: + method: patch + uri: https://localhost:4000/api/members//enrollments/ + body: + encoding: UTF-8 + string: '{"enrollment":{"expires_at":"2026-01-01T00:00:00Z"}}' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - "192" + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 14 Jan 2026 00:36:02 GMT + Server: + - Cowboy + X-Request-Id: + - GIpx1Nz2UjqdE_MAACmB + body: + encoding: UTF-8 + string: '{"data":{"id":"","expires_at":"2026-01-01T00:00:00Z","enrolled_at":"2025-08-28T15:09:28.840837Z","enrollment_events":[],"program_id":"","member_id":""}}' + recorded_at: Wed, 14 Jan 2026 00:36:02 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/members/find.yml b/spec/cassettes/members/find.yml new file mode 100644 index 0000000..6f44895 --- /dev/null +++ b/spec/cassettes/members/find.yml @@ -0,0 +1,44 @@ +--- +http_interactions: +- request: + method: get + uri: https://localhost:4000/api/members/ + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1944' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 13 Jan 2026 21:30:29 GMT + Server: + - Cowboy + X-Request-Id: + - GIpntOiBhsbXDEsAACdB + body: + encoding: UTF-8 + string: '{"data":{"id":"","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[{"id":1,"expires_at":null,"enrolled_at":"2025-08-28T15:09:28.840837Z","enrollment_events":[],"program_id":"","member_id":""}],"consents":[{"id":5,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":6,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":1,"category":"sms","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:08Z","program_id":"","member_id":""},{"id":4,"category":"hipaa","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}],"first_name":"Betty + Jo","middle_name":"","last_name":"Brown","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+15551234567","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787871","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787871","parent_sponsor_member_identifier":"878787871","relationship_type":"PP","insurance_identifiers":[]}}' + recorded_at: Tue, 13 Jan 2026 21:30:30 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/members/list.yml b/spec/cassettes/members/list.yml new file mode 100644 index 0000000..42bbe02 --- /dev/null +++ b/spec/cassettes/members/list.yml @@ -0,0 +1,96 @@ +--- +http_interactions: +- request: + method: get + uri: https://localhost:4000/api/members + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '22024' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 13 Jan 2026 21:30:27 GMT + Server: + - Cowboy + X-End-Cursor: + - g3QAAAABdwJpZGEZ + X-Request-Id: + - GIpntGj3Xv_qHrMAAAAj + X-Start-Cursor: + - g3QAAAABdwJpZGEB + body: + encoding: UTF-8 + string: '{"data":[{"id":"YEJ73","state":"CO","email":"Test17@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"John","middle_name":"","last_name":"Smith","born_on":"1964-12-30","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787867","soda_sponsor_id":null,"employee_id":"9999117","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test17@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787867","parent_sponsor_member_identifier":"878787867","relationship_type":"PP","insurance_identifiers":[]},{"id":"oEA5E","state":"CO","email":"Test18@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Mary","middle_name":"","last_name":"Smith","born_on":"1957-07-19","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787868","soda_sponsor_id":null,"employee_id":"9999118","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test18@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787867","parent_sponsor_member_identifier":"878787867","relationship_type":"S","insurance_identifiers":[]},{"id":"jrG7r","state":"CO","email":"Test19@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Robert","middle_name":"","last_name":"McTester","born_on":"1975-08-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551008","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787869","soda_sponsor_id":null,"employee_id":"9999119","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"Test19@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787869","parent_sponsor_member_identifier":"878787869","relationship_type":"PP","insurance_identifiers":[]},{"id":"9mq03","state":"CO","email":"Test20@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Sara","middle_name":"","last_name":"Jones","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787870","soda_sponsor_id":null,"employee_id":"9999120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test20@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787870","parent_sponsor_member_identifier":"878787870","relationship_type":"PP","insurance_identifiers":[]},{"id":"93WMr","state":"CO","email":"Test20Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Wendy","middle_name":"","last_name":"Jones","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787870","soda_sponsor_id":null,"employee_id":"9998120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test20Spouse@emsanacare.com"}],"work_email":null,"health_plan_identifier":"9999120","parent_sponsor_member_identifier":"9999120","relationship_type":"S","insurance_identifiers":[]},{"id":"AmOom","state":"CO","email":"TestTwentySpouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Alice","middle_name":"","last_name":"Jones","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787870","soda_sponsor_id":null,"employee_id":"9997120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"TestTwentySpouse@emsanacare.com"}],"work_email":null,"health_plan_identifier":"9999120","parent_sponsor_member_identifier":"9999120","relationship_type":"C","insurance_identifiers":[]},{"id":"","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[{"id":1,"expires_at":null,"enrolled_at":"2025-08-28T15:09:28.840837Z","enrollment_events":[],"program_id":"","member_id":""}],"consents":[{"id":5,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":6,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":1,"category":"sms","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:08Z","program_id":"","member_id":""},{"id":4,"category":"hipaa","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}],"first_name":"Betty + Jo","middle_name":"","last_name":"Brown","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+15551234567","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787871","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787871","parent_sponsor_member_identifier":"878787871","relationship_type":"PP","insurance_identifiers":[]},{"id":"jmQNr","state":"CO","email":"test22@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Carrie","middle_name":"","last_name":"Collins","born_on":"1998-04-18","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551022","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787872","soda_sponsor_id":null,"employee_id":"9999122","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test22@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787872","parent_sponsor_member_identifier":"878787872","relationship_type":"PP","insurance_identifiers":[]},{"id":"ar9Qr","state":"CO","email":"test23@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Denise","middle_name":"","last_name":"Danson","born_on":"1998-05-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787873","soda_sponsor_id":null,"employee_id":"9999123","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test23@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787873","parent_sponsor_member_identifier":"878787873","relationship_type":"PP","insurance_identifiers":[]},{"id":"o3z4r","state":"CO","email":"Test24@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Elise","middle_name":"","last_name":"Edwards","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551024","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787874","soda_sponsor_id":null,"employee_id":"9999124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"OmV53","state":"CO","email":"Test24Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Francesca","middle_name":"","last_name":"Edwards","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787874","soda_sponsor_id":null,"employee_id":"9998124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"S","insurance_identifiers":[]},{"id":"wr27E","state":"CO","email":"Test24Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Gabrielle","middle_name":"","last_name":"Edwards","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787874","soda_sponsor_id":null,"employee_id":"9997124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"C","insurance_identifiers":[]},{"id":"ymLlm","state":"CO","email":"Test25@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Hadley","middle_name":"","last_name":"Hall","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787875","soda_sponsor_id":null,"employee_id":"9999125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"PP","insurance_identifiers":[]},{"id":"QEyqE","state":"CO","email":"Test25Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Isis","middle_name":"","last_name":"Hall","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787875","soda_sponsor_id":null,"employee_id":"9998125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"S","insurance_identifiers":[]},{"id":"0r7y3","state":"CO","email":"Test25Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Jenesta","middle_name":"","last_name":"Hall","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787875","soda_sponsor_id":null,"employee_id":"9997125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"C","insurance_identifiers":[]},{"id":"YEoVr","state":"CO","email":"Test27@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Katherine","middle_name":"","last_name":"Keene","born_on":"1998-06-17","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":"+18005551017","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787877","soda_sponsor_id":null,"employee_id":"9999127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"PP","insurance_identifiers":[]},{"id":"93Kqr","state":"CO","email":"Test27Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Lynette","middle_name":"","last_name":"Keene","born_on":"1997-09-17","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787877","soda_sponsor_id":null,"employee_id":"9998127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"S","insurance_identifiers":[]},{"id":"ZrxDr","state":"CO","email":"Test27Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Nancy","middle_name":"","last_name":"Keene","born_on":"2024-05-07","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787877","soda_sponsor_id":null,"employee_id":"9997127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"C","insurance_identifiers":[]},{"id":"YEP0E","state":"CO","email":"Test28@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Len","middle_name":"","last_name":"Levinson","born_on":"1964-12-30","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"278787868","soda_sponsor_id":null,"employee_id":"9999117","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test28@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"278787868","parent_sponsor_member_identifier":"278787868","relationship_type":"PP","insurance_identifiers":[]},{"id":"JrBym","state":"CO","email":"Test29@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Michael","middle_name":"","last_name":"McTester","born_on":"1975-08-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551008","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787870","soda_sponsor_id":null,"employee_id":"9999119","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"Test29@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787870","parent_sponsor_member_identifier":"178787870","relationship_type":"PP","insurance_identifiers":[]},{"id":"brMar","state":"CO","email":"Test30@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Nora","middle_name":"","last_name":"Night","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787871","soda_sponsor_id":null,"employee_id":"9999120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test30@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787871","parent_sponsor_member_identifier":"178787871","relationship_type":"PP","insurance_identifiers":[]},{"id":"0mw7m","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Olivia","middle_name":"","last_name":"O''Bannon","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787874","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787874","parent_sponsor_member_identifier":"178787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"8m693","state":"CO","email":"test22@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Portia","middle_name":"","last_name":"Patrick","born_on":"1998-04-18","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551022","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787875","soda_sponsor_id":null,"employee_id":"9999122","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test22@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787875","parent_sponsor_member_identifier":"178787875","relationship_type":"PP","insurance_identifiers":[]},{"id":"q3843","state":"CO","email":"test23@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Quinn","middle_name":"","last_name":"Quinlan","born_on":"1998-05-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787876","soda_sponsor_id":null,"employee_id":"9999123","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test23@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787876","parent_sponsor_member_identifier":"878787876","relationship_type":"PP","insurance_identifiers":[]},{"id":"XEnAr","state":"CO","email":"Test24@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Randall","middle_name":"","last_name":"Ross","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551024","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787877","soda_sponsor_id":null,"employee_id":"9999124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787877","parent_sponsor_member_identifier":"178787877","relationship_type":"PP","insurance_identifiers":[]}],"end_cursor":"g3QAAAABdwJpZGEZ","start_cursor":"g3QAAAABdwJpZGEB"}' + recorded_at: Tue, 13 Jan 2026 21:30:28 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/members/list_by_born_on.yml b/spec/cassettes/members/list_by_born_on.yml new file mode 100644 index 0000000..066394d --- /dev/null +++ b/spec/cassettes/members/list_by_born_on.yml @@ -0,0 +1,56 @@ +--- +http_interactions: +- request: + method: get + uri: https://localhost:4000/api/members?born_on= + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '5267' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 13 Jan 2026 21:30:28 GMT + Server: + - Cowboy + X-End-Cursor: + - g3QAAAABdwJpZGEx + X-Request-Id: + - GIpntJwheJqcisgAACXB + X-Start-Cursor: + - g3QAAAABdwJpZGEH + body: + encoding: UTF-8 + string: '{"data":[{"id":"","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[{"id":1,"expires_at":null,"enrolled_at":"2025-08-28T15:09:28.840837Z","enrollment_events":[],"program_id":"","member_id":""}],"consents":[{"id":5,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":6,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":1,"category":"sms","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:08Z","program_id":"","member_id":""},{"id":4,"category":"hipaa","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}],"first_name":"Betty + Jo","middle_name":"","last_name":"Brown","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+15551234567","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787871","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787871","parent_sponsor_member_identifier":"878787871","relationship_type":"PP","insurance_identifiers":[]},{"id":"0mw7m","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Olivia","middle_name":"","last_name":"O''Bannon","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787874","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787874","parent_sponsor_member_identifier":"178787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"jmjXE","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Xavier","middle_name":"","last_name":"Xanadu","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"578787874","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"578787874","parent_sponsor_member_identifier":"578787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"2rZd3","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Grace","middle_name":"","last_name":"Gammon","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"278787874","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"278787874","parent_sponsor_member_identifier":"278787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"3WvME","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Percy","middle_name":"","last_name":"Price","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"378787874","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"378787874","parent_sponsor_member_identifier":"378787874","relationship_type":"PP","insurance_identifiers":[]}],"end_cursor":"g3QAAAABdwJpZGEx","start_cursor":"g3QAAAABdwJpZGEH"}' + recorded_at: Tue, 13 Jan 2026 21:30:29 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/members/list_by_name.yml b/spec/cassettes/members/list_by_name.yml new file mode 100644 index 0000000..17bb7eb --- /dev/null +++ b/spec/cassettes/members/list_by_name.yml @@ -0,0 +1,41 @@ +--- +http_interactions: +- request: + method: get + uri: https://localhost:4000/api/members?first_name=Test&last_name=User + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '49' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 13 Jan 2026 21:30:28 GMT + Server: + - Cowboy + X-Request-Id: + - GIpntIq3tetFwbwAACUh + body: + encoding: UTF-8 + string: '{"data":[],"end_cursor":null,"start_cursor":null}' + recorded_at: Tue, 13 Jan 2026 21:30:29 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/members/list_by_program.yml b/spec/cassettes/members/list_by_program.yml new file mode 100644 index 0000000..959b2f5 --- /dev/null +++ b/spec/cassettes/members/list_by_program.yml @@ -0,0 +1,96 @@ +--- +http_interactions: +- request: + method: get + uri: https://localhost:4000/api/members?program_id= + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '22024' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 13 Jan 2026 21:30:28 GMT + Server: + - Cowboy + X-End-Cursor: + - g3QAAAABdwJpZGEZ + X-Request-Id: + - GIpntK5SeCwJDxgAACXh + X-Start-Cursor: + - g3QAAAABdwJpZGEB + body: + encoding: UTF-8 + string: '{"data":[{"id":"YEJ73","state":"CO","email":"Test17@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"John","middle_name":"","last_name":"Smith","born_on":"1964-12-30","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787867","soda_sponsor_id":null,"employee_id":"9999117","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test17@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787867","parent_sponsor_member_identifier":"878787867","relationship_type":"PP","insurance_identifiers":[]},{"id":"oEA5E","state":"CO","email":"Test18@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Mary","middle_name":"","last_name":"Smith","born_on":"1957-07-19","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787868","soda_sponsor_id":null,"employee_id":"9999118","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test18@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787867","parent_sponsor_member_identifier":"878787867","relationship_type":"S","insurance_identifiers":[]},{"id":"jrG7r","state":"CO","email":"Test19@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Robert","middle_name":"","last_name":"McTester","born_on":"1975-08-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551008","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787869","soda_sponsor_id":null,"employee_id":"9999119","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"Test19@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787869","parent_sponsor_member_identifier":"878787869","relationship_type":"PP","insurance_identifiers":[]},{"id":"9mq03","state":"CO","email":"Test20@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Sara","middle_name":"","last_name":"Jones","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787870","soda_sponsor_id":null,"employee_id":"9999120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test20@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787870","parent_sponsor_member_identifier":"878787870","relationship_type":"PP","insurance_identifiers":[]},{"id":"93WMr","state":"CO","email":"Test20Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Wendy","middle_name":"","last_name":"Jones","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787870","soda_sponsor_id":null,"employee_id":"9998120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test20Spouse@emsanacare.com"}],"work_email":null,"health_plan_identifier":"9999120","parent_sponsor_member_identifier":"9999120","relationship_type":"S","insurance_identifiers":[]},{"id":"AmOom","state":"CO","email":"TestTwentySpouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Alice","middle_name":"","last_name":"Jones","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787870","soda_sponsor_id":null,"employee_id":"9997120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"TestTwentySpouse@emsanacare.com"}],"work_email":null,"health_plan_identifier":"9999120","parent_sponsor_member_identifier":"9999120","relationship_type":"C","insurance_identifiers":[]},{"id":"","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[{"id":1,"expires_at":null,"enrolled_at":"2025-08-28T15:09:28.840837Z","enrollment_events":[],"program_id":"","member_id":""}],"consents":[{"id":5,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":6,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":1,"category":"sms","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:08Z","program_id":"","member_id":""},{"id":4,"category":"hipaa","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}],"first_name":"Betty + Jo","middle_name":"","last_name":"Brown","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+15551234567","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787871","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787871","parent_sponsor_member_identifier":"878787871","relationship_type":"PP","insurance_identifiers":[]},{"id":"jmQNr","state":"CO","email":"test22@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Carrie","middle_name":"","last_name":"Collins","born_on":"1998-04-18","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551022","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787872","soda_sponsor_id":null,"employee_id":"9999122","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test22@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787872","parent_sponsor_member_identifier":"878787872","relationship_type":"PP","insurance_identifiers":[]},{"id":"ar9Qr","state":"CO","email":"test23@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Denise","middle_name":"","last_name":"Danson","born_on":"1998-05-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787873","soda_sponsor_id":null,"employee_id":"9999123","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test23@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787873","parent_sponsor_member_identifier":"878787873","relationship_type":"PP","insurance_identifiers":[]},{"id":"o3z4r","state":"CO","email":"Test24@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Elise","middle_name":"","last_name":"Edwards","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551024","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787874","soda_sponsor_id":null,"employee_id":"9999124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"OmV53","state":"CO","email":"Test24Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Francesca","middle_name":"","last_name":"Edwards","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787874","soda_sponsor_id":null,"employee_id":"9998124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"S","insurance_identifiers":[]},{"id":"wr27E","state":"CO","email":"Test24Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Gabrielle","middle_name":"","last_name":"Edwards","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787874","soda_sponsor_id":null,"employee_id":"9997124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"C","insurance_identifiers":[]},{"id":"ymLlm","state":"CO","email":"Test25@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Hadley","middle_name":"","last_name":"Hall","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787875","soda_sponsor_id":null,"employee_id":"9999125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"PP","insurance_identifiers":[]},{"id":"QEyqE","state":"CO","email":"Test25Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Isis","middle_name":"","last_name":"Hall","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787875","soda_sponsor_id":null,"employee_id":"9998125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"S","insurance_identifiers":[]},{"id":"0r7y3","state":"CO","email":"Test25Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Jenesta","middle_name":"","last_name":"Hall","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787875","soda_sponsor_id":null,"employee_id":"9997125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"C","insurance_identifiers":[]},{"id":"YEoVr","state":"CO","email":"Test27@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Katherine","middle_name":"","last_name":"Keene","born_on":"1998-06-17","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":"+18005551017","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787877","soda_sponsor_id":null,"employee_id":"9999127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"PP","insurance_identifiers":[]},{"id":"93Kqr","state":"CO","email":"Test27Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Lynette","middle_name":"","last_name":"Keene","born_on":"1997-09-17","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787877","soda_sponsor_id":null,"employee_id":"9998127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"S","insurance_identifiers":[]},{"id":"ZrxDr","state":"CO","email":"Test27Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Nancy","middle_name":"","last_name":"Keene","born_on":"2024-05-07","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787877","soda_sponsor_id":null,"employee_id":"9997127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"C","insurance_identifiers":[]},{"id":"YEP0E","state":"CO","email":"Test28@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Len","middle_name":"","last_name":"Levinson","born_on":"1964-12-30","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"278787868","soda_sponsor_id":null,"employee_id":"9999117","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test28@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"278787868","parent_sponsor_member_identifier":"278787868","relationship_type":"PP","insurance_identifiers":[]},{"id":"JrBym","state":"CO","email":"Test29@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Michael","middle_name":"","last_name":"McTester","born_on":"1975-08-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551008","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787870","soda_sponsor_id":null,"employee_id":"9999119","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"Test29@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787870","parent_sponsor_member_identifier":"178787870","relationship_type":"PP","insurance_identifiers":[]},{"id":"brMar","state":"CO","email":"Test30@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Nora","middle_name":"","last_name":"Night","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787871","soda_sponsor_id":null,"employee_id":"9999120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test30@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787871","parent_sponsor_member_identifier":"178787871","relationship_type":"PP","insurance_identifiers":[]},{"id":"0mw7m","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Olivia","middle_name":"","last_name":"O''Bannon","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787874","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787874","parent_sponsor_member_identifier":"178787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"8m693","state":"CO","email":"test22@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Portia","middle_name":"","last_name":"Patrick","born_on":"1998-04-18","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551022","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787875","soda_sponsor_id":null,"employee_id":"9999122","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test22@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787875","parent_sponsor_member_identifier":"178787875","relationship_type":"PP","insurance_identifiers":[]},{"id":"q3843","state":"CO","email":"test23@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Quinn","middle_name":"","last_name":"Quinlan","born_on":"1998-05-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787876","soda_sponsor_id":null,"employee_id":"9999123","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test23@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787876","parent_sponsor_member_identifier":"878787876","relationship_type":"PP","insurance_identifiers":[]},{"id":"XEnAr","state":"CO","email":"Test24@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Randall","middle_name":"","last_name":"Ross","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551024","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787877","soda_sponsor_id":null,"employee_id":"9999124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787877","parent_sponsor_member_identifier":"178787877","relationship_type":"PP","insurance_identifiers":[]}],"end_cursor":"g3QAAAABdwJpZGEZ","start_cursor":"g3QAAAABdwJpZGEB"}' + recorded_at: Tue, 13 Jan 2026 21:30:29 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/members/list_by_updated_since.yml b/spec/cassettes/members/list_by_updated_since.yml new file mode 100644 index 0000000..9881ada --- /dev/null +++ b/spec/cassettes/members/list_by_updated_since.yml @@ -0,0 +1,96 @@ +--- +http_interactions: +- request: + method: get + uri: https://localhost:4000/api/members?updated_since=2020-01-01T00:00:00Z + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '22024' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 13 Jan 2026 21:30:29 GMT + Server: + - Cowboy + X-End-Cursor: + - g3QAAAABdwJpZGEZ + X-Request-Id: + - GIpntMJ7bjqufrkAACYh + X-Start-Cursor: + - g3QAAAABdwJpZGEB + body: + encoding: UTF-8 + string: '{"data":[{"id":"YEJ73","state":"CO","email":"Test17@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"John","middle_name":"","last_name":"Smith","born_on":"1964-12-30","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787867","soda_sponsor_id":null,"employee_id":"9999117","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test17@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787867","parent_sponsor_member_identifier":"878787867","relationship_type":"PP","insurance_identifiers":[]},{"id":"oEA5E","state":"CO","email":"Test18@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Mary","middle_name":"","last_name":"Smith","born_on":"1957-07-19","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787868","soda_sponsor_id":null,"employee_id":"9999118","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test18@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787867","parent_sponsor_member_identifier":"878787867","relationship_type":"S","insurance_identifiers":[]},{"id":"jrG7r","state":"CO","email":"Test19@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Robert","middle_name":"","last_name":"McTester","born_on":"1975-08-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551008","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787869","soda_sponsor_id":null,"employee_id":"9999119","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"Test19@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787869","parent_sponsor_member_identifier":"878787869","relationship_type":"PP","insurance_identifiers":[]},{"id":"9mq03","state":"CO","email":"Test20@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Sara","middle_name":"","last_name":"Jones","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787870","soda_sponsor_id":null,"employee_id":"9999120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test20@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787870","parent_sponsor_member_identifier":"878787870","relationship_type":"PP","insurance_identifiers":[]},{"id":"93WMr","state":"CO","email":"Test20Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Wendy","middle_name":"","last_name":"Jones","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787870","soda_sponsor_id":null,"employee_id":"9998120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test20Spouse@emsanacare.com"}],"work_email":null,"health_plan_identifier":"9999120","parent_sponsor_member_identifier":"9999120","relationship_type":"S","insurance_identifiers":[]},{"id":"AmOom","state":"CO","email":"TestTwentySpouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Alice","middle_name":"","last_name":"Jones","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787870","soda_sponsor_id":null,"employee_id":"9997120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"TestTwentySpouse@emsanacare.com"}],"work_email":null,"health_plan_identifier":"9999120","parent_sponsor_member_identifier":"9999120","relationship_type":"C","insurance_identifiers":[]},{"id":"","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[{"id":1,"expires_at":null,"enrolled_at":"2025-08-28T15:09:28.840837Z","enrollment_events":[],"program_id":"","member_id":""}],"consents":[{"id":5,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":6,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":1,"category":"sms","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:08Z","program_id":"","member_id":""},{"id":4,"category":"hipaa","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}],"first_name":"Betty + Jo","middle_name":"","last_name":"Brown","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+15551234567","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787871","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787871","parent_sponsor_member_identifier":"878787871","relationship_type":"PP","insurance_identifiers":[]},{"id":"jmQNr","state":"CO","email":"test22@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Carrie","middle_name":"","last_name":"Collins","born_on":"1998-04-18","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551022","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787872","soda_sponsor_id":null,"employee_id":"9999122","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test22@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787872","parent_sponsor_member_identifier":"878787872","relationship_type":"PP","insurance_identifiers":[]},{"id":"ar9Qr","state":"CO","email":"test23@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Denise","middle_name":"","last_name":"Danson","born_on":"1998-05-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787873","soda_sponsor_id":null,"employee_id":"9999123","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test23@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787873","parent_sponsor_member_identifier":"878787873","relationship_type":"PP","insurance_identifiers":[]},{"id":"o3z4r","state":"CO","email":"Test24@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Elise","middle_name":"","last_name":"Edwards","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551024","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787874","soda_sponsor_id":null,"employee_id":"9999124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"OmV53","state":"CO","email":"Test24Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Francesca","middle_name":"","last_name":"Edwards","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787874","soda_sponsor_id":null,"employee_id":"9998124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"S","insurance_identifiers":[]},{"id":"wr27E","state":"CO","email":"Test24Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Gabrielle","middle_name":"","last_name":"Edwards","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787874","soda_sponsor_id":null,"employee_id":"9997124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"C","insurance_identifiers":[]},{"id":"ymLlm","state":"CO","email":"Test25@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Hadley","middle_name":"","last_name":"Hall","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787875","soda_sponsor_id":null,"employee_id":"9999125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"PP","insurance_identifiers":[]},{"id":"QEyqE","state":"CO","email":"Test25Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Isis","middle_name":"","last_name":"Hall","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787875","soda_sponsor_id":null,"employee_id":"9998125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"S","insurance_identifiers":[]},{"id":"0r7y3","state":"CO","email":"Test25Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Jenesta","middle_name":"","last_name":"Hall","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787875","soda_sponsor_id":null,"employee_id":"9997125","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test25Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787875","parent_sponsor_member_identifier":"878787875","relationship_type":"C","insurance_identifiers":[]},{"id":"YEoVr","state":"CO","email":"Test27@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Katherine","middle_name":"","last_name":"Keene","born_on":"1998-06-17","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":"+18005551017","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787877","soda_sponsor_id":null,"employee_id":"9999127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"PP","insurance_identifiers":[]},{"id":"93Kqr","state":"CO","email":"Test27Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Lynette","middle_name":"","last_name":"Keene","born_on":"1997-09-17","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787877","soda_sponsor_id":null,"employee_id":"9998127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"S","insurance_identifiers":[]},{"id":"ZrxDr","state":"CO","email":"Test27Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Nancy","middle_name":"","last_name":"Keene","born_on":"2024-05-07","street_address_1":"2591 + Ridge Dr","street_address_2":null,"city":"Broomfield","postal_code":"80020","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787877","soda_sponsor_id":null,"employee_id":"9997127","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test27Spouse@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787877","parent_sponsor_member_identifier":"878787877","relationship_type":"C","insurance_identifiers":[]},{"id":"YEP0E","state":"CO","email":"Test28@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Len","middle_name":"","last_name":"Levinson","born_on":"1964-12-30","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"278787868","soda_sponsor_id":null,"employee_id":"9999117","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test28@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"278787868","parent_sponsor_member_identifier":"278787868","relationship_type":"PP","insurance_identifiers":[]},{"id":"JrBym","state":"CO","email":"Test29@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Michael","middle_name":"","last_name":"McTester","born_on":"1975-08-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551008","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787870","soda_sponsor_id":null,"employee_id":"9999119","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"Test29@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787870","parent_sponsor_member_identifier":"178787870","relationship_type":"PP","insurance_identifiers":[]},{"id":"brMar","state":"CO","email":"Test30@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Nora","middle_name":"","last_name":"Night","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787871","soda_sponsor_id":null,"employee_id":"9999120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test30@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787871","parent_sponsor_member_identifier":"178787871","relationship_type":"PP","insurance_identifiers":[]},{"id":"0mw7m","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Olivia","middle_name":"","last_name":"O''Bannon","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787874","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787874","parent_sponsor_member_identifier":"178787874","relationship_type":"PP","insurance_identifiers":[]},{"id":"8m693","state":"CO","email":"test22@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Portia","middle_name":"","last_name":"Patrick","born_on":"1998-04-18","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551022","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787875","soda_sponsor_id":null,"employee_id":"9999122","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test22@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787875","parent_sponsor_member_identifier":"178787875","relationship_type":"PP","insurance_identifiers":[]},{"id":"q3843","state":"CO","email":"test23@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Quinn","middle_name":"","last_name":"Quinlan","born_on":"1998-05-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787876","soda_sponsor_id":null,"employee_id":"9999123","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test23@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787876","parent_sponsor_member_identifier":"878787876","relationship_type":"PP","insurance_identifiers":[]},{"id":"XEnAr","state":"CO","email":"Test24@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Randall","middle_name":"","last_name":"Ross","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551024","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"178787877","soda_sponsor_id":null,"employee_id":"9999124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"178787877","parent_sponsor_member_identifier":"178787877","relationship_type":"PP","insurance_identifiers":[]}],"end_cursor":"g3QAAAABdwJpZGEZ","start_cursor":"g3QAAAABdwJpZGEB"}' + recorded_at: Tue, 13 Jan 2026 21:30:30 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/members/list_paginated.yml b/spec/cassettes/members/list_paginated.yml new file mode 100644 index 0000000..f0660fb --- /dev/null +++ b/spec/cassettes/members/list_paginated.yml @@ -0,0 +1,66 @@ +--- +http_interactions: +- request: + method: get + uri: https://localhost:4000/api/members?first=10 + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '9437' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 13 Jan 2026 21:30:29 GMT + Server: + - Cowboy + X-End-Cursor: + - g3QAAAABdwJpZGEK + X-Request-Id: + - GIpntNaP12CLpVYAACbB + X-Start-Cursor: + - g3QAAAABdwJpZGEB + body: + encoding: UTF-8 + string: '{"data":[{"id":"YEJ73","state":"CO","email":"Test17@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"John","middle_name":"","last_name":"Smith","born_on":"1964-12-30","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787867","soda_sponsor_id":null,"employee_id":"9999117","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test17@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787867","parent_sponsor_member_identifier":"878787867","relationship_type":"PP","insurance_identifiers":[]},{"id":"oEA5E","state":"CO","email":"Test18@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Mary","middle_name":"","last_name":"Smith","born_on":"1957-07-19","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787868","soda_sponsor_id":null,"employee_id":"9999118","health_plan_coverage_tier":"You + + Spouse","health_plan_description":"Blue Cross Blue Shield of IL","emails":[{"type":"personal","value":"Test18@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787867","parent_sponsor_member_identifier":"878787867","relationship_type":"S","insurance_identifiers":[]},{"id":"jrG7r","state":"CO","email":"Test19@emsanacare.com","option_id":null,"gender":"male","enrollments":[],"consents":[],"first_name":"Robert","middle_name":"","last_name":"McTester","born_on":"1975-08-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551008","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787869","soda_sponsor_id":null,"employee_id":"9999119","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"Test19@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787869","parent_sponsor_member_identifier":"878787869","relationship_type":"PP","insurance_identifiers":[]},{"id":"9mq03","state":"CO","email":"Test20@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Sara","middle_name":"","last_name":"Jones","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551016","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787870","soda_sponsor_id":null,"employee_id":"9999120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test20@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787870","parent_sponsor_member_identifier":"878787870","relationship_type":"PP","insurance_identifiers":[]},{"id":"93WMr","state":"CO","email":"Test20Spouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Wendy","middle_name":"","last_name":"Jones","born_on":"1997-09-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"978787870","soda_sponsor_id":null,"employee_id":"9998120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test20Spouse@emsanacare.com"}],"work_email":null,"health_plan_identifier":"9999120","parent_sponsor_member_identifier":"9999120","relationship_type":"S","insurance_identifiers":[]},{"id":"AmOom","state":"CO","email":"TestTwentySpouse@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Alice","middle_name":"","last_name":"Jones","born_on":"2024-05-01","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"778787870","soda_sponsor_id":null,"employee_id":"9997120","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"TestTwentySpouse@emsanacare.com"}],"work_email":null,"health_plan_identifier":"9999120","parent_sponsor_member_identifier":"9999120","relationship_type":"C","insurance_identifiers":[]},{"id":"","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[{"id":1,"expires_at":null,"enrolled_at":"2025-08-28T15:09:28.840837Z","enrollment_events":[],"program_id":"","member_id":""}],"consents":[{"id":5,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":6,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":1,"category":"sms","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:08Z","program_id":"","member_id":""},{"id":4,"category":"hipaa","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}],"first_name":"Betty + Jo","middle_name":"","last_name":"Brown","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+15551234567","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787871","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787871","parent_sponsor_member_identifier":"878787871","relationship_type":"PP","insurance_identifiers":[]},{"id":"jmQNr","state":"CO","email":"test22@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Carrie","middle_name":"","last_name":"Collins","born_on":"1998-04-18","street_address_1":"123 + Oak St","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551022","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787872","soda_sponsor_id":null,"employee_id":"9999122","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test22@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787872","parent_sponsor_member_identifier":"878787872","relationship_type":"PP","insurance_identifiers":[]},{"id":"ar9Qr","state":"CO","email":"test23@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Denise","middle_name":"","last_name":"Danson","born_on":"1998-05-09","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":null,"eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787873","soda_sponsor_id":null,"employee_id":"9999123","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test23@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787873","parent_sponsor_member_identifier":"878787873","relationship_type":"PP","insurance_identifiers":[]},{"id":"o3z4r","state":"CO","email":"Test24@emsanacare.com","option_id":null,"gender":"female","enrollments":[],"consents":[],"first_name":"Elise","middle_name":"","last_name":"Edwards","born_on":"1998-06-14","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+18005551024","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787874","soda_sponsor_id":null,"employee_id":"9999124","health_plan_coverage_tier":"You + + Spouse and Child(ren)","health_plan_description":"Cigna HealthCare","emails":[{"type":"personal","value":"Test24@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787874","parent_sponsor_member_identifier":"878787874","relationship_type":"PP","insurance_identifiers":[]}],"end_cursor":"g3QAAAABdwJpZGEK","start_cursor":"g3QAAAABdwJpZGEB"}' + recorded_at: Tue, 13 Jan 2026 21:30:30 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/cassettes/members/update.yml b/spec/cassettes/members/update.yml new file mode 100644 index 0000000..dce3fbe --- /dev/null +++ b/spec/cassettes/members/update.yml @@ -0,0 +1,44 @@ +--- +http_interactions: +- request: + method: patch + uri: https://localhost:4000/api/members/ + body: + encoding: UTF-8 + string: '{"member":{"phone_number":"+15551234567"}}' + headers: + Authorization: + - apikey + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - data-nexus-ruby/0.1.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1921' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 13 Jan 2026 21:30:29 GMT + Server: + - Cowboy + X-Request-Id: + - GIpntPno2CPcdg4AACgh + body: + encoding: UTF-8 + string: '{"data":{"id":"","state":"CO","email":"test21@emsanacare.com","option_id":null,"gender":"female","enrollments":[{"id":1,"expires_at":null,"enrolled_at":"2025-08-28T15:09:28.840837Z","program_id":"","member_id":""}],"consents":[{"id":5,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":6,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":7,"category":"sms","consent_details":{"sms_phone_number":"+15558675309"},"member_response":true,"consented_at":null,"program_id":"","member_id":""},{"id":1,"category":"sms","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:08Z","program_id":"","member_id":""},{"id":4,"category":"hipaa","consent_details":{},"member_response":true,"consented_at":"2025-10-08T20:18:10Z","program_id":"","member_id":""}],"first_name":"Betty + Jo","middle_name":"","last_name":"Brown","born_on":"","street_address_1":"401 + Shady Holw","street_address_2":null,"city":"Nederland","postal_code":"80466","phone_number":"+15551234567","eligibility_start_on":"2024-01-01","eligibility_end_on":null,"sponsor_member_identifier":"878787871","soda_sponsor_id":null,"employee_id":"","health_plan_coverage_tier":"You + Only","health_plan_description":"Aetna, Inc.","emails":[{"type":"personal","value":"test21@emsanacare.com"},{"type":"primary_member","value":null}],"work_email":null,"health_plan_identifier":"878787871","parent_sponsor_member_identifier":"878787871","relationship_type":"PP","insurance_identifiers":[]}}' + recorded_at: Tue, 13 Jan 2026 21:30:30 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/integration/member_consents_spec.rb b/spec/integration/member_consents_spec.rb index db6fd55..0294001 100644 --- a/spec/integration/member_consents_spec.rb +++ b/spec/integration/member_consents_spec.rb @@ -10,7 +10,7 @@ describe 'creating a consent' do it 'returns the created consent', vcr: { cassette_name: 'member_consents/create' } do - response = client.programs(program_id).members.consents(member_id).create( + response = client.programs(program_id).members(member_id).consents.create( consent: { category: 'sms', member_response: true, @@ -25,7 +25,7 @@ end it 'auto-injects the program_id', vcr: { cassette_name: 'member_consents/create' } do - response = client.programs(program_id).members.consents(member_id).create( + response = client.programs(program_id).members(member_id).consents.create( consent: { category: 'sms', member_response: true, @@ -41,7 +41,7 @@ let(:consent_id) { 2 } it 'returns the consent data', vcr: { cassette_name: 'member_consents/find' } do - consent = client.programs(program_id).members.consents(member_id).find(consent_id) + consent = client.programs(program_id).members(member_id).consents.find(consent_id) expect(consent).to be_a(Hash) expect(consent).to have_key(:id) @@ -55,7 +55,7 @@ let(:consent_id) { 2 } it 'returns the updated consent', vcr: { cassette_name: 'member_consents/update' } do - response = client.programs(program_id).members.consents(member_id).update( + response = client.programs(program_id).members(member_id).consents.update( consent_id, consent: { member_response: false } ) @@ -69,7 +69,7 @@ let(:consent_id) { 2 } it 'returns an empty response', vcr: { cassette_name: 'member_consents/delete' } do - response = client.programs(program_id).members.consents(member_id).delete(consent_id) + response = client.programs(program_id).members(member_id).consents.delete(consent_id) expect(response).to eq({}) end diff --git a/spec/integration/member_enrollments_spec.rb b/spec/integration/member_enrollments_spec.rb new file mode 100644 index 0000000..7e1c57f --- /dev/null +++ b/spec/integration/member_enrollments_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +RSpec.describe 'Member Enrollments', :vcr, order: :defined do + let(:api_key) { ENV.fetch('DATANEXUS_API_KEY', 'test-api-key') } + let(:base_url) { ENV.fetch('DATANEXUS_BASE_URL', 'http://localhost:4000') } + let(:ssl_verify) { ENV.fetch('DATANEXUS_SSL_VERIFY', 'true') == 'true' } + let(:program_id) { ENV.fetch('DATANEXUS_TEST_PROGRAM_ID', 'test-program-id') } + let(:member_id) { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } + let(:client) { DataNexus::Client.new(api_key: api_key, base_url: base_url, ssl_verify: ssl_verify) } + + describe 'creating an enrollment' do + it 'returns the created enrollment', vcr: { cassette_name: 'member_enrollments/create' } do + response = client.programs(program_id).members(member_id).enrollments.create( + enrollment: { + enrolled_at: '2024-01-01T00:00:00Z', + expires_at: '2025-01-01T00:00:00Z' + } + ) + + expect(response).to be_a(Hash) + expect(response).to have_key(:data) + expect(response[:data][:enrolled_at]).not_to be_nil + end + + it 'auto-injects the program_id', vcr: { cassette_name: 'member_enrollments/create' } do + response = client.programs(program_id).members(member_id).enrollments.create( + enrollment: { + enrolled_at: '2024-01-01T00:00:00Z' + } + ) + + expect(response[:data][:program_id]).to eq(program_id) + end + end + + describe 'finding an enrollment' do + let(:enrollment_id) { ENV.fetch('DATANEXUS_TEST_ENROLLMENT_ID', 'test-enrollment-id') } + + it 'returns the enrollment data', vcr: { cassette_name: 'member_enrollments/find' } do + enrollment = client.programs(program_id).members(member_id).enrollments.find(enrollment_id) + + expect(enrollment).to be_a(Hash) + expect(enrollment).to have_key(:id) + expect(enrollment).to have_key(:program_id) + expect(enrollment).to have_key(:enrolled_at) + end + end + + describe 'updating an enrollment' do + let(:enrollment_id) { ENV.fetch('DATANEXUS_TEST_ENROLLMENT_ID', 'test-enrollment-id') } + + it 'returns the updated enrollment', vcr: { cassette_name: 'member_enrollments/update' } do + response = client.programs(program_id).members(member_id).enrollments.update( + enrollment_id, + enrollment: { expires_at: '2026-01-01T00:00:00Z' } + ) + + expect(response).to be_a(Hash) + expect(response).to have_key(:data) + end + end + + describe 'deleting an enrollment' do + let(:enrollment_id) { ENV.fetch('DATANEXUS_TEST_ENROLLMENT_ID', 'test-enrollment-id') } + + it 'returns an empty response', vcr: { cassette_name: 'member_enrollments/delete' } do + response = client.programs(program_id).members(member_id).enrollments.delete(enrollment_id) + + expect(response).to eq({}) + end + end +end diff --git a/spec/integration/members_spec.rb b/spec/integration/members_spec.rb new file mode 100644 index 0000000..90b003c --- /dev/null +++ b/spec/integration/members_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +RSpec.describe 'Members', :vcr do + let(:api_key) { ENV.fetch('DATANEXUS_API_KEY', 'test-api-key') } + let(:base_url) { ENV.fetch('DATANEXUS_BASE_URL', 'http://localhost:4000') } + let(:ssl_verify) { ENV.fetch('DATANEXUS_SSL_VERIFY', 'true') == 'true' } + let(:client) { DataNexus::Client.new(api_key: api_key, base_url: base_url, ssl_verify: ssl_verify) } + + describe 'listing members' do + it 'returns a collection', vcr: { cassette_name: 'members/list' } do + collection = client.members.list + + expect(collection).to be_a(DataNexus::Collection) + expect(collection.data).to be_an(Array) + end + + it 'includes pagination cursors', vcr: { cassette_name: 'members/list' } do + collection = client.members.list + + # Cursors may be nil if there are no results, but should respond to the methods + expect(collection).to respond_to(:start_cursor) + expect(collection).to respond_to(:end_cursor) + end + + it 'supports filtering by name', vcr: { cassette_name: 'members/list_by_name' } do + collection = client.members.list( + first_name: 'Test', + last_name: 'User' + ) + + expect(collection).to be_a(DataNexus::Collection) + expect(collection.data).to be_an(Array) + end + + it 'supports filtering by born_on', vcr: { cassette_name: 'members/list_by_born_on' } do + test_born_on = ENV.fetch('DATANEXUS_TEST_BORN_ON', '1970-01-01') + collection = client.members.list(born_on: test_born_on) + + expect(collection).to be_a(DataNexus::Collection) + expect(collection.data).to be_an(Array) + end + + it 'supports filtering by program_id', vcr: { cassette_name: 'members/list_by_program' } do + program_id = ENV.fetch('DATANEXUS_TEST_PROGRAM_ID', 'test-program-id') + collection = client.members.list(program_id: program_id) + + expect(collection).to be_a(DataNexus::Collection) + expect(collection.data).to be_an(Array) + end + + it 'supports filtering by updated_since', vcr: { cassette_name: 'members/list_by_updated_since' } do + collection = client.members.list(updated_since: '2020-01-01T00:00:00Z') + + expect(collection).to be_a(DataNexus::Collection) + expect(collection.data).to be_an(Array) + end + + it 'supports pagination parameters', vcr: { cassette_name: 'members/list_paginated' } do + collection = client.members.list(first: 10) + + expect(collection).to be_a(DataNexus::Collection) + expect(collection.data).to be_an(Array) + end + end + + describe 'finding a member' do + let(:member_id) { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } + + it 'returns the member data', vcr: { cassette_name: 'members/find' } do + member = client.members.find(member_id) + + expect(member).to be_a(Hash) + expect(member).to have_key(:id) + expect(member).to have_key(:first_name) + expect(member).to have_key(:last_name) + end + end + + describe 'updating a member' do + let(:member_id) { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } + + it 'returns the updated member', vcr: { cassette_name: 'members/update' } do + response = client.members.update( + member_id, + member: { phone_number: '+15551234567' } + ) + + expect(response).to be_a(Hash) + expect(response).to have_key(:data) + end + end +end diff --git a/spec/integration/program_members_spec.rb b/spec/integration/program_members_spec.rb index 127f698..1f887bb 100644 --- a/spec/integration/program_members_spec.rb +++ b/spec/integration/program_members_spec.rb @@ -46,7 +46,7 @@ let(:member_id) { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } it 'returns the member data', vcr: { cassette_name: 'program_members/find' } do - member = client.programs(program_id).members.find(member_id) + member = client.programs(program_id).members(member_id).find expect(member).to be_a(Hash) expect(member).to have_key(:id) @@ -57,8 +57,7 @@ let(:member_id) { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } it 'returns the updated member', vcr: { cassette_name: 'program_members/update' } do - response = client.programs(program_id).members.update( - member_id, + response = client.programs(program_id).members(member_id).update( member: { phone_number: '+15551234567' } ) @@ -71,7 +70,7 @@ let(:member_id) { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } it 'returns household members', vcr: { cassette_name: 'program_members/household' } do - collection = client.programs(program_id).members.household(member_id) + collection = client.programs(program_id).members(member_id).household expect(collection).to be_a(DataNexus::Collection) expect(collection.data).to be_an(Array) diff --git a/spec/support/vcr.rb b/spec/support/vcr.rb index 804d7d3..f183392 100644 --- a/spec/support/vcr.rb +++ b/spec/support/vcr.rb @@ -15,6 +15,7 @@ config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_BORN_ON', '1970-01-01') } config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_EMPLOYEE_ID', 'test-employee-id') } + config.filter_sensitive_data('') { ENV.fetch('DATANEXUS_TEST_ENROLLMENT_ID', 'test-enrollment-id') } # Match requests on method and path only (ignore query params for flexibility) config.default_cassette_options = { From bd63c8118c2a63b22f6113055899ae225ac33f74 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Tue, 13 Jan 2026 18:00:45 -0700 Subject: [PATCH 14/15] 4763: Fix return type for household route --- lib/data_nexus/resources/program_member.rb | 6 +++--- spec/integration/program_members_spec.rb | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/data_nexus/resources/program_member.rb b/lib/data_nexus/resources/program_member.rb index 705dc0f..aaa96ec 100644 --- a/lib/data_nexus/resources/program_member.rb +++ b/lib/data_nexus/resources/program_member.rb @@ -81,14 +81,14 @@ def update(member:) # Get household members for this member # - # @return [Collection] Collection of household members + # @return [Array] Array of household member data # # @example # household = client.programs("uuid").members("member-id").household - # household.data.each { |m| puts "#{m[:first_name]} - #{m[:relationship_type]}" } + # household.each { |m| puts "#{m[:first_name]} - #{m[:relationship_type]}" } def household response = connection.get("#{base_path}/#{member_id}/household") - Collection.new(response, resource: self, params: {}) + response[:data] end # Access consents for this member diff --git a/spec/integration/program_members_spec.rb b/spec/integration/program_members_spec.rb index 1f887bb..36eeab5 100644 --- a/spec/integration/program_members_spec.rb +++ b/spec/integration/program_members_spec.rb @@ -70,10 +70,9 @@ let(:member_id) { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') } it 'returns household members', vcr: { cassette_name: 'program_members/household' } do - collection = client.programs(program_id).members(member_id).household + household = client.programs(program_id).members(member_id).household - expect(collection).to be_a(DataNexus::Collection) - expect(collection.data).to be_an(Array) + expect(household).to be_an(Array) end end end From eec947fe48a58075ee880cb0587e9265fe4f7af8 Mon Sep 17 00:00:00 2001 From: Alex Kibler Date: Wed, 14 Jan 2026 10:16:52 -0700 Subject: [PATCH 15/15] 4763: Make sure README matches --- README.md | 108 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bd12dba..33ab8f0 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ Ruby client for the DataNexus API. ## Installation +Install from GitHub (before gem is published): + +```ruby +gem 'data_nexus', git: 'https://github.com/DartHealth/datanexus-ruby.git', tag: 'v0.1.0' +``` + +Or from RubyGems (once published): + ```ruby gem 'data_nexus' ``` @@ -17,6 +25,8 @@ client = DataNexus::Client.new( ) ``` +## Program Members + ### List Members Filters are required. Valid combinations: @@ -54,14 +64,14 @@ end ### Find Member ```ruby -member = client.programs('program-id').members.find('member-id') +member = client.programs('program-id').members('member-id').find puts member[:first_name] ``` ### Update Member ```ruby -response = client.programs('program-id').members.update('member-id', +response = client.programs('program-id').members('member-id').update( member: { phone_number: '+15551234567' } ) ``` @@ -69,8 +79,8 @@ response = client.programs('program-id').members.update('member-id', ### Household Members ```ruby -household = client.programs('program-id').members.household('member-id') -household.data.each { |member| puts member[:first_name] } +household = client.programs('program-id').members('member-id').household +household.each { |member| puts member[:first_name] } ``` ### Member Consents @@ -78,7 +88,7 @@ household.data.each { |member| puts member[:first_name] } #### Create Consent ```ruby -response = client.programs('program-id').members.consents('member-id').create( +response = client.programs('program-id').members('member-id').consents.create( consent: { category: 'sms', member_response: true, @@ -91,14 +101,14 @@ response = client.programs('program-id').members.consents('member-id').create( #### Find Consent ```ruby -consent = client.programs('program-id').members.consents('member-id').find(123) +consent = client.programs('program-id').members('member-id').consents.find(123) puts consent[:category] ``` #### Update Consent ```ruby -response = client.programs('program-id').members.consents('member-id').update(123, +response = client.programs('program-id').members('member-id').consents.update(123, consent: { member_response: false } ) ``` @@ -106,14 +116,87 @@ response = client.programs('program-id').members.consents('member-id').update(12 #### Delete Consent ```ruby -client.programs('program-id').members.consents('member-id').delete(123) +client.programs('program-id').members('member-id').consents.delete(123) +``` + +### Member Enrollments + +#### Create Enrollment + +```ruby +response = client.programs('program-id').members('member-id').enrollments.create( + enrollment: { + enrolled_at: '2024-01-01T00:00:00Z', + expires_at: '2025-01-01T00:00:00Z' + } +) +# program_id is automatically injected +``` + +#### Find Enrollment + +```ruby +enrollment = client.programs('program-id').members('member-id').enrollments.find(123) +puts enrollment[:enrolled_at] +``` + +#### Update Enrollment + +```ruby +response = client.programs('program-id').members('member-id').enrollments.update(123, + enrollment: { expires_at: '2026-01-01T00:00:00Z' } +) +``` + +#### Delete Enrollment + +```ruby +client.programs('program-id').members('member-id').enrollments.delete(123) +``` + +## Top-Level Members + +You can also access members without a program scope: + +### List Members + +```ruby +collection = client.members.list( + first_name: 'George', + last_name: 'Washington', + born_on: '1976-07-04' +) + +# Filter by program eligibility +collection = client.members.list(program_id: 'program-uuid') + +# Filter by update time +collection = client.members.list(updated_since: '2024-01-01T00:00:00Z') + +# Pagination +collection = client.members.list(first: 50, after: 'cursor') +``` + +### Find Member + +```ruby +member = client.members.find('member-id') +puts member[:first_name] +``` + +### Update Member + +```ruby +response = client.members.update('member-id', + member: { phone_number: '+15551234567' } +) ``` ## Error Handling ```ruby begin - client.programs('id').members.find('id') + client.programs('id').members('id').find rescue DataNexus::AuthenticationError # 401 rescue DataNexus::NotFoundError @@ -129,11 +212,12 @@ end ## Development -``` +```bash cp .mise.local.toml.example .mise.local.toml # Edit .mise.local.toml with your test credentials -mise run test -mise run lint +bundle install +bundle exec rspec +bundle exec rubocop ``` ## License