Skip to content

Commit

Permalink
[CT-1040] Push Notifications (3 of 4) - Add RegisterToken to Comlink (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
adamfraser authored Sep 12, 2024
1 parent 9e93027 commit 51e6d2f
Show file tree
Hide file tree
Showing 17 changed files with 659 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export async function findAll(
{
address,
limit,
updatedBeforeOrAt,
}: FirebaseNotificationTokenQueryConfig,
requiredFields: QueryableField[],
options: Options = DEFAULT_POSTGRES_OPTIONS,
Expand All @@ -42,6 +43,10 @@ export async function findAll(
baseQuery = baseQuery.where(FirebaseNotificationTokenColumns.address, address);
}

if (updatedBeforeOrAt) {
baseQuery = baseQuery.where(FirebaseNotificationTokenColumns.updatedAt, '<=', updatedBeforeOrAt);
}

if (options.orderBy !== undefined) {
for (const [column, order] of options.orderBy) {
baseQuery = baseQuery.orderBy(
Expand Down
1 change: 1 addition & 0 deletions indexer/packages/postgres/src/types/query-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,5 @@ export interface AffiliateInfoQueryConfig extends QueryConfig {
export interface FirebaseNotificationTokenQueryConfig extends QueryConfig {
[QueryableField.ADDRESS]?: string,
[QueryableField.TOKEN]?: string,
[QueryableField.UPDATED_BEFORE_OR_AT]?: IsoString,
}
2 changes: 2 additions & 0 deletions indexer/pnpm-lock.yaml

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

3 changes: 3 additions & 0 deletions indexer/services/comlink/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ DB_PORT=5436
RATE_LIMIT_ENABLED=false
INDEXER_LEVEL_GEOBLOCKING_ENABLED=false
EXPOSE_SET_COMPLIANCE_ENDPOINT=true
FIREBASE_PROJECT_ID=projectID
FIREBASE_PRIVATE_KEY='-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----'
FIREBASE_CLIENT_EMAIL=[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import {
BlockTable,
liquidityTierRefresher,
SubaccountTable,
FirebaseNotificationTokenTable,
} from '@dydxprotocol-indexer/postgres';
import { RequestMethod } from '../../../../src/types';
import request from 'supertest';
import { getFixedRepresentation, sendRequest } from '../../../helpers/helpers';
import { stats } from '@dydxprotocol-indexer/base';
import config from '../../../../src/config';

describe('addresses-controller#V4', () => {
const latestHeight: string = '3';
Expand Down Expand Up @@ -42,6 +44,7 @@ describe('addresses-controller#V4', () => {

afterEach(async () => {
await dbHelpers.clearData();
jest.clearAllMocks();
});

const invalidAddress: string = 'invalidAddress';
Expand Down Expand Up @@ -574,4 +577,142 @@ describe('addresses-controller#V4', () => {
});
});

describe('/:address/testNotification', () => {
it('Post /:address/testNotification throws error in production', async () => {
// Mock the config to simulate production environment
const originalNodeEnv = config.NODE_ENV;
config.NODE_ENV = 'production';

const response: request.Response = await sendRequest({
type: RequestMethod.POST,
path: `/v4/addresses/${testConstants.defaultAddress}/testNotification`,
expectedStatus: 404,
});

expect(response.statusCode).toEqual(404);
// Restore the original NODE_ENV
config.NODE_ENV = originalNodeEnv;
});
});

describe('/:address/registerToken', () => {
it('Post /:address/registerToken with valid params returns 200', async () => {
const token = 'validToken';
const language = 'en';
const response: request.Response = await sendRequest({
type: RequestMethod.POST,
path: `/v4/addresses/${testConstants.defaultAddress}/registerToken`,
body: { token, language },
expectedStatus: 200,
});

expect(response.body).toEqual({});
expect(stats.increment).toHaveBeenCalledWith('comlink.addresses-controller.response_status_code.200', 1, {
path: '/:address/registerToken',
method: 'POST',
});
});

it('should register a new token', async () => {
// Register a new token
const newToken = 'newToken';
const language = 'en';
await sendRequest({
type: RequestMethod.POST,
path: `/v4/addresses/${testConstants.defaultAddress}/registerToken`,
body: { token: newToken, language },
expectedStatus: 200,
});

// Check that old tokens are deleted and new token is registered
const remainingTokens = await FirebaseNotificationTokenTable.findAll({}, []);
expect(remainingTokens.map((t) => t.token)).toContain(newToken);
});

it('Post /:address/registerToken with valid params calls TokenTable registerToken', async () => {
jest.spyOn(FirebaseNotificationTokenTable, 'registerToken');
const token = 'validToken';
const language = 'en';
await sendRequest({
type: RequestMethod.POST,
path: `/v4/addresses/${testConstants.defaultAddress}/registerToken`,
body: { token, language },
expectedStatus: 200,
});
expect(FirebaseNotificationTokenTable.registerToken).toHaveBeenCalledWith(
token, testConstants.defaultAddress, language,
);
expect(stats.increment).toHaveBeenCalledWith('comlink.addresses-controller.response_status_code.200', 1, {
path: '/:address/registerToken',
method: 'POST',
});
});

it('Post /:address/registerToken with invalid address returns 404', async () => {
const token = 'validToken';
const response: request.Response = await sendRequest({
type: RequestMethod.POST,
path: `/v4/addresses/${invalidAddress}/registerToken`,
body: { token },
expectedStatus: 404,
});

expect(response.body).toEqual({
errors: [
{
msg: 'No wallet found with address: invalidAddress',
},
],
});
expect(stats.increment).toHaveBeenCalledWith('comlink.addresses-controller.response_status_code.404', 1, {
path: '/:address/registerToken',
method: 'POST',
});
});

it.each([
['validToken', '', 'Invalid language code', 'language'],
['validToken', 'qq', 'Invalid language code', 'language'],
])('Post /:address/registerToken with bad language params returns 400', async (token, language, errorMsg, errorParam) => {
const response: request.Response = await sendRequest({
type: RequestMethod.POST,
path: `/v4/addresses/${testConstants.defaultAddress}/registerToken`,
body: { token, language },
expectedStatus: 400,
});

expect(response.body).toEqual({
errors: [
{
location: 'body',
msg: errorMsg,
param: errorParam,
value: language,
},
],
});
});

it.each([
['', 'en', 'Token cannot be empty', 'token'],
])('Post /:address/registerToken with bad token params returns 400', async (token, language, errorMsg, errorParam) => {
const response: request.Response = await sendRequest({
type: RequestMethod.POST,
path: `/v4/addresses/${testConstants.defaultAddress}/registerToken`,
body: { token, language },
expectedStatus: 400,
});

expect(response.body).toEqual({
errors: [
{
location: 'body',
msg: errorMsg,
param: errorParam,
value: token,
},
],
});
});
});
});
1 change: 1 addition & 0 deletions indexer/services/comlink/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@dydxprotocol-indexer/v4-proto-parser": "workspace:^0.0.1",
"@dydxprotocol-indexer/v4-protos": "workspace:^0.0.1",
"@keplr-wallet/cosmos": "^0.12.122",
"@dydxprotocol-indexer/notifications": "workspace:^0.0.1",
"@tsoa/runtime": "^5.0.0",
"big.js": "^6.2.1",
"body-parser": "^1.20.0",
Expand Down
135 changes: 135 additions & 0 deletions indexer/services/comlink/public/api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,141 @@ fetch(`${baseURL}/addresses/{address}/parentSubaccountNumber/{parentSubaccountNu
This operation does not require authentication
</aside>

## RegisterToken

<a id="opIdRegisterToken"></a>

> Code samples
```python
import requests
headers = {
'Content-Type': 'application/json'
}

# For the deployment by DYDX token holders, use
# baseURL = 'https://indexer.dydx.trade/v4'
baseURL = 'https://dydx-testnet.imperator.co/v4'

r = requests.post(f'{baseURL}/addresses/{address}/registerToken', headers = headers)

print(r.json())

```

```javascript
const inputBody = '{
"language": "string",
"token": "string"
}';
const headers = {
'Content-Type':'application/json'
};

// For the deployment by DYDX token holders, use
// const baseURL = 'https://indexer.dydx.trade/v4';
const baseURL = 'https://dydx-testnet.imperator.co/v4';

fetch(`${baseURL}/addresses/{address}/registerToken`,
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});

```

`POST /addresses/{address}/registerToken`

> Body parameter
```json
{
"language": "string",
"token": "string"
}
```

### Parameters

|Name|In|Type|Required|Description|
|---|---|---|---|---|
|address|path|string|true|none|
|body|body|object|true|none|
|» language|body|string|true|none|
|» token|body|string|true|none|

### Responses

|Status|Meaning|Description|Schema|
|---|---|---|---|
|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|No content|None|

<aside class="success">
This operation does not require authentication
</aside>

## TestNotification

<a id="opIdTestNotification"></a>

> Code samples
```python
import requests

# For the deployment by DYDX token holders, use
# baseURL = 'https://indexer.dydx.trade/v4'
baseURL = 'https://dydx-testnet.imperator.co/v4'

r = requests.post(f'{baseURL}/addresses/{address}/testNotification')

print(r.json())

```

```javascript

// For the deployment by DYDX token holders, use
// const baseURL = 'https://indexer.dydx.trade/v4';
const baseURL = 'https://dydx-testnet.imperator.co/v4';

fetch(`${baseURL}/addresses/{address}/testNotification`,
{
method: 'POST'

})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});

```

`POST /addresses/{address}/testNotification`

### Parameters

|Name|In|Type|Required|Description|
|---|---|---|---|---|
|address|path|string|true|none|

### Responses

|Status|Meaning|Description|Schema|
|---|---|---|---|
|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|No content|None|

<aside class="success">
This operation does not require authentication
</aside>

## GetMetadata

<a id="opIdGetMetadata"></a>
Expand Down
Loading

0 comments on commit 51e6d2f

Please sign in to comment.