Skip to content

Commit

Permalink
[STRAT-3865] | Re-authentication Flow for CreateAudience and GetAudie…
Browse files Browse the repository at this point in the history
…nce to handle 401 (#2287)

* Revert "Revert "[STRAT-3865] | Re-authentication Flow for CreateAudience and …"

This reverts commit 7e928fe.

* passed options in subscriptions

* Simplified code

---------

Co-authored-by: Gaurav Kochar <[email protected]>
  • Loading branch information
Innovative-GauravKochar and Gaurav Kochar authored Aug 26, 2024
1 parent d682103 commit 840ac25
Show file tree
Hide file tree
Showing 2 changed files with 339 additions and 26 deletions.
255 changes: 254 additions & 1 deletion packages/core/src/__tests__/destination-kit.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IntegrationError } from '../errors'
import { ActionDefinition } from '../destination-kit/action'
import {
StateContext,
Expand All @@ -6,10 +7,13 @@ import {
Logger,
StatsClient,
StatsContext,
TransactionContext
TransactionContext,
AudienceDestinationDefinition,
AuthenticationScheme
} from '../destination-kit'
import { JSONObject } from '../json-object'
import { SegmentEvent } from '../segment-event'
const WRONG_AUDIENCE_ID = '1234567890'

const destinationCustomAuth: DestinationDefinition<JSONObject> = {
name: 'Actions Google Analytic 4',
Expand Down Expand Up @@ -78,6 +82,73 @@ const destinationOAuth2: DestinationDefinition<JSONObject> = {
}
}

const authentication: AuthenticationScheme<JSONObject> = {
scheme: 'oauth2',
fields: {},
refreshAccessToken: (_request) => {
return new Promise((resolve, _reject) => {
resolve({
accessToken: 'fresh-token'
})
})
}
}

const audienceDestination: AudienceDestinationDefinition<JSONObject> = {
name: 'Amazon AMC (Actions)',
mode: 'cloud',
authentication: authentication,
audienceFields: {},
audienceConfig: {
mode: {
type: 'synced', // Indicates that the audience is synced on some schedule; update as necessary
full_audience_sync: false // If true, we send the entire audience. If false, we just send the delta.
},

// Mocked createAudience Handler
async createAudience(_request, createAudienceInput) {
const settings: any = createAudienceInput.settings
const audienceSettings: any = createAudienceInput.audienceSettings

// it could be due to invalid input or Bad Request
if (!audienceSettings?.advertiserId)
throw new IntegrationError('Missing advertiserId Value', 'MISSING_REQUIRED_FIELD', 400)

// invalid access token
if (settings.oauth.access_token == 'invalid-access-token' || settings.oauth.clientId == 'invalid_client_id') {
return new Promise((_resolve, reject) => {
reject(new IntegrationError('Unauthorized', 'UNAUTHORIZED', 401))
})
}

return new Promise((resolve, _reject) => {
resolve({ externalId: '123456789' })
})
},

// Mocked getAudience Handler
async getAudience(_request, getAudienceInput) {
const settings: any = getAudienceInput.settings
const audience_id = getAudienceInput.externalId

if (audience_id == WRONG_AUDIENCE_ID) {
throw new IntegrationError('audienceId not found', 'AUDIENCEID_NOT_FOUND', 400)
}

if (settings.oauth.access_token == 'invalid-access-token' || settings.oauth.clientId == 'invalid_client_id') {
return new Promise((_resolve, reject) => {
reject(new IntegrationError('Unauthorized', 'UNAUTHORIZED', 401))
})
}

return new Promise((resolve, _reject) => {
resolve({ externalId: audience_id })
})
}
},
actions: {}
}

const destinationWithOptions: DestinationDefinition<JSONObject> = {
name: 'Actions Google Analytic 4',
mode: 'cloud',
Expand Down Expand Up @@ -961,4 +1032,186 @@ describe('destination kit', () => {
})
})
})
describe('Reauthentication Flow', () => {
beforeEach(async () => {
jest.restoreAllMocks()
jest.resetAllMocks()
})
describe('createAudience', () => {
test('Refreshes the access-token in case of Unauthorized(401)', async () => {
const createAudienceInput = {
audienceName: 'Test Audience',
settings: {
oauth: {
clientId: 'valid-client-id',
clientSecret: 'valid-client-secret',
access_token: 'invalid-access-token',
refresh_token: 'refresh-token',
token_type: 'bearer'
}
},
audienceSettings: {
advertiserId: '12334745462532'
}
}
const destinationTest = new Destination(audienceDestination)
const spy = jest.spyOn(authentication, 'refreshAccessToken')
const res = await destinationTest.createAudience(createAudienceInput)
expect(res).toEqual({ externalId: '123456789' })
expect(spy).toHaveBeenCalledTimes(1)
})

test('Will not refresh access-token in case of any non 401 error', async () => {
const createAudienceInput = {
audienceName: 'Test Audience',
settings: {
oauth: {
clientId: 'valid-client-id',
clientSecret: 'valid-client-secret',
access_token: 'access-token',
refresh_token: 'refresh-token',
token_type: 'bearer'
}
},
audienceSettings: {}
}
const destinationTest = new Destination(audienceDestination)
const spy = jest.spyOn(authentication, 'refreshAccessToken')
await expect(destinationTest.createAudience(createAudienceInput)).rejects.toThrowError()
expect(spy).not.toHaveBeenCalled()
})

test('Will not refresh access-token if token is already valid', async () => {
const createAudienceInput = {
audienceName: 'Test Audience',
settings: {
oauth: {
clientId: 'valid-client-id',
clientSecret: 'valid-client-secret',
access_token: 'valid-access-token',
refresh_token: 'refresh-token',
token_type: 'bearer'
}
},
audienceSettings: {
advertiserId: '12334745462532'
}
}

const destinationTest = new Destination(audienceDestination)
const spy = jest.spyOn(authentication, 'refreshAccessToken')
const res = await destinationTest.createAudience(createAudienceInput)
expect(res).toEqual({ externalId: '123456789' })
expect(spy).not.toHaveBeenCalled()
})

test('Will not refresh the access-token for non-Oauth authentication scheme', async () => {
const createAudienceInput = {
audienceName: 'Test Audience',
settings: {
oauth: {
clientId: 'invalid_client_id',
clientSecret: 'valid-client-secret'
}
},
audienceSettings: {
advertiserId: '12334745462532'
}
}
// Non-Oauth authentication scheme
audienceDestination.authentication = {
scheme: 'custom',
fields: {}
}
const destinationTest = new Destination(audienceDestination)
const spy = jest.spyOn(authentication, 'refreshAccessToken')
await expect(destinationTest.createAudience(createAudienceInput)).rejects.toThrowError()
expect(spy).not.toHaveBeenCalled()
})
})

describe('getAudience', () => {
test('Refreshes the access-token in case of Unauthorized(401)', async () => {
const getAudienceInput = {
externalId: '366170701270726115',
settings: {
oauth: {
clientId: 'valid-client-id',
clientSecret: 'valid-client-secret',
access_token: 'invalid-access-token',
refresh_token: 'refresh-token',
token_type: 'bearer'
}
}
}
audienceDestination.authentication = authentication
const destinationTest = new Destination(audienceDestination)
const spy = jest.spyOn(authentication, 'refreshAccessToken')
const res = await destinationTest.getAudience(getAudienceInput)
expect(res).toEqual({ externalId: '366170701270726115' })
expect(spy).toHaveBeenCalledTimes(1)
})

test('Will not refresh access-token in case of any non 401 error', async () => {
const getAudienceInput = {
externalId: WRONG_AUDIENCE_ID,
settings: {
oauth: {
clientId: 'valid-client-id',
clientSecret: 'valid-client-secret',
access_token: 'valid-access-token',
refresh_token: 'refresh-token',
token_type: 'bearer'
}
}
}
const destinationTest = new Destination(audienceDestination)
const spy = jest.spyOn(authentication, 'refreshAccessToken')
await expect(destinationTest.getAudience(getAudienceInput)).rejects.toThrowError()
expect(spy).not.toHaveBeenCalled()
})

test('Will not refresh access-token if token is already valid', async () => {
const getAudienceInput = {
externalId: '366170701270726115',
settings: {
oauth: {
clientId: 'valid-client-id',
clientSecret: 'valid-client-secret',
access_token: 'valid-access-token',
refresh_token: 'refresh-token',
token_type: 'bearer'
}
}
}
const destinationTest = new Destination(audienceDestination)
const spy = jest.spyOn(authentication, 'refreshAccessToken')
const res = await destinationTest.getAudience(getAudienceInput)
expect(res).toEqual({ externalId: '366170701270726115' })
expect(spy).not.toHaveBeenCalled()
})

test('Will not refresh the access-token for non-Oauth authentication scheme', async () => {
const getAudienceInput = {
externalId: '366170701270726115',
settings: {
oauth: {
clientId: 'invalid_client_id',
clientSecret: 'valid-client-secret'
}
}
}

// Non-Oauth authentication scheme
audienceDestination.authentication = {
scheme: 'custom',
fields: {}
}
const destinationTest = new Destination(audienceDestination)
const spy = jest.spyOn(authentication, 'refreshAccessToken')
await expect(destinationTest.getAudience(getAudienceInput)).rejects.toThrowError()
expect(spy).not.toHaveBeenCalled()
})
})
})
})
Loading

0 comments on commit 840ac25

Please sign in to comment.