Skip to content

feat(auth): Added unit tests for native and shared errors and types #14146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: feat/android-webauthn
Choose a base branch
from
Draft
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
135 changes: 135 additions & 0 deletions packages/auth/__tests__/client/utils/errors/native.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// 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/shared';
import {
handlePasskeyAuthenticationError,
handlePasskeyRegistrationError,
} from '../../../../src/client/utils/passkey/errors/native';

describe('Native Passkey Error Handlers', () => {
describe('handlePasskeyRegistrationError', () => {
it('should return existing PasskeyError unchanged', () => {
const existingError = new PasskeyError({
name: PasskeyErrorCode.PasskeyNotSupported,
message: 'test message',
});

const result = handlePasskeyRegistrationError(existingError);
expect(result).toBe(existingError);
});

it('should map UserCancelled native error', () => {
const nativeError = new Error('cancelled');
(nativeError as any).code = 'UserCancelled';

const result = handlePasskeyRegistrationError(nativeError);
expect(result.name).toBe(PasskeyErrorCode.PasskeyRegistrationCanceled);
});

it('should map NotSupported native error', () => {
const nativeError = new Error('not supported');
(nativeError as any).code = 'NotSupported';

const result = handlePasskeyRegistrationError(nativeError);
expect(result.name).toBe(PasskeyErrorCode.PasskeyNotSupported);
});

it('should handle unknown errors', () => {
const result = handlePasskeyRegistrationError(new Error('unknown'));
expect(result.name).toBe(AmplifyErrorCode.Unknown);
});

it('should map NotConfigured native error', () => {
const nativeError = new Error('not configured');
(nativeError as any).code = 'NotConfigured';

const result = handlePasskeyRegistrationError(nativeError);
expect(result.name).toBe(
PasskeyErrorCode.InvalidPasskeyRegistrationOptions,
);
});

it('should map Interrupted native error', () => {
const nativeError = new Error('interrupted');
(nativeError as any).code = 'Interrupted';

const result = handlePasskeyRegistrationError(nativeError);
expect(result.name).toBe(PasskeyErrorCode.PasskeyOperationAborted);
});

it('should handle non-Error objects', () => {
const result = handlePasskeyRegistrationError('not an error');
expect(result.name).toBe(AmplifyErrorCode.Unknown);
});
});

describe('handlePasskeyAuthenticationError', () => {
it('should return existing PasskeyError unchanged', () => {
const existingError = new PasskeyError({
name: PasskeyErrorCode.PasskeyNotSupported,
message: 'test message',
});

const result = handlePasskeyAuthenticationError(existingError);
expect(result).toBe(existingError);
});

it('should map NoCredentials native error', () => {
const nativeError = new Error('no credentials');
(nativeError as any).code = 'NoCredentials';

const result = handlePasskeyAuthenticationError(nativeError);
expect(result.name).toBe(PasskeyErrorCode.PasskeyRetrievalFailed);
});

it('should map UserCancelled native error', () => {
const nativeError = new Error('cancelled');
(nativeError as any).code = 'UserCancelled';

const result = handlePasskeyAuthenticationError(nativeError);
expect(result.name).toBe(PasskeyErrorCode.PasskeyAuthenticationCanceled);
});

it('should map NotSupported native error', () => {
const nativeError = new Error('not supported');
(nativeError as any).code = 'NotSupported';

const result = handlePasskeyAuthenticationError(nativeError);
expect(result.name).toBe(PasskeyErrorCode.PasskeyNotSupported);
});

it('should map NotConfigured native error', () => {
const nativeError = new Error('not configured');
(nativeError as any).code = 'NotConfigured';

const result = handlePasskeyAuthenticationError(nativeError);
expect(result.name).toBe(
PasskeyErrorCode.InvalidPasskeyAuthenticationOptions,
);
});

it('should map Interrupted native error', () => {
const nativeError = new Error('interrupted');
(nativeError as any).code = 'Interrupted';

const result = handlePasskeyAuthenticationError(nativeError);
expect(result.name).toBe(PasskeyErrorCode.PasskeyOperationAborted);
});

it('should handle non-Error objects', () => {
const result = handlePasskeyAuthenticationError('not an error');
expect(result.name).toBe(AmplifyErrorCode.Unknown);
});

it('should handle unknown errors', () => {
const result = handlePasskeyAuthenticationError(new Error('unknown'));
expect(result.name).toBe(AmplifyErrorCode.Unknown);
});
});
});
158 changes: 158 additions & 0 deletions packages/auth/__tests__/client/utils/errors/shared.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import {
PasskeyCreateOptionsJson,
PasskeyCreateResultJson,
PasskeyGetOptionsJson,
PasskeyGetResultJson,
PkcDescriptor,
assertValidCredentialCreationOptions,
} from '../../../../src/client/utils/passkey/types/shared';

describe('Shared Passkey Types', () => {
describe('assertValidCredentialCreationOptions', () => {
const validOptions: PasskeyCreateOptionsJson = {
challenge: 'test-challenge',
rp: {
id: 'test-rp-id',
name: 'test-rp-name',
},
user: {
id: 'test-user-id',
name: 'test-user',
displayName: 'Test User',
},
pubKeyCredParams: [
{
alg: -7,
type: 'public-key',
},
],
};

it('should not throw for valid options', () => {
expect(() => {
assertValidCredentialCreationOptions(validOptions);
}).not.toThrow();
});

it('should throw for null input', () => {
expect(() => {
assertValidCredentialCreationOptions(null);
}).toThrow('Invalid passkey registration options.');
});

it('should throw for missing challenge', () => {
const invalidOptions = {
...validOptions,
challenge: undefined,
};
expect(() => {
assertValidCredentialCreationOptions(invalidOptions);
}).toThrow('Invalid passkey registration options.');
});

it('should throw for missing user', () => {
const invalidOptions = {
...validOptions,
user: undefined,
};
expect(() => {
assertValidCredentialCreationOptions(invalidOptions);
}).toThrow('Invalid passkey registration options.');
});

it('should throw for missing rp', () => {
const invalidOptions = {
...validOptions,
rp: undefined,
};
expect(() => {
assertValidCredentialCreationOptions(invalidOptions);
}).toThrow('Invalid passkey registration options.');
});

it('should throw for missing pubKeyCredParams', () => {
const invalidOptions = {
...validOptions,
pubKeyCredParams: undefined,
};
expect(() => {
assertValidCredentialCreationOptions(invalidOptions);
}).toThrow('Invalid passkey registration options.');
});
});

describe('Type validations', () => {
describe('PkcDescriptor', () => {
it('should accept valid transport types', () => {
const validDescriptor: PkcDescriptor<string> = {
type: 'public-key',
id: 'test-id',
transports: ['ble', 'nfc', 'usb', 'internal', 'hybrid'],
};
// TypeScript compilation would fail if types are invalid
expect(validDescriptor.transports).toBeDefined();
});
});

describe('PasskeyCreateResultJson', () => {
it('should validate response structure', () => {
const validResult: PasskeyCreateResultJson = {
id: 'test-id',
rawId: 'test-raw-id',
type: 'public-key',
clientExtensionResults: {},
response: {
clientDataJSON: 'test-client-data',
attestationObject: 'test-attestation',
transports: ['internal'],
publicKeyAlgorithm: -7,
authenticatorData: 'test-auth-data',
},
};
// TypeScript compilation would fail if structure is invalid
expect(validResult.response.attestationObject).toBeDefined();
});
});

describe('PasskeyGetOptionsJson', () => {
it('should validate options structure', () => {
const validOptions: PasskeyGetOptionsJson = {
challenge: 'test-challenge',
rpId: 'test-rp',
timeout: 60000,
allowCredentials: [
{
type: 'public-key',
id: 'test-id',
transports: ['internal'],
},
],
userVerification: 'preferred',
};
// TypeScript compilation would fail if structure is invalid
expect(validOptions.userVerification).toBeDefined();
});
});

describe('PasskeyGetResultJson', () => {
it('should validate result structure', () => {
const validResult: PasskeyGetResultJson = {
id: 'test-id',
rawId: 'test-raw-id',
type: 'public-key',
clientExtensionResults: {},
response: {
authenticatorData: 'test-auth-data',
clientDataJSON: 'test-client-data',
signature: 'test-signature',
},
};
// TypeScript compilation would fail if structure is invalid
expect(validResult.response.signature).toBeDefined();
});
});
});
});
95 changes: 95 additions & 0 deletions packages/auth/__tests__/client/utils/types/index.native.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import {
assertValidNativeAuthenticationResponse,
assertValidNativeRegistrationResponse,
assertValidNativeResponse,
} from '../../../../src/client/utils/passkey/types/index.native';
import {
PasskeyError,
PasskeyErrorCode,
} from '../../../../src/client/utils/passkey/errors';

describe('Native type assertions', () => {
describe('assertValidNativeResponse', () => {
// ... first test case remains the same ...

it('should throw for invalid response', () => {
expect(() => {
assertValidNativeResponse({});
}).toThrow(
new PasskeyError({
name: PasskeyErrorCode.PasskeyRetrievalFailed,
message: 'Device failed to retrieve passkey.',
}),
);
});

it('should throw for null input', () => {
expect(() => {
assertValidNativeResponse(null);
}).toThrow(
new PasskeyError({
name: PasskeyErrorCode.PasskeyRetrievalFailed,
message: 'Device failed to retrieve passkey.',
}),
);
});
});

describe('assertValidNativeRegistrationResponse', () => {
// ... first test case remains the same ...

it('should throw for missing attestationObject', () => {
const invalidResponse = {
clientDataJSON: 'test-data',
};

expect(() => {
assertValidNativeRegistrationResponse(invalidResponse);
}).toThrow(
new PasskeyError({
name: PasskeyErrorCode.PasskeyRegistrationFailed,
message: 'Device failed to create passkey.',
}),
);
});
});

describe('assertValidNativeAuthenticationResponse', () => {
// ... first test case remains the same ...

it('should throw for missing authenticatorData', () => {
const invalidResponse = {
clientDataJSON: 'test-data',
signature: 'test-signature',
};

expect(() => {
assertValidNativeAuthenticationResponse(invalidResponse);
}).toThrow(
new PasskeyError({
name: PasskeyErrorCode.PasskeyRetrievalFailed,
message: 'Device failed to retrieve passkey.',
}),
);
});

it('should throw for missing signature', () => {
const invalidResponse = {
clientDataJSON: 'test-data',
authenticatorData: 'test-auth-data',
};

expect(() => {
assertValidNativeAuthenticationResponse(invalidResponse);
}).toThrow(
new PasskeyError({
name: PasskeyErrorCode.PasskeyRetrievalFailed,
message: 'Device failed to retrieve passkey.',
}),
);
});
});
});
Loading
Loading