Skip to content

Commit

Permalink
feat: 🎸 handle when artemis is unconfigured
Browse files Browse the repository at this point in the history
adds checks for when artemis is not configured + add some helpful fields
to user exposed models
  • Loading branch information
polymath-eric committed Jan 17, 2024
1 parent 4902eb8 commit 13f9bd7
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 7 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,16 @@ NOTIFICATIONS_LEGITIMACY_SECRET=## A secret used to create HMAC signatures ##
AUTH_STRATEGY=## list of comma separated auth strategies to use e.g. (`apiKey,open`) ##
API_KEYS=## list of comma separated api keys to initialize the `apiKey` strategy with ##
# Datastore:
REST_POSTGRES_HOST=## Domain or IP indicating of the DB ##
REST_POSTGRES_HOST=## Domain or IP of DB instance ##
REST_POSTGRES_PORT=## Port the DB is listening (usually 5432) ##
REST_POSTGRES_USER=## DB user to use##
REST_POSTGRES_PASSWORD=## Password of the user ##
REST_POSTGRES_DATABASE=## Database to use ##
# Artemis:
ARTEMIS_HOST=localhost## Domain or IP of artemis instance ##
ARTEMIS_USERNAME=artemis ## Artemis user ##
ARTEMIS_PASSWORD=artemis ## Artemis password ##
ARTEMIS_PORT=5672 ## Port of AMQP acceptor ##
```

## Signing Transactions
Expand Down
12 changes: 12 additions & 0 deletions src/artemis/artemis.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,16 @@ jest.mock('rhea-promise', () => {
describe('ArtemisService', () => {
let service: ArtemisService;
let logger: DeepMocked<PolymeshLogger>;
let configSpy: jest.SpyInstance;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ArtemisService, mockPolymeshLoggerProvider],
}).compile();

service = module.get<ArtemisService>(ArtemisService);
configSpy = jest.spyOn(service, 'isConfigured');
configSpy.mockReturnValue(true);
logger = module.get<typeof logger>(PolymeshLogger);
});

Expand Down Expand Up @@ -166,5 +169,14 @@ describe('ArtemisService', () => {

expect(logger.error).toHaveBeenCalled();
});

it('should do no work if service is not configured', async () => {
configSpy.mockReturnValue(false);

const initialCallCount = mockConnectionClose.mock.calls.length;
await service.onApplicationShutdown();

expect(mockConnectionClose.mock.calls.length).toEqual(initialCallCount);
});
});
});
8 changes: 8 additions & 0 deletions src/artemis/artemis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ export class ArtemisService implements OnApplicationShutdown {
this.logger.setContext(ArtemisService.name);
}

public isConfigured(): boolean {
return !!process.env.ARTEMIS_HOST;
}

public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
this.logger.debug(
`artemis service received application shutdown request, sig: ${signal} - now closing connections`
);

if (!this.isConfigured()) {
return;
}

const closePromises = [
...this.receivers.map(receiver => receiver.close()),
...this.addressEntries().map(entry => entry.sender.close()),
Expand Down
5 changes: 5 additions & 0 deletions src/offline-starter/models/offline-receipt.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { TransactionPayload } from '@polymeshassociation/polymesh-sdk/types';
import { FromBigNumber } from '~/common/decorators/transformation';

export class OfflineReceiptModel {
@ApiProperty({
description: 'The internal ID of the transaction',
})
readonly id: string;

@ApiProperty({
description: 'The AMQP delivery ID',
type: BigNumber,
Expand Down
15 changes: 11 additions & 4 deletions src/offline-starter/offline-starter.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { GenericPolymeshTransaction } from '@polymeshassociation/polymesh-sdk/types';

import { ArtemisService } from '~/artemis/artemis.service';
import { AppConfigError } from '~/common/errors';
import { AddressName } from '~/common/utils/amqp';
import { mockPolymeshLoggerProvider } from '~/logger/mock-polymesh-logger';
import { OfflineStarterService } from '~/offline-starter/offline-starter.service';
Expand All @@ -27,17 +28,23 @@ describe('OfflineStarterService', () => {
});

describe('method: beginTransaction', () => {
const mockTx = new MockPolymeshTransaction();
mockTx.toSignablePayload.mockReturnValue('mockPayload');
const tx = mockTx as unknown as GenericPolymeshTransaction<unknown, unknown>;
it('should submit the transaction to the queue', async () => {
const mockTx = new MockPolymeshTransaction();
mockTx.toSignablePayload.mockReturnValue('mockPayload');
const tx = mockTx as unknown as GenericPolymeshTransaction<unknown, unknown>;

await service.beginTransaction(tx, { clientId: 'someId' });

expect(mockArtemisService.sendMessage).toHaveBeenCalledWith(
AddressName.Requests,
expect.objectContaining({ id: expect.any(String), payload: 'mockPayload' })
);
});

it('should throw a config error if artemis is not active', async () => {
mockArtemisService.isConfigured.mockReturnValue(false);
const expectedError = new AppConfigError('artemis', 'service is not configured');

expect(service.beginTransaction(tx, { clientId: 'someId' })).rejects.toThrow(expectedError);
});
});
});
6 changes: 6 additions & 0 deletions src/offline-starter/offline-starter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GenericPolymeshTransaction } from '@polymeshassociation/polymesh-sdk/ty
import { randomUUID } from 'crypto';

import { ArtemisService } from '~/artemis/artemis.service';
import { AppConfigError } from '~/common/errors';
import { AddressName } from '~/common/utils/amqp';
import { PolymeshLogger } from '~/logger/polymesh-logger.service';
import { OfflineReceiptModel } from '~/offline-starter/models/offline-receipt.model';
Expand All @@ -23,6 +24,10 @@ export class OfflineStarterService {
transaction: GenericPolymeshTransaction<ReturnType, TransformedReturnType>,
metadata?: Record<string, string>
): Promise<OfflineReceiptModel> {
if (!this.artemisService.isConfigured()) {
throw new AppConfigError('artemis', 'service is not configured');
}

const internalTxId = this.generateTxId();

const payload = await transaction.toSignablePayload({ ...metadata, internalTxId });
Expand All @@ -37,6 +42,7 @@ export class OfflineStarterService {
const delivery = await this.artemisService.sendMessage(topicName, request);

const model = new OfflineReceiptModel({
id: internalTxId,
deliveryId: new BigNumber(delivery.id),
payload: payload.payload,
metadata: payload.metadata,
Expand Down
11 changes: 9 additions & 2 deletions src/offline-submitter/offline-submitter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,16 @@ export class OfflineSubmitterService {
);
this.logger.log(`transaction finalized: ${id}`);

const msg = JSON.parse(JSON.stringify(result)); // make sure its serializes properly
const resultData = JSON.parse(JSON.stringify(result)); // make sure its serializes properly

await this.artemisService.sendMessage(AddressName.Finalizations, msg);
const finalizationMsg = {
...resultData,
id,
address,
nonce,
};

await this.artemisService.sendMessage(AddressName.Finalizations, finalizationMsg);

transaction.blockHash = result.blockHash as string;
transaction.txIndex = result.txIndex as string;
Expand Down
1 change: 1 addition & 0 deletions src/transactions/transactions.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ describe('TransactionsService', () => {

describe('submit (with AMQP)', () => {
const fakeReceipt = new OfflineReceiptModel({
id: 'someId',
deliveryId: new BigNumber(1),
topicName: AddressName.Requests,
payload: {} as SignerPayloadJSON,
Expand Down

0 comments on commit 13f9bd7

Please sign in to comment.