virtuous 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +26 -0
  3. data/.gitignore +5 -0
  4. data/.reek.yml +36 -0
  5. data/.rubocop.yml +87 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +18 -0
  9. data/Gemfile +17 -0
  10. data/LICENSE +21 -0
  11. data/README.md +54 -0
  12. data/Rakefile +24 -0
  13. data/lib/virtuous/client/contact.rb +220 -0
  14. data/lib/virtuous/client/contact_address.rb +78 -0
  15. data/lib/virtuous/client/gift.rb +394 -0
  16. data/lib/virtuous/client/gift_designation.rb +59 -0
  17. data/lib/virtuous/client/individual.rb +125 -0
  18. data/lib/virtuous/client/recurring_gift.rb +86 -0
  19. data/lib/virtuous/client.rb +272 -0
  20. data/lib/virtuous/error.rb +54 -0
  21. data/lib/virtuous/helpers/hash_helper.rb +28 -0
  22. data/lib/virtuous/helpers/string_helper.rb +31 -0
  23. data/lib/virtuous/parse_oj.rb +24 -0
  24. data/lib/virtuous/version.rb +5 -0
  25. data/lib/virtuous.rb +12 -0
  26. data/logo/virtuous.svg +1 -0
  27. data/spec/spec_helper.rb +25 -0
  28. data/spec/support/client_factory.rb +10 -0
  29. data/spec/support/fixtures/contact.json +112 -0
  30. data/spec/support/fixtures/contact_address.json +20 -0
  31. data/spec/support/fixtures/contact_addresses.json +42 -0
  32. data/spec/support/fixtures/contact_gifts.json +80 -0
  33. data/spec/support/fixtures/gift.json +55 -0
  34. data/spec/support/fixtures/gift_designation_query_options.json +2701 -0
  35. data/spec/support/fixtures/gift_designations.json +175 -0
  36. data/spec/support/fixtures/gifts.json +112 -0
  37. data/spec/support/fixtures/import.json +0 -0
  38. data/spec/support/fixtures/individual.json +46 -0
  39. data/spec/support/fixtures/recurring_gift.json +26 -0
  40. data/spec/support/fixtures_helper.rb +5 -0
  41. data/spec/support/virtuous_mock.rb +101 -0
  42. data/spec/virtuous/client_spec.rb +270 -0
  43. data/spec/virtuous/error_spec.rb +74 -0
  44. data/spec/virtuous/resources/contact_address_spec.rb +75 -0
  45. data/spec/virtuous/resources/contact_spec.rb +137 -0
  46. data/spec/virtuous/resources/gift_designation_spec.rb +70 -0
  47. data/spec/virtuous/resources/gift_spec.rb +249 -0
  48. data/spec/virtuous/resources/individual_spec.rb +95 -0
  49. data/spec/virtuous/resources/recurring_gift_spec.rb +67 -0
  50. data/spec/virtuous_spec.rb +7 -0
  51. data/virtuous.gemspec +25 -0
  52. metadata +121 -0
@@ -0,0 +1,175 @@
1
+ {
2
+ "list": [
3
+ {
4
+ "id": 1,
5
+ "createDate": "9/14/2023",
6
+ "amountDesignated": 1000.0,
7
+ "giftId": 79,
8
+ "giftDate": "10/26/2022",
9
+ "giftType": "Check",
10
+ "giftIsPrivate": false,
11
+ "giftCustomFields": {},
12
+ "giftUrl": "/api/Gift/79",
13
+ "contactId": 13,
14
+ "contactName": "Donor 13",
15
+ "contactType": "Household",
16
+ "contactUrl": "/api/Contact/13",
17
+ "projectId": 3,
18
+ "project": "Direct Support",
19
+ "projectCode": "DirectSupport",
20
+ "projectExternalAccountingCode": null,
21
+ "projectType": "Unspecified",
22
+ "projectLocation": "Unspecified",
23
+ "projectCustomFields": {},
24
+ "projectUrl": "/api/Project/3",
25
+ "parentProjectId": null,
26
+ "parentProject": null,
27
+ "parentProjectCode": null,
28
+ "parentProjectExternalAccountingCode": null,
29
+ "parentProjectType": null,
30
+ "parentProjectLocation": null,
31
+ "parentProjectCustomFields": null,
32
+ "parentProjectUrl": null,
33
+ "batch": null,
34
+ "segmentName": null,
35
+ "segmentCode": null
36
+ },
37
+ {
38
+ "id": 2,
39
+ "createDate": "9/14/2023",
40
+ "amountDesignated": 9250.26,
41
+ "giftId": 102,
42
+ "giftDate": "10/27/2022",
43
+ "giftType": "Credit",
44
+ "giftIsPrivate": false,
45
+ "giftCustomFields": {},
46
+ "giftUrl": "/api/Gift/102",
47
+ "contactId": 422,
48
+ "contactName": "Donor 422",
49
+ "contactType": "Organization",
50
+ "contactUrl": "/api/Contact/422",
51
+ "projectId": 3,
52
+ "project": "Direct Support",
53
+ "projectCode": "DirectSupport",
54
+ "projectExternalAccountingCode": null,
55
+ "projectType": "Unspecified",
56
+ "projectLocation": "Unspecified",
57
+ "projectCustomFields": {},
58
+ "projectUrl": "/api/Project/3",
59
+ "parentProjectId": null,
60
+ "parentProject": null,
61
+ "parentProjectCode": null,
62
+ "parentProjectExternalAccountingCode": null,
63
+ "parentProjectType": null,
64
+ "parentProjectLocation": null,
65
+ "parentProjectCustomFields": null,
66
+ "parentProjectUrl": null,
67
+ "batch": null,
68
+ "segmentName": null,
69
+ "segmentCode": null
70
+ },
71
+ {
72
+ "id": 3,
73
+ "createDate": "9/14/2023",
74
+ "amountDesignated": 250.26,
75
+ "giftId": 102,
76
+ "giftDate": "10/27/2022",
77
+ "giftType": "Credit",
78
+ "giftIsPrivate": false,
79
+ "giftCustomFields": {},
80
+ "giftUrl": "/api/Gift/102",
81
+ "contactId": 422,
82
+ "contactName": "Donor 422",
83
+ "contactType": "Organization",
84
+ "contactUrl": "/api/Contact/422",
85
+ "projectId": 4,
86
+ "project": "Processing Fee",
87
+ "projectCode": "ProcessingFee",
88
+ "projectExternalAccountingCode": null,
89
+ "projectType": "Unspecified",
90
+ "projectLocation": "Unspecified",
91
+ "projectCustomFields": {},
92
+ "projectUrl": "/api/Project/4",
93
+ "parentProjectId": null,
94
+ "parentProject": null,
95
+ "parentProjectCode": null,
96
+ "parentProjectExternalAccountingCode": null,
97
+ "parentProjectType": null,
98
+ "parentProjectLocation": null,
99
+ "parentProjectCustomFields": null,
100
+ "parentProjectUrl": null,
101
+ "batch": null,
102
+ "segmentName": null,
103
+ "segmentCode": null
104
+ },
105
+ {
106
+ "id": 4,
107
+ "createDate": "9/14/2023",
108
+ "amountDesignated": 4042.0,
109
+ "giftId": 78,
110
+ "giftDate": "10/6/2022",
111
+ "giftType": "Check",
112
+ "giftIsPrivate": false,
113
+ "giftCustomFields": {},
114
+ "giftUrl": "/api/Gift/78",
115
+ "contactId": 159,
116
+ "contactName": "Donor 159",
117
+ "contactType": "Organization",
118
+ "contactUrl": "/api/Contact/159",
119
+ "projectId": 3,
120
+ "project": "Direct Support",
121
+ "projectCode": "DirectSupport",
122
+ "projectExternalAccountingCode": null,
123
+ "projectType": "Unspecified",
124
+ "projectLocation": "Unspecified",
125
+ "projectCustomFields": {},
126
+ "projectUrl": "/api/Project/3",
127
+ "parentProjectId": null,
128
+ "parentProject": null,
129
+ "parentProjectCode": null,
130
+ "parentProjectExternalAccountingCode": null,
131
+ "parentProjectType": null,
132
+ "parentProjectLocation": null,
133
+ "parentProjectCustomFields": null,
134
+ "parentProjectUrl": null,
135
+ "batch": null,
136
+ "segmentName": null,
137
+ "segmentCode": null
138
+ },
139
+ {
140
+ "id": 5,
141
+ "createDate": "9/14/2023",
142
+ "amountDesignated": 8333.33,
143
+ "giftId": 50,
144
+ "giftDate": "1/10/2023",
145
+ "giftType": "Check",
146
+ "giftIsPrivate": false,
147
+ "giftCustomFields": {},
148
+ "giftUrl": "/api/Gift/50",
149
+ "contactId": 429,
150
+ "contactName": "Donor 429",
151
+ "contactType": "Organization",
152
+ "contactUrl": "/api/Contact/429",
153
+ "projectId": 3,
154
+ "project": "Direct Support",
155
+ "projectCode": "DirectSupport",
156
+ "projectExternalAccountingCode": null,
157
+ "projectType": "Unspecified",
158
+ "projectLocation": "Unspecified",
159
+ "projectCustomFields": {},
160
+ "projectUrl": "/api/Project/3",
161
+ "parentProjectId": null,
162
+ "parentProject": null,
163
+ "parentProjectCode": null,
164
+ "parentProjectExternalAccountingCode": null,
165
+ "parentProjectType": null,
166
+ "parentProjectLocation": null,
167
+ "parentProjectCustomFields": null,
168
+ "parentProjectUrl": null,
169
+ "batch": null,
170
+ "segmentName": null,
171
+ "segmentCode": null
172
+ }
173
+ ],
174
+ "total": 110
175
+ }
@@ -0,0 +1,112 @@
1
+ [
2
+ {
3
+ "id": 1,
4
+ "transactionSource": null,
5
+ "transactionId": null,
6
+ "contactId": 1,
7
+ "contactName": "Test Contact",
8
+ "contactUrl": "/api/Contact/1",
9
+ "giftType": "Cash",
10
+ "giftTypeFormatted": "Cash",
11
+ "giftDate": "2023-12-08T00:00:00",
12
+ "giftDateFormatted": "12/8/2023",
13
+ "amount": 10.5,
14
+ "amountFormatted": "$10.50",
15
+ "currencyCode": "USD",
16
+ "exchangeRate": 1.0,
17
+ "baseCurrencyCode": "USD",
18
+ "batch": null,
19
+ "createDateTimeUtc": "2023-12-08T16:56:52.6126031Z",
20
+ "createdByUser": "Test User",
21
+ "modifiedDateTimeUtc": "2023-12-08T16:56:52.6126031Z",
22
+ "modifiedByUser": "Test User",
23
+ "segmentId": null,
24
+ "segment": null,
25
+ "segmentCode": null,
26
+ "segmentUrl": null,
27
+ "mediaOutletId": null,
28
+ "mediaOutlet": null,
29
+ "grantId": null,
30
+ "grant": null,
31
+ "grantUrl": null,
32
+ "notes": null,
33
+ "tribute": null,
34
+ "tributeId": null,
35
+ "tributeType": null,
36
+ "acknowledgeeIndividualId": null,
37
+ "isAcknowledgedGift": false,
38
+ "acknowledgementDate": null,
39
+ "acknowledgementNotes": null,
40
+ "receiptDate": null,
41
+ "receiptDateFormatted": "",
42
+ "contactPassthroughId": null,
43
+ "contactPassthroughUrl": null,
44
+ "contactIndividualId": null,
45
+ "cashAccountingCode": null,
46
+ "giftAskId": null,
47
+ "contactMembershipId": null,
48
+ "giftDesignations": [],
49
+ "giftPremiums": [],
50
+ "pledgePayments": [],
51
+ "recurringGiftPayments": [],
52
+ "giftUrl": "/api/Gift/1",
53
+ "isPrivate": false,
54
+ "isTaxDeductible": true,
55
+ "customFields": []
56
+ },
57
+ {
58
+ "id": 2,
59
+ "transactionSource": null,
60
+ "transactionId": null,
61
+ "contactId": 1,
62
+ "contactName": "Test Contact",
63
+ "contactUrl": "/api/Contact/1",
64
+ "giftType": "Cash",
65
+ "giftTypeFormatted": "Cash",
66
+ "giftDate": "2023-12-08T00:00:00",
67
+ "giftDateFormatted": "12/8/2023",
68
+ "amount": 5.0,
69
+ "amountFormatted": "$5.00",
70
+ "currencyCode": "USD",
71
+ "exchangeRate": 1.0,
72
+ "baseCurrencyCode": "USD",
73
+ "batch": null,
74
+ "createDateTimeUtc": "2023-12-08T16:56:52.6282311Z",
75
+ "createdByUser": "Test User",
76
+ "modifiedDateTimeUtc": "2023-12-08T16:56:52.6282311Z",
77
+ "modifiedByUser": "Test User",
78
+ "segmentId": null,
79
+ "segment": null,
80
+ "segmentCode": null,
81
+ "segmentUrl": null,
82
+ "mediaOutletId": null,
83
+ "mediaOutlet": null,
84
+ "grantId": null,
85
+ "grant": null,
86
+ "grantUrl": null,
87
+ "notes": null,
88
+ "tribute": null,
89
+ "tributeId": null,
90
+ "tributeType": null,
91
+ "acknowledgeeIndividualId": null,
92
+ "isAcknowledgedGift": false,
93
+ "acknowledgementDate": null,
94
+ "acknowledgementNotes": null,
95
+ "receiptDate": null,
96
+ "receiptDateFormatted": "",
97
+ "contactPassthroughId": null,
98
+ "contactPassthroughUrl": null,
99
+ "contactIndividualId": null,
100
+ "cashAccountingCode": null,
101
+ "giftAskId": null,
102
+ "contactMembershipId": null,
103
+ "giftDesignations": [],
104
+ "giftPremiums": [],
105
+ "pledgePayments": [],
106
+ "recurringGiftPayments": [],
107
+ "giftUrl": "/api/Gift/2",
108
+ "isPrivate": false,
109
+ "isTaxDeductible": true,
110
+ "customFields": []
111
+ }
112
+ ]
File without changes
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": 2,
3
+ "contactId": 1,
4
+ "prefix": "Mr",
5
+ "prefix2": null,
6
+ "firstName": "Test",
7
+ "middleName": null,
8
+ "lastName": "Individual",
9
+ "preMarriageName": null,
10
+ "suffix": null,
11
+ "nickname": null,
12
+ "gender": "Male",
13
+ "isPrimary": true,
14
+ "canBePrimary": false,
15
+ "isSecondary": false,
16
+ "canBeSecondary": false,
17
+ "birthMonth": 9,
18
+ "birthDay": 4,
19
+ "birthYear": 1998,
20
+ "birthDate": "9/4/1998",
21
+ "approximateAge": 25,
22
+ "isDeceased": false,
23
+ "deceasedDate": "",
24
+ "passion": null,
25
+ "avatarUrl": null,
26
+ "contactMethods": [
27
+ {
28
+ "id": 2081,
29
+ "type": "Work Email",
30
+ "value": "email@test.com",
31
+ "isOptedIn": false,
32
+ "isPrimary": true,
33
+ "canBePrimary": false,
34
+ "createDateTimeUtc": "2023-11-30T14:20:56.323",
35
+ "createdByUser": "Test User",
36
+ "modifiedDateTimeUtc": "2023-11-30T14:20:56.37",
37
+ "modifiedByUser": "Test User"
38
+ }
39
+ ],
40
+ "createDateTimeUtc": "2023-11-30T14:20:56.293",
41
+ "createdByUser": "Test User",
42
+ "modifiedDateTimeUtc": "2023-11-30T14:20:56.353",
43
+ "modifiedByUser": "Test User",
44
+ "customFields": [],
45
+ "customCollections": []
46
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "id": 1,
3
+ "transactionSource": null,
4
+ "transactionId": null,
5
+ "contactId": 1,
6
+ "recurringGiftDate": "2023-12-21T00:00:00",
7
+ "startDate": "2023-12-21T00:00:00",
8
+ "amount": 1000,
9
+ "frequency": "Monthly",
10
+ "anticipatedEndDate": null,
11
+ "cancelDateTimeUtc": null,
12
+ "createDateTimeUtc": "2023-12-21T19:44:49.317",
13
+ "createdByUser": "Test User",
14
+ "modifiedDateTimeUtc": "2023-12-22T13:42:34.34",
15
+ "modifiedByUser": "Test User",
16
+ "segmentId": null,
17
+ "segment": null,
18
+ "segmentUrl": null,
19
+ "automatedPayments": false,
20
+ "trackPayments": false,
21
+ "isPrivate": false,
22
+ "status": "UpToDate",
23
+ "nextExpectedPaymentDate": "2023-12-21T00:00:00",
24
+ "designations": [],
25
+ "customFields": []
26
+ }
@@ -0,0 +1,5 @@
1
+ class FixturesHelper
2
+ def self.read(path)
3
+ File.binread(File.join(FIXTURES_DIR, path))
4
+ end
5
+ end
@@ -0,0 +1,101 @@
1
+ require 'sinatra/base'
2
+ require 'securerandom'
3
+ require_relative 'fixtures_helper'
4
+
5
+ class VirtuousMock < Sinatra::Base
6
+ # GET requests
7
+ {
8
+ 'Contact/Find' => :contact,
9
+ 'Contact/:id' => :contact,
10
+ 'ContactIndividual/Find' => :individual,
11
+ 'ContactIndividual/:id' => :individual,
12
+ 'Gift/ByContact/:id' => :contact_gifts,
13
+ 'ContactAddress/ByContact/:id' => :contact_addresses,
14
+ 'Gift/:id' => :gift,
15
+ 'RecurringGift/:id' => :recurring_gift,
16
+ 'Gift/:transaction_source/:transaction_id' => :gift,
17
+ 'GiftDesignation/QueryOptions' => :gift_designation_query_options
18
+ }.each do |end_point, json|
19
+ get "/api/#{end_point}" do
20
+ json_response 200, "#{json}.json"
21
+ end
22
+ end
23
+
24
+ # POST requests
25
+ {
26
+ 'Contact/Transaction' => :import,
27
+ 'Contact' => :contact,
28
+ 'ContactIndividual' => :individual,
29
+ 'v2/Gift/Transaction' => :import,
30
+ 'v2/Gift/Transactions' => :import,
31
+ 'Gift' => :gift,
32
+ 'RecurringGift' => :recurring_gift,
33
+ 'Gift/Bulk' => :gifts,
34
+ 'GiftDesignation/Query' => :gift_designations,
35
+ 'ContactAddress' => :contact_address
36
+ }.each do |end_point, json|
37
+ post "/api/#{end_point}" do
38
+ json_response 200, "#{json}.json"
39
+ end
40
+ end
41
+
42
+ # PUT requests
43
+ {
44
+ 'Contact/:id' => :contact,
45
+ 'ContactIndividual/:id' => :individual,
46
+ 'Gift/:id' => :gift,
47
+ 'RecurringGift/:id' => :recurring_gift,
48
+ 'ContactAddress/:id' => :contact_address
49
+ }.each do |end_point, json|
50
+ put "/api/#{end_point}" do
51
+ json_response 200, "#{json}.json"
52
+ end
53
+ end
54
+
55
+ # DELETE requests
56
+ [
57
+ 'ContactIndividual/:id',
58
+ 'Gift/:id'
59
+ ].each do |end_point|
60
+ delete "/api/#{end_point}" do
61
+ content_type :json
62
+ status 204
63
+ end
64
+ end
65
+
66
+ # Auth request
67
+
68
+ post '/Token' do
69
+ content_type :json
70
+
71
+ body = URI.decode_www_form(request.body.read).to_h
72
+
73
+ if body['username'] == 'otp@user.com' && body['otp'].nil?
74
+ status 202
75
+ return {
76
+ error: 'awaiting_verification',
77
+ error_description: '2-step verification code (OTP) sent.'
78
+ }.to_json
79
+ end
80
+
81
+ status 200
82
+ {
83
+ access_token: 'new_access_token',
84
+ token_type: 'bearer',
85
+ expires_in: 1_295_999,
86
+ refresh_token: 'new_refresh_token',
87
+ userName: 'user@email.com',
88
+ twoFactorEnabled: 'False',
89
+ '.issued': Time.now.gmtime,
90
+ '.expires': (Time.now + 1_295_999).gmtime
91
+ }.to_json
92
+ end
93
+
94
+ private
95
+
96
+ def json_response(response_code, file_name)
97
+ content_type :json
98
+ status response_code
99
+ FixturesHelper.read(file_name)
100
+ end
101
+ end