Skip to content

Commit 15da539

Browse files
saikumarrsAI Assistant
andcommitted
fix: integration name validation (#2311)
Co-authored-by: AI Assistant <ai@rudderstack.com>
1 parent f27c65a commit 15da539

File tree

3 files changed

+106
-50
lines changed

3 files changed

+106
-50
lines changed

packages/analytics-js-plugins/__tests__/deviceModeDestinations/index.test.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jest.mock('../../src/shared-chunks/deviceModeDestinations', () => ({
2222
'Google Analytics 4 (GA4)': 'GA4',
2323
'Google Analytics': 'GoogleAnalytics',
2424
Amplitude: 'Amplitude',
25+
VWO: 'VWO',
2526
},
2627
filterDestinations: jest.fn((integrations, destinations) => destinations),
2728
isHybridModeDestination: jest.fn(() => false),
@@ -1173,8 +1174,6 @@ describe('DeviceModeDestinations Plugin', () => {
11731174

11741175
beforeEach(() => {
11751176
mockState.nativeDestinations.activeDestinations.value = [];
1176-
mockState.nativeDestinations.configuredDestinations.value = [];
1177-
mockState.nativeDestinations.initializedDestinations.value = [];
11781177
});
11791178

11801179
it('should add custom integration to active destinations when validation passes', () => {
@@ -1223,11 +1222,8 @@ describe('DeviceModeDestinations Plugin', () => {
12231222
);
12241223
});
12251224

1226-
it('should not add custom integration when name conflicts with configured destination', () => {
1227-
const conflictingName = 'GA4';
1228-
mockState.nativeDestinations.configuredDestinations.value = [
1229-
{ displayName: conflictingName } as Destination,
1230-
];
1225+
it('should not add custom integration when name conflicts with supported destination', () => {
1226+
const conflictingName = 'VWO';
12311227

12321228
plugin.nativeDestinations.addCustomIntegration(
12331229
conflictingName,
@@ -1242,9 +1238,9 @@ describe('DeviceModeDestinations Plugin', () => {
12421238
);
12431239
});
12441240

1245-
it('should not add custom integration when name conflicts with initialized destination', () => {
1246-
const conflictingName = 'GoogleAnalytics';
1247-
mockState.nativeDestinations.initializedDestinations.value = [
1241+
it('should not add custom integration when name conflicts with already added custom destination', () => {
1242+
const conflictingName = 'Custom Integration 1';
1243+
mockState.nativeDestinations.activeDestinations.value = [
12481244
{ displayName: conflictingName } as Destination,
12491245
];
12501246

@@ -1255,7 +1251,9 @@ describe('DeviceModeDestinations Plugin', () => {
12551251
mockLogger,
12561252
);
12571253

1258-
expect(mockState.nativeDestinations.activeDestinations.value).toHaveLength(0);
1254+
expect(mockState.nativeDestinations.activeDestinations.value).toEqual([
1255+
{ displayName: conflictingName } as Destination,
1256+
]);
12591257
expect(mockLogger.error).toHaveBeenCalledWith(
12601258
`DeviceModeDestinationsPlugin:: An integration with name "${conflictingName}" already exists.`,
12611259
);

packages/analytics-js-plugins/__tests__/deviceModeDestinations/utils.test.ts

Lines changed: 90 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,7 @@ describe('deviceModeDestinations utils', () => {
11421142
expect(result).toEqual([]);
11431143
});
11441144
});
1145+
11451146
describe('getCumulativeIntegrationsConfig', () => {
11461147
it('should return the cumulative integrations config', () => {
11471148
const destination = {
@@ -1549,18 +1550,8 @@ describe('deviceModeDestinations utils', () => {
15491550
});
15501551

15511552
describe('name conflict validation', () => {
1552-
it('should return false when name conflicts with configured destination', () => {
1553-
const conflictingName = 'ExistingDestination';
1554-
1555-
// Mock existing configured destination
1556-
state.nativeDestinations.configuredDestinations.value = [
1557-
{
1558-
displayName: 'ExistingDestination',
1559-
id: 'existing_123',
1560-
userFriendlyId: 'existing_123',
1561-
enabled: true,
1562-
} as any,
1563-
];
1553+
it('should return false when name conflicts with supported destinations', () => {
1554+
const conflictingName = 'Google Analytics 4 (GA4)';
15641555

15651556
const result = validateCustomIntegration(
15661557
conflictingName,
@@ -1571,19 +1562,19 @@ describe('deviceModeDestinations utils', () => {
15711562

15721563
expect(result).toBe(false);
15731564
expect(defaultLogger.error).toHaveBeenCalledWith(
1574-
'DeviceModeDestinationsPlugin:: An integration with name "ExistingDestination" already exists.',
1565+
'DeviceModeDestinationsPlugin:: An integration with name "Google Analytics 4 (GA4)" already exists.',
15751566
);
15761567
});
15771568

1578-
it('should return false when name conflicts with initialized destination', () => {
1579-
const conflictingName = 'InitializedDestination';
1569+
it('should return false when name conflicts with active destination', () => {
1570+
const conflictingName = 'Custom Integration 1';
15801571

1581-
// Mock existing initialized destination
1582-
state.nativeDestinations.initializedDestinations.value = [
1572+
// Mock existing active destination (not initialized)
1573+
state.nativeDestinations.activeDestinations.value = [
15831574
{
1584-
displayName: 'InitializedDestination',
1585-
id: 'initialized_123',
1586-
userFriendlyId: 'initialized_123',
1575+
displayName: 'Custom Integration 1',
1576+
id: 'custom_integration_1',
1577+
userFriendlyId: 'custom_integration_1',
15871578
enabled: true,
15881579
} as any,
15891580
];
@@ -1597,22 +1588,18 @@ describe('deviceModeDestinations utils', () => {
15971588

15981589
expect(result).toBe(false);
15991590
expect(defaultLogger.error).toHaveBeenCalledWith(
1600-
'DeviceModeDestinationsPlugin:: An integration with name "InitializedDestination" already exists.',
1591+
'DeviceModeDestinationsPlugin:: An integration with name "Custom Integration 1" already exists.',
16011592
);
16021593
});
16031594

1604-
it('should return true when name does not conflict with existing destinations', () => {
1595+
it('should return true when name does not conflict with any destinations', () => {
16051596
const uniqueName = 'UniqueCustomIntegration';
16061597

1607-
// Mock existing destinations with different names
1608-
state.nativeDestinations.configuredDestinations.value = [
1609-
{ displayName: 'GA4', id: 'ga4_123', userFriendlyId: 'ga4_123', enabled: true } as any,
1610-
];
1611-
state.nativeDestinations.initializedDestinations.value = [
1598+
state.nativeDestinations.activeDestinations.value = [
16121599
{
1613-
displayName: 'Amplitude',
1614-
id: 'amp_123',
1615-
userFriendlyId: 'amp_123',
1600+
displayName: 'Custom Integration 1',
1601+
id: 'custom_integration_1',
1602+
userFriendlyId: 'custom_integration_1',
16161603
enabled: true,
16171604
} as any,
16181605
];
@@ -1632,8 +1619,7 @@ describe('deviceModeDestinations utils', () => {
16321619
const validName = 'CustomIntegration';
16331620

16341621
// Ensure arrays are empty
1635-
state.nativeDestinations.configuredDestinations.value = [];
1636-
state.nativeDestinations.initializedDestinations.value = [];
1622+
state.nativeDestinations.activeDestinations.value = [];
16371623

16381624
const result = validateCustomIntegration(
16391625
validName,
@@ -1650,8 +1636,7 @@ describe('deviceModeDestinations utils', () => {
16501636
const validName = 'CustomIntegration';
16511637

16521638
// Set arrays to null
1653-
state.nativeDestinations.configuredDestinations.value = null as any;
1654-
state.nativeDestinations.initializedDestinations.value = null as any;
1639+
state.nativeDestinations.activeDestinations.value = null as any;
16551640

16561641
const result = validateCustomIntegration(
16571642
validName,
@@ -1663,6 +1648,77 @@ describe('deviceModeDestinations utils', () => {
16631648
expect(result).toBe(true);
16641649
expect(defaultLogger.error).not.toHaveBeenCalled();
16651650
});
1651+
1652+
it('should handle case sensitivity in name conflicts', () => {
1653+
const conflictingName = 'custom integration 1';
1654+
1655+
// Mock existing destination with different case
1656+
state.nativeDestinations.activeDestinations.value = [
1657+
{
1658+
displayName: 'Custom Integration 1',
1659+
id: 'custom_integration_1',
1660+
userFriendlyId: 'custom_integration_1',
1661+
enabled: true,
1662+
} as any,
1663+
];
1664+
1665+
const result = validateCustomIntegration(
1666+
conflictingName,
1667+
mockValidIntegration,
1668+
state,
1669+
defaultLogger,
1670+
);
1671+
1672+
// Should pass since JavaScript string comparison is case-sensitive
1673+
expect(result).toBe(true);
1674+
expect(defaultLogger.error).not.toHaveBeenCalled();
1675+
});
1676+
1677+
it('should return false when name conflicts with additional supported destinations', () => {
1678+
// Test with more built-in destination names to ensure comprehensive coverage
1679+
const additionalTestCases = [
1680+
'HubSpot',
1681+
'Hotjar',
1682+
'Facebook Pixel',
1683+
'Intercom',
1684+
'Mixpanel',
1685+
'PostHog',
1686+
];
1687+
1688+
additionalTestCases.forEach(destinationName => {
1689+
jest.clearAllMocks();
1690+
1691+
const result = validateCustomIntegration(
1692+
destinationName,
1693+
mockValidIntegration,
1694+
state,
1695+
defaultLogger,
1696+
);
1697+
1698+
expect(result).toBe(false);
1699+
expect(defaultLogger.error).toHaveBeenCalledWith(
1700+
`DeviceModeDestinationsPlugin:: An integration with name "${destinationName}" already exists.`,
1701+
);
1702+
});
1703+
});
1704+
1705+
it('should be case-sensitive when checking built-in destination names', () => {
1706+
const modifiedName = 'google analytics 4 (ga4)'; // lowercase version
1707+
1708+
// Ensure no existing destinations
1709+
state.nativeDestinations.activeDestinations.value = [];
1710+
1711+
const result = validateCustomIntegration(
1712+
modifiedName,
1713+
mockValidIntegration,
1714+
state,
1715+
defaultLogger,
1716+
);
1717+
1718+
// Should pass since the exact case doesn't match the built-in destination
1719+
expect(result).toBe(true);
1720+
expect(defaultLogger.error).not.toHaveBeenCalled();
1721+
});
16661722
});
16671723

16681724
describe('integration validation', () => {

packages/analytics-js-plugins/src/deviceModeDestinations/utils.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ import {
4545
CUSTOM_INTEGRATION_ALREADY_EXISTS_ERROR,
4646
INVALID_CUSTOM_INTEGRATION_ERROR,
4747
} from './logMessages';
48-
import { isHybridModeDestination } from '../shared-chunks/deviceModeDestinations';
48+
import {
49+
destDisplayNamesToFileNamesMap,
50+
isHybridModeDestination,
51+
} from '../shared-chunks/deviceModeDestinations';
4952
import { getSanitizedValue, isFunction } from '../shared-chunks/common';
5053

5154
/**
@@ -355,12 +358,11 @@ const validateCustomIntegration = (
355358
}
356359

357360
// Check against existing configured destinations
358-
const configuredDestinations = state.nativeDestinations.configuredDestinations.value || [];
359-
const initializedDestinations = state.nativeDestinations.initializedDestinations.value || [];
361+
const activeDestinations = state.nativeDestinations.activeDestinations.value || [];
360362

361363
if (
362-
configuredDestinations.some(dest => dest.displayName === name) ||
363-
initializedDestinations.some(dest => dest.displayName === name)
364+
isDefined(destDisplayNamesToFileNamesMap[name]) ||
365+
activeDestinations.some(dest => dest.displayName === name)
364366
) {
365367
logger.error(CUSTOM_INTEGRATION_ALREADY_EXISTS_ERROR(DEVICE_MODE_DESTINATIONS_PLUGIN, name));
366368
return false;

0 commit comments

Comments
 (0)