Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/integ-config/detox-integ-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@
working_directory: amplify-js-samples-staging/samples/react-native/auth/HosteduiApp
timeout_minutes: 120
host_signout_page: true
- test_name: 'integ_rn_ios_passkeys'
working_directory: amplify-js-samples-staging/samples/react-native/auth/Passkeys
timeout_minutes: 120
12 changes: 9 additions & 3 deletions .github/workflows/callable-get-package-list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
packages:
description: The json encoded package list
value: ${{ jobs.get-package-list.outputs.packages }}
nativePackages:
description: The json encoded native package list
value: ${{ jobs.get-package-list.outputs.nativePackages }}
jobs:
get-package-list:
name: Get packages list
Expand All @@ -31,11 +34,14 @@ jobs:
- name: Dump Package List
if: steps.cache-package-list.outputs.cache-hit != 'true'
run: |
echo "packages=$(yarn lerna ll | egrep -v "lerna|Done|yarn" | jq -R -s -c 'split("\n")[:-1] | map({name: split(" ")[0], path: split(" ")[-1]})')" > package-list.json
yarn lerna ll | egrep -v "lerna|Done|yarn" | jq -R -s -c 'split("\n")[:-1] | map({name: split(" ")[0], path: split(" ")[-1]})' > package-list.json
- name: Get Package List
id: get_package_list
run: |
cat package-list.json >> $GITHUB_OUTPUT
cat package-list.json
echo "packages=$(cat package-list.json)" >> $GITHUB_OUTPUT
# temporarily filter to only packages with runnable tests
echo "nativePackages=$(jq -c 'map(select(.name | test("rtn-passkeys|react-native")))' package-list.json)" >> $GITHUB_OUTPUT
outputs:
packages: ${{ steps.get_package_list.outputs.packages }}
# todo: expand native package list as other tests are made runnable
nativePackages: ${{ steps.get_package_list.outputs.nativePackages }}
36 changes: 36 additions & 0 deletions .github/workflows/callable-native-unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Native Unit Tests

on: workflow_call

jobs:
get-package-list:
uses: ./.github/workflows/callable-get-package-list.yml
unit_test:
name: Native Unit Test - ${{ matrix.package.name }}
runs-on: macos-latest
needs: get-package-list
strategy:
matrix:
package: ${{ fromJSON(needs.get-package-list.outputs.nativePackages) }}
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
path: amplify-js
- name: Setup node and build the repository
uses: ./amplify-js/.github/actions/node-and-build
- name: Run iOS Tests
working-directory: ./amplify-js
env:
TEST_PACKAGE: ${{ matrix.package.name }}
run: |
npx lerna exec --scope $TEST_PACKAGE yarn prepare:ios
npx lerna exec --scope $TEST_PACKAGE yarn test:ios
- name: Run Android Tests
working-directory: ./amplify-js
env:
TEST_PACKAGE: ${{ matrix.package.name }}
run: |
npx lerna exec --scope $TEST_PACKAGE yarn prepare:android
npx lerna exec --scope $TEST_PACKAGE yarn test:android
4 changes: 4 additions & 0 deletions .github/workflows/callable-release-verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
needs:
- prebuild-ubuntu
uses: ./.github/workflows/callable-unit-tests.yml
native-unit-tests:
needs:
- prebuild-macos
uses: ./.github/workflows/callable-native-unit-tests.yml
bundle-size-tests:
needs: prebuild-ubuntu
uses: ./.github/workflows/callable-bundle-size-tests.yml
4 changes: 4 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ jobs:
needs:
- prebuild
uses: ./.github/workflows/callable-unit-tests.yml
native-unit-tests:
needs:
- prebuild
uses: ./.github/workflows/callable-native-unit-tests.yml
bundle-size-tests:
needs: prebuild
uses: ./.github/workflows/callable-bundle-size-tests.yml
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ xcuserdata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
**/xcshareddata/WorkspaceSettings.xcsettings
**/.xcode.env.local
*.xcode.env.local

### Coverage ###
coverage/
Expand All @@ -65,7 +65,12 @@ coverage-ts/
**/example/ios/Podfile.lock
**/example/.bundle
**/example/yarn.lock
**/example/amplify_outputs.json

# rollup
**/.rollup.cache
**/buildMeta

# ruby
vendor/
Gemfile.lock
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ export default [
'**/__tests__/**',
'**/packages/adapter-nextjs/**',
'**/packages/react-native/example/**',
'**/packages/rtn-passkeys/example/**',
],
rules: {
'import/no-extraneous-dependencies': 'error',
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"packages/rtn-web-browser",
"packages/react-native",
"packages/react-native/example",
"packages/rtn-passkeys",
"packages/rtn-passkeys/example",
"scripts/tsc-compliance-test"
],
"nohoist": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { Observable, Observer } from 'rxjs';
import { Reachability } from '@aws-amplify/core/internals/utils';
import { ConsoleLogger } from '@aws-amplify/core';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { getIsNativeError } from '@aws-amplify/react-native';

import {
PasskeyError,
PasskeyErrorCode,
} from '../../../../../src/client/utils/passkey/errors';
import { handlePasskeyAuthenticationError } from '../../../../../src/client/utils/passkey/errors/handlePasskeyAuthenticationError.native';
import { handlePasskeyError } from '../../../../../src/client/utils/passkey/errors/handlePasskeyError';
import { passkeyErrorMap } from '../../../../../src/client/utils/passkey/errors/passkeyError';
import { MockNativeError } from '../../../../mockData';

const mockHandlePasskeyError = jest.mocked(handlePasskeyError);
jest.mock('../../../../../src/client/utils/passkey/errors/handlePasskeyError');

jest.mock('@aws-amplify/react-native', () => ({
getIsNativeError: jest.fn(() => true),
}));

const mockGetIsNativeError = jest.mocked(getIsNativeError);

describe('handlePasskeyAuthenticationError', () => {
it('returns early if err is already instanceof PasskeyError', () => {
const err = new PasskeyError({
name: 'PasskeyErrorName',
message: 'Error Message',
});

expect(handlePasskeyAuthenticationError(err)).toBe(err);
expect(mockGetIsNativeError).not.toHaveBeenCalled();
});

it('returns new instance of PasskeyError with correct attributes when input error code is FAILED', () => {
const err = new MockNativeError();
err.code = 'FAILED';

const { message, recoverySuggestion } =
passkeyErrorMap[PasskeyErrorCode.PasskeyRetrievalFailed];

expect(handlePasskeyAuthenticationError(err)).toMatchObject(
new PasskeyError({
name: PasskeyErrorCode.PasskeyRetrievalFailed,
message,
recoverySuggestion,
underlyingError: err,
}),
);
expect(mockGetIsNativeError).toHaveBeenCalledWith(err);
});

it('returns new instance of PasskeyError with correct attributes when input error code is CANCELED', () => {
const err = new MockNativeError();
err.code = 'CANCELED';

const { message, recoverySuggestion } =
passkeyErrorMap[PasskeyErrorCode.PasskeyAuthenticationCanceled];

expect(handlePasskeyAuthenticationError(err)).toMatchObject(
new PasskeyError({
name: PasskeyErrorCode.PasskeyAuthenticationCanceled,
message,
recoverySuggestion,
underlyingError: err,
}),
);
expect(mockGetIsNativeError).toHaveBeenCalledWith(err);
});

it('invokes handlePasskeyError when input error does not match expected cases', () => {
const err = new Error();
err.name = 'Unknown';

handlePasskeyAuthenticationError(err);

expect(mockHandlePasskeyError).toHaveBeenCalledWith(err);
expect(mockGetIsNativeError).toHaveBeenCalledWith(err);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import {
PasskeyError,
PasskeyErrorCode,
handlePasskeyAuthenticationError,
} from '../../../../../src/client/utils/passkey/errors';
import { handlePasskeyError } from '../../../../../src/client/utils/passkey/errors/handlePasskeyError';
import { passkeyErrorMap } from '../../../../../src/client/utils/passkey/errors/passkeyError';

const mockHandlePasskeyError = jest.mocked(handlePasskeyError);
jest.mock('../../../../../src/client/utils/passkey/errors/handlePasskeyError');

describe('handlePasskeyAuthenticationError', () => {
it('returns early if err is already instanceof PasskeyError', () => {
const err = new PasskeyError({
name: 'PasskeyErrorName',
message: 'Error Message',
});

expect(handlePasskeyAuthenticationError(err)).toBe(err);
});

it('returns new instance of PasskeyError with correct attributes when input error name is NotAllowedError', () => {
const err = new Error();
err.name = 'NotAllowedError';

const { message, recoverySuggestion } =
passkeyErrorMap[PasskeyErrorCode.PasskeyAuthenticationCanceled];

expect(handlePasskeyAuthenticationError(err)).toMatchObject(
new PasskeyError({
name: PasskeyErrorCode.PasskeyAuthenticationCanceled,
message,
recoverySuggestion,
underlyingError: err,
}),
);
});

it('invokes handlePasskeyError when input error does not match expected cases', () => {
const err = new Error();
err.name = 'Unknown';

handlePasskeyAuthenticationError(err);

expect(mockHandlePasskeyError).toHaveBeenCalledWith(err);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { AmplifyErrorCode } from '@aws-amplify/core/internals/utils';

import {
PasskeyError,
PasskeyErrorCode,
} from '../../../../../src/client/utils/passkey/errors';
import { handlePasskeyError } from '../../../../../src/client/utils/passkey/errors/handlePasskeyError.native';
import { passkeyErrorMap } from '../../../../../src/client/utils/passkey/errors/passkeyError';
import { MockNativeError } from '../../../../mockData';

jest.mock('@aws-amplify/react-native', () => ({
getIsNativeError: jest.fn(() => true),
}));

describe('handlePasskeyError', () => {
it('returns new instance of PasskeyError with correct attributes when input error code is RELYING_PARTY_MISMATCH', () => {
const err = new MockNativeError();
err.code = 'RELYING_PARTY_MISMATCH';

const { message, recoverySuggestion } =
passkeyErrorMap[PasskeyErrorCode.RelyingPartyMismatch];

expect(handlePasskeyError(err)).toMatchObject(
new PasskeyError({
name: PasskeyErrorCode.RelyingPartyMismatch,
message,
recoverySuggestion,
underlyingError: err,
}),
);
});

it('returns new instance of PasskeyError with correct attributes when input error code is NOT_SUPPORTED', () => {
const err = new MockNativeError();
err.code = 'NOT_SUPPORTED';

const { message, recoverySuggestion } =
passkeyErrorMap[PasskeyErrorCode.PasskeyNotSupported];

expect(handlePasskeyError(err)).toMatchObject(
new PasskeyError({
name: PasskeyErrorCode.PasskeyNotSupported,
message,
recoverySuggestion,
underlyingError: err,
}),
);
});

it('returns unknown PasskeyError when input does not match expected cases', () => {
const err = new MockNativeError();
err.name = 'UNKNOWN';

expect(handlePasskeyError(err)).toMatchObject(
new PasskeyError({
name: AmplifyErrorCode.Unknown,
message: 'An unknown error has occurred.',
underlyingError: err,
}),
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { AmplifyErrorCode } from '@aws-amplify/core/internals/utils';

import {
PasskeyError,
PasskeyErrorCode,
} from '../../../../../src/client/utils/passkey/errors';
import { handlePasskeyError } from '../../../../../src/client/utils/passkey/errors/handlePasskeyError';
import { passkeyErrorMap } from '../../../../../src/client/utils/passkey/errors/passkeyError';

describe('handlePasskeyError', () => {
it('returns new instance of PasskeyError with correct attributes when input error name is AbortError', () => {
const err = new Error();
err.name = 'AbortError';

const { message, recoverySuggestion } =
passkeyErrorMap[PasskeyErrorCode.PasskeyOperationAborted];

expect(handlePasskeyError(err)).toMatchObject(
new PasskeyError({
name: PasskeyErrorCode.PasskeyOperationAborted,
message,
recoverySuggestion,
underlyingError: err,
}),
);
});

it('returns new instance of PasskeyError with correct attributes when input error name is SecurityError', () => {
const err = new Error();
err.name = 'SecurityError';

const { message, recoverySuggestion } =
passkeyErrorMap[PasskeyErrorCode.RelyingPartyMismatch];

expect(handlePasskeyError(err)).toMatchObject(
new PasskeyError({
name: PasskeyErrorCode.RelyingPartyMismatch,
message,
recoverySuggestion,
underlyingError: err,
}),
);
});

it('returns unknown PasskeyError when input does not match expected cases', () => {
const err = new Error();
err.name = 'Unknown';

expect(handlePasskeyError(err)).toMatchObject(
new PasskeyError({
name: AmplifyErrorCode.Unknown,
message: 'An unknown error has occurred.',
underlyingError: err,
}),
);
});
});
Loading
Loading