Skip to content

Commit

Permalink
fix: keyword preservation for clientGrants handling (#1032)
Browse files Browse the repository at this point in the history
* refactor: improve keyword preservation logic and enhance client grant handling

* test: add tests for clientGrants keyword preservation and logging behavior
  • Loading branch information
kushalshit27 authored Feb 19, 2025
1 parent 900c4a5 commit ae2849f
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 15 deletions.
50 changes: 35 additions & 15 deletions src/keywordPreservation.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { get as getByDotNotation, set as setByDotNotation } from 'dot-prop';
import { keywordReplace } from './tools/utils';
import { cloneDeep, isArray } from 'lodash';
import { AssetTypes, KeywordMappings } from './types';
import { keywordReplaceArrayRegExp, keywordReplaceStringRegExp } from './tools/utils';
import { cloneDeep, forEach, isArray } from 'lodash';
import {
keywordReplace,
keywordReplaceArrayRegExp,
keywordReplaceStringRegExp,
} from './tools/utils';
import APIHandler from './tools/auth0/handlers/default';
import { convertClientIdToName } from './utils';
import log from './logger';

/*
RFC for Keyword Preservation: https://github.com/auth0/auth0-deploy-cli/issues/688
Original Github Issue: https://github.com/auth0/auth0-deploy-cli/issues/328
*/

export const doesHaveKeywordMarker = (
string: string,
keywordMappings: KeywordMappings
): boolean => {
return !Object.keys(keywordMappings).every((keyword) => {
export const doesHaveKeywordMarker = (string: string, keywordMappings: KeywordMappings): boolean =>
!Object.keys(keywordMappings).every((keyword) => {
const hasArrayMarker = keywordReplaceArrayRegExp(keyword).test(string);
const hasStringMarker = keywordReplaceStringRegExp(keyword).test(string);

return !hasArrayMarker && !hasStringMarker;
});
};

export const getPreservableFieldsFromAssets = (
asset: object,
Expand Down Expand Up @@ -110,8 +111,8 @@ export const getPreservableFieldsFromAssets = (
// Object: `{ actions: [ { name: "action-1", code: "..."}] }`
// Address: `.actions[name=action-1].code`
export const getAssetsValueByAddress = (address: string, assets: any): any => {
//Look ahead and see if the address path only contains dots (ex: `tenant.friendly_name`)
//if so the address is trivial and can use the dot-prop package to return the value
// Look ahead and see if the address path only contains dots (ex: `tenant.friendly_name`)
// if so the address is trivial and can use the dot-prop package to return the value

const isTrivialAddress = address.indexOf('[') === -1;
if (isTrivialAddress) {
Expand Down Expand Up @@ -156,7 +157,7 @@ export const convertAddressToDotNotation = (
address: string,
finalAddressTrail = ''
): string | null => {
if (assets === null) return null; //Asset does not exist on remote
if (assets === null) return null; // Asset does not exist on remote

const directions = address.split(/\.(?![^\[]*\])/g);

Expand Down Expand Up @@ -248,6 +249,24 @@ export const preserveKeywords = ({
''
);

// Convert client_id to client name in clientGrants if clients are available for keyword preservation in clientGrants
if (remoteAssets && (remoteAssets as any).clientGrants) {
if ((remoteAssets as any).clients) {
for (let i = 0; i < (remoteAssets as any).clientGrants.length; i++) {
const clientGrant = (remoteAssets as any).clientGrants[i];
clientGrant.client_id = convertClientIdToName(
clientGrant.client_id,
(remoteAssets as any).clients
);
(remoteAssets as any).clientGrants[i] = clientGrant;
}
} else {
log.debug(
"Keyword preservation for 'clientGrants' has dependency on the 'clients' resource, make sure to include both in the export."
);
}
}

let updatedRemoteAssets = cloneDeep(remoteAssets);

addresses.forEach((address) => {
Expand All @@ -268,7 +287,8 @@ export const preserveKeywords = ({
if (typeof remoteValue === 'string') {
return localValueWithReplacement === remoteValue;
}
//TODO: Account for non-string replacements via @@ syntax
// TODO: Account for non-string replacements via @@ syntax
return false; // Default to false
})();

if (!localAndRemoteValuesAreEqual) {
Expand All @@ -285,12 +305,12 @@ export const preserveKeywords = ({
// Example: `customDomains.[domain=##DOMAIN].domain` and `customDomains.[domain=travel0.com].domain`
updatedRemoteAssets = updateAssetsByAddress(
updatedRemoteAssets,
address, //Two possible addresses need to be passed, one with identifier field keyword replaced and one where it is not replaced. Ex: `customDomains.[domain=##DOMAIN].domain` and `customDomains.[domain=travel0.com].domain`
address, // Two possible addresses need to be passed, one with identifier field keyword replaced and one where it is not replaced. Ex: `customDomains.[domain=##DOMAIN].domain` and `customDomains.[domain=travel0.com].domain`
localValue
);
updatedRemoteAssets = updateAssetsByAddress(
updatedRemoteAssets,
remoteAssetsAddress, //Two possible addresses need to be passed, one with identifier field keyword replaced and one where it is not replaced. Ex: `customDomains.[domain=##DOMAIN].domain` and `customDomains.[domain=travel0.com].domain`
remoteAssetsAddress, // Two possible addresses need to be passed, one with identifier field keyword replaced and one where it is not replaced. Ex: `customDomains.[domain=##DOMAIN].domain` and `customDomains.[domain=travel0.com].domain`
localValue
);
});
Expand Down
111 changes: 111 additions & 0 deletions test/keywordPreservation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
preserveKeywords,
} from '../src/keywordPreservation';
import { cloneDeep } from 'lodash';
import log from '../src/logger';

describe('#Keyword Preservation', () => {
describe('doesHaveKeywordMarker', () => {
Expand Down Expand Up @@ -964,4 +965,114 @@ describe('preserveKeywords', () => {
],
});
});

it('should handle clientGrants with client_id converted to client name', () => {
const mockLocalAssets = {
clientGrants: [
{
client_id: 'API Explorer Application',
audience: 'https://##ENV##.travel0.com/api/v1',
scope: ['update:account'],
name: 'API Explorer Application',
},
],
clients: [
{
client_id: 'API Explorer Application',
name: 'API Explorer Application',
},
],
};

const mockRemoteAssets = {
clientGrants: [
{
client_id: 'API Explorer Application',
audience: 'https://dev.travel0.com/api/v1',
scope: ['update:account'],
name: 'API Explorer Application',
},
],
clients: [
{
client_id: 'API Explorer Application',
name: 'API Explorer Application',
},
],
};

const preservedAssets = preserveKeywords({
localAssets: mockLocalAssets,
remoteAssets: mockRemoteAssets,
keywordMappings: {
ENV: 'dev',
},
auth0Handlers: [
{
id: 'id',
identifiers: ['id', 'name'],
type: 'clientGrants',
},
{
id: 'id',
identifiers: ['id', 'name'],
type: 'clients',
},
],
});

expect(preservedAssets).to.deep.equal(mockLocalAssets);
});

it('should log a debug message if clients are not included for clientGrants keyword preservation', () => {
const mockLocalAssets = {
clientGrants: [
{
client_id: 'API Explorer Application',
audience: 'https://##ENV##.travel0.com/api/v1',
scope: ['update:account'],
name: 'API Explorer Application',
},
],
};

const mockRemoteAssets = {
clientGrants: [
{
client_id: 'API Explorer Application',
audience: 'https://dev.travel0.com/api/v1',
scope: ['update:account'],
name: 'API Explorer Application',
},
],
};

const originalLogDebug = log.debug;
let logMessage = '';

log.debug = (message) => {
logMessage = message;
};

preserveKeywords({
localAssets: mockLocalAssets,
remoteAssets: mockRemoteAssets,
keywordMappings: {
ENV: 'dev',
},
auth0Handlers: [
{
id: 'id',
identifiers: ['id', 'name'],
type: 'clientGrants',
},
],
});

expect(logMessage).to.equal(
"Keyword preservation for 'clientGrants' has dependency on the 'clients' resource, make sure to include both in the export."
);

log.debug = originalLogDebug;
});
});

0 comments on commit ae2849f

Please sign in to comment.