mirror of
https://github.com/mozilla/fxa.git
synced 2025-12-13 20:36:41 +01:00
930 lines
35 KiB
TypeScript
930 lines
35 KiB
TypeScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import proxyquire from 'proxyquire';
|
|
import sinon, { SinonStub } from 'sinon';
|
|
import { assert } from 'chai';
|
|
import { AppError } from '@fxa/accounts/errors';
|
|
import mocks from '../../mocks';
|
|
import { GleanMetricsType } from '../../../lib/metrics/glean';
|
|
import { AuthRequest } from '../../../lib/types';
|
|
import { Dictionary } from 'lodash';
|
|
|
|
const recordStub = sinon.stub();
|
|
|
|
const recordRegAccCreatedStub = sinon.stub();
|
|
const recordRegEmailSentStub = sinon.stub();
|
|
const recordRegAccVerifiedStub = sinon.stub();
|
|
const recordRegCompleteStub = sinon.stub();
|
|
const recordRegSubmitErrorStub = sinon.stub();
|
|
const recordLoginSuccessStub = sinon.stub();
|
|
const recordLoginSubmitBackendErrorStub = sinon.stub();
|
|
const recordLoginTotpCodeSuccessStub = sinon.stub();
|
|
const recordLoginTotpCodeFailureStub = sinon.stub();
|
|
const recordLoginBackupCodeSuccessStub = sinon.stub();
|
|
const recordLoginRecoveryPhoneSuccessStub = sinon.stub();
|
|
const recordLoginEmailConfirmationSentStub = sinon.stub();
|
|
const recordLoginEmailConfirmationSuccessStub = sinon.stub();
|
|
const recordLoginCompleteStub = sinon.stub();
|
|
const recordPasswordResetEmailSentStub = sinon.stub();
|
|
const recordPasswordResetCreateNewSuccessStub = sinon.stub();
|
|
const recordAccountPasswordResetStub = sinon.stub();
|
|
const recordPasswordResetRecoveryKeySuccessStub = sinon.stub();
|
|
const recordPasswordResetRecoveryKeyCreateSuccessStub = sinon.stub();
|
|
const recordAccessTokenCreatedStub = sinon.stub();
|
|
const recordAccessTokenCheckedStub = sinon.stub();
|
|
const recordThirdPartyAuthGoogleLoginCompleteStub = sinon.stub();
|
|
const recordThirdPartyAuthAppleLoginCompleteStub = sinon.stub();
|
|
const recordThirdPartyAuthGoogleRegCompleteStub = sinon.stub();
|
|
const recordThirdPartyAuthAppleRegCompleteStub = sinon.stub();
|
|
const recordThirdPartyAuthSetPasswordCompleteStub = sinon.stub();
|
|
const recordAccountDeleteCompleteStub = sinon.stub();
|
|
const recordAccountDeleteTaskHandledStub = sinon.stub();
|
|
const recordPasswordResetEmailConfirmationSentStub = sinon.stub();
|
|
const recordPasswordResetEmailConfirmationSuccessStub = sinon.stub();
|
|
const recordTwoFactorAuthCodeCompleteStub = sinon.stub();
|
|
const recordTwoFactorAuthReplaceCodeCompleteStub = sinon.stub();
|
|
const recordTwoFactorAuthSetCodesCompleteStub = sinon.stub();
|
|
const recordTwoFactorAuthSetupVerifySuccessStub = sinon.stub();
|
|
const recordTwoFactorAuthSetupInvalidCodeErrorStub = sinon.stub();
|
|
const recordTwoFactorAuthReplaceSuccessStub = sinon.stub();
|
|
const recordTwoFactorAuthReplaceFailureStub = sinon.stub();
|
|
const recordTwoStepAuthPhoneCodeSentStub = sinon.stub();
|
|
const recordTwoStepAuthPhoneCodeSendErrorStub = sinon.stub();
|
|
const recordTwoStepAuthPhoneCodeCompleteStub = sinon.stub();
|
|
const recordTwoStepAuthPhoneRemoveSuccessStub = sinon.stub();
|
|
const recordTwoStepAuthRemoveSuccessStub = sinon.stub();
|
|
const recordPasswordResetTwoFactorSuccessStub = sinon.stub();
|
|
const recordPasswordResetRecoveryCodeSuccessStub = sinon.stub();
|
|
const recordInactiveAccountDeletionStatusCheckedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionFirstEmailTaskRequestStub = sinon.stub();
|
|
const recordInactiveAccountDeletionFirstEmailTaskEnqueuedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionFirstEmailTaskRejectedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionFirstEmailSkippedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionSecondEmailTaskRequestStub = sinon.stub();
|
|
const recordInactiveAccountDeletionSecondEmailTaskEnqueuedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionSecondEmailTaskRejectedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionSecondEmailSkippedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionFinalEmailTaskRequestStub = sinon.stub();
|
|
const recordInactiveAccountDeletionFinalEmailTaskEnqueuedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionFinalEmailTaskRejectedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionFinalEmailSkippedStub = sinon.stub();
|
|
const recordInactiveAccountDeletionDeletionScheduledStub = sinon.stub();
|
|
const recordInactiveAccountDeletionDeletionSkippedStub = sinon.stub();
|
|
const recordEmailDeliverySuccessStub = sinon.stub();
|
|
const recordPasswordResetRecoveryPhoneCodeSentStub = sinon.stub();
|
|
const recordPasswordResetRecoveryPhoneCodeSendErrorStub = sinon.stub();
|
|
const recordPasswordResetRecoveryPhoneCodeCompleteStub = sinon.stub();
|
|
const recordTwoStepAuthPhoneReplaceSuccess = sinon.stub();
|
|
const recordTwoStepAuthPhoneReplaceFailure = sinon.stub();
|
|
const recordLoginConfirmSkipForKnownIp = sinon.stub();
|
|
const recordLoginConfirmSkipForNewAccount = sinon.stub();
|
|
const recordLoginConfirmSkipForKnownDevice = sinon.stub();
|
|
|
|
const gleanProxy = proxyquire('../../../lib/metrics/glean', {
|
|
'./server_events': {
|
|
createAccountsEventsEvent: () => ({ record: recordStub }),
|
|
// this is out of hand! we need to switch to use sinon.mock or some such thing
|
|
createEventsServerEventLogger: () => ({
|
|
recordRegAccCreated: recordRegAccCreatedStub,
|
|
recordRegEmailSent: recordRegEmailSentStub,
|
|
recordRegAccVerified: recordRegAccVerifiedStub,
|
|
recordRegComplete: recordRegCompleteStub,
|
|
recordRegSubmitError: recordRegSubmitErrorStub,
|
|
recordLoginSuccess: recordLoginSuccessStub,
|
|
recordLoginSubmitBackendError: recordLoginSubmitBackendErrorStub,
|
|
recordLoginTotpCodeSuccess: recordLoginTotpCodeSuccessStub,
|
|
recordLoginTotpCodeFailure: recordLoginTotpCodeFailureStub,
|
|
recordLoginBackupCodeSuccess: recordLoginBackupCodeSuccessStub,
|
|
recordLoginRecoveryPhoneSuccess: recordLoginRecoveryPhoneSuccessStub,
|
|
recordLoginEmailConfirmationSent: recordLoginEmailConfirmationSentStub,
|
|
recordLoginEmailConfirmationSuccess:
|
|
recordLoginEmailConfirmationSuccessStub,
|
|
recordLoginComplete: recordLoginCompleteStub,
|
|
recordPasswordResetEmailSent: recordPasswordResetEmailSentStub,
|
|
recordPasswordResetCreateNewSuccess:
|
|
recordPasswordResetCreateNewSuccessStub,
|
|
recordAccountPasswordReset: recordAccountPasswordResetStub,
|
|
recordPasswordResetRecoveryKeySuccess:
|
|
recordPasswordResetRecoveryKeySuccessStub,
|
|
recordPasswordResetRecoveryKeyCreateSuccess:
|
|
recordPasswordResetRecoveryKeyCreateSuccessStub,
|
|
recordAccessTokenCreated: recordAccessTokenCreatedStub,
|
|
recordAccessTokenChecked: recordAccessTokenCheckedStub,
|
|
recordThirdPartyAuthGoogleLoginComplete:
|
|
recordThirdPartyAuthGoogleLoginCompleteStub,
|
|
recordThirdPartyAuthAppleLoginComplete:
|
|
recordThirdPartyAuthAppleLoginCompleteStub,
|
|
recordThirdPartyAuthGoogleRegComplete:
|
|
recordThirdPartyAuthGoogleRegCompleteStub,
|
|
recordThirdPartyAuthAppleRegComplete:
|
|
recordThirdPartyAuthAppleRegCompleteStub,
|
|
recordThirdPartyAuthSetPasswordComplete:
|
|
recordThirdPartyAuthSetPasswordCompleteStub,
|
|
recordAccountDeleteComplete: recordAccountDeleteCompleteStub,
|
|
recordAccountDeleteTaskHandled: recordAccountDeleteTaskHandledStub,
|
|
recordPasswordResetEmailConfirmationSent:
|
|
recordPasswordResetEmailConfirmationSentStub,
|
|
recordPasswordResetEmailConfirmationSuccess:
|
|
recordPasswordResetEmailConfirmationSuccessStub,
|
|
recordTwoFactorAuthCodeComplete: recordTwoFactorAuthCodeCompleteStub,
|
|
recordTwoFactorAuthReplaceCodeComplete:
|
|
recordTwoFactorAuthReplaceCodeCompleteStub,
|
|
recordTwoFactorAuthSetCodesComplete:
|
|
recordTwoFactorAuthSetCodesCompleteStub,
|
|
recordTwoFactorAuthSetupVerifySuccess:
|
|
recordTwoFactorAuthSetupVerifySuccessStub,
|
|
recordTwoFactorAuthSetupInvalidCodeError:
|
|
recordTwoFactorAuthSetupInvalidCodeErrorStub,
|
|
recordTwoFactorAuthReplaceSuccess: recordTwoFactorAuthReplaceSuccessStub,
|
|
recordTwoFactorAuthReplaceFailure: recordTwoFactorAuthReplaceFailureStub,
|
|
recordTwoStepAuthPhoneCodeSent: recordTwoStepAuthPhoneCodeSentStub,
|
|
recordTwoStepAuthPhoneCodeSendError:
|
|
recordTwoStepAuthPhoneCodeSendErrorStub,
|
|
recordTwoStepAuthPhoneCodeComplete:
|
|
recordTwoStepAuthPhoneCodeCompleteStub,
|
|
recordTwoStepAuthPhoneRemoveSuccess:
|
|
recordTwoStepAuthPhoneRemoveSuccessStub,
|
|
recordTwoStepAuthRemoveSuccess: recordTwoStepAuthRemoveSuccessStub,
|
|
recordPasswordResetTwoFactorSuccess:
|
|
recordPasswordResetTwoFactorSuccessStub,
|
|
recordPasswordResetRecoveryCodeSuccess:
|
|
recordPasswordResetRecoveryCodeSuccessStub,
|
|
recordInactiveAccountDeletionStatusChecked:
|
|
recordInactiveAccountDeletionStatusCheckedStub,
|
|
recordInactiveAccountDeletionFirstEmailTaskRequest:
|
|
recordInactiveAccountDeletionFirstEmailTaskRequestStub,
|
|
recordInactiveAccountDeletionFirstEmailTaskEnqueued:
|
|
recordInactiveAccountDeletionFirstEmailTaskEnqueuedStub,
|
|
recordInactiveAccountDeletionFirstEmailTaskRejected:
|
|
recordInactiveAccountDeletionFirstEmailTaskRejectedStub,
|
|
recordInactiveAccountDeletionFirstEmailSkipped:
|
|
recordInactiveAccountDeletionFirstEmailSkippedStub,
|
|
recordInactiveAccountDeletionSecondEmailTaskRequest:
|
|
recordInactiveAccountDeletionSecondEmailTaskRequestStub,
|
|
recordInactiveAccountDeletionSecondEmailTaskEnqueued:
|
|
recordInactiveAccountDeletionSecondEmailTaskEnqueuedStub,
|
|
recordInactiveAccountDeletionSecondEmailTaskRejected:
|
|
recordInactiveAccountDeletionSecondEmailTaskRejectedStub,
|
|
recordInactiveAccountDeletionSecondEmailSkipped:
|
|
recordInactiveAccountDeletionSecondEmailSkippedStub,
|
|
recordInactiveAccountDeletionFinalEmailTaskRequest:
|
|
recordInactiveAccountDeletionFinalEmailTaskRequestStub,
|
|
recordInactiveAccountDeletionFinalEmailTaskEnqueued:
|
|
recordInactiveAccountDeletionFinalEmailTaskEnqueuedStub,
|
|
recordInactiveAccountDeletionFinalEmailTaskRejected:
|
|
recordInactiveAccountDeletionFinalEmailTaskRejectedStub,
|
|
recordInactiveAccountDeletionFinalEmailSkipped:
|
|
recordInactiveAccountDeletionFinalEmailSkippedStub,
|
|
recordInactiveAccountDeletionDeletionScheduled:
|
|
recordInactiveAccountDeletionDeletionScheduledStub,
|
|
recordInactiveAccountDeletionDeletionSkipped:
|
|
recordInactiveAccountDeletionDeletionSkippedStub,
|
|
recordEmailDeliverySuccess: recordEmailDeliverySuccessStub,
|
|
recordPasswordResetRecoveryPhoneCodeSent:
|
|
recordPasswordResetRecoveryPhoneCodeSentStub,
|
|
recordPasswordResetRecoveryPhoneCodeSendError:
|
|
recordPasswordResetRecoveryPhoneCodeSendErrorStub,
|
|
recordPasswordResetRecoveryPhoneCodeComplete:
|
|
recordPasswordResetRecoveryPhoneCodeCompleteStub,
|
|
recordTwoStepAuthPhoneReplaceSuccess:
|
|
recordTwoStepAuthPhoneReplaceSuccess,
|
|
recordTwoStepAuthPhoneReplaceFailure:
|
|
recordTwoStepAuthPhoneReplaceFailure,
|
|
recordLoginConfirmSkipForKnownIp: recordLoginConfirmSkipForKnownIp,
|
|
recordLoginConfirmSkipForNewAccount: recordLoginConfirmSkipForNewAccount,
|
|
recordLoginConfirmSkipForKnownDevice:
|
|
recordLoginConfirmSkipForKnownDevice,
|
|
}),
|
|
},
|
|
});
|
|
const gleanMetrics: (config: any) => GleanMetricsType = gleanProxy.gleanMetrics;
|
|
const logErrorWithGlean = gleanProxy.logErrorWithGlean;
|
|
|
|
const config = {
|
|
gleanMetrics: {
|
|
enabled: true,
|
|
applicationId: 'accounts_backend_test',
|
|
channel: 'test',
|
|
loggerAppName: 'auth-server-tests',
|
|
},
|
|
oauth: {
|
|
clientIds: {},
|
|
},
|
|
};
|
|
|
|
const request = {
|
|
app: {
|
|
isMetricsEnabled: true,
|
|
metricsContext: {},
|
|
ua: {},
|
|
clientAddress: '10.10.10.10',
|
|
},
|
|
auth: { credentials: {} },
|
|
headers: {
|
|
'user-agent': 'ELinks/0.9.3 (textmode; SunOS)',
|
|
},
|
|
} as unknown as AuthRequest;
|
|
|
|
describe('Glean server side events', () => {
|
|
afterEach(() => {
|
|
recordStub.reset();
|
|
recordRegAccCreatedStub.reset();
|
|
recordRegEmailSentStub.reset();
|
|
recordRegAccVerifiedStub.reset();
|
|
recordRegCompleteStub.reset();
|
|
recordRegSubmitErrorStub.reset();
|
|
recordAccessTokenCheckedStub.reset();
|
|
});
|
|
|
|
describe('enabled state', () => {
|
|
it('can be disabled via config', async () => {
|
|
const gleanConfig = {
|
|
...config,
|
|
gleanMetrics: { ...config.gleanMetrics, enabled: false },
|
|
};
|
|
const glean = gleanMetrics(gleanConfig);
|
|
await glean.login.success(request);
|
|
|
|
sinon.assert.notCalled(recordStub);
|
|
});
|
|
|
|
it('can be disabled by the account', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.login.success({
|
|
...request,
|
|
app: { ...request.app, isMetricsEnabled: false },
|
|
} as unknown as AuthRequest);
|
|
|
|
sinon.assert.notCalled(recordStub);
|
|
});
|
|
|
|
it('logs when enabled', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.login.success(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
});
|
|
});
|
|
|
|
describe('metrics', () => {
|
|
let glean: GleanMetricsType;
|
|
|
|
beforeEach(() => {
|
|
glean = gleanMetrics(config);
|
|
});
|
|
|
|
it('defaults', async () => {
|
|
await glean.login.success(request);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics.user_agent, request.headers['user-agent']);
|
|
assert.equal(metrics.ip_address, request.app.clientAddress);
|
|
|
|
delete metrics.event_name; // there's always a name of course
|
|
delete metrics.user_agent;
|
|
delete metrics.ip_address;
|
|
|
|
// the rest should default to an empty string
|
|
assert.isTrue(Object.values(metrics).every((x) => x === ''));
|
|
});
|
|
|
|
describe('user id', () => {
|
|
it('uses the id from the passed in data', async () => {
|
|
await glean.login.success(request, { uid: 'rome_georgia' });
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['account_user_id_sha256'],
|
|
'7c05994f542f257aac8ee13eebc711f07e480b06de5498c7e63f9b3e615ac8af'
|
|
);
|
|
});
|
|
|
|
it('uses the id from the session token', async () => {
|
|
const sessionAuthedReq = {
|
|
...request,
|
|
auth: {
|
|
...request.auth,
|
|
credentials: { ...request.auth.credentials, uid: 'athens_texas' },
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(sessionAuthedReq);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['account_user_id_sha256'],
|
|
'0c1d07d948132bcec965796e16a0bef4bd8aca2bc920c26f3a6d4f46e8971fcd'
|
|
);
|
|
});
|
|
|
|
it('uses the id from oauth token', async () => {
|
|
const oauthReq = {
|
|
...request,
|
|
auth: {
|
|
...request.auth,
|
|
credentials: {
|
|
...request.auth.credentials,
|
|
user: 'paris_tennessee',
|
|
},
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(oauthReq);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['account_user_id_sha256'],
|
|
'b2710dc44cb98ec552e189e48b43e460366f1ae40f922bf325e2635b098962e7'
|
|
);
|
|
});
|
|
|
|
it('uses the "reason" event property from the data argument', async () => {
|
|
await glean.login.error(request, { reason: 'too_cool_for_school' });
|
|
const metrics = recordStub.args[0][0];
|
|
|
|
assert.equal(metrics['event_reason'], 'too_cool_for_school');
|
|
});
|
|
});
|
|
|
|
describe('oauth', () => {
|
|
it('uses the client id from the oauth token', async () => {
|
|
const req = {
|
|
...request,
|
|
auth: {
|
|
...request.auth,
|
|
credentials: {
|
|
...request.auth.credentials,
|
|
client_id: 'runny_eggs',
|
|
},
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(req);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['relying_party_oauth_client_id'], 'runny_eggs');
|
|
});
|
|
|
|
it('uses the client id from the payload', async () => {
|
|
const req = {
|
|
...request,
|
|
payload: { ...(request.payload as object), client_id: 'corny_jokes' },
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(req);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['relying_party_oauth_client_id'], 'corny_jokes');
|
|
});
|
|
|
|
it('uses the client id from the event data', async () => {
|
|
await glean.login.success(request, { oauthClientId: 'runny_eggs' });
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['relying_party_oauth_client_id'], 'runny_eggs');
|
|
});
|
|
|
|
it('uses the service name from the metrics context', async () => {
|
|
const req = {
|
|
...request,
|
|
app: {
|
|
...request.app,
|
|
metricsContext: {
|
|
...request.app.metricsContext,
|
|
service: 'brass_monkey',
|
|
},
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(req);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['relying_party_service'], 'brass_monkey');
|
|
});
|
|
|
|
it('uses the client id in the service name property', async () => {
|
|
const req = {
|
|
...request,
|
|
app: {
|
|
...request.app,
|
|
metricsContext: {
|
|
...request.app.metricsContext,
|
|
client_id: undefined,
|
|
service: '7f1a38488a0df47b',
|
|
},
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(req);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['relying_party_oauth_client_id'],
|
|
'7f1a38488a0df47b'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('user session', () => {
|
|
it('sets the device type', async () => {
|
|
const req = {
|
|
...request,
|
|
app: {
|
|
...request.app,
|
|
ua: {
|
|
...request.app.ua,
|
|
deviceType: 'phablet',
|
|
},
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(req);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['session_device_type'], 'phablet');
|
|
});
|
|
|
|
it('sets the entrypoint', async () => {
|
|
const req = {
|
|
...request,
|
|
app: {
|
|
...request.app,
|
|
metricsContext: {
|
|
...request.app.metricsContext,
|
|
entrypoint: 'homepage',
|
|
},
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(req);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['session_entrypoint'], 'homepage');
|
|
});
|
|
|
|
it('sets the flow id', async () => {
|
|
const req = {
|
|
...request,
|
|
app: {
|
|
...request.app,
|
|
metricsContext: {
|
|
...request.app.metricsContext,
|
|
flowId: '101',
|
|
},
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(req);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['session_flow_id'], '101');
|
|
});
|
|
});
|
|
|
|
describe('utm', () => {
|
|
let metrics: Dictionary<string>;
|
|
|
|
beforeEach(async () => {
|
|
const req = {
|
|
...request,
|
|
app: {
|
|
...request.app,
|
|
metricsContext: {
|
|
...request.app.metricsContext,
|
|
utmCampaign: 'camp',
|
|
utmContent: 'con',
|
|
utmMedium: 'mid',
|
|
utmSource: 'sour',
|
|
utmTerm: 'erm',
|
|
},
|
|
},
|
|
} as unknown as AuthRequest;
|
|
await glean.login.success(req);
|
|
metrics = recordStub.args[0][0];
|
|
});
|
|
|
|
it('sets the campaign', async () => {
|
|
assert.equal(metrics['utm_campaign'], 'camp');
|
|
});
|
|
|
|
it('sets the content', async () => {
|
|
assert.equal(metrics['utm_content'], 'con');
|
|
});
|
|
|
|
it('sets the medium', async () => {
|
|
assert.equal(metrics['utm_medium'], 'mid');
|
|
});
|
|
|
|
it('sets the source', async () => {
|
|
assert.equal(metrics['utm_source'], 'sour');
|
|
});
|
|
|
|
it('sets the term', async () => {
|
|
assert.equal(metrics['utm_term'], 'erm');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('account events', () => {
|
|
let glean: GleanMetricsType;
|
|
|
|
beforeEach(() => {
|
|
glean = gleanMetrics(config);
|
|
});
|
|
|
|
it('logs a "account_password_reset" event', async () => {
|
|
await glean.resetPassword.accountReset(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'account_password_reset');
|
|
sinon.assert.calledOnce(recordAccountPasswordResetStub);
|
|
});
|
|
|
|
it('logs a "account_delete_complete" event', async () => {
|
|
await glean.account.deleteComplete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'account_delete_complete');
|
|
sinon.assert.calledOnce(recordAccountDeleteCompleteStub);
|
|
});
|
|
});
|
|
|
|
describe('two factor auth', () => {
|
|
let glean: GleanMetricsType;
|
|
|
|
beforeEach(() => {
|
|
glean = gleanMetrics(config);
|
|
});
|
|
|
|
it('logs a "two_factor_auth_code_complete" event', async () => {
|
|
await glean.twoFactorAuth.codeComplete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'two_factor_auth_code_complete');
|
|
sinon.assert.calledOnce(recordTwoFactorAuthCodeCompleteStub);
|
|
});
|
|
|
|
it('logs a "two_factor_auth_replace_code_complete" event', async () => {
|
|
await glean.twoFactorAuth.replaceCodeComplete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'two_factor_auth_replace_code_complete'
|
|
);
|
|
sinon.assert.calledOnce(recordTwoFactorAuthReplaceCodeCompleteStub);
|
|
});
|
|
|
|
it('logs a "two_factor_auth_setup_invalid_code_error" event', async () => {
|
|
await glean.twoFactorAuth.setupInvalidCodeError(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'two_factor_auth_setup_invalid_code_error'
|
|
);
|
|
sinon.assert.calledOnce(recordTwoFactorAuthSetupInvalidCodeErrorStub);
|
|
});
|
|
|
|
it('logs a "two_factor_auth_replace_success" event', async () => {
|
|
await glean.twoFactorAuth.replaceSuccess(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'two_factor_auth_replace_success');
|
|
sinon.assert.calledOnce(recordTwoFactorAuthReplaceSuccessStub);
|
|
});
|
|
|
|
it('logs a "two_factor_auth_replace_failure" event', async () => {
|
|
await glean.twoFactorAuth.replaceFailure(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'two_factor_auth_replace_failure');
|
|
sinon.assert.calledOnce(recordTwoFactorAuthReplaceFailureStub);
|
|
});
|
|
});
|
|
|
|
describe('registration', () => {
|
|
let glean: GleanMetricsType;
|
|
|
|
beforeEach(() => {
|
|
glean = gleanMetrics(config);
|
|
});
|
|
|
|
describe('accountCreated', () => {
|
|
it('logs a "reg_acc_created" event', async () => {
|
|
await glean.registration.accountCreated(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'reg_acc_created');
|
|
sinon.assert.calledOnce(recordRegAccCreatedStub);
|
|
});
|
|
});
|
|
|
|
describe('confirmationEmailSent', () => {
|
|
it('logs a "reg_email_sent" event', async () => {
|
|
await glean.registration.confirmationEmailSent(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'reg_email_sent');
|
|
sinon.assert.calledOnce(recordRegEmailSentStub);
|
|
});
|
|
});
|
|
|
|
describe('accountVerified', () => {
|
|
it('logs a "reg_acc_verified" event', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.registration.accountVerified(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'reg_acc_verified');
|
|
sinon.assert.calledOnce(recordRegAccVerifiedStub);
|
|
});
|
|
});
|
|
|
|
describe('reg_complete', () => {
|
|
it('logs a "reg_complete" event', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.registration.complete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'reg_complete');
|
|
sinon.assert.calledOnce(recordRegCompleteStub);
|
|
});
|
|
});
|
|
|
|
describe('reg_submit_error', () => {
|
|
it('logs a "reg_submit_error" event', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.registration.error(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'reg_submit_error');
|
|
sinon.assert.calledOnce(recordRegSubmitErrorStub);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('login', () => {
|
|
let glean: GleanMetricsType;
|
|
|
|
beforeEach(() => {
|
|
glean = gleanMetrics(config);
|
|
});
|
|
|
|
describe('success', () => {
|
|
it('logs a "login_success" event', async () => {
|
|
await glean.login.success(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'login_success');
|
|
});
|
|
});
|
|
|
|
describe('error', () => {
|
|
it('logs a "login_submit_backend_error" event', async () => {
|
|
await glean.login.error(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'login_submit_backend_error');
|
|
});
|
|
});
|
|
|
|
describe('totp', () => {
|
|
it('logs a "login_totp_code_success" event', async () => {
|
|
await glean.login.totpSuccess(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'login_totp_code_success');
|
|
});
|
|
|
|
it('logs a "login_totp_code_failure" event', async () => {
|
|
await glean.login.totpFailure(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'login_totp_code_failure');
|
|
});
|
|
});
|
|
|
|
describe('verifyCodeEmail', () => {
|
|
it('logs a "login_email_confirmation_sent" event', async () => {
|
|
await glean.login.verifyCodeEmailSent(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'login_email_confirmation_sent');
|
|
});
|
|
|
|
it('logs a "login_email_confirmation_success" event', async () => {
|
|
await glean.login.verifyCodeConfirmed(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['event_name'], 'login_email_confirmation_success');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('oauth', () => {
|
|
describe('tokenChecked', () => {
|
|
it('sends an empty ip address', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.oauth.tokenChecked(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(metrics['ip_address'], '');
|
|
});
|
|
|
|
it('handles undefined scopes', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.oauth.tokenChecked(request, {
|
|
scopes: undefined,
|
|
});
|
|
sinon.assert.calledOnce(recordAccessTokenCheckedStub);
|
|
const metrics = recordAccessTokenCheckedStub.args[0][0];
|
|
assert.equal(metrics['scopes'], '');
|
|
});
|
|
|
|
it('handles empty scopes', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.oauth.tokenChecked(request, {
|
|
scopes: '',
|
|
});
|
|
sinon.assert.calledOnce(recordAccessTokenCheckedStub);
|
|
const metrics = recordAccessTokenCheckedStub.args[0][0];
|
|
assert.equal(metrics['scopes'], '');
|
|
});
|
|
|
|
it('includes sorted comma separated scopes as array', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.oauth.tokenChecked(request, {
|
|
scopes: ['profile', 'openid'],
|
|
});
|
|
sinon.assert.calledOnce(recordAccessTokenCheckedStub);
|
|
const metrics = recordAccessTokenCheckedStub.args[0][0];
|
|
assert.equal(metrics['scopes'], 'openid,profile');
|
|
});
|
|
|
|
it('includes sorted comma separated scopes as string', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.oauth.tokenChecked(request, {
|
|
scopes: 'profile,openid',
|
|
});
|
|
sinon.assert.calledOnce(recordAccessTokenCheckedStub);
|
|
const metrics = recordAccessTokenCheckedStub.args[0][0];
|
|
assert.equal(metrics['scopes'], 'openid,profile');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('thirdPartyAuth', () => {
|
|
describe('googleLoginComplete', () => {
|
|
beforeEach(() => {
|
|
recordThirdPartyAuthGoogleLoginCompleteStub.reset();
|
|
});
|
|
|
|
it('log string and event metrics with account linking for Google', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.thirdPartyAuth.googleLoginComplete(request, {
|
|
reason: 'linking',
|
|
});
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'third_party_auth_google_login_complete'
|
|
);
|
|
assert.equal(metrics['event_reason'], 'linking');
|
|
sinon.assert.calledOnce(recordThirdPartyAuthGoogleLoginCompleteStub);
|
|
assert.isTrue(
|
|
recordThirdPartyAuthGoogleLoginCompleteStub.args[0][0].linking
|
|
);
|
|
});
|
|
|
|
it('log string and event metrics without account linking for Google', async () => {
|
|
const glean = gleanMetrics(config);
|
|
await glean.thirdPartyAuth.googleLoginComplete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'third_party_auth_google_login_complete'
|
|
);
|
|
assert.equal(metrics['event_reason'], '');
|
|
sinon.assert.calledOnce(recordThirdPartyAuthGoogleLoginCompleteStub);
|
|
assert.isFalse(
|
|
recordThirdPartyAuthGoogleLoginCompleteStub.args[0][0].linking
|
|
);
|
|
});
|
|
});
|
|
describe('appleLoginComplete', () => {
|
|
beforeEach(() => {
|
|
recordThirdPartyAuthAppleLoginCompleteStub.reset();
|
|
});
|
|
|
|
it('log string and event metrics with account linking for Apple', async () => {
|
|
const glean: GleanMetricsType = gleanMetrics(config);
|
|
await glean.thirdPartyAuth.appleLoginComplete(request, {
|
|
reason: 'linking',
|
|
});
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'third_party_auth_apple_login_complete'
|
|
);
|
|
assert.equal(metrics['event_reason'], 'linking');
|
|
sinon.assert.calledOnce(recordThirdPartyAuthAppleLoginCompleteStub);
|
|
assert.isTrue(
|
|
recordThirdPartyAuthAppleLoginCompleteStub.args[0][0].linking
|
|
);
|
|
});
|
|
|
|
it('log string and event metrics without account linking for Apple', async () => {
|
|
const glean: GleanMetricsType = gleanMetrics(config);
|
|
await glean.thirdPartyAuth.appleLoginComplete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'third_party_auth_apple_login_complete'
|
|
);
|
|
assert.equal(metrics['event_reason'], '');
|
|
sinon.assert.calledOnce(recordThirdPartyAuthAppleLoginCompleteStub);
|
|
assert.isFalse(
|
|
recordThirdPartyAuthAppleLoginCompleteStub.args[0][0].linking
|
|
);
|
|
});
|
|
});
|
|
describe('googleRegComplete', () => {
|
|
beforeEach(() => {
|
|
recordThirdPartyAuthGoogleRegCompleteStub.reset();
|
|
});
|
|
|
|
it('log string and event metrics for Google', async () => {
|
|
const glean: GleanMetricsType = gleanMetrics(config);
|
|
await glean.thirdPartyAuth.googleRegComplete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'third_party_auth_google_reg_complete'
|
|
);
|
|
sinon.assert.calledOnce(recordThirdPartyAuthGoogleRegCompleteStub);
|
|
});
|
|
});
|
|
describe('appleRegComplete', () => {
|
|
beforeEach(() => {
|
|
recordThirdPartyAuthAppleRegCompleteStub.reset();
|
|
});
|
|
|
|
it('log string and event metrics for Google', async () => {
|
|
const glean: GleanMetricsType = gleanMetrics(config);
|
|
await glean.thirdPartyAuth.appleRegComplete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'third_party_auth_apple_reg_complete'
|
|
);
|
|
sinon.assert.calledOnce(recordThirdPartyAuthAppleRegCompleteStub);
|
|
});
|
|
});
|
|
describe('setPasswordComplete', () => {
|
|
beforeEach(() => {
|
|
recordThirdPartyAuthSetPasswordCompleteStub.reset();
|
|
});
|
|
|
|
it('log string and event metrics with account linking', async () => {
|
|
const glean: GleanMetricsType = gleanMetrics(config);
|
|
await glean.thirdPartyAuth.setPasswordComplete(request);
|
|
sinon.assert.calledOnce(recordStub);
|
|
const metrics = recordStub.args[0][0];
|
|
assert.equal(
|
|
metrics['event_name'],
|
|
'third_party_auth_set_password_complete'
|
|
);
|
|
sinon.assert.calledOnce(recordThirdPartyAuthSetPasswordCompleteStub);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('logErrorWithGlean hapi preResponse error logger', () => {
|
|
const error = AppError.requestBlocked();
|
|
const glean = mocks.mockGlean();
|
|
|
|
describe('/account/create', () => {
|
|
it('logs a ping with glean.registration.error', () => {
|
|
(glean.registration.error as SinonStub).reset();
|
|
const request = { path: '/account/create' };
|
|
logErrorWithGlean({
|
|
glean,
|
|
request,
|
|
error,
|
|
});
|
|
sinon.assert.calledOnce(glean.registration.error as SinonStub);
|
|
sinon.assert.calledWithExactly(
|
|
glean.registration.error as SinonStub,
|
|
request,
|
|
{ reason: 'REQUEST_BLOCKED' }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('/account/login', () => {
|
|
it('logs a ping with glean.login.error', () => {
|
|
(glean.login.error as SinonStub).reset();
|
|
const request = { path: '/account/login' };
|
|
logErrorWithGlean({
|
|
glean,
|
|
request,
|
|
error,
|
|
});
|
|
sinon.assert.calledOnce(glean.login.error as SinonStub);
|
|
sinon.assert.calledWithExactly(
|
|
glean.login.error as SinonStub,
|
|
request,
|
|
{ reason: 'REQUEST_BLOCKED' }
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|