Skip to content

Commit

Permalink
Enable hooks to be triggered via HTTP when the feature flag is activa…
Browse files Browse the repository at this point in the history
…ted.
  • Loading branch information
PooyaRaki committed Dec 16, 2024
1 parent 5ae92b6 commit 87d6c56
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/config/entities/__tests__/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export default (): ReturnType<typeof configuration> => ({
counterfactualBalances: false,
accounts: false,
pushNotifications: false,
hookHttpPostEvent: false,
improvedAddressPoisoning: false,
},
httpClient: { requestTimeout: faker.number.int() },
Expand Down
2 changes: 2 additions & 0 deletions src/config/entities/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ export default () => ({
accounts: process.env.FF_ACCOUNTS?.toLowerCase() === 'true',
pushNotifications:
process.env.FF_PUSH_NOTIFICATIONS?.toLowerCase() === 'true',
hookHttpPostEvent:
process.env.FF_HOOK_HTTP_POST_EVENT?.toLowerCase() === 'true',
improvedAddressPoisoning:
process.env.FF_IMPROVED_ADDRESS_POISONING?.toLowerCase() === 'true',
},
Expand Down
14 changes: 12 additions & 2 deletions src/routes/hooks/hooks.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,34 @@ import { ILoggingService, LoggingService } from '@/logging/logging.interface';
import { EventProtocolChangedError } from '@/routes/hooks/errors/event-protocol-changed.error';
import { EventProtocolChangedFilter } from '@/routes/hooks/filters/event-protocol-changed.filter';
import { ConfigEventType } from '@/routes/hooks/entities/event-type.entity';
import { IConfigurationService } from '@/config/configuration.service.interface';

@Controller({
path: '',
version: '1',
})
@ApiExcludeController()
export class HooksController {
private isHookHttpPostEventEnabled: boolean;
constructor(
private readonly hooksService: HooksService,
@Inject(LoggingService) private readonly loggingService: ILoggingService,
) {}

@Inject(IConfigurationService)
private readonly configurationService: IConfigurationService,
) {
this.isHookHttpPostEventEnabled =
this.configurationService.getOrThrow<boolean>(
'features.hookHttpPostEvent',
);
}

@UseGuards(BasicAuthGuard)
@Post('/hooks/events')
@UseFilters(EventProtocolChangedFilter)
@HttpCode(202)
postEvent(@Body(new ValidationPipe(EventSchema)) event: Event): void {
if (this.isConfigEvent(event)) {
if (this.isConfigEvent(event) || this.isHookHttpPostEventEnabled) {
this.hooksService.onEvent(event).catch((error) => {
this.loggingService.error(error);
});
Expand Down
106 changes: 106 additions & 0 deletions src/routes/hooks/hooks.http.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { faker } from '@faker-js/faker';
import type { INestApplication } from '@nestjs/common';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { TestCacheModule } from '@/datasources/cache/__tests__/test.cache.module';
import { TestNetworkModule } from '@/datasources/network/__tests__/test.network.module';
import { TestLoggingModule } from '@/logging/__tests__/test.logging.module';
import configuration from '@/config/entities/__tests__/configuration';
import { IConfigurationService } from '@/config/configuration.service.interface';
import { AppModule } from '@/app.module';
import { CacheModule } from '@/datasources/cache/cache.module';
import { RequestScopedLoggingModule } from '@/logging/logging.module';
import { NetworkModule } from '@/datasources/network/network.module';
import { TestQueuesApiModule } from '@/datasources/queues/__tests__/test.queues-api.module';
import { QueuesApiModule } from '@/datasources/queues/queues-api.module';
import type { Server } from 'net';
import { TestPostgresDatabaseModule } from '@/datasources/db/__tests__/test.postgres-database.module';
import { PostgresDatabaseModule } from '@/datasources/db/v1/postgres-database.module';
import { PostgresDatabaseModuleV2 } from '@/datasources/db/v2/postgres-database.module';
import { TestPostgresDatabaseModuleV2 } from '@/datasources/db/v2/test.postgres-database.module';
import { TestTargetedMessagingDatasourceModule } from '@/datasources/targeted-messaging/__tests__/test.targeted-messaging.datasource.module';
import { TargetedMessagingDatasourceModule } from '@/datasources/targeted-messaging/targeted-messaging.datasource.module';

describe('Post Hook Events Http (Unit)', () => {
let app: INestApplication<Server>;
let authToken: string;
let configurationService: IConfigurationService;

async function initApp(): Promise<void> {
const defaultConfiguration = configuration();

const testConfiguration = (): typeof defaultConfiguration => ({
...defaultConfiguration,
auth: {
...defaultConfiguration.auth,
token: faker.string.hexadecimal({ length: 32 }),
},
features: {
...defaultConfiguration.features,
hookHttpPostEvent: true,
},
});
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule.register(testConfiguration)],
})
.overrideModule(PostgresDatabaseModule)
.useModule(TestPostgresDatabaseModule)
.overrideModule(TargetedMessagingDatasourceModule)
.useModule(TestTargetedMessagingDatasourceModule)
.overrideModule(CacheModule)
.useModule(TestCacheModule)
.overrideModule(RequestScopedLoggingModule)
.useModule(TestLoggingModule)
.overrideModule(NetworkModule)
.useModule(TestNetworkModule)
.overrideModule(QueuesApiModule)
.useModule(TestQueuesApiModule)
.overrideModule(PostgresDatabaseModuleV2)
.useModule(TestPostgresDatabaseModuleV2)
.compile();
app = moduleFixture.createNestApplication();

configurationService = moduleFixture.get(IConfigurationService);
authToken = configurationService.getOrThrow('auth.token');

await app.init();
}

beforeEach(async () => {
jest.resetAllMocks();
await initApp();
});

afterAll(async () => {
await app.close();
});

it('should not return 410 if the feature flag is enabled', async () => {
const payload = {
type: 'INCOMING_TOKEN',
tokenAddress: faker.finance.ethereumAddress(),
txHash: faker.string.hexadecimal({ length: 32 }),
};
const safeAddress = faker.finance.ethereumAddress();
const chainId = faker.string.numeric();
const data = {
address: safeAddress,
chainId: chainId,
...payload,
};

await request(app.getHttpServer())
.post(`/hooks/events`)
.set('Authorization', `Basic ${authToken}`)
.send(data)
.expect(202);
});

it('should throw an error if authorization is not sent in the request headers', async () => {
await request(app.getHttpServer())
.post(`/hooks/events`)
.send({})
.expect(403);
});
});

0 comments on commit 87d6c56

Please sign in to comment.