Skip to content

Commit

Permalink
Feature/ffs 675 (#16)
Browse files Browse the repository at this point in the history
* added new migration for connected_argyle_accounts

* added ConnectedArgyleAccount model

* added ability to query payroll-data from argyle for connected account. added webhook logic to persist connected accounts into the database

* uncommented signature check

* Passing test for Argyle payroll

* fixed test assertions and stubs

* updated test descriptions and made them look pretty

* updates per PR feedback

* moved test setup logic to before block
  • Loading branch information
George Byers authored May 7, 2024
1 parent e1120f3 commit 1550724
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 12 deletions.
1 change: 1 addition & 0 deletions .github/actions/setup-project/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ inputs:
default: postgres://cidbuser:postgres@localhost:5432/iv_cbv_payroll_test
outputs:
database_url:
description: The database URL that was set
value: ${{ inputs.database_url }}
runs:
using: composite
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Brewfile.lock.json

# Ignore local dotenv overrides
.env*.local

.env.test
# Ignore OWASP files
/zap_report.html
/zap.yaml
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ GEM
net-protocol
newrelic_rpm (8.16.0)
nio4r (2.7.1)
nokogiri (1.16.4-aarch64-linux)
racc (~> 1.4)
nokogiri (1.16.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.4-x86_64-darwin)
Expand Down Expand Up @@ -337,6 +339,7 @@ GEM
zeitwerk (2.6.13)

PLATFORMS
aarch64-linux
arm64-darwin-23
x86_64-darwin-20
x86_64-linux
Expand Down
20 changes: 15 additions & 5 deletions app/controllers/webhooks/argyle/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@ class Webhooks::Argyle::EventsController < ApplicationController
skip_before_action :verify_authenticity_token

def create
signature = OpenSSL::HMAC.hexdigest('SHA512', ENV['ARGYLE_WEBHOOK_SECRET'], request.raw_post)
signature = OpenSSL::HMAC.hexdigest("SHA512", ENV["ARGYLE_WEBHOOK_SECRET"], request.raw_post)

unless request.headers["X-Argyle-Signature"] == signature
return render json: { error: 'Invalid signature' }, status: :unauthorized
return render json: { error: "Invalid signature" }, status: :unauthorized
end

if params['event'] == 'paystubs.fully_synced' || params['event'] == 'paystubs.partially_synced'
@cbv_flow = CbvFlow.find_by_argyle_user_id(params['data']['user'])
if params["event"] == "paystubs.fully_synced" || params["event"] == "paystubs.partially_synced"
@cbv_flow = CbvFlow.find_by_argyle_user_id(params["data"]["user"])

if @cbv_flow
@cbv_flow.update(payroll_data_available_from: params['data']['available_from'])
@cbv_flow.update(payroll_data_available_from: params["data"]["available_from"])
ArgylePaystubsChannel.broadcast_to(@cbv_flow, params)
end
end

if params["event"] == "accounts.connected"
rep = ConnectedArgyleAccount.create!(
user_id: params["data"]["user"],
account_id: params["data"]["account"]
)
Rails.logger.info "ConnectedArgyleAccount created: #{rep}"
render json: { message: "ConnectedArgyleAccount created", data: rep }, status: :created
end

end
end
4 changes: 4 additions & 0 deletions app/models/connected_argyle_account.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class ConnectedArgyleAccount < ApplicationRecord
validates :user_id, presence: true
validates :account_id, presence: true
end
10 changes: 9 additions & 1 deletion app/services/argyle_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class ArgyleService
def initialize
@api_key = Rails.application.credentials.argyle[:api_key]
base_url = ENV["ARGYLE_API_URL"] || "https://api-sandbox.argyle.com/v2"

client_options = {
request: {
open_timeout: 5,
Expand All @@ -21,8 +22,15 @@ def initialize

# Fetch all Argyle items
def items(query = nil)
# get "items" and pass the query as the q parameter for the Faraday instance
response = @http.get("items", { q: query })
JSON.parse(response.body)
end

def payroll_documents(account_id, user_id)
account_exists = ConnectedArgyleAccount.exists?(user_id: user_id, account_id: account_id)
raise "Argyle error: Account not connected" unless account_exists
response = @http.get("payroll-documents", { account: account_id, user: user_id })
JSON.parse(response.body)
end

end
2 changes: 1 addition & 1 deletion config/credentials/development.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Btn0OVg1L730VpNEbQ+H15cFKg1y3q/z6IwIaDc9c4iExboCtzTUsYU/NO4sXKVZ0m0ShItWOKR4IxwCqw7GrIRKckilMnNhJKKMZJ9clj6CieYC9efIb8WeUe3c11AA--KLu1KB+ynGorNBrB--+jEPdTuPzExEtrlSeJzR9g==
pWx+QNd/gK0RyL9jUoME8AVCKEPpGVXDzQrBov/o5dQNXhb3AF8ZLVEFErUXesZPt+ZFjO0kOWfgwHALtRtwkWHZpt6mF7odr9fG4W6pQkorMPHdwaVVJoSTCHbQ5a357qF0EcuDXXmHCGqqr0sG0ly+8NNLSmOM4zdV0Rq1Rwi0hnQjo4RPSZ6hIiYTR3VqwAxbAaWqLwC01Q/wbt/bxNHKq0f6k/Q9yQQ=--MpaWHAhpoYnJd8WO--pz1XxYcKma6dhIuVugJv8w==
4 changes: 4 additions & 0 deletions config/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ development:
test:
<<: *default
database: iv_cbv_payroll_test
host: <%= ENV.fetch("DB_HOST") { "localhost" } %>
username: <%= ENV.fetch("DB_USERNAME") { nil } %>
# The password associated with the postgres role (username).
password: <%= ENV.fetch("DB_PASSWORD") { nil } %>

# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
Expand Down
10 changes: 10 additions & 0 deletions db/migrate/20240502195250_create_connected_argyle_accounts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateConnectedArgyleAccounts < ActiveRecord::Migration[7.0]
def change
create_table :connected_argyle_accounts do |t|
t.uuid :user_id, null: false
t.uuid :account_id, null: false
t.timestamps null: false
t.index [ :user_id, :account_id ], unique: true
end
end
end
9 changes: 8 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified doc/compliance/rendered/apps/data.logical.pdf
Binary file not shown.
44 changes: 41 additions & 3 deletions spec/services/argyle_service_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
require 'rails_helper'
require 'support/account_connected_webhook_stub'
require 'support/payroll_documents_response_stub'

RSpec.describe ArgyleService, type: :service do
let(:service) { ArgyleService.new }
let(:account_id) { 'account_id' }
let(:user_id) { 'user_id' }
let(:fake_response) { instance_double(Faraday::Response, body: payroll_documents_response_stub) }

describe 'Initialization' do
it 'has a default API endpoint pointing to the sandbox' do
service = ArgyleService.new
Expand All @@ -11,15 +18,46 @@
end

describe '#items' do
it 'returns a non-empty response' do
service = ArgyleService.new
before do
# Stub the HTTP call to return a non-empty JSON response
fake_response = instance_double(Faraday::Response, body: '[{"id": "12345"}]')
allow_any_instance_of(Faraday::Connection).to receive(:get).with("items", { q: nil }).and_return(fake_response)

end
it 'returns a non-empty response' do
service = ArgyleService.new
response = service.items
expect(response).not_to be_empty
expect(response.first['id']).to eq("12345")
end
end

describe '#payroll_documents' do
context 'when ConnectedArgyleAccount exists' do
before do
# simulate that fetching the payroll documents returns a non-empty JSON response resembling payroll data
allow_any_instance_of(Faraday::Connection).to receive(:get).with("payroll-documents", { account: account_id, user: user_id }).and_return(fake_response)
end

it 'invokes the ArguleApiService and returns payroll documents' do
# simulate that we have a ConnectedArgyleAccount record
allow(ConnectedArgyleAccount).to receive(:exists?).with(user_id: user_id, account_id: account_id).and_return(true)
response = service.payroll_documents(account_id, user_id)

# expect that user_id, account_id stored in the ConnectedArgyleAccount record match the id and acount in the response
expect(response).not_to be_empty
expect(response['data'][0]['id']).to eq(JSON.parse(fake_response.body)['data'][0]['id'])
expect(response['data'][0]['account']).to eq(JSON.parse(fake_response.body)['data'][0]['account'])
end
end

context 'when ConnectedArgyleAccount does not exist' do
before do
allow(ConnectedArgyleAccount).to receive(:exists?).with(user_id: user_id, account_id: account_id).and_return(false)
end

it 'raises an error' do
expect { service.payroll_documents(account_id, user_id) }.to raise_error("Argyle error: Account not connected")
end
end
end
end
44 changes: 44 additions & 0 deletions spec/support/account_connected_webhook_stub.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
def account_connected_webhook_stub
'{
"event": "accounts.connected",
"name": "Account Connected",
"data": {
"account": "018f3fb8-47b7-7a9f-b95a-530117e8522e",
"user": "018f110a-39c1-3a5f-826f-a004eb7ed0b5",
"resource": {
"id": "018f3fb8-47b7-7a9f-b95a-530117e8522e",
"user": "018f110a-39c1-3a5f-826f-a004eb7ed0b5",
"employers": [],
"item": "item_000012375",
"source": "argyle_sandbox",
"created_at": "2024-05-03T18:29:52.780604Z",
"updated_at": "2024-05-03T18:30:09.761659Z",
"connection": {
"status": "connected",
"error_code": null,
"error_message": null,
"updated_at": "2024-05-03T18:30:09.137853Z"
},
"direct_deposit_switch": {
"status": "idle",
"error_code": null,
"error_message": null,
"updated_at": "2024-05-03T18:29:53.219572Z"
},
"availability": {
"gigs": null,
"paystubs": null,
"payroll_documents": null,
"identities": null,
"ratings": null,
"vehicles": null,
"deposit_destinations": null,
"user_forms": null
},
"ongoing_refresh": {
"status": "idle"
}
}
}
}'
end
36 changes: 36 additions & 0 deletions spec/support/payroll_documents_response_stub.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
def payroll_documents_response_stub
'{
"data": [
{
"id": "018f110a-39c1-3a5f-826f-a004eb7ed0b5",
"account": "018f3fb8-47b7-7a9f-b95a-530117e8522e",
"document_number": null,
"available_date": "2020-05-13T17:25:59Z",
"expiration_date": null,
"employer": "Whole Goods",
"document_type": "payout-statement",
"document_type_description": null,
"file_url": "api.argyle.com/v2/payroll-documents/{id}/file",
"created_at": "2023-03-13T17:27:01.501Z",
"updated_at": "2023-03-13T17:27:01.501Z",
"ocr_data": {},
"metadata": {}
},
{
"id": "018f110a-39c1-3a5f-826f-a004eb7ed0b5",
"account": "018f3fb8-47b7-7a9f-b95a-530117e8522e",
"document_number": "ced46eb3-7586-3cd7-2418-8eb9482bc3ec",
"available_date": "2019-03-14T17:46:25Z",
"expiration_date": "2027-03-12T17:46:25Z",
"employer": "GigAndGo",
"document_type": "drivers-licence",
"document_type_description": "Driver\'s license",
"file_url": "api.argyle.com/v2/payroll-documents/{id}/file",
"created_at": "2023-03-13T17:46:28.240Z",
"updated_at": "2023-03-13T17:46:28.240Z",
"ocr_data": {},
"metadata": {}
}
]
}'
end
3 changes: 3 additions & 0 deletions spec/support/webhook_x_argyle_signature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def webhook_x_argyle_signature
'X-Argyle-Signature: cbc0045ca9ba037086d7396966516349bc7a0c9f48b0eaf1e2d0a5002d77dc7202b48e88bfb0ad3a8973e56998a6ad519f98801cee9527be4297f53223d3f8bf'
end

0 comments on commit 1550724

Please sign in to comment.