# Getting Started

Start testing in prod

## Prerequisites

The asserted CLI requires at least Node v10. Check which version of Node you have installed with:

```bash
node -v
```

If you do not have Node installed, or have the wrong version, we recommend installing Node using one of the following:

* Mac / Linux - [nvm](https://github.com/nvm-sh/nvm)
* Windows - [nvm-windows](https://github.com/coreybutler/nvm-windows)

## Installation

{% hint style="info" %}
GitHub: <https://github.com/assertedio/asrtd>

NPM: <https://www.npmjs.com/package/asrtd>
{% endhint %}

Use NPM to globally install the asserted CLI:

```
npm i -g asrtd
```

At any time you can run the following to get a full breakdown of all CLI commands:

```bash
asrtd --help
```

Login to the CLI with:

```bash
asrtd login
```

This will open a browser to [Token Management](https://app.asserted.io/tokens) so you can create a token and paste it into the interactive prompt. Tokens are associated with the user, not the project, and take on all of the abilities of that user.

## Initialize Routine

Usually routines are associated with a specific git repo or project. Move to the directory where you want to create the test files associated with a routine.

```bash
cd my-project-repo/
```

Run the following command to create a `.asserted` directory with all the necessary asserted dependencies and configuration:

```bash
astrd init
```

Inside `.asserted`, you'll find:

```bash
routine.json    ## Routine configuration with interval and mocha config
package.json    ## NPM package defining the (currently) fixed set of dependencies available during testing
examples/       ## Directory containing examples, can be modified or removed  
```

Your routine is now configured, but only contains examples. To get an idea of how everything works, lets push the current version with the examples.

```bash
asrtd push
```

This will push the current version of the code to [asserted.io](https://app.asserted.io) and you should start seeing results in the dashboard within a few seconds.

## Downtime Notifications

Once you have a routine running, you'll want to know if it detects any errors.

Go to the settings page of that routine within [asserted.io](https://app.asserted.io) to add a notification configuration.&#x20;

You can be notified by email or Slack webhook, or by phone if you have a paid plan or have purchased extra SMS.

{% hint style="warning" %}
Any routines that do not have notifications configured are disabled 48 hours they are created. You can re-enable them at any time.

You'll receive an email before this happens, but there is no point in running a routine continuously that doesn't have any way of notifying someone of downtime.
{% endhint %}

## Development

From here on out, it works mostly like writing any other Mocha test. The free plans only have a fixed set of dependencies, but the paid plans can include any dependencies you wish. Check out [Dependencies](/reference/included-dependencies) for more on that.

You can create whatever tests or files you'd like inside the `.asserted` directory and they'll be included in the next push.&#x20;

By default, Mocha will run any files with the `.asrtd.js` suffix as tests. Though this can be configured inside the `routine.json` file.

### Running Locally

To run tests locally, you can use:

```bash
asrtd run
```

The results will look something like:

![Local run result](/files/-M8DPk_B1dzdFtQ5EOMt)

### Running Online

In the event that you have an issue that only seems to manifest when run online, you can use the following option to run the test inside [asserted.io](https://app.asserted.io):

```bash
asrtd run --online
```

{% hint style="info" %}
Online runs are rate-limited to prevent abuse, and should only be used to debug issues.
{% endhint %}


# Test Time Calculation

How daily test time is calculated

All of the routines within a project share a pool of test time.&#x20;

{% hint style="info" %}
For a given day, allocated test time is calculated as:

allocatedTestTime = timeoutSec **X** runs-per-day

* runs-per-day is derived from the routine interval
* timeoutSec and interval are configured in the [`routine.json`](/reference/routine-json)
  {% endhint %}

{% hint style="warning" %}
Test time is billed by how it is allocated based on timeout and interval, NOT based on how long a given test takes to complete.

If you configure a routine for 10 seconds of timeout and run it every minute, but it only takes 2 seconds to complete, you will still be billed for 10 seconds X the interval.
{% endhint %}

During execution, once the timeout for a specific routine is reached, the test is immediately aborted.&#x20;

The results of the completed tests (before the timeout occured) are still collected and recorded, but the test is marked as **TIMEOUT** and a notification is sent.


# Environment Variables

How to include environment variables in your Asserted test at runtime

It's likely that at some point you'll need to include some sensitive information like test tokens or usernames in your Asserted tests. Follow the instructions below to do so securely without saving these values directly in your git repo.

Included in the [fixed dependencies](/reference/included-dependencies#fixed-dependencies) is [dotenv](https://www.npmjs.com/package/dotenv) and [getenv](https://www.npmjs.com/package/getenv). These are simple libraries that are commonly used to pull in secrets or configuration from environment variables.

* `dotenv` loads environment variables from an `.env` file into [process.env](https://nodejs.org/docs/latest/api/process.html#process_process_env)
* `getenv` reads the variables out of process.env and throws if they are missing rather than defaulting to an empty variable

{% hint style="danger" %}
If you plan on publishing to NPM it is extremely important that you exclude your `.asserted` directory if you have included a .env file in it.

[Use the "files" property](https://zellwk.com/blog/ignoring-files-from-npm-package/) of your root package.json to include only the files and folders you want when publishing to NPM.
{% endhint %}

### Storing Environment Variables

To access environment variables at runtime in Asserted, while still not checking them into your repo, do the following:

1. Create a `.env` file containing the variables within the `.asserted/` directory of your project
2. Ensure that the `.gitignore` file inside the `.asserted/` directory includes a reference to the `.env` file you just added (it should by default)
3. Add an entry to the `package.json` in your `.asserted/` directory for `"files": [ "**/*.js", ".env" ]`, this will include the `.env` file in your package when you run `asrtd push`
4. **If your root package is going to be published to NPM, exclude `.asserted` from being published**

After doing the above, when you run `asrtd push`, you should see the `.env` file listed in the files includes in the routine package, while still omitting the file from your git repo.

### Reading Environment Variables

Populate `process.env` from the variables stored in the `.env` file using the following:

```javascript
const path = require('path');

require('dotenv').config({ path: path.join(__dirname, './.env') });
```

It's best to use `getenv` to read whatever environment variables you're interested in, but you can just read directly from `process.env` if you prefer.

```javascript
const getenv = require('getenv');

// Throws if process.env.TOKEN does not exist
const TOKEN = getenv('TOKEN');

// Works, but just silently defaults to undefined or an empty string if 
// the environment variable is not properly set
const TOKEN = process.env.TOKEN;
```


# Concepts

## Projects

A project is the highest-level collection of entities in asserted.io.&#x20;

It has a plan attached to it that defines the maximum number of routines that can be added within a project, and the maximum total test time available for all of those routines to run.

You can also invite team members to a project, so they can modify, push, or remove routines.

## Routines

A routine is a collection of files and tests (written for Mocha), that is executed on the interval specified in the [`routine.json`](/reference/routine-json) file within the `.asserted` directory of a given repo.

## Records

Test records are the individual recorded results for a given test run. They can be viewed in the UI at [asserted.io](https://app.asserted.io) or using the CLI command `asrtd records`.

![asrtd records CLI command output](/files/-M8DWy2U_gostUsWg7fe)

## Timeline and Timeline Events

The aggregated history of the changes in routine status (from DOWN to UP, etc) are captured in the timeline and a collection of all the records of a specific state are called a timeline event. This can be viewed as **History** within the UI for a given routine, or by running `asrtd timeline` with the CLI.


# asrtd CLI

The command line interface tool for asserted is key to managing routines

## Prerequisites

The asserted CLI requires at least Node v10. Check which version of Node you have installed with:

```bash
node -v
```

If you do not have Node installed, or have the wrong version, we recommend installing Node using one of the following:

* Mac / Linux - [nvm](https://github.com/nvm-sh/nvm)
* Windows - [nvm-windows](https://github.com/coreybutler/nvm-windows)

## Installation

Use NPM to globally install the asserted CLI:

```
npm i -g asrtd
```

## Commands

At any time you can run the following to get a full breakdown of all CLI commands:

```bash
asrtd --help
```

![asrtd --help output](/files/-M941HPYzJaIHIp3EEwt)

The best source for information on how to run commands in the CLI, is the CLI itself by running `asrtd --help` or adding `--help` to any individual command for more details.


# .asserted directory

The directory within your repo that holds all routine-related code and config

## Initialization

To create a new routine and it's .asserted directory within a repo, go to the repo and run `asrtd init` using the CLI.

This will walk you through a few configuration options, and then create the directory, create the routine, and install the dependencies.

## Structure and Files

A newly initialized `.asserted` directory will contain the following:

```bash
examples/      # A directory containing example tests
node_modules/  # Packges installed based on the package.json
.gitignore     # A standard Node .gitignore file
package.json      # Mostly standard package.json file
package-lock.json # Lockfile for packages
routine.json   # Routine configuration file
```

The examples can be removed or altered in any way, but the rest are required.

The `routine.json` file should be updated if you want to change anything about the routine configuration. That's covered [here](/reference/routine-json).

Any additional files you wish to create can be added to this directory and they will be included with the routine when it's pushed.&#x20;

{% hint style="info" %}
For the more technical people, a modified [`npm pack`](https://docs.npmjs.com/cli-commands/pack.html) is used under the hood to collect and package routine-related files. As such, the same configuration options apply (the `files: []` property in package.json, .npmignore, etc).&#x20;

However, the pack-related npm lifecycle scripts are ignored.
{% endhint %}


# routine.json

The routine.json configures the details of how your routine runs

## Routine

As covered in [concepts](/concepts#routines), a routine is a collection of files and tests (written for Mocha), that is executed on an interval.&#x20;

This interval, and the rest of the routine configuration is specified in the `routine.json` file within the `.asserted` directory of a given repo.

## .asserted/routine.json

This is what a typical `routine.json` file looks like after running `asrtd init` inside a given repo.

```javascript
{
  // Globally-unique Routine ID
  "id": "rt-I5zgwerPGE",
  
  // Globally-unique Project ID
  "projectId": "p-1Hewr0s9Z",
  
  // Routine Name (can be anything up to 30 characters)
  "name": "my-routine",
  
  // Routine Description (can be up to 100 characters)
  "description": "some awesome description",
  
  // Interval to run the routine on
  "interval": {
    "unit": "min",
    "value": 5
  },
  
  // Version of the fixed dependencies included during the
  // run. 
  // - "v1" is the only option for free plans
  // - "custom" may be used for paid plans that require extra dependencies
  "dependencies": "v1",
  
  // Mocha-specific configuration
  "mocha": {
    "files": [
      "**/*.asrtd.js"
    ],
    "ignore": [],
    "bail": false,
    "ui": "bdd"
  },
  
  // Overall timeout in seconds for this routine
  "timeoutSec": 1
}
```

## Updates

Updating the `routine.json` is the only way to modify the interval and other routine configuration parameters. They cannot be modified within the UI.

Updating is simple though, simply open the file in the text editor or IDE of your choice, make the modifications, and then run `asrtd push` using the CLI to push the latest routine code and configuration.


# Dependencies

Fixed and Custom Dependencies are available

## Fixed Dependencies

The dependencies available on the free plan are fixed, but they should cover most major use cases.

For cases where custom dependencies are required, upgrade to a paid plan.

### Major Dependencies

* mocha - [NPM](http://npmjs.com/package/mocha) - [Docs](https://mochajs.org/)
* chai - [NPM](https://www.npmjs.com/package/chai) - [Docs](https://www.chaijs.com/)
* sinon - [NPM](https://www.npmjs.com/package/sinon) - [Docs](https://sinonjs.org/)
* axios - [NPM](https://www.npmjs.com/package/axios) - [Docs](https://www.npmjs.com/package/axios)
* lodash - [NPM](https://www.npmjs.com/package/lodash) - [Docs](https://lodash.com/)

### All Available Dependencies

```javascript
{
    "ajv": "6.12.2",
    "async": "3.2.0",
    "axios": "0.19.2",
    "bcrypt": "5.0.0",
    "bluebird": "3.7.2",
    "chai": "4.2.0",
    "cookie": "0.4.1",
    "crypto-js": "4.0.0",
    "dotenv": "8.2.0",
    "faker": "4.1.0",
    "fs-extra": "^9.0.1",
    "getenv": "1.0.0",
    "got": "^11.3.0",
    "http-status": "1.4.2",
    "ip": "1.1.5",
    "jsdom": "16.2.2",
    "jsonwebtoken": "8.5.1",
    "lodash": "4.17.15",
    "luxon": "1.24.1",
    "mocha": "8.0.1",
    "moment": "^2.26.0",
    "ms": "2.1.2",
    "node-fetch": "2.6.0",
    "qs": "6.9.4",
    "ramda": "0.27.0",
    "request": "2.88.2",
    "request-promise": "4.2.5",
    "sinon": "9.0.2",
    "ssl-checker": "2.0.4",
    "tar": "6.0.2",
    "underscore": "1.10.2",
    "uuid": "^8.1.0",
    "validator": "^13.1.1"
}
```

## Custom Dependencies

For paid plans, custom dependencies are an option.

To use custom dependencies, just change the "dependencies" entry in your [`routine.json`](/reference/routine-json) to "custom", as shown below.

```javascript
{
  "id": "rt-GKgRG",
  "projectId": "p-1HLbs9Z",
  "name": "custom-dep-tests",
  "description": "Tests with Custom Dependencies",
  "interval": {
    "unit": "min",
    "value": 10
  },
  "dependencies": "custom", // The "custom" option is available on paid plans
  "mocha": {
    "files": [
      "**/*.asrtd.js"
    ],
    "ignore": [],
    "bail": false,
    "ui": "bdd"
  },
  "timeoutSec": 10
}
```

Once that change is made, any subsequent pushes will include all of the dependencies listed in the "dependencies" entry of your `package.json`. "devDependencies" and "peerDependencies" are ignored.

A modified version of [`npm-shrinkwrap`](https://docs.npmjs.com/cli/shrinkwrap) is used to capture the exact versions of the dependencies in your current `node_modules` folder during the push.


# Overview

Each of the full examples included here follows a similar template, and none of them require an account to try them out locally.

They all include a functional but simple server exposing the features to be demonstrated, and tests in the [`.asserted`](/reference/.asserted) directory that show how to deeply test these features.

To try out any of them, just do the following:

* git clone the relevant repo
* execute `npm install` inside the repo (this will install the server dependencies, and the asserted dependencies)
* execute `npm run test:asrtd` inside the repo to start the server and run all of the example tests

### Full Examples

* [Standard REST or HTTP API](/examples/rest-api) with authentication
* [GraphQL API](/examples/graphql)
* [Socket.IO API](/examples/socket.io)
* [gRPC API](/examples/grpc)


# REST

Common tests for REST APIs

## Features of this Example

* Basic authentication
* Storing and Reading Environment Variables in Asserted
* Test the list, create, get, update, and remove REST endpoints

### Walkthrough

This example was referenced in a walkthrough about [Node API Health Checks and Uptime](https://asserted.io/posts/node-api-health-check-uptime)

### Try it out

Repo is available [here](https://github.com/assertedio/node-uptime).

```bash
# Clone example
git clone https://github.com/assertedio/node-uptime

# Enter directory and install
cd graphql-uptime/
npm install

# Run asserted tests
npm run test:asrtd
```

### Tests

Can also be viewed on github here.

```javascript
const { expect } = require('chai');
const got = require('got');
const getenv = require('getenv');
const path = require('path');

require('dotenv').config({ path: path.join(__dirname, './.env') });

const TOKEN = getenv('TOKEN');

const client = got.extend({
  prefixUrl: 'http://localhost:3000',
  headers: { authorization: TOKEN },
});

describe('node api tests', () => {
  let userId;

  it('get all users', async () => {
    const { data } = await client.get('users').json();
    expect(data.length).to.eql(4);
  });

  it('create user', async () => {
    const { data } = await client.post('users', { json: { name: 'Foo Bario', email: 'foo@bar.io' }}).json();

    const { id, name, email } = data;
    userId = id;

    expect(userId).to.exist;
    expect(name).to.eql('Foo Bario');
    expect(email).to.eql('foo@bar.io');
  });

  it('get user', async () => {
    expect(userId).to.exist;

    const { data } = await client.get('users/' + userId).json();

    const { id, name, email } = data;

    expect(id).to.eql(userId);
    expect(name).to.eql('Foo Bario');
    expect(email).to.eql('foo@bar.io');
  });

  it('update user', async () => {
    expect(userId).to.exist;

    const { data } = await client.put('users/' + userId, { json: { name: 'Bar Yaz', email: 'bar@yaz.io' }}).json();

    const { id, name, email } = data;

    expect(id).to.eql(userId);
    expect(name).to.eql('Bar Yaz');
    expect(email).to.eql('bar@yaz.io');
  });

  it('remove user', async () => {
    expect(userId).to.exist;

    await client.delete('users/' + userId).json();

    const { data } = await client.get('users/' + userId).json();
    expect(data).to.eql(null);
  });
});

```


# GraphQL

Common tests for GraphQL APIs

## Features of this Example

* Nested GraphQL queries
* GraphQL mutations

### Walkthrough

This example was referenced in a walkthrough about [GraphQL Health Checks and Uptime](https://asserted.io/posts/graphql-health-check-uptime)

### Try it out

Repo is available [here](https://github.com/assertedio/node-uptime).

```bash
# Clone example
git clone https://github.com/assertedio/graphql-uptime

# Enter directory and install
cd node-uptime/
npm install

# Run asserted tests
npm run test:asrtd
```

### Tests

Can also be viewed on github [here](https://github.com/assertedio/graphql-uptime/blob/master/.asserted/example.asrtd.js).&#x20;

```javascript
const { expect } = require('chai');
const got = require('got');
const { v4: uuid } = require('uuid');

const client = got.extend({
  prefixUrl: 'http://localhost:4000',
});

const createdBookName = `name-${uuid()}`;

describe('graphql api tests', () => {
  let createBookId;

  it('get all books', async () => {
    const { data } = await client
      .post('', {
        headers: {
          'content-type': 'application/json',
        },
        json: {
          query: `{
          books {
            id
          }
        }`,
        },
      })
      .json();

    expect(data).to.eql({
      books: [
        {
          id: 'design-patterns',
        },
        {
          id: 'refactoring',
        },
        {
          id: 'patterns-of-enterprise-application-architecture',
        },
        {
          id: 'domain-driven-design',
        },
        {
          id: 'clean-code',
        },
        {
          id: 'agile-software-development',
        },
      ],
    });
  });

  it('get other books written by the author', async () => {
    const { data } = await client
      .post('', {
        headers: {
          'content-type': 'application/json',
        },
        json: {
          query: `{
          book(id: "clean-code") {
            name
            authors {
              name
              books {
                name
              }
            }
          }
        }`,
        },
      })
      .json();

    expect(data).to.eql({
      book: {
        name: 'Clean Code - A Handbook of Agile Software Craftsmanship',
        authors: [
          {
            name: 'Robert C. Martin',
            books: [
              {
                name: 'Clean Code - A Handbook of Agile Software Craftsmanship',
              },
              {
                name: 'Agile Software Development, Principles, Patterns, and Practices',
              },
            ],
          },
        ],
      },
    });
  });

  it('create a book', async () => {
    const { data } = await client
      .post('', {
        headers: {
          'content-type': 'application/json',
        },
        json: {
          query: `mutation($newBook: BookInput!) {
            createBook(book: $newBook) {
              id
              name
            }
          }`,
          variables: {
            newBook: {
              name: createdBookName,
              publisherId: 'new-pub',
            },
          },
        },
      })
      .json();

    const { id, name } = data.createBook;
    createBookId = id;

    expect(createBookId).to.exist;
    expect(name).to.eql(createdBookName);
  });

  it('update a book', async () => {
    expect(createBookId).to.exist;
    const { data } = await client
      .post('', {
        headers: {
          'content-type': 'application/json',
        },
        json: {
          query: `mutation($bookId: ID!, $updatedBook: BookInput!) {
            updateBook(bookId: $bookId, book: $updatedBook) {
              id
              name
            }
          }`,
          variables: {
            bookId: createBookId,
            updatedBook: {
              name: 'new-name',
              publisherId: 'new-pub',
            },
          },
        },
      })
      .json();

    const { id, name } = data.updateBook;

    expect(id).to.eql(createBookId);
    expect(name).to.eql('new-name');
  });

  it('remove a book', async () => {
    expect(createBookId).to.exist;
    await client
      .post('', {
        headers: {
          'content-type': 'application/json',
        },
        json: {
          query: `mutation($bookId: ID!) {
            deleteBook(bookId: $bookId)
          }`,
          variables: {
            bookId: createBookId,
          },
        },
      })
      .json();

    const { data } = await client
      .post('', {
        headers: {
          'content-type': 'application/json',
        },
        json: {
          query: `query($bookId: ID!) {
            book(id: $bookId) {
              id
            }
          }`,
          variables: {
            bookId: createBookId,
          },
        },
      })
      .json();

    expect(data.book).to.eql(null);
  });
});
```


# Socket.IO

Common tests for Socket.IO APIs

## Features of this Example

* Socket.IO connect and disconnect
* Emit events to and from client

### Walkthrough

This example was referenced in a walkthrough about [Socket.IO Health Checks and Uptime](https://asserted.io/posts/socketio-health-check-uptime).

### Try it out

Repo is available [here](https://github.com/assertedio/socketio-uptime).

```bash
# Clone example
git clone https://github.com/assertedio/graphql-uptime

# Enter directory and install
cd node-uptime/
npm install

# Run asserted tests
npm run test:asrtd
```

### Tests

Can also be viewed on github [here](https://github.com/assertedio/graphql-uptime/blob/master/.asserted/example.asrtd.js).&#x20;

```javascript
const { expect } = require('chai');
const io = require('socket.io-client');
const util = require('util');
const sinon = require('sinon');

const sleep = util.promisify(setTimeout);

const monitoringClient = io('http://localhost:3000', { forceNew: true });

describe('socketio api tests', () => {
  let client;

  before((done) => {
    monitoringClient.emit('add user', 'monitoring');
    monitoringClient.once('connect', () => done());
  });

  beforeEach((done) => {
    client = io('http://localhost:3000', { forceNew: true });
    client.once('connect', () => done());
  });

  afterEach(() => {
    client.disconnect();
  });

  after(() => {
    monitoringClient.disconnect();
  });

  it('user joined and login', async () => {
    const login = sinon.stub();
    const joined = sinon.stub();

    monitoringClient.once('user joined', joined);
    client.once('login', login);

    client.emit('add user', 'new-user');

    // Admittedly a bit gross to use sleep here, but just wanted something simple.
    // The less brittle approach would be to block on a promise until the stubs are called.
    await sleep(100);

    expect(login.args).to.eql([[{ numUsers: 2 }]]);
    expect(joined.args).to.eql([[{ numUsers: 2, username: 'new-user' }]]);
  });

  it('user sent message', async () => {
    const monitoringMessage = sinon.stub();
    const clientMessage = sinon.stub();

    monitoringClient.once('new message', monitoringMessage);
    client.once('new message', clientMessage);

    client.emit('add user', 'new-user');
    await sleep(100);
    client.emit('new message', 'some-message');
    await sleep(100);

    expect(monitoringMessage.args).to.eql([[{ message: 'some-message', username: 'new-user' }]]);
    expect(clientMessage.args).to.eql([]);
  });
});
```


# gRPC

Common tests for gRPC APIs

## Features of this Example

* Simple RPC
* Server-side streaming and client-side streaming RPC
* Bidirectional streaming RPC

### Walkthrough

This example was referenced in a walkthrough about [gRPC Health Checks and Uptime](https://asserted.io/posts/grpc-health-check-uptime).

### Try it out

Repo is available [here](https://github.com/assertedio/grpc-uptime).

```bash
# Clone example
git clone https://github.com/assertedio/graphql-uptime

# Enter directory and install
cd node-uptime/
npm install

# Run asserted tests
npm run test:asrtd
```

### Tests

Can also be viewed on github [here](https://github.com/assertedio/grpc-uptime/blob/master/.asserted/example.asrtd.js).&#x20;

```javascript
const { expect } = require('chai');
const Bluebird = require('bluebird');
const path = require('path');
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');

const PROTO_PATH = path.join(__dirname, '../protos/route_guide.proto');
const DATA = require('../route_guide/route_guide_db.json');

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});
const { routeguide } = grpc.loadPackageDefinition(packageDefinition);
const client = new routeguide.RouteGuide('localhost:50051', grpc.credentials.createInsecure());

const sortByName = ({ name: name1 }, { name: name2 }) => name1.localeCompare(name2);

describe('grpc api tests', () => {
  it('get feature - simple rpc', async () => {
    const point1 = {
      latitude: 409146138,
      longitude: -746188906,
    };
    const point2 = {
      latitude: 0,
      longitude: 0,
    };

    const expectedFeature1 = {
      name: 'Berkshire Valley Management Area Trail, Jefferson, NJ, USA',
      location: {
        latitude: 409146138,
        longitude: -746188906,
      },
    };
    const feature1 = await Bluebird.fromCallback((cb) => client.getFeature(point1, cb));
    expect(feature1).to.eql(expectedFeature1);

    const expectedFeature2 = {
      name: '',
      location: {
        latitude: 0,
        longitude: 0,
      },
    };
    const feature2 = await Bluebird.fromCallback((cb) => client.getFeature(point2, cb));
    expect(feature2).to.eql(expectedFeature2);
  });

  it('list features - server-side streaming rpc', async () => {
    const rectangle = {
      lo: {
        latitude: 400000000,
        longitude: -750000000,
      },
      hi: {
        latitude: 420000000,
        longitude: -730000000,
      },
    };

    const call = client.listFeatures(rectangle);

    const features = [];

    call.on('data', (feature) => features.push(feature));
    await Bluebird.fromCallback((cb) => call.on('end', cb));

    expect(features.length).to.eql(64);
    expect(features.sort(sortByName)).to.eql(DATA.sort(sortByName).filter(({ name }) => name.length > 0));
  });

  it('record route - client-side streaming rpc', async () => {
    const num_points = 10;

    let call;
    const recorded = Bluebird.fromCallback((callback) => (call = client.recordRoute(callback)));

    for (let i = 0; i < num_points; i++) {
      const {
        location: { latitude, longitude },
      } = DATA[30 + i];

      call.write({ latitude, longitude });
      // eslint-disable-next-line no-await-in-loop
      await Bluebird.delay(100);
    }

    await call.end();
    const stats = await recorded;

    expect(stats).to.eql({
      point_count: 10,
      feature_count: 4,
      distance: 455927,
      elapsed_time: 1,
    });
  });

  it('route chat - bidirectional streaming RPC', async () => {
    const call = client.routeChat();

    const gotNotes = [];
    call.on('data', (note) => gotNotes.push(note));

    const result = Bluebird.fromCallback((callback) => call.on('end', callback));

    const notes = [
      {
        location: {
          latitude: 0,
          longitude: 0,
        },
        message: 'First message',
      },
      {
        location: {
          latitude: 0,
          longitude: 1,
        },
        message: 'Second message',
      },
      {
        location: {
          latitude: 1,
          longitude: 0,
        },
        message: 'Third message',
      },
      {
        location: {
          latitude: 0,
          longitude: 0,
        },
        message: 'Fourth message',
      },
    ];

    notes.forEach((note) => call.write(note));
    call.end();

    await result;

    const expectedNotes = [
      {
        location: {
          latitude: 0,
          longitude: 0,
        },
        message: 'First message',
      },
    ];

    expect(gotNotes).to.eql(expectedNotes);
  });
});

```


