Skip to content

Commit

Permalink
chore: update unit tests for redactions and interactive flows
Browse files Browse the repository at this point in the history
  • Loading branch information
Sachin Panemangalore committed Apr 30, 2022
1 parent 499389d commit 66fd9c0
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 286 deletions.
2 changes: 1 addition & 1 deletion packages/amplify-category-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export async function executeAmplifyCommand(context: $TSContext) {
}

export const executeAmplifyHeadlessCommand = async (context: $TSContext, headlessPayload: string) => {
context.flowData?.pushHeadlessFlow(headlessPayload);
context.flowData.pushHeadlessFlow(headlessPayload, context.input);
switch (context.input.command) {
case 'add':
await getCfnApiArtifactHandler(context).createArtifacts(await validateAddApiRequest(headlessPayload));
Expand Down
6 changes: 3 additions & 3 deletions packages/amplify-category-auth/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ async function initEnv(context) {
await sequential(authTasks);
}

async function console(context) {
async function authConsole(context) {
const { amplify } = context;
const amplifyMeta = amplify.getProjectMeta();

Expand Down Expand Up @@ -425,7 +425,7 @@ async function executeAmplifyCommand(context) {
* @param {string} headlessPayload The serialized payload from the platform
*/
const executeAmplifyHeadlessCommand = async (context, headlessPayload) => {
context.flowData.pushHeadlessFlow(headlessPayload);
context.flowData.pushHeadlessFlow(headlessPayload, context.input);
switch (context.input.command) {
case 'add':
if (projectHasAuth(context)) {
Expand Down Expand Up @@ -516,7 +516,7 @@ module.exports = {
add,
migrate,
initEnv,
console,
console : authConsole,
getPermissionPolicies,
executeAmplifyCommand,
executeAmplifyHeadlessCommand,
Expand Down
4 changes: 2 additions & 2 deletions packages/amplify-category-geo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export const getPermissionPolicies = (context: $TSContext, resourceOpsMapping: $
* @param {any} context The amplify context object
* @param {string} headlessPayload The serialized payload from the platform
*/
export const executeAmplifyHeadlessCommand = async (context: $TSContext, headlessPayload: string) => {
context.flowData?.pushHeadlessFlow(headlessPayload);
export const executeAmplifyHeadlessCommand = async (context: $TSContext, headlessPayload: string) => {
context.flowData.pushHeadlessFlow(headlessPayload, context.input);
switch (context.input.command) {
case 'add':
await addResourceHeadless(context, headlessPayload);
Expand Down
2 changes: 1 addition & 1 deletion packages/amplify-category-storage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export async function executeAmplifyCommand(context: any) {
}

export const executeAmplifyHeadlessCommand = async (context: $TSContext, headlessPayload: string) => {
context.flowData?.pushHeadlessFlow(headlessPayload);
context.flowData.pushHeadlessFlow(headlessPayload, context.input);
switch (context.input.command) {
case 'add':
await headlessAddStorage(context, await validateAddStorageRequest(headlessPayload));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ICommandInput } from './amplify-cli-interactions';

export interface IOptionFlowHeadlessData {
input: string,
timestamp: number
export interface IOptionFlowHeadlessData {
input: string,
timestamp: number
}
export interface IOptionFlowCLIData {
prompt: string,
input: unknown,
timestamp: number
prompt: string,
input: unknown,
timestamp: number
}

export type TypeOptionFlowData = IOptionFlowHeadlessData | IOptionFlowCLIData
Expand All @@ -17,27 +17,27 @@ export type TypeOptionFlowData = IOptionFlowHeadlessData | IOptionFlowCLIData
* Flow Report data logged by the CLI walk-through.
*/
export interface IFlowReport {
version : string,
runtime : string,
executable : string,
category : string,
isHeadless : boolean,
cmd : string,
subCmd: string| undefined,
optionFlowData : Array<TypeOptionFlowData>, //IOptionFlowHeadlessData | IOptionFlowCLIData
input : ICommandInput,
timestamp : string,
projectEnvIdentifier? : string, // hash(ProjectName + Amplify AppId + EnvName)
version: string,
runtime: string,
executable: string,
category: string,
isHeadless: boolean,
cmd: string,
subCmd: string | undefined,
optionFlowData: Array<TypeOptionFlowData>, //IOptionFlowHeadlessData | IOptionFlowCLIData
input: ICommandInput,
timestamp: string,
projectEnvIdentifier?: string, // hash(ProjectName + Amplify AppId + EnvName)
projectIdentifier?: string, // hash( ProjectName + Amplify App Id)
}

/**
* CLI walk-through and headless flow data
*/
export interface IFlowData {
setIsHeadless : (headless: boolean)=> void,
pushHeadlessFlow: (headlessFlowDataString: string) => void,
setIsHeadless: (headless: boolean) => void,
pushHeadlessFlow: (headlessFlowDataString: string, input: ICommandInput) => void,
pushInteractiveFlow: (prompt: string, input: unknown) => void,
getFlowReport: ()=>IFlowReport | Record<string, never>
assignProjectIdentifier: (envName?:string)=>string|undefined
getFlowReport: () => IFlowReport | Record<string, never>
assignProjectIdentifier: (envName?: string) => string | undefined
}
179 changes: 96 additions & 83 deletions packages/amplify-cli/src/__tests__/flow-report.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { CLIFlowReport } from '../domain/amplify-usageData/FlowReport';
import { stateManager } from 'amplify-cli-core';
import { AddS3ServiceConfiguration, AddStorageRequest,
ImportAuthRequest, CrudOperation, AddGeoRequest,
GeoServiceConfiguration, AccessType, MapStyle, AppSyncServiceConfiguration, AppSyncAPIKeyAuthType, AddApiRequest} from 'amplify-headless-interface';
import {
AddS3ServiceConfiguration, AddStorageRequest,
ImportAuthRequest, CrudOperation, AddGeoRequest,
GeoServiceConfiguration, AccessType, MapStyle, AppSyncServiceConfiguration, AppSyncAPIKeyAuthType, AddApiRequest
} from 'amplify-headless-interface';
import { v4 as uuid } from 'uuid';
import { Redactor } from 'amplify-cli-logger';



Expand All @@ -16,106 +19,113 @@ describe('Test FlowReport Logging', () => {
// CLIFlowReport.reset();
jest.clearAllMocks();
});

it('flow-log-interactive-payload: Interactive payload is available in payload', () => {
const flowReport = CLIFlowReport.instance;
flowReport.pushInteractiveFlow(mockInputs.interactivePrompt, mockInputs.interactiveValue);
expect(flowReport.optionFlowData[0].input).toContain(mockInputs.interactiveValue);
})
it('Project Identifiers are correctly formed', () => {
const flowReport = CLIFlowReport.instance;
flowReport.setIsHeadless(true);
//Mock the state-manager functions
jest.mock('amplify-cli-core');
jest.spyOn(stateManager, 'getProjectName').mockReturnValue(mockInputs.projectName);
jest.spyOn(stateManager, 'getCurrentEnvName').mockReturnValueOnce(mockInputs.envName);
jest.spyOn(stateManager, 'getAppID').mockReturnValue(mockInputs.appID)
flowReport.assignProjectIdentifier();
expect( flowReport.projectEnvIdentifier ).toEqual(`${mockInputs.projectName}${mockInputs.appID}${mockInputs.envName}`);
expect( flowReport.projectIdentifier ).toEqual(`${mockInputs.projectName}${mockInputs.appID}`);
const flowReport = CLIFlowReport.instance;
flowReport.setIsHeadless(true);
flowReport.setVersion(mockInputs.version);
//Mock the state-manager functions
jest.mock('amplify-cli-core');
jest.spyOn(stateManager, 'getProjectName').mockReturnValue(mockInputs.projectName);
jest.spyOn(stateManager, 'getCurrentEnvName').mockReturnValueOnce(mockInputs.envName);
jest.spyOn(stateManager, 'getAppID').mockReturnValue(mockInputs.appID)
flowReport.assignProjectIdentifier();
expect(flowReport.projectEnvIdentifier).toEqual(`${mockInputs.projectName}${mockInputs.appID}${mockInputs.envName}`);
expect(flowReport.projectIdentifier).toEqual(`${mockInputs.projectName}${mockInputs.appID}`);
expect(flowReport.version).toEqual(mockInputs.version);
});

it('flow-log-headless-payload: (Auth) is redacted in flowReport', () => {
const input = getAuthHeadlessTestInput();
const inputString = JSON.stringify(input);
const flowReport = CLIFlowReport.instance;
flowReport.setIsHeadless(true);
flowReport.setInput(mockInputs.headlessInput('auth', 'add'));
flowReport.pushHeadlessFlow(inputString);
expect(flowReport.optionFlowData[0].input).not.toContain(input.identityPoolId);
expect(flowReport.optionFlowData[0].input).not.toContain(input.webClientId);
expect(flowReport.optionFlowData[0].input).not.toContain(input.nativeClientId);
const redactedInputString = flowReport.optionFlowData[0].input;
const report = flowReport.getFlowReport();
expect(report.isHeadless).toEqual(true);
expect(report.input.plugin).toEqual('auth');
expect(report.input.command).toEqual('add');
expect(report.optionFlowData[0].input).toEqual(redactedInputString);
const input = getAuthHeadlessTestInput();
const inputString = JSON.stringify(input);
const flowReport = CLIFlowReport.instance;
flowReport.pushHeadlessFlow(inputString, mockInputs.headlessInput('auth', 'add'));
expect(flowReport.optionFlowData[0].input).not.toContain(input.identityPoolId);
expect(flowReport.optionFlowData[0].input).not.toContain(input.webClientId);
expect(flowReport.optionFlowData[0].input).not.toContain(input.nativeClientId);
const report = flowReport.getFlowReport();
expect(report.isHeadless).toEqual(true);
expect(report.input.plugin).toEqual('auth');
expect(report.input.command).toEqual('add');
expect(report.optionFlowData[0].input).toEqual(Redactor(inputString));
});

it('flow-log-headless-payload: (Storage S3) is redacted in flowReport', () => {
const input = getAddStorageS3HeadlessTestInput();
const inputString = JSON.stringify(input);
const flowReport = CLIFlowReport.instance;
flowReport.setIsHeadless(true);
flowReport.assignProjectIdentifier();
flowReport.setInput(mockInputs.headlessInput('storage', 'add'));
flowReport.pushHeadlessFlow(inputString);
flowReport.pushHeadlessFlow(inputString, mockInputs.headlessInput('storage', 'add'));
expect(flowReport.optionFlowData[0].input).not.toContain(input.serviceConfiguration.bucketName);
expect(flowReport.optionFlowData[0].input).not.toContain(input.serviceConfiguration.resourceName);
expect(flowReport.optionFlowData[0].input).not.toContain(input.serviceConfiguration.lambdaTrigger?.name as string);
const redactedInputString = flowReport.optionFlowData[0].input;
expect(flowReport.optionFlowData[0].input).toContain(redactValue('bucketName', input.serviceConfiguration.bucketName));
expect(flowReport.optionFlowData[0].input).toContain(redactValue('resourceName', input.serviceConfiguration.resourceName));
expect(flowReport.optionFlowData[0].input).toContain(redactValue('name', input.serviceConfiguration.lambdaTrigger?.name as string));

const report = flowReport.getFlowReport();
expect(report.isHeadless).toEqual(true);
expect(report.input.plugin).toEqual('storage');
expect(report.input.command).toEqual('add');
expect(report.optionFlowData[0].input).toEqual(redactedInputString);

expect(report.optionFlowData[0].input).toEqual(Redactor(inputString));
});

it('flow-log-headless-payload: (API GraphQL) headless-payload is redacted in flowReport', () => {
const input = getGraphQLHeadlessTestInput();

const inputString = JSON.stringify(input);
const flowReport = CLIFlowReport.instance;
flowReport.setIsHeadless(true);
flowReport.assignProjectIdentifier();
flowReport.setInput(mockInputs.headlessInput('api', 'add'));
flowReport.pushHeadlessFlow(inputString);
flowReport.pushHeadlessFlow(inputString, mockInputs.headlessInput('api', 'add'));
expect(flowReport.optionFlowData[0].input).not.toContain(input.serviceConfiguration.apiName); //resource name redacted
const redactedInput = JSON.parse( flowReport.optionFlowData[0].input as unknown as string );
expect(flowReport.optionFlowData[0].input).toContain(redactValue("apiName", input.serviceConfiguration.apiName));
const redactedInput = JSON.parse(flowReport.optionFlowData[0].input as unknown as string);
expect(redactedInput.serviceConfiguration.transformSchema).toEqual(input.serviceConfiguration.transformSchema);//transform schema must exist
});

it('flow-log-headless-payload: (Geo) is redacted in flowReport', () => {
const input = getGeoHeadlessTestInput();
const inputString = JSON.stringify(input);
const redactedName = redactValue('name', input.serviceConfiguration.name);
const flowReport = CLIFlowReport.instance;
flowReport.setIsHeadless(true);
flowReport.assignProjectIdentifier();
flowReport.setInput(mockInputs.headlessInput('geo', 'add'));
flowReport.pushHeadlessFlow(inputString);
flowReport.pushHeadlessFlow(inputString, mockInputs.headlessInput('geo', 'add'));
expect(flowReport.optionFlowData[0].input).not.toContain(input.serviceConfiguration.name); //resource name redacted
expect(flowReport.optionFlowData[0].input).toContain(redactedName); //resource name redacted
expect(flowReport.optionFlowData[0].input).toContain(input.serviceConfiguration.accessType);
expect(flowReport.optionFlowData[0].input).toContain(input.serviceConfiguration.mapStyle);
expect(flowReport.optionFlowData[0].input).toEqual(Redactor(inputString));
});

});
});

const redactValue = (key: string, value: unknown) => {
const retVal = JSON.parse(Redactor(JSON.stringify({ [key]: value })));
return retVal[key];
}

/*** Helper functions and test data ***/
const mockAddStorageInput : AddS3ServiceConfiguration = {
const mockAddStorageInput: AddS3ServiceConfiguration = {
serviceName: 'S3',
permissions: {
auth: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.READ, CrudOperation.DELETE],
guest: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.READ],
groups: {
Admin : [CrudOperation.CREATE_AND_UPDATE, CrudOperation.READ, CrudOperation.DELETE],
Guest : [CrudOperation.CREATE_AND_UPDATE, CrudOperation.READ],
Reader : [CrudOperation.READ]
Admin: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.READ, CrudOperation.DELETE],
Guest: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.READ],
Reader: [CrudOperation.READ]
}
},
resourceName: 'testMockS3ResourceName',
bucketName: 'testMockS3BucketName',
lambdaTrigger: {
mode: 'new',
name: 'existingFunctionName'
},
mode: 'new',
name: 'existingFunctionName'
},
}

const getShortId = (prefix: string) => {
Expand All @@ -124,20 +134,20 @@ const getShortId = (prefix: string) => {
return mapId;
}

const mockAddGeoInput : GeoServiceConfiguration = {
const mockAddGeoInput: GeoServiceConfiguration = {
serviceName: "Map",
name: getShortId('map'),
accessType: AccessType.AuthorizedUsers,
mapStyle: MapStyle.VectorEsriDarkGrayCanvas,
setAsDefault: true
}

const appSyncAPIKeyAuthType : AppSyncAPIKeyAuthType = {
mode: 'API_KEY',
expirationTime: Math.floor((Date.now()/1000) + 86400), //one day
}
const appSyncAPIKeyAuthType: AppSyncAPIKeyAuthType = {
mode: 'API_KEY',
expirationTime: Math.floor((Date.now() / 1000) + 86400), //one day
}

const mockAddAPIInput : AppSyncServiceConfiguration = {
const mockAddAPIInput: AppSyncServiceConfiguration = {
serviceName: 'AppSync',
apiName: "mockGQLAPIName",
transformSchema: 'type User @model(subscriptions: null)\
Expand All @@ -155,38 +165,41 @@ const mockAddAPIInput : AppSyncServiceConfiguration = {

const mockInputs = {
projectName: 'mockProjectName',
envName : 'dev',
appID : 'mockAppID',
envName: 'dev',
appID: 'mockAppID',
Auth: {
USER_POOL_ID : 'user-pool-123',
IDENTITY_POOL_ID : 'identity-pool-123',
NATIVE_CLIENT_ID : 'native-app-client-123',
WEB_CLIENT_ID : 'web-app-client-123'
USER_POOL_ID: 'user-pool-123',
IDENTITY_POOL_ID: 'identity-pool-123',
NATIVE_CLIENT_ID: 'native-app-client-123',
WEB_CLIENT_ID: 'web-app-client-123'
},
StorageS3 : mockAddStorageInput,
Geo : mockAddGeoInput,
GraphQLAPI : mockAddAPIInput,
headlessInput : (feature, command) => ({
argv : [],
plugin : feature,
command : command
})
StorageS3: mockAddStorageInput,
Geo: mockAddGeoInput,
GraphQLAPI: mockAddAPIInput,
headlessInput: (feature, command) => ({
argv: [],
plugin: feature,
command: command
}),
interactivePrompt: "Enter resource name",
interactiveValue: "mockResourceID",
version: "1.0"
}
const getAuthHeadlessTestInput = () => {
const headlessPayload: ImportAuthRequest = {
version: 1,
userPoolId: mockInputs.Auth.USER_POOL_ID,
identityPoolId: mockInputs.Auth.IDENTITY_POOL_ID,
nativeClientId: mockInputs.Auth.NATIVE_CLIENT_ID,
webClientId: mockInputs.Auth.WEB_CLIENT_ID,
};
return headlessPayload;
const headlessPayload: ImportAuthRequest = {
version: 1,
userPoolId: mockInputs.Auth.USER_POOL_ID,
identityPoolId: mockInputs.Auth.IDENTITY_POOL_ID,
nativeClientId: mockInputs.Auth.NATIVE_CLIENT_ID,
webClientId: mockInputs.Auth.WEB_CLIENT_ID,
};
return headlessPayload;
}

const getAddStorageS3HeadlessTestInput = () => {
const headlessPayload: AddStorageRequest = {
version: 1,
serviceConfiguration : mockInputs.StorageS3
serviceConfiguration: mockInputs.StorageS3
};
return headlessPayload;
}
Expand All @@ -209,6 +222,6 @@ const getGraphQLHeadlessTestInput = () => {
serviceConfiguration: mockInputs.GraphQLAPI
}
return headlessPayload;

}

Loading

0 comments on commit 66fd9c0

Please sign in to comment.