Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
58e77c8
chore: update dependencies
dgandhi62 Oct 8, 2025
5d41a41
fix: prevent infinite retry loops in deleteProject when CloudFormatio…
dgandhi62 Oct 8, 2025
37eff25
chore: update dependency
dgandhi62 Oct 8, 2025
51cba3f
fix: change err.code to err.name for NotFoundException handling
dgandhi62 Oct 8, 2025
9567edd
fix: increase macOS binary size threshold to 1100MB
dgandhi62 Oct 9, 2025
ae486c9
fix: resolve Jest worker SIGKILL and hanging process issues
dgandhi62 Oct 9, 2025
7fdd750
fix: add failing tests to RUN_SOLO to prevent Jest worker SIGKILL errors
dgandhi62 Oct 10, 2025
e40271e
fix: remove 'as any' assertions from notification tests and add SIGKI…
dgandhi62 Oct 10, 2025
7499c10
feat: migrate notifications category to AWS SDK v3
dgandhi62 Oct 13, 2025
e28a09a
fix: retry logic in deleteProject
dgandhi62 Oct 13, 2025
45ea649
Merge dev branch into notifications SDK v3 migration
dgandhi62 Oct 15, 2025
dd17aad
fix: update dependencies
dgandhi62 Oct 15, 2025
98aba33
chore: update dependencies
dgandhi62 Oct 17, 2025
cc4b6c4
chore: merge upstream/dev with notifications category SDK v3 migration
dgandhi62 Oct 17, 2025
322f32f
fix: add tests to run_duo in codebuild
dgandhi62 Oct 17, 2025
5e7f7b4
chore: update codebuild config files
dgandhi62 Oct 17, 2025
b9bed4b
fix: update api extractor
dgandhi62 Oct 20, 2025
78a620f
chore: update API documentation files
dgandhi62 Oct 21, 2025
a520503
chore: merge upstream/dev
dgandhi62 Oct 21, 2025
f33b57c
chore: update dependencies
dgandhi62 Oct 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/amplify-category-notifications/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@
"@aws-amplify/amplify-environment-parameters": "1.9.20",
"@aws-amplify/amplify-prompts": "2.8.7",
"@aws-amplify/amplify-provider-awscloudformation": "8.11.10",
"aws-sdk": "^2.1464.0",
"@aws-sdk/client-iam": "^3.901.0",
"@aws-sdk/client-pinpoint": "^3.901.0",
"@smithy/node-http-handler": "^4.3.0",
"chalk": "^4.1.1",
"fs-extra": "^8.1.0",
"lodash": "^4.17.21",
"ora": "^4.0.3",
"promise-sequential": "^1.1.1",
"proxy-agent": "^6.3.0"
},
"devDependencies": {
"aws-sdk-client-mock": "^4.1.0",
"aws-sdk-client-mock-jest": "^4.1.0"
},
"jest": {
"testEnvironmentOptions": {
"url": "http://localhost"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { $TSAny, $TSContext, AmplifyCategories, AmplifyFault, AmplifySupportedService, IContextPrint } from '@aws-amplify/amplify-cli-core';
import { prompter } from '@aws-amplify/amplify-prompts';
import { mockClient } from 'aws-sdk-client-mock';
import 'aws-sdk-client-mock-jest';
import { PinpointClient, UpdateApnsChannelCommand, UpdateApnsSandboxChannelCommand } from '@aws-sdk/client-pinpoint';
import * as configureKey from '../apns-key-config';
import * as configureCertificate from '../apns-cert-config';

Expand All @@ -14,6 +17,8 @@ jest.mock('../apns-cert-config');
jest.mock('@aws-amplify/amplify-prompts');
const prompterMock = prompter as jest.Mocked<typeof prompter>;

const mockPinpointClient = mockClient(PinpointClient as any);

class NoErrorThrownError extends Error {}
// wrapper to avoid conditional error checks
const getError = async <TError>(call: () => unknown): Promise<TError> => {
Expand Down Expand Up @@ -44,7 +49,7 @@ describe('channel-APNS', () => {

const mockPinpointResponseErr = new Error('channel-APNS.test.js error');
const mockPinpointResponseData = {
APNSChannelResponse: {},
APNSChannelResponse: { Enabled: true, ApplicationId: 'test-app-id' },
};

const mockAPNSChannelResponseData = (status: boolean, action: ChannelAction, output: $TSAny): IChannelAPIResponse => ({
Expand All @@ -64,49 +69,22 @@ describe('channel-APNS', () => {
const mockKeyConfig = {};
const mockCertificateConfig = {};

const mockPinpointClient = {
updateApnsChannel: jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue(mockPinpointResponseData),
}),
updateApnsSandboxChannel: jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue(mockPinpointResponseData),
}),
};

const mockPinpointClientReject = {
updateApnsChannel: jest.fn().mockReturnValue({
promise: jest.fn().mockRejectedValue(mockPinpointResponseErr),
}),
updateApnsSandboxChannel: jest.fn().mockReturnValue({
promise: jest.fn().mockRejectedValue(mockPinpointResponseErr),
}),
};

const mockContext: $TSContext = {
exeInfo: {
serviceMeta: {
output: mockServiceOutput,
},
pinpointClient: mockPinpointClient,
pinpointClient: mockPinpointClient as any,
},
print: {
info: jest.fn(),
error: jest.fn(),
} as unknown as IContextPrint,
} as unknown as $TSContext;

const mockContextReject = {
exeInfo: {
serviceMeta: {
output: mockServiceOutput,
},
pinpointClient: mockPinpointClientReject,
},
print: {
info: jest.fn(),
error: jest.fn(),
},
};
beforeEach(() => {
mockPinpointClient.reset();
});

beforeAll(() => {
global.console = { ...global.console, log: jest.fn() };
Expand All @@ -118,69 +96,73 @@ describe('channel-APNS', () => {
});

test('configure', async () => {
mockPinpointClient.on(UpdateApnsChannelCommand as any).resolves(mockPinpointResponseData as any);
mockPinpointClient.on(UpdateApnsSandboxChannelCommand as any).resolves(mockPinpointResponseData as any);

mockChannelOutput.Enabled = true;
prompterMock.yesOrNo.mockResolvedValueOnce(true);
await channelAPNS.configure(mockContext).then(() => {
expect(mockPinpointClient.updateApnsChannel).toBeCalled();
});
await channelAPNS.configure(mockContext);
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);

mockChannelOutput.Enabled = true;
prompterMock.yesOrNo.mockResolvedValueOnce(false);
await channelAPNS.configure(mockContext).then(() => {
expect(mockPinpointClient.updateApnsChannel).toBeCalled();
});
prompterMock.pick.mockResolvedValueOnce('Certificate');
await channelAPNS.configure(mockContext);
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);

mockChannelOutput.Enabled = false;
prompterMock.yesOrNo.mockResolvedValueOnce(true);
prompterMock.pick.mockResolvedValueOnce('Certificate');
await channelAPNS.configure(mockContext).then(() => {
expect(mockPinpointClient.updateApnsChannel).toBeCalled();
});
await channelAPNS.configure(mockContext);
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);
});

test('enable', async () => {
prompterMock.pick.mockResolvedValueOnce('Certificate');
mockPinpointClient.on(UpdateApnsChannelCommand as any).resolves(mockPinpointResponseData as any);
mockPinpointClient.on(UpdateApnsSandboxChannelCommand as any).resolves(mockPinpointResponseData as any);

prompterMock.pick.mockResolvedValueOnce('Certificate');
const disableData = await channelAPNS.enable(mockContext, 'successMessage');
expect(mockPinpointClient.updateApnsChannel).toBeCalled();
expect(mockPinpointClient.updateApnsSandboxChannel).toBeCalled();
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsSandboxChannelCommand as any);
expect(disableData).toEqual(mockAPNSChannelResponseData(true, ChannelAction.ENABLE, mockPinpointResponseData.APNSChannelResponse));

prompterMock.pick.mockResolvedValueOnce('Key');
const enableData = await channelAPNS.enable(mockContext, 'successMessage');
expect(mockPinpointClient.updateApnsChannel).toBeCalled();
expect(mockPinpointClient.updateApnsSandboxChannel).toBeCalled();
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsSandboxChannelCommand as any);
expect(enableData).toEqual(mockAPNSChannelResponseData(true, ChannelAction.ENABLE, mockPinpointResponseData.APNSChannelResponse));
});

test('enable unsuccessful', async () => {
prompterMock.pick.mockResolvedValueOnce('Certificate');
mockPinpointClient.on(UpdateApnsChannelCommand as any).rejects(mockPinpointResponseErr);

const errCert: AmplifyFault = await getError(async () =>
channelAPNS.enable(mockContextReject as unknown as $TSContext, 'successMessage'),
);
expect(mockContextReject.exeInfo.pinpointClient.updateApnsChannel).toBeCalled();
prompterMock.pick.mockResolvedValueOnce('Certificate');
const errCert: AmplifyFault = await getError(async () => channelAPNS.enable(mockContext, 'successMessage'));
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);
expect(errCert?.downstreamException?.message).toContain(mockPinpointResponseErr.message);

prompterMock.pick.mockResolvedValueOnce('Key');
const errKey: AmplifyFault = await getError(async () =>
channelAPNS.enable(mockContextReject as unknown as $TSContext, 'successMessage'),
);
expect(mockPinpointClient.updateApnsChannel).toBeCalled();
const errKey: AmplifyFault = await getError(async () => channelAPNS.enable(mockContext, 'successMessage'));
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);
expect(errKey?.downstreamException?.message).toContain(mockPinpointResponseErr.message);
});

test('disable', async () => {
await channelAPNS.disable(mockContext).then((data) => {
expect(mockPinpointClient.updateApnsChannel).toBeCalled();
expect(mockPinpointClient.updateApnsSandboxChannel).toBeCalled();
expect(data).toEqual(mockAPNSChannelResponseData(true, ChannelAction.DISABLE, mockPinpointResponseData.APNSChannelResponse));
});
mockPinpointClient.on(UpdateApnsChannelCommand as any).resolves(mockPinpointResponseData as any);
mockPinpointClient.on(UpdateApnsSandboxChannelCommand as any).resolves(mockPinpointResponseData as any);

const data = await channelAPNS.disable(mockContext);
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsSandboxChannelCommand as any);
expect(data).toEqual(mockAPNSChannelResponseData(true, ChannelAction.DISABLE, mockPinpointResponseData.APNSChannelResponse));
});

test('disable unsuccessful', async () => {
const errKey: AmplifyFault = await getError(async () => channelAPNS.disable(mockContextReject as unknown as $TSContext));
expect(mockPinpointClient.updateApnsChannel).toBeCalled();
mockPinpointClient.on(UpdateApnsChannelCommand as any).rejects(mockPinpointResponseErr);

const errKey: AmplifyFault = await getError(async () => channelAPNS.disable(mockContext));
expect(mockPinpointClient).toHaveReceivedCommand(UpdateApnsChannelCommand as any);
expect(errKey?.downstreamException?.message).toContain(mockPinpointResponseErr.message);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { prompter } from '@aws-amplify/amplify-prompts';
import { mockClient } from 'aws-sdk-client-mock';
import 'aws-sdk-client-mock-jest';
import { PinpointClient, UpdateEmailChannelCommand } from '@aws-sdk/client-pinpoint';
import * as channelEmail from '../channel-email';
import { ChannelAction, ChannelConfigDeploymentType, IChannelAPIResponse } from '../channel-types';
import { $TSAny, $TSContext, AmplifyCategories, AmplifySupportedService } from '@aws-amplify/amplify-cli-core';
Expand All @@ -19,11 +22,13 @@ jest.mock('@aws-amplify/amplify-cli-core', () => {
jest.mock('@aws-amplify/amplify-prompts');
const prompterMock = prompter as jest.Mocked<typeof prompter>;

const mockPinpointResponseData = (status: boolean, action: ChannelAction): IChannelAPIResponse => ({
const mockPinpointClient = mockClient(PinpointClient as any);

const mockPinpointResponseData = (status: boolean, action: ChannelAction, output: any): IChannelAPIResponse => ({
action,
channel: ChannelType.Email,
deploymentType: ChannelConfigDeploymentType.INLINE,
output: undefined,
output,
response: {
capability: AmplifyCategories.NOTIFICATIONS,
pluginName: AmplifyCategories.NOTIFICATIONS,
Expand All @@ -33,36 +38,45 @@ const mockPinpointResponseData = (status: boolean, action: ChannelAction): IChan
},
});

const mockPinpointClient = {
updateEmailChannel: jest.fn().mockImplementation(() => ({
promise: jest.fn(() => mockPinpointResponseData(true, ChannelAction.ENABLE)),
})),
};

const mockContext = (output: $TSAny, client: $TSAny): $TSContext =>
const mockContext = (output: $TSAny): $TSContext =>
({
exeInfo: {
serviceMeta: {
output,
},
pinpointClient: client,
pinpointClient: mockPinpointClient as any,
},
print: {
info: jest.fn(),
error: jest.fn(),
},
} as unknown as $TSContext);

describe('channel-FCM', () => {
describe('channel-Email', () => {
const mockEmailChannelResponse = { Enabled: true, ApplicationId: 'test-app-id' };

beforeEach(() => {
mockPinpointClient.reset();
});

test('enable should store role arn', async () => {
mockPinpointClient.on(UpdateEmailChannelCommand as any).resolves({ EmailChannelResponse: mockEmailChannelResponse } as any);
prompterMock.input.mockResolvedValueOnce('[email protected]');
prompterMock.input.mockResolvedValueOnce('fake:arn:identity');
prompterMock.input.mockResolvedValueOnce('fake:arn:role');

const mockContextObj = mockContext({ Enabled: true }, mockPinpointClient);
const mockContextObj = mockContext({ Enabled: true });
const data = await channelEmail.enable(mockContextObj, 'successMessage');
expect(mockPinpointClient.updateEmailChannel).toBeCalled();
expect(data).toEqual(mockPinpointResponseData(true, ChannelAction.ENABLE));
expect(mockPinpointClient).toHaveReceivedCommandWith(UpdateEmailChannelCommand as any, {
ApplicationId: undefined,
EmailChannelRequest: {
FromAddress: '[email protected]',
Identity: 'fake:arn:identity',
RoleArn: 'fake:arn:role',
Enabled: true,
},
});
expect(data).toEqual(mockPinpointResponseData(true, ChannelAction.ENABLE, mockEmailChannelResponse));
expect(mockContextObj.exeInfo.serviceMeta.output['Email'].RoleArn).toEqual('fake:arn:role');
});
});
Loading
Loading