Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOTORG-839: Create or Update Individual Constituent Action #1

Merged
merged 25 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0497edf
Scaffold Blackbaud Raiser's Edge NXT Destination
noahcooper Jan 20, 2023
695a7af
Update action
noahcooper Jan 21, 2023
8ae4144
Update action
noahcooper Jan 21, 2023
d89b709
Update tests
noahcooper Jan 22, 2023
29404f2
Add comments
noahcooper Jan 23, 2023
ed040ae
Don't send type in update call
noahcooper Jan 23, 2023
aeedd89
Don't update constituent if there is nothing to update
noahcooper Jan 23, 2023
f94046d
Add gender
noahcooper Jan 23, 2023
6b550dd
Convert strings to lowercase before comparing
noahcooper Jan 23, 2023
116d1b1
Add gender default
noahcooper Jan 23, 2023
8ffac6c
Add birthdate
noahcooper Jan 23, 2023
f278bf0
Add SegmentEvent type
noahcooper Jan 23, 2023
8f8bbea
Update tests
noahcooper Jan 23, 2023
a8aba5b
Return true for constituent update
noahcooper Jan 23, 2023
ec0fef8
Update defaults
noahcooper Jan 23, 2023
0b929c1
Throw RetryableError if Constituent search returns 429/500
noahcooper Jan 23, 2023
8a91c75
Aggregate all errors for existing constituent
noahcooper Jan 24, 2023
ddf8cab
Throw RetryableError if creating constituent returns 429/500
noahcooper Jan 24, 2023
9cc7979
Create fixtures
noahcooper Jan 24, 2023
64289e6
Update error tests
noahcooper Jan 24, 2023
cd42418
Allow retry on 401 errors
noahcooper Jan 24, 2023
eaf3a2d
Include status code in aggregated error message
noahcooper Jan 24, 2023
7c5de6d
Update fixtures
noahcooper Jan 24, 2023
4428960
Abstract isRequestErrorRetryable logic
noahcooper Jan 24, 2023
d1b80a3
Update statusCode type
noahcooper Jan 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import nock from 'nock'
// import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import { createTestIntegration } from '@segment/actions-core'
import Definition from '../index'

const testDestination = createTestIntegration(Definition)

describe("Blackbaud Raiser's Edge NXT", () => {
describe('testAuthentication', () => {
it('should validate authentication inputs', async () => {
nock('https://your.destination.endpoint').get('*').reply(200, {})

// This should match your authentication.fields
const authData = {}

await expect(testDestination.testAuthentication(authData)).resolves.not.toThrowError()
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import { generateTestData } from '../../../lib/test-data'
import destination from '../index'
import nock from 'nock'

const testDestination = createTestIntegration(destination)
const destinationSlug = 'actions-blackbaud-raisers-edge-nxt'

describe(`Testing snapshot for ${destinationSlug} destination:`, () => {
for (const actionSlug in destination.actions) {
it(`${actionSlug} action - required fields`, async () => {
const seedName = `${destinationSlug}#${actionSlug}`
const action = destination.actions[actionSlug]
const [eventData, settingsData] = generateTestData(seedName, destination, action, true)

nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/).persist().put(/.*/).reply(200)

const event = createTestEvent({
properties: eventData
})

const responses = await testDestination.testAction(actionSlug, {
event: event,
mapping: event.properties,
settings: settingsData,
auth: undefined
})

const request = responses[0].request
const rawBody = await request.text()

try {
const json = JSON.parse(rawBody)
expect(json).toMatchSnapshot()
return
} catch (err) {
expect(rawBody).toMatchSnapshot()
}

expect(request.headers).toMatchSnapshot()
})

it(`${actionSlug} action - all fields`, async () => {
const seedName = `${destinationSlug}#${actionSlug}`
const action = destination.actions[actionSlug]
const [eventData, settingsData] = generateTestData(seedName, destination, action, false)

nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/).persist().put(/.*/).reply(200)

const event = createTestEvent({
properties: eventData
})

const responses = await testDestination.testAction(actionSlug, {
event: event,
mapping: event.properties,
settings: settingsData,
auth: undefined
})

const request = responses[0].request
const rawBody = await request.text()

try {
const json = JSON.parse(rawBody)
expect(json).toMatchSnapshot()
return
} catch (err) {
expect(rawBody).toMatchSnapshot()
}
})
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SKY_API_BASE_URL = 'https://api.sky.blackbaud.com/constituent/v1'
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { SegmentEvent } from '@segment/actions-core'

// identify events
export const identifyEventData: Partial<SegmentEvent> = {
type: 'identify',
traits: {
address: {
city: 'New York City',
postal_code: '10108',
state: 'NY',
street: 'PO Box 963'
},
address_type: 'Home',
email: '[email protected]',
email_type: 'Personal',
first_name: 'John',
last_name: 'Doe',
phone: '+18774466722',
phone_type: 'Home',
website: 'https://www.facebook.com/john.doe',
website_type: 'Facebook'
}
}

export const identifyEventDataNoEmail: Partial<SegmentEvent> = {
type: 'identify',
traits: {
first_name: 'John',
last_name: 'Doe',
phone: '+18774466722',
phone_type: 'Home'
}
}

export const identifyEventDataNoLastName: Partial<SegmentEvent> = {
type: 'identify',
traits: {
email: '[email protected]'
}
}

export const identifyEventDataUpdated: Partial<SegmentEvent> = {
...identifyEventData,
traits: {
...identifyEventData.traits,
address: {
city: 'New York',
postal_code: '10005',
state: 'NY',
street: '11 Wall St'
},
address_type: 'Work',
email_type: 'Work',
phone: '+18774466723',
phone_type: 'Work',
website: 'https://www.example.biz',
website_type: 'Website'
}
}

export const identifyEventDataWithLookupId: Partial<SegmentEvent> = {
...identifyEventDataUpdated,
traits: {
...identifyEventDataUpdated.traits,
address: {
...(typeof identifyEventDataUpdated.traits?.address === 'object' ? identifyEventDataUpdated.traits.address : {}),
street: '11 Wall Street'
},
birthday: '2001-01-01T01:01:01-05:00',
email: '[email protected]',
email_type: 'Personal',
lookup_id: 'abcd1234'
}
}

export const identifyEventDataWithInvalidWebsite: Partial<SegmentEvent> = {
...identifyEventDataUpdated,
traits: {
...identifyEventDataUpdated.traits,
website_type: 'Invalid'
}
}

// constituent data
export const constituentPayload = {
address: {
address_lines: 'PO Box 963',
city: 'New York City',
state: 'NY',
postal_code: '10108',
type: 'Home'
},
email: {
address: '[email protected]',
type: 'Personal'
},
first: 'John',
last: 'Doe',
online_presence: {
address: 'https://www.facebook.com/john.doe',
type: 'Facebook'
},
phone: {
number: '+18774466722',
type: 'Home'
},
type: 'Individual'
}

export const constituentPayloadNoEmail = {
first: 'John',
last: 'Doe',
phone: {
number: '+18774466722',
type: 'Home'
},
type: 'Individual'
}

export const constituentPayloadWithLookupId = {
birthdate: {
d: '1',
m: '1',
y: '2001'
},
first: 'John',
last: 'Doe',
lookup_id: 'abcd1234'
}

// address data
export const addressPayloadUpdated = {
address_lines: '11 Wall St',
city: 'New York',
state: 'NY',
postal_code: '10005',
type: 'Work'
}

export const addressPayloadWithUpdatedStreet = {
address_lines: '11 Wall Street',
city: 'New York',
state: 'NY',
postal_code: '10005',
type: 'Work'
}

// email data
export const emailPayloadUpdated = {
address: '[email protected]',
type: 'Work'
}

export const emailPayloadPersonal = {
address: '[email protected]',
type: 'Personal'
}

// online presence data
export const onlinePresencePayloadUpdated = {
address: 'https://www.example.biz',
type: 'Website'
}

// phone data
export const phonePayloadUpdated = {
number: '+18774466723',
type: 'Work'
}
Loading