diff --git a/packages/destination-actions/src/destinations/liveramp-audiences/__tests__/snapshot.test.ts b/packages/destination-actions/src/destinations/liveramp-audiences/__tests__/snapshot.test.ts index 0b5dc4d67ae..30098980585 100644 --- a/packages/destination-actions/src/destinations/liveramp-audiences/__tests__/snapshot.test.ts +++ b/packages/destination-actions/src/destinations/liveramp-audiences/__tests__/snapshot.test.ts @@ -20,6 +20,11 @@ jest.mock('ssh2-sftp-client', () => { return jest.fn(() => sftpClient) }) +jest.mock('../audienceEnteredSftp/sftp', () => ({ + ...jest.requireActual('../audienceEnteredSftp/sftp'), + testAuthenticationSFTP: jest.fn().mockResolvedValue(true) +})) + describe(`Testing snapshot for ${destinationSlug}'s audienceEnteredS3 destination action:`, () => { const actionSlug = 'audienceEnteredS3' const seedName = `${destinationSlug}#${actionSlug}` diff --git a/packages/destination-actions/src/destinations/liveramp-audiences/audienceEnteredSftp/index.ts b/packages/destination-actions/src/destinations/liveramp-audiences/audienceEnteredSftp/index.ts index adddcc7331f..1fa942d1c66 100644 --- a/packages/destination-actions/src/destinations/liveramp-audiences/audienceEnteredSftp/index.ts +++ b/packages/destination-actions/src/destinations/liveramp-audiences/audienceEnteredSftp/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition, PayloadValidationError } from '@segment/actions-core' -import { uploadSFTP, validateSFTP, Client as ClientSFTP } from './sftp' +import { ActionDefinition, InvalidAuthenticationError, PayloadValidationError } from '@segment/actions-core' +import { uploadSFTP, validateSFTP, Client as ClientSFTP, testAuthenticationSFTP } from './sftp' import { generateFile } from '../operations' import { sendEventToAWS } from '../awsClient' -import { LIVERAMP_MIN_RECORD_COUNT, LIVERAMP_LEGACY_FLOW_FLAG_NAME } from '../properties' +import { LIVERAMP_LEGACY_FLOW_FLAG_NAME, LIVERAMP_MIN_RECORD_COUNT } from '../properties' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' @@ -87,28 +87,30 @@ const action: ActionDefinition = { }, perform: async ( request, - { payload, features, rawData, subscriptionMetadata }: ExecuteInputRaw + { payload, features, rawData, subscriptionMetadata, settings }: ExecuteInputRaw ) => { return processData( { request, payloads: [payload], features, - rawData: rawData ? [rawData] : [] + rawData: rawData ? [rawData] : [], + settings }, subscriptionMetadata ) }, performBatch: ( request, - { payload, features, rawData, subscriptionMetadata }: ExecuteInputRaw + { payload, features, rawData, subscriptionMetadata, settings }: ExecuteInputRaw ) => { return processData( { request, payloads: payload, features, - rawData + rawData, + settings }, subscriptionMetadata ) @@ -116,6 +118,26 @@ const action: ActionDefinition = { } async function processData(input: ProcessDataInput, subscriptionMetadata?: SubscriptionMetadata) { + // Check if this request is from the event tester first + if (input.settings && input.settings.__segment_internal_from_event_tester === true) { + try { + // Create a new SFTP client for authentication + const authSftpClient = new ClientSFTP() + await testAuthenticationSFTP(authSftpClient, input.payloads[0]) + + // Return early with a validation-only response + return { + status: 200, + data: { + message: + 'SFTP credentials validated successfully. Event not delivered as this is a validation-only request from the event tester.' + } + } + } catch (error) { + throw new InvalidAuthenticationError(`SFTP authentication failed: ${error.message}`) + } + } + if (input.payloads.length < LIVERAMP_MIN_RECORD_COUNT) { throw new PayloadValidationError( `received payload count below LiveRamp's ingestion limits. expected: >=${LIVERAMP_MIN_RECORD_COUNT} actual: ${input.payloads.length}` @@ -130,8 +152,10 @@ async function processData(input: ProcessDataInput, subscriptionMetadat //------------ // LEGACY FLOW // ----------- - const sftpClient = new ClientSFTP() - return uploadSFTP(sftpClient, input.payloads[0], filename, fileContents) + + // Create a new SFTP client for the actual upload operation + const uploadSftpClient = new ClientSFTP() + return uploadSFTP(uploadSftpClient, input.payloads[0], filename, fileContents) } else { //------------ // AWS FLOW diff --git a/packages/destination-actions/src/destinations/liveramp-audiences/generated-types.ts b/packages/destination-actions/src/destinations/liveramp-audiences/generated-types.ts index b0221b5a79d..d5c82674bfd 100644 --- a/packages/destination-actions/src/destinations/liveramp-audiences/generated-types.ts +++ b/packages/destination-actions/src/destinations/liveramp-audiences/generated-types.ts @@ -3,4 +3,8 @@ export interface Settings { __segment_internal_engage_force_full_sync: boolean __segment_internal_engage_batch_sync: boolean + /** + * Flag to indicate if the request is coming from the event tester + */ + __segment_internal_from_event_tester: boolean } diff --git a/packages/destination-actions/src/destinations/liveramp-audiences/index.ts b/packages/destination-actions/src/destinations/liveramp-audiences/index.ts index f7d5123fccc..dfca8e27ea6 100644 --- a/packages/destination-actions/src/destinations/liveramp-audiences/index.ts +++ b/packages/destination-actions/src/destinations/liveramp-audiences/index.ts @@ -25,6 +25,13 @@ const destination: DestinationDefinition = { type: 'boolean', required: true, default: true + }, + __segment_internal_from_event_tester: { + label: 'From Event Tester', + description: 'Flag to indicate if the request is coming from the event tester', + type: 'boolean', + required: true, + default: false } } }, diff --git a/packages/destination-actions/src/destinations/liveramp-audiences/operations.ts b/packages/destination-actions/src/destinations/liveramp-audiences/operations.ts index 165b167ca71..3576665aa53 100644 --- a/packages/destination-actions/src/destinations/liveramp-audiences/operations.ts +++ b/packages/destination-actions/src/destinations/liveramp-audiences/operations.ts @@ -1,6 +1,7 @@ import { RequestClient, ExecuteInput } from '@segment/actions-core' import type { Payload as s3Payload } from './audienceEnteredS3/generated-types' import type { Payload as sftpPayload } from './audienceEnteredSftp/generated-types' +import type { Settings } from './generated-types' import { processHashing } from '../../lib/hashing-utils' // Type definitions @@ -19,6 +20,7 @@ export type ProcessDataInput = { payloads: T[] features?: Record rawData?: RawData[] + settings?: Settings } export type ExecuteInputRaw = ExecuteInput<