Skip to content
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

Add e2e tests for websockets #70

Closed
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
20 changes: 20 additions & 0 deletions .github/workflows/check-sdk-version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Test - Check SDK version

on:
pull_request

jobs:
check-sdk-version:
runs-on: ubuntu-20.04

steps:
- name: 🏗 Setup repo
uses: actions/checkout@v3

- name: 📦 Check it is using the latest version of the SDK
uses: actions/setup-node@v3
with:
node-version: 18.12.1
- run: corepack enable
- run: yarn install --immutable
- run: yarn run updateSdkVersion $GITHUB_BASE_REF check
28 changes: 0 additions & 28 deletions .github/workflows/master-pr-tests.yaml

This file was deleted.

14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@
yarn install
```

For the `development` branch

```bash
yarn add @tonomy/tonomy-id-sdk@development
```

## Running the app

```bash
Expand All @@ -29,6 +23,14 @@ yarn run start:dev
yarn run start:prod
```

## Update the Tonomy-ID-SDK version to the latest

```bash
yarn run updateSdkVersion development
# or
yarn run updateSdkVersion master
```

## Environment variables and configuration

`NODE_ENV` - Determines which config file in `./src/config` to use
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"updateSdkVersion": "./update_sdk_version.sh"
},
"dependencies": {
"@nestjs/common": "^10.2.3",
Expand All @@ -27,7 +28,7 @@
"@nestjs/platform-socket.io": "^10.2.3",
"@nestjs/swagger": "^7.1.10",
"@nestjs/websockets": "^10.2.3",
"@tonomy/tonomy-id-sdk": "^0.15.0-development.2",
"@tonomy/tonomy-id-sdk": "0.15.0-development.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"hcaptcha": "^0.1.1",
Expand Down
60 changes: 43 additions & 17 deletions src/communication/communication.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@
UsePipes,
} from '@nestjs/common';
import { TransformVcPipe } from './transform-vc/transform-vc.pipe';
import { MessageDto, MessageRto } from './dto/message.dto';
import { Client } from './dto/client.dto';
import { WsExceptionFilter } from './ws-exception/ws-exception.filter';
import { AuthenticationMessage } from '@tonomy/tonomy-id-sdk';
import { CommunicationGuard } from './communication.guard';
import { BodyDto } from './dto/body.dto';

export type WebsocketReturnType = {
status: HttpStatus;
details?: any;

Check warning on line 27 in src/communication/communication.gateway.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected any. Specify a different type
error?: any;

Check warning on line 28 in src/communication/communication.gateway.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected any. Specify a different type
};

@UseFilters(WsExceptionFilter)
@UsePipes(new TransformVcPipe())
Expand All @@ -39,38 +45,58 @@
/**
* Logs in the user and added it to the loggedIn map
*
* @param {MessageDto} message - the VC the user sent
* @param {BodyDto} body - The message VC or an error from the transformer
* @param {Client} client - user socket
* @returns void
*/
@SubscribeMessage('login')
connectUser(
@MessageBody() message: MessageDto,
async connectUser(
@MessageBody() body: BodyDto,
@ConnectedSocket() client: Client,
) {
if (message.getType() !== AuthenticationMessage.getType()) {
throw new HttpException(
"Message type must be 'AuthenticationMessage'",
HttpStatus.BAD_REQUEST,
);
}
try {
if (body.error) throw body.error;
if (!body.value) throw new Error('Body not found');
const message = body.value;

if (message.getType() !== AuthenticationMessage.getType()) {
throw new HttpException(
"Message type must be 'AuthenticationMessage'",
HttpStatus.BAD_REQUEST,
);
}

return this.usersService.login(message.getSender(), client);
return {
status: HttpStatus.OK,
details: await this.usersService.login(message.getSender(), client),
};
} catch (e) {
return this.usersService.handleError(e);
}
}

/**
* sends the message to the VC recipient if is connected and loggedIn
* @param message the VC the user sent
* @param {BodyDto} body - The message VC or an error from the transformer
* @param client user socket
* @returns void
*/
@SubscribeMessage('message')
@UseGuards(CommunicationGuard)
relayMessage(
@MessageBody() message: MessageDto,
async relayMessage(
@MessageBody() body: BodyDto,
@ConnectedSocket() client: Client,
) {
return this.usersService.sendMessage(client, message);
try {
if (body.error) throw body.error;
if (!body.value) throw new Error('Body not found');
const message = body.value;

return {
status: HttpStatus.OK,
details: await this.usersService.sendMessage(client, message),
};
} catch (e) {
return this.usersService.handleError(e);
}
}

/**
Expand Down
18 changes: 18 additions & 0 deletions src/communication/communication.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Client } from './dto/client.dto';
import { MessageDto } from './dto/message.dto';
import settings from '../settings';
import { WebsocketReturnType } from './communication.gateway';

@Injectable()
export class CommunicationService {
Expand Down Expand Up @@ -67,4 +68,21 @@

return true;
}

handleError(e): WebsocketReturnType {
if (settings.env !== 'test') console.error(e);

Check warning on line 73 in src/communication/communication.service.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected console statement
// this.logger.error(e); // This does not print stack trace

if (e instanceof HttpException) {
return {
status: e.getStatus(),
error: e.getResponse(),
};
}

return {
status: HttpStatus.INTERNAL_SERVER_ERROR,
error: e.message,
};
}
}
6 changes: 6 additions & 0 deletions src/communication/dto/body.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { MessageDto } from './message.dto';

export type BodyDto = {
value?: MessageDto;
error?: any;

Check warning on line 5 in src/communication/dto/body.dto.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected any. Specify a different type
};
67 changes: 39 additions & 28 deletions src/communication/transform-vc/transform-vc.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,53 @@ import {
PipeTransform,
} from '@nestjs/common';
import { MessageDto, MessageRto } from '../dto/message.dto';
import { BodyDto } from '../dto/body.dto';

@Injectable()
export class TransformVcPipe implements PipeTransform {
async transform(value: MessageRto, metadata: ArgumentMetadata) {
if (metadata.type === 'body') {
const message = new MessageDto(value.message);
async transform(
value: MessageRto,
metadata: ArgumentMetadata,
): Promise<BodyDto | MessageRto> {
try {
if (metadata.type === 'body') {
const message = new MessageDto(value.message);

try {
const result = await message.verify();
try {
const result = await message.verify();

if (!result)
throw new HttpException(
`VC not could not verify signer from ${message.getSender()}`,
HttpStatus.UNAUTHORIZED,
);
return message;
} catch (e) {
if (
e.message?.startsWith(
'resolver_error: Unable to resolve DID document for',
)
) {
throw new HttpException(
`DID could not be resolved from ${message.getSender()}`,
HttpStatus.NOT_FOUND,
);
}
if (!result)
throw new HttpException(
`VC not could not verify signer from ${message.getSender()}`,
HttpStatus.UNAUTHORIZED,
);
return { value: message };
} catch (e) {
if (
e.message?.startsWith(
'resolver_error: Unable to resolve DID document for',
)
) {
throw new HttpException(
`DID could not be resolved from ${message.getSender()}`,
HttpStatus.NOT_FOUND,
);
}

if (e instanceof HttpException) {
throw e;
}
if (e instanceof HttpException) {
throw e;
}

throw new HttpException(e, HttpStatus.INTERNAL_SERVER_ERROR);
throw new HttpException(e, HttpStatus.INTERNAL_SERVER_ERROR);
}
} else {
// Do nothing. transform() is not needed
return value;
}
} catch (e) {
return {
error: e,
};
}

return value;
}
}
4 changes: 2 additions & 2 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

const env = process.env.NODE_ENV || 'development';

console.log(`NODE_ENV=${env}`);
if (env !== 'test') console.log(`NODE_ENV=${env}`);

Check warning on line 8 in src/settings.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected console statement

type ConfigType = {
blockchainUrl: string;
Expand Down Expand Up @@ -69,12 +69,12 @@
settings.config = Object.assign({}, config);

if (process.env.BLOCKCHAIN_URL) {
console.log(`Using BLOCKCHAIN_URL from env: ${process.env.BLOCKCHAIN_URL}`);

Check warning on line 72 in src/settings.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected console statement

settings.config.blockchainUrl = process.env.BLOCKCHAIN_URL;
}

console.log('settings', settings);
if (env !== 'test') console.log('settings', settings);

Check warning on line 77 in src/settings.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected console statement

settings.secrets = {
createAccountPrivateKey: EosioUtil.defaultAntelopePrivateKey.toString(),
Expand Down
68 changes: 68 additions & 0 deletions test/communication.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { AppModule } from '../src/app.module';
import { connectSocket, emitMessage } from './ws-client.helper';
import { Socket } from 'socket.io-client';
import {
AuthenticationMessage,
ES256KSigner,
generateRandomKeyPair,
setSettings,
} from '@tonomy/tonomy-id-sdk';
// @ts-expect-error - cannot find module or its corresponding type declarations
import { createJWK, toDid } from '@tonomy/tonomy-id-sdk/util';

setSettings({
blockchainUrl: 'http://localhost:8888',
});

describe('CommunicationGateway (e2e)', () => {
let app: INestApplication;
let socket: Socket;

beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();

app = moduleFixture.createNestApplication();
app.listen(5000);

socket = await connectSocket();
});

afterEach(async () => {
await socket.disconnect();
await socket.close();
await app.close();
});

describe('login event', () => {
it('fails when provide an empty body', async () => {
await expect(() => emitMessage(socket, 'login', {})).rejects.toThrow(
"Cannot read properties of undefined (reading 'getCredentialSubject')",
);
});

it('succeeds for did:jwk message', async () => {
const { privateKey, publicKey } = generateRandomKeyPair();
const signer = ES256KSigner(privateKey.data.array, true);
const jwk = await createJWK(publicKey);
const did = toDid(jwk);

const issuer = {
did,
signer,
alg: 'ES256K-R',
};
const message = await AuthenticationMessage.signMessageWithoutRecipient(
{ data: 'test' },
issuer,
);

const response = await emitMessage(socket, 'login', { message });

expect(response).toBeTruthy();
});
});
});
Loading
Loading