Skip to content

Commit

Permalink
test: Sentry integration adding breadcrumbs and tags with a mock
Browse files Browse the repository at this point in the history
  • Loading branch information
tux3 committed Jan 8, 2024
1 parent bf88db0 commit d966da5
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 12 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/functional-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"package.json"
],
"dependencies": {
"@sentry/types": "7.76.0",
"@tanker/client-browser": "0.0.1",
"@tanker/client-node": "0.0.1",
"@tanker/core": "0.0.1",
Expand Down
5 changes: 3 additions & 2 deletions packages/functional-tests/src/__tests__/client.spec.node.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import Tanker from '@tanker/client-node';
import { pouchDBMemory } from '@tanker/datastore-pouchdb-memory';

import type { b64string } from '@tanker/core';
import type { TankerOptions, b64string } from '@tanker/core';

import type { DefaultDownloadType, TestResources } from '../helpers';
import { appdUrl, makeRandomUint8Array } from '../helpers';
import { generateFunctionalTests } from '..';

const makeTanker = (appId: b64string, storagePrefix: string): Tanker => {
const makeTanker = (appId: b64string, storagePrefix: string, extraOpts: TankerOptions): Tanker => {
const tanker = new Tanker({
appId,
dataStore: { adapter: pouchDBMemory, prefix: storagePrefix },
sdkType: 'js-functional-tests-node',
url: appdUrl,
...extraOpts,
});

return tanker;
Expand Down
5 changes: 3 additions & 2 deletions packages/functional-tests/src/__tests__/client.spec.web.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import Tanker from '@tanker/client-browser';

import type { b64string } from '@tanker/core';
import type { TankerOptions, b64string } from '@tanker/core';

import type { DefaultDownloadType, TestResources } from '../helpers';
import { appdUrl, makeRandomUint8Array } from '../helpers';
import { generateFunctionalTests } from '..';

const makeTanker = (appId: b64string, storagePrefix: string): Tanker => {
const makeTanker = (appId: b64string, storagePrefix: string, extraOpts: TankerOptions): Tanker => {
const tanker = new Tanker({
appId,
dataStore: { prefix: storagePrefix },
sdkType: 'js-functional-tests-web',
url: appdUrl,
...extraOpts,
});

return tanker;
Expand Down
7 changes: 4 additions & 3 deletions packages/functional-tests/src/helpers/Device.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Tanker } from '@tanker/core';
import { Tanker, type TankerOptions } from '@tanker/core';
import { uuid } from '@tanker/test-utils';

export type TankerFactory = (appId: string, storagePrefix: string) => Tanker;
export type TankerFactory = (appId: string, storagePrefix: string, extraOpts: TankerOptions) => Tanker;

const VERIFICATION = { passphrase: 'passphrase' };

Expand All @@ -18,14 +18,15 @@ export class Device {
this.storagePrefix = storagePrefix;
}

static async create(makeTanker: (appId: string, storagePrefix: string) => Tanker, appId: string, identity: string): Promise<Device> {
static async create(makeTanker: TankerFactory, appId: string, identity: string): Promise<Device> {
return new Device(makeTanker, appId, identity, uuid.v4());
}

async open(): Promise<Tanker> {
const tanker = this.makeTanker(
this.appId,
this.storagePrefix,
{},
);
const status = await tanker.start(this.identity);
if (status === Tanker.statuses.IDENTITY_REGISTRATION_NEEDED)
Expand Down
4 changes: 2 additions & 2 deletions packages/functional-tests/src/helpers/TestArgs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Tanker } from '@tanker/core';
import type { Tanker, TankerOptions } from '@tanker/core';
import type { Class, Data } from '@tanker/types';

import type { AppHelper } from './AppHelper';
Expand All @@ -12,5 +12,5 @@ export type TestArgs = {
appHelper: AppHelper;
resources: TestResources;
defaultDownloadType: DefaultDownloadType;
makeTanker: (b64AppId?: string) => Tanker;
makeTanker: (b64AppId?: string, extraOpts?: TankerOptions) => Tanker;
};
8 changes: 5 additions & 3 deletions packages/functional-tests/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ready as cryptoReady, utils } from '@tanker/crypto';
import type { Tanker, b64string } from '@tanker/core';
import type { Tanker, TankerOptions, b64string } from '@tanker/core';
import { silencer } from '@tanker/test-utils';

import { makePrefix, AppHelper, appdUrl, managementSettings, oidcSettings, trustchaindUrl } from './helpers';
Expand All @@ -15,11 +15,12 @@ import { generateSessionTests } from './session';
import { generateUploadTests } from './upload';
import { generateVerificationTests } from './verification';
import { generateConcurrencyTests } from './concurrency';
import { generateSentryTests } from './sentry';
import { generateSessionTokenTests } from './sessionToken';

export function generateFunctionalTests(
name: string,
makeTanker: (appId: b64string, storagePrefix: string) => Tanker,
makeTanker: (appId: b64string, storagePrefix: string, extraOpts: TankerOptions) => Tanker,
generateTestResources: () => { resources: TestResources; defaultDownloadType: DefaultDownloadType },
) {
if (!appdUrl || !managementSettings || !oidcSettings || !trustchaindUrl) {
Expand Down Expand Up @@ -50,7 +51,7 @@ export function generateFunctionalTests(
args.appHelper = await AppHelper.newApp(makeTanker);
const b64DefaultAppId = utils.toBase64(args.appHelper.appId);

args.makeTanker = (b64AppId = b64DefaultAppId) => makeTanker(b64AppId, makePrefix());
args.makeTanker = (b64AppId = b64DefaultAppId, extraOpts = {}) => makeTanker(b64AppId, makePrefix(), extraOpts);

silencer.silence('warn', /deprecated/);
});
Expand All @@ -73,6 +74,7 @@ export function generateFunctionalTests(
generateUploadTests(args);
generateNetworkTests(args);
generateConcurrencyTests(args);
generateSentryTests(args);
generateSessionTokenTests(args);
});
}
Expand Down
105 changes: 105 additions & 0 deletions packages/functional-tests/src/sentry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Tanker, type b64string } from '@tanker/core';
import { expect } from '@tanker/test-utils';
import { errors } from '@tanker/core';
import { utils } from '@tanker/crypto';

import type { TestArgs, AppHelper } from './helpers';
import type { Breadcrumb, BreadcrumbHint, Primitive } from '@sentry/types';
import { getPublicIdentity } from '@tanker/identity';

class MockSentryHub {
breadcrumbs: Array<Breadcrumb>;
tags: { [key: string]: Primitive };

constructor() {
this.breadcrumbs = [];
this.tags = {};
}

addBreadcrumb = (breadcrumb: Breadcrumb, _?: BreadcrumbHint) => {
this.breadcrumbs.push(breadcrumb);
};

setTag = (key: string, value: Primitive) => {
this.tags[key] = value;
};
}

export const generateSentryTests = (args: TestArgs) => {
const fakeMissingResource = utils.fromBase64('CrrQdawRM9/icauwqmrgFiHal4v3uMQnqptJcz4nOCV1Lag+RKvttOr6XAzfQSQai9PGtoi5hLcELy+e');

describe('Sentry integration', () => {
let hub: MockSentryHub;
let alice: Tanker;
let aliceIdentity: b64string;
let appHelper: AppHelper;

before(async () => {
({ appHelper } = args);
});

beforeEach(async () => {
hub = new MockSentryHub();
// @ts-expect-error Mock doesn't implement the full interface
alice = args.makeTanker(undefined, { sentryHub: hub });
aliceIdentity = await appHelper.generateIdentity();
await alice.start(aliceIdentity);
await alice.registerIdentity({ passphrase: 'passphrase' });
});

afterEach(async () => {
await alice.stop();
});

it("doesn't set tags when everything goes well", async () => {
const encrypted = await alice.encrypt('foo');
await alice.decrypt(encrypted);
expect(hub.tags).to.deep.equal({});
});

it('sets tags when decryption fails', async () => {
await expect(alice.decrypt(fakeMissingResource)).to.be.rejectedWith(errors.InvalidArgument);

const aliceUserId = utils.fromB64Json(aliceIdentity)['value'];

expect(hub.tags['tanker_app_id']).to.equal(utils.toBase64(appHelper.appId));
expect(hub.tags['tanker_user_id']).to.equal(aliceUserId);
expect(hub.tags['tanker_status']).to.equal('READY');
});

it('logs a breadcrumb when decryption fails', async () => {
await expect(alice.decrypt(fakeMissingResource)).to.be.rejectedWith(errors.InvalidArgument);

expect(hub.breadcrumbs).to.have.lengthOf(2);
expect(hub.breadcrumbs[0]?.message).to.contain('Key not found'); // Transparent session key
expect(hub.breadcrumbs[1]?.message).to.contain('Key not found'); // Individual resource key
});

it('keeps breadcrumbs of previous operations', async () => {
const encryptedGood = await alice.encrypt('good');
await alice.decrypt(encryptedGood);

await expect(alice.decrypt(fakeMissingResource)).to.be.rejectedWith(errors.InvalidArgument);

expect(hub.breadcrumbs).to.have.lengthOf(3);
expect(hub.breadcrumbs[0]?.message).to.contain('Tanker key found in cache'); // 1st decrypt key found
expect(hub.breadcrumbs[1]?.message).to.contain('Key not found'); // 2nd transparent session key
expect(hub.breadcrumbs[2]?.message).to.contain('Key not found'); // 2nd individual resource key
});

it('logs a breadcrumb when decrypting with a key fetched from the server', async () => {
const bob = args.makeTanker();
await bob.start(await appHelper.generateIdentity());
await bob.registerIdentity({ passphrase: 'passphrase' });
const options = {
shareWithUsers: [await getPublicIdentity(aliceIdentity)],
};
const encrypted = await bob.encrypt('foo', options);

await alice.decrypt(encrypted);

expect(hub.breadcrumbs).to.have.lengthOf(1);
expect(hub.breadcrumbs[0]?.message).to.contain('Tanker key not found in cache, but fetched from server');
});
});
};

0 comments on commit d966da5

Please sign in to comment.