Step 6. Implementing an HTTP service

The Pip.Services Toolkit has a dedicated component in the RPC module for processing external requests. To make use of this service, create a new class BeaconsHttpServiceV1, extending the CommandableHttpService class:

/src/service/version1/BeaconsHttpServicesV1.ts

import { CommandableHttpService } from 'pip-services3-rpc-nodex';
import { Descriptor } from 'pip-services3-commons-nodex';

export class BeaconsHttpServiceV1 extends CommandableHttpService {
    public constructor() {
        super('v1/beacons');
        this._dependencyResolver.put('controller', new Descriptor('beacons', 'controller', '*', '*', '1.0'));
    }
}

/src/service/service/version1/BeaconsHttpServicesV1.py

namespace Beacons.Services.Version1
{
    public class BeaconsHttpServiceV1: CommandableHttpService
    {
        public BeaconsHttpServiceV1()
            : base("v1/beacons")
        {
            _dependencyResolver.Put("controller", new Descriptor("beacons", "controller", "default", "*", "1.0"));
        }
    }
}

/service/version1/BeaconsHttpServicesV1.go

package services1

import (
	cref "github.com/pip-services3-go/pip-services3-commons-go/refer"
	cservices "github.com/pip-services3-go/pip-services3-rpc-go/services"
)

type BeaconsHttpServiceV1 struct {
	cservices.CommandableHttpService
}

func NewBeaconsHttpServiceV1() *BeaconsHttpServiceV1 {
	c := &BeaconsHttpServiceV1{}
	c.CommandableHttpService = *cservices.InheritCommandableHttpService(c, "v1/beacons")
	c.DependencyResolver.Put("controller", cref.NewDescriptor("beacons", "controller", "*", "*", "1.0"))
	return c
}

/lib/service/version1/BeaconsCommandableHttpServiceV1.dart

import 'package:pip_services3_rpc/pip_services3_rpc.dart';
import 'package:pip_services3_commons/pip_services3_commons.dart';

class BeaconsCommandableHttpServiceV1 extends CommandableHttpService {
  BeaconsCommandableHttpServiceV1() : super('v1/beacons') {
    dependencyResolver.put(
        'controller', Descriptor('beacons', 'controller', '*', '*', '1.0'));
  }
}


/src/service/version1/BeaconsHttpServicesV1.py

from pip_services3_commons.refer import Descriptor
from pip_services3_rpc.services import CommandableHttpService


class BeaconsHttpServiceV1(CommandableHttpService):
    def __init__(self):
        super(BeaconsHttpServiceV1, self).__init__("v1/beacons")
        self._dependency_resolver.put("controller", Descriptor('beacons', 'controller', '*', '*', '1.0'))
Not available

The CommandableHttpService class from the pip-services3-rpc module implements all of the basic functionality needed by the service, right out of the box. All that we need to do on our side is configure it in the child class. This is done by defining a base route to the API (e.g. ‘v1/beacons’) and by setting references to the controller. The rest is taken care of by the parent class and the process container: a controller will be searched for and referenced, after which the service will receive a set of commands, register it, and make those commands available through the API interface. This allows us to run commands by simply posting requests to a URL of the following format:

http://{ip}:{port}/v1/beacons/{command_name}

Even though the BeaconsHttpServiceV1 class barely has any lines of code, there’s a large amount of code being executed in the service itself. To make sure that everything is working as it should, we should add tests for the service itself, as well as for the commands we wrote in the CommandSet. Create a file for the service’s test and paste the following code:

/test/services/version1/BeaconsHttpServiceV1.test.ts

const assert = require('chai').assert;

import { DataPage } from 'pip-services3-commons-nodex';
import { ConfigParams } from 'pip-services3-commons-nodex';
import { Descriptor } from 'pip-services3-commons-nodex';
import { References } from 'pip-services3-commons-nodex';
import { FilterParams } from 'pip-services3-commons-nodex';
import { PagingParams } from 'pip-services3-commons-nodex';
import { TestCommandableHttpClient } from 'pip-services3-rpc-nodex';

import { BeaconV1 } from '../../../src/data/version1/BeaconV1';
import { BeaconTypeV1 } from '../../../src/data/version1/BeaconTypeV1';
import { BeaconsMemoryPersistence } from '../../../src/persistence/BeaconsMemoryPersistence';
import { BeaconsController } from '../../../src/logic/BeaconsController';
import { BeaconsHttpServiceV1 } from '../../../src/services/version1/BeaconsHttpServiceV1';

const BEACON1: BeaconV1 = {
    id: '1',
    udi: '00001',
    type: BeaconTypeV1.AltBeacon,
    site_id: '1',
    label: 'TestBeacon1',
    center: { type: 'Point', coordinates: [ 0, 0 ] },
    radius: 50
};
const BEACON2: BeaconV1 = {
    id: '2',
    udi: '00002',
    type: BeaconTypeV1.iBeacon,
    site_id: '1',
    label: 'TestBeacon2',
    center: { type: 'Point', coordinates: [ 2, 2 ] },
    radius: 70
};

suite('BeaconsHttpServiceV1', () => {
    let persistence: BeaconsMemoryPersistence;
    let controller: BeaconsController;
    let service: BeaconsHttpServiceV1;
    let client: TestCommandableHttpClient;

    setup(async () => {
        let restConfig = ConfigParams.fromTuples(
            'connection.protocol', 'http',
            'connection.port', 3000,
            'connection.host', 'localhost'
        );

        persistence = new BeaconsMemoryPersistence();
        persistence.configure(new ConfigParams());

        controller = new BeaconsController();
        controller.configure(new ConfigParams());

        service = new BeaconsHttpServiceV1();
        service.configure(restConfig);

        client = new TestCommandableHttpClient('v1/beacons')
        client.configure(restConfig);

        let references = References.fromTuples(
            new Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'), persistence,
            new Descriptor('beacons', 'controller', 'default', 'default', '1.0'), controller,
            new Descriptor('beacons', 'service', 'http', 'default', '1.0'), service
        );

        controller.setReferences(references);
        service.setReferences(references);

        await persistence.open(null);
        await service.open(null);
        await client.open(null);
    });

    teardown(async () => {
        await client.close(null);
        await service.close(null);
        await persistence.close(null);
    });

    test('CRUD Operations', async () => {
        let beacon1: BeaconV1;

        // Create the first beacon
        let beacon = await client.callCommand<BeaconV1>(
            'create_beacon',
            null, 
            {
                beacon: BEACON1
            }
        );
        assert.isObject(beacon);
        assert.equal(BEACON1.udi, beacon.udi);
        assert.equal(BEACON1.site_id, beacon.site_id);
        assert.equal(BEACON1.type, beacon.type);
        assert.equal(BEACON1.label, beacon.label);
        assert.isNotNull(beacon.center);

        // Create the second beacon
        beacon = await client.callCommand<BeaconV1>(
            'create_beacon',
            null, 
            {
                beacon: BEACON2
            }
        );
        assert.isObject(beacon);
        assert.equal(BEACON2.udi, beacon.udi);
        assert.equal(BEACON2.site_id, beacon.site_id);
        assert.equal(BEACON2.type, beacon.type);
        assert.equal(BEACON2.label, beacon.label);
        assert.isNotNull(beacon.center);

        // Get all beacons
        let page = await client.callCommand<DataPage<BeaconV1>>(
            'get_beacons',
            null,
            {
                filter: new FilterParams(),
                paging: new PagingParams()
            }
        );
        assert.isObject(page);
        assert.lengthOf(page.data, 2);

        beacon1 = page.data[0];

        // Update the beacon
        beacon1.label = 'ABC';

        beacon = await client.callCommand(
            'update_beacon',
            null,
            {
                beacon: beacon1
            }
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);
        assert.equal('ABC', beacon.label);

        // Get beacon by udi
        beacon = await client.callCommand(
            'get_beacon_by_udi',
            null,
            {
                udi: beacon1.udi
            }
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);

        // Calculate position for one beacon
        let position = await client.callCommand<any>(
            'calculate_position',
            null,
            {
                site_id: '1',
                udis: ['00001']
            }
        );
        assert.isObject(position);
        assert.equal('Point', position.type);
        assert.lengthOf(position.coordinates, 2);
        assert.equal(0, position.coordinates[0]);
        assert.equal(0, position.coordinates[1]);

        // Delete the beacon
        beacon = await client.callCommand(
            'delete_beacon_by_id',
            null,
            {
                beacon_id: beacon1.id
            }
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);

        // Try to get deleted beacon
        beacon = await client.callCommand(
            'get_beacon_by_id',
            null,
            {
                beacon_id: beacon1.id
            }
        );
        assert.isNull(beacon || null);
    });

});

/test/service.test/services/version1/BeaconsHttpServiceV1Test.py

namespace Beacons.Services.Version1
{
    public class BeaconsHttpServiceV1Test
    {
        private BeaconV1 BEACON1 = new BeaconV1
        {
            Id = "1",
            Udi = "00001",
            Type = BeaconTypeV1.AltBeacon,
            SiteId = "1",
            Label = "TestBeacon1",
            Center = new CenterObjectV1 { Type = "Point", Coordinates = new double[] { 0, 0 } },
            Radius = 50
        };
        private BeaconV1 BEACON2 = new BeaconV1
        {
            Id = "2",
            Udi = "00002",
            Type = BeaconTypeV1.iBeacon,
            SiteId = "1",
            Label = "TestBeacon2",
            Center = new CenterObjectV1 { Type = "Point", Coordinates = new double[] { 2, 2 } },
            Radius = 70
        };

        private static readonly ConfigParams HttpConfig = ConfigParams.FromTuples(
            "connection.protocol", "http",
            "connection.host", "localhost",
            "connection.port", "3000"
        );

        private BeaconsMemoryPersistence _persistence;
        private BeaconsController _controller;
        private BeaconsHttpServiceV1 _service;
        private TestCommandableHttpClient _client;

        public BeaconsHttpServiceV1Test()
        {
            _persistence = new BeaconsMemoryPersistence();
            _controller = new BeaconsController();
            _service = new BeaconsHttpServiceV1();
            _client = new TestCommandableHttpClient("v1/beacons");

            IReferences references = References.FromTuples(
                new Descriptor("beacons", "persistence", "memory", "default", "1.0"), _persistence,
                new Descriptor("beacons", "controller", "default", "default", "1.0"), _controller,
                new Descriptor("beacons", "service", "http", "default", "1.0"), _service,
                new Descriptor("beacons", "client", "http", "default", "1.0"), _client
            );

            _controller.SetReferences(references);

            _service.Configure(HttpConfig);
            _service.SetReferences(references);

            _client.Configure(HttpConfig);

            _service.OpenAsync(null).Wait();
            // Todo: This is defect! Open shall not block the tread
            //Task.Run(() => _service.OpenAsync(null));
            //Thread.Sleep(1000); // Just let service a sec to be initialized

            _client.OpenAsync(null).Wait();
        }

        [Fact]
        public async Task TestCrudOperationsAsync()
        {
            // Create the first beacon
            var beacon = await _client.CallCommandAsync<BeaconV1>(
                "create_beacon", null, new { beacon = BEACON1 });
            Assert.NotNull(beacon);
            Assert.Equal(BEACON1.Udi, beacon.Udi);
            Assert.Equal(BEACON1.SiteId, beacon.SiteId);
            Assert.Equal(BEACON1.Type, beacon.Type);
            Assert.Equal(BEACON1.Label, beacon.Label);
            Assert.NotNull(beacon.Center);

            // Create the second beacon
            beacon = await _client.CallCommandAsync<BeaconV1>(
                "create_beacon", null, new { beacon = BEACON2 });
            Assert.NotNull(beacon);
            Assert.Equal(BEACON2.Udi, beacon.Udi);
            Assert.Equal(BEACON2.SiteId, beacon.SiteId);
            Assert.Equal(BEACON2.Type, beacon.Type);
            Assert.Equal(BEACON2.Label, beacon.Label);
            Assert.NotNull(beacon.Center);

            // Get all beacons
            var page = await _client.CallCommandAsync<DataPage<BeaconV1>>(
                "get_beacons",
                null,
                new
                {
                    filter = new FilterParams(),
                    paging = new PagingParams()
                }
            );
            Assert.NotNull(page);
            Assert.Equal(2, page.Data.Count);

            var beacon1 = page.Data[0];

            // Update the beacon
            beacon1.Label = "ABC";

            beacon = await _client.CallCommandAsync<BeaconV1>(
                "update_beacon", null, new { beacon = beacon1 });
            Assert.NotNull(beacon);
            Assert.Equal(beacon1.Id, beacon.Id);
            Assert.Equal("ABC", beacon.Label);

            // Get beacon by udi
            beacon = await _client.CallCommandAsync<BeaconV1>(
                "get_beacon_by_udi", null, new { udi = beacon1.Udi });
            Assert.NotNull(beacon);
            Assert.Equal(beacon1.Id, beacon.Id);

            // Delete the beacon
            beacon = await _client.CallCommandAsync<BeaconV1>(
                "delete_beacon_by_id", null, new { beacon_id = beacon1.Id });
            Assert.NotNull(beacon);
            Assert.Equal(beacon1.Id, beacon.Id);

            // Try to get deleted beacon
            beacon = await _client.CallCommandAsync<BeaconV1>(
                "get_beacon_by_id", null, new { beacon_id = beacon1.Id });
            Assert.Null(beacon);
        }

    }
}

/test/services/version1/BeaconsHttpServiceV1_test.go

package test_services1

import (
	"reflect"
	"testing"

	data1 "github.com/pip-services-samples/service-beacons-go/data/version1"
	logic "github.com/pip-services-samples/service-beacons-go/logic"
	persist "github.com/pip-services-samples/service-beacons-go/persistence"
	services1 "github.com/pip-services-samples/service-beacons-go/services/version1"
	cconf "github.com/pip-services3-go/pip-services3-commons-go/config"
	cdata "github.com/pip-services3-go/pip-services3-commons-go/data"
	cref "github.com/pip-services3-go/pip-services3-commons-go/refer"
	tclients "github.com/pip-services3-go/pip-services3-rpc-go/test"
	"github.com/stretchr/testify/assert"
)

type beaconsHttpServiceV1Test struct {
	BEACON1            *data1.BeaconV1
	BEACON2            *data1.BeaconV1
	beaconDataPageType reflect.Type
	beaconType         reflect.Type
	geoPointType       reflect.Type
	persistence        *persist.BeaconsMemoryPersistence
	controller         *logic.BeaconsController
	service            *services1.BeaconsHttpServiceV1
	client             *tclients.TestCommandableHttpClient
}

func newBeaconsHttpServiceV1Test() *beaconsHttpServiceV1Test {
	BEACON1 := &data1.BeaconV1{
		Id:     "1",
		Udi:    "00001",
		Type:   data1.AltBeacon,
		SiteId: "1",
		Label:  "TestBeacon1",
		Center: data1.GeoPointV1{Type: "Point", Coordinates: [][]float32{{0.0, 0.0}}},
		Radius: 50,
	}

	BEACON2 := &data1.BeaconV1{
		Id:     "2",
		Udi:    "00002",
		Type:   data1.IBeacon,
		SiteId: "1",
		Label:  "TestBeacon2",
		Center: data1.GeoPointV1{Type: "Point", Coordinates: [][]float32{{2.0, 2.0}}},
		Radius: 70,
	}

	restConfig := cconf.NewConfigParamsFromTuples(
		"connection.protocol", "http",
		"connection.port", "3000",
		"connection.host", "localhost",
	)

	persistence := persist.NewBeaconsMemoryPersistence()
	persistence.Configure(cconf.NewEmptyConfigParams())

	controller := logic.NewBeaconsController()
	controller.Configure(cconf.NewEmptyConfigParams())

	service := services1.NewBeaconsHttpServiceV1()
	service.Configure(restConfig)

	client := tclients.NewTestCommandableHttpClient("v1/beacons")
	client.Configure(restConfig)

	references := cref.NewReferencesFromTuples(
		cref.NewDescriptor("beacons", "persistence", "memory", "default", "1.0"), persistence,
		cref.NewDescriptor("beacons", "controller", "default", "default", "1.0"), controller,
		cref.NewDescriptor("beacons", "service", "http", "default", "1.0"), service,
		cref.NewDescriptor("beacons", "client", "http", "default", "1.0"), client,
	)

	controller.SetReferences(references)
	service.SetReferences(references)

	return &beaconsHttpServiceV1Test{
		BEACON1:            BEACON1,
		BEACON2:            BEACON2,
		beaconDataPageType: reflect.TypeOf(&data1.BeaconV1DataPage{}),
		beaconType:         reflect.TypeOf(&data1.BeaconV1{}),
		geoPointType:       reflect.TypeOf(&data1.GeoPointV1{}),
		persistence:        persistence,
		controller:         controller,
		service:            service,
		client:             client,
	}
}

func (c *beaconsHttpServiceV1Test) setup(t *testing.T) {
	err := c.persistence.Open("")
	if err != nil {
		t.Error("Failed to open persistence", err)
	}

	err = c.service.Open("")
	if err != nil {
		t.Error("Failed to open service", err)
	}

	err = c.client.Open("")
	if err != nil {
		t.Error("Failed to open client", err)
	}

	err = c.persistence.Clear("")
	if err != nil {
		t.Error("Failed to clear persistence", err)
	}
}

func (c *beaconsHttpServiceV1Test) teardown(t *testing.T) {
	err := c.client.Close("")
	if err != nil {
		t.Error("Failed to close client", err)
	}

	err = c.service.Close("")
	if err != nil {
		t.Error("Failed to close service", err)
	}

	err = c.persistence.Close("")
	if err != nil {
		t.Error("Failed to close persistence", err)
	}
}

func (c *beaconsHttpServiceV1Test) testCrudOperations(t *testing.T) {
	var beacon1 *data1.BeaconV1

	// Create the first beacon
	params := cdata.NewAnyValueMapFromTuples(
		"beacon", c.BEACON1,
	)
	result, err := c.client.CallCommand(c.beaconType, "create_beacon", "", params)
	beacon := result.(*data1.BeaconV1)
	assert.Nil(t, err)
	assert.NotNil(t, beacon)
	assert.Equal(t, c.BEACON1.Udi, beacon.Udi)
	assert.Equal(t, c.BEACON1.SiteId, beacon.SiteId)
	assert.Equal(t, c.BEACON1.Type, beacon.Type)
	assert.Equal(t, c.BEACON1.Label, beacon.Label)
	assert.NotNil(t, beacon.Center)

	// Create the second beacon
	params = cdata.NewAnyValueMapFromTuples(
		"beacon", c.BEACON2,
	)
	result, err = c.client.CallCommand(c.beaconType, "create_beacon", "", params)
	beacon = result.(*data1.BeaconV1)
	assert.Nil(t, err)
	assert.NotNil(t, beacon)
	assert.Equal(t, c.BEACON2.Udi, beacon.Udi)
	assert.Equal(t, c.BEACON2.SiteId, beacon.SiteId)
	assert.Equal(t, c.BEACON2.Type, beacon.Type)
	assert.Equal(t, c.BEACON2.Label, beacon.Label)
	assert.NotNil(t, beacon.Center)

	// Get all beacons
	params = cdata.NewAnyValueMapFromTuples(
		"filter", cdata.NewEmptyFilterParams(),
		"paging", cdata.NewEmptyFilterParams(),
	)
	result, err = c.client.CallCommand(c.beaconDataPageType, "get_beacons", "", params)
	page := result.(*data1.BeaconV1DataPage)
	assert.Nil(t, err)
	assert.NotNil(t, page)
	assert.Len(t, page.Data, 2)
	beacon1 = page.Data[0]

	// Update the beacon
	beacon1.Label = "ABC"
	params = cdata.NewAnyValueMapFromTuples(
		"beacon", beacon1,
	)
	result, err = c.client.CallCommand(c.beaconType, "update_beacon", "", params)
	beacon = result.(*data1.BeaconV1)
	assert.Nil(t, err)
	assert.NotNil(t, beacon)
	assert.Equal(t, c.BEACON1.Id, beacon.Id)
	assert.Equal(t, "ABC", beacon.Label)

	// Get beacon by udi
	params = cdata.NewAnyValueMapFromTuples(
		"udi", beacon1.Udi,
	)
	result, err = c.client.CallCommand(c.beaconType, "get_beacon_by_udi", "", params)
	beacon = result.(*data1.BeaconV1)
	assert.Nil(t, err)
	assert.NotNil(t, beacon)
	assert.Equal(t, c.BEACON1.Id, beacon.Id)

	// Calculate position for one beacon
	params = cdata.NewAnyValueMapFromTuples(
		"site_id", "1",
		"udis", []string{"00001"},
	)
	result, err = c.client.CallCommand(c.geoPointType, "calculate_position", "", params)
	position := result.(*data1.GeoPointV1)
	assert.Nil(t, err)
	assert.NotNil(t, position)
	assert.Equal(t, "Point", position.Type)
	assert.Equal(t, (float32)(0.0), position.Coordinates[0][0])
	assert.Equal(t, (float32)(0.0), position.Coordinates[0][1])

	// Delete the beacon
	params = cdata.NewAnyValueMapFromTuples(
		"beacon_id", beacon1.Id,
	)
	result, err = c.client.CallCommand(c.beaconType, "delete_beacon_by_id", "", params)
	beacon = result.(*data1.BeaconV1)
	assert.Nil(t, err)
	assert.NotNil(t, beacon)
	assert.Equal(t, c.BEACON1.Id, beacon.Id)

	// Try to get deleted beacon
	params = cdata.NewAnyValueMapFromTuples(
		"beacon_id", beacon1.Id,
	)
	result, err = c.client.CallCommand(c.beaconType, "get_beacon_by_id", "", params)
	assert.Nil(t, err)
	assert.Nil(t, result)
}

func TestBeaconsCommmandableHttpServiceV1(t *testing.T) {
	c := newBeaconsHttpServiceV1Test()

	c.setup(t)
	t.Run("CRUD Operations", c.testCrudOperations)
	c.teardown(t)
}

/test/services/version1/BeaconsCommandableHttpServiceV1_test.dart

import 'dart:convert';
import 'package:test/test.dart';
import 'package:http/http.dart' as http;
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services_beacons_dart/pip_services_beacons_dart.dart';

final BEACON1 = BeaconV1(
    id: '1',
    udi: '00001',
    type: BeaconTypeV1.altBeacon,
    site_id: '1',
    label: 'TestBeacon1',
    center: {
      'type': 'Point',
      'coordinates': [0.0, 0.0]
    },
    radius: 50.0);
final BEACON2 = BeaconV1(
    id: '2',
    udi: '00002',
    type: BeaconTypeV1.iBeacon,
    site_id: '1',
    label: 'TestBeacon2',
    center: {
      'type': 'Point',
      'coordinates': [2.0, 2.0]
    },
    radius: 70.0);

var httpConfig = ConfigParams.fromTuples([
  'connection.protocol',
  'http',
  'connection.host',
  'localhost',
  'connection.port',
  3000
]);

void main() {
  group('BeaconsCommandableHttpServiceV1', () {
    BeaconsMemoryPersistence persistence;
    BeaconsController controller;
    BeaconsCommandableHttpServiceV1 service;
    http.Client rest;
    String url;

    setUp(() async {
      url = 'http://localhost:3000';
      rest = http.Client();

      persistence = BeaconsMemoryPersistence();
      persistence.configure(ConfigParams());

      controller = BeaconsController();
      controller.configure(ConfigParams());

      service = BeaconsCommandableHttpServiceV1();
      service.configure(httpConfig);

      var references = References.fromTuples([
        Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'),
        persistence,
        Descriptor('beacons', 'controller', 'default', 'default', '1.0'),
        controller,
        Descriptor('beacons', 'service', 'http', 'default', '1.0'),
        service
      ]);

      controller.setReferences(references);
      service.setReferences(references);

      await persistence.open(null);
      await service.open(null);
    });

    tearDown(() async {
      await service.close(null);
      await persistence.close(null);
    });

    test('CRUD Operations', () async {
      BeaconV1 beacon1;

      // Create the first beacon
      var resp = await rest.post(url + '/v1/beacons/create_beacon',
          headers: {'Content-Type': 'application/json'},
          body: json.encode({'beacon': BEACON1}));
      var beacon = BeaconV1();
      beacon.fromJson(json.decode(resp.body));
      expect(beacon, isNotNull);
      expect(BEACON1.udi, beacon.udi);
      expect(BEACON1.site_id, beacon.site_id);
      expect(BEACON1.type, beacon.type);
      expect(BEACON1.label, beacon.label);
      expect(beacon.center, isNotNull);

      // Create the second beacon
      resp = await rest.post(url + '/v1/beacons/create_beacon',
          headers: {'Content-Type': 'application/json'},
          body: json.encode({'beacon': BEACON2}));
      beacon = BeaconV1();
      beacon.fromJson(json.decode(resp.body));
      expect(beacon, isNotNull);
      expect(BEACON2.udi, beacon.udi);
      expect(BEACON2.site_id, beacon.site_id);
      expect(BEACON2.type, beacon.type);
      expect(BEACON2.label, beacon.label);
      expect(beacon.center, isNotNull);

      // Get all beacons
      resp = await rest.post(url + '/v1/beacons/get_beacons',
          headers: {'Content-Type': 'application/json'},
          body: json
              .encode({'filter': FilterParams(), 'paging': PagingParams()}));
      var page = DataPage<BeaconV1>.fromJson(json.decode(resp.body), (item) {
        var beacon = BeaconV1();
        beacon.fromJson(item);
        return beacon;
      });
      expect(page, isNotNull);
      expect(page.data.length, 2);

      beacon1 = page.data[0];

      // Update the beacon
      beacon1.label = 'ABC';

      resp = await rest.post(url + '/v1/beacons/update_beacon',
          headers: {'Content-Type': 'application/json'},
          body: json.encode({'beacon': beacon1}));
      beacon = BeaconV1();
      beacon.fromJson(json.decode(resp.body));
      expect(beacon, isNotNull);
      expect(beacon1.id, beacon.id);
      expect('ABC', beacon.label);

      // Get beacon by udi
      resp = await rest.post(url + '/v1/beacons/get_beacon_by_udi',
          headers: {'Content-Type': 'application/json'},
          body: json.encode({'udi': beacon1.udi}));
      beacon = BeaconV1();
      beacon.fromJson(json.decode(resp.body));
      expect(beacon, isNotNull);
      expect(beacon1.id, beacon.id);

      // Calculate position for one beacon
      resp = await rest.post(url + '/v1/beacons/calculate_position',
          headers: {'Content-Type': 'application/json'},
          body: json.encode({
            'site_id': '1',
            'udis': ['00001']
          }));
      var position = json.decode(resp.body);

      expect(position, isNotNull);
      expect('Point', position['type']);
      expect(position['coordinates'].length, 2);
      expect(0, position['coordinates'][0]);
      expect(0, position['coordinates'][1]);

      // Delete the beacon
      resp = await rest.post(url + '/v1/beacons/delete_beacon_by_id',
          headers: {'Content-Type': 'application/json'},
          body: json.encode({'beacon_id': beacon1.id}));
      beacon = BeaconV1();
      beacon.fromJson(json.decode(resp.body));
      expect(beacon, isNotNull);
      expect(beacon1.id, beacon.id);

      // Try to get deleted beacon
      resp = await rest.post(url + '/v1/beacons/get_beacon_by_id',
          headers: {'Content-Type': 'application/json'},
          body: json.encode({'beacon_id': beacon1.id}));
      expect(resp.body, isEmpty);
    });
  });
}

/test/services/version1/test_BeaconsHttpServiceV1.py

import json
import time
from json import JSONDecodeError
from typing import Union

import requests
from pip_services3_commons.config import ConfigParams
from pip_services3_commons.refer import References, Descriptor
from pip_services3_commons.reflect import PropertyReflector
from pip_services3_commons.run import Parameters

from src.data.version1 import BeaconV1, BeaconTypeV1
from src.logic.BeaconsController import BeaconsController
from src.persistence.BeaconsMemoryPersistence import BeaconsMemoryPersistence
from src.services.version1.BeaconsHttpServiceV1 import BeaconsHttpServiceV1

BEACON1 = BeaconV1("1", "1", BeaconTypeV1.AltBeacon, "00001", "TestBeacon1", {"type": 'Point', "coordinates": [0, 0]},
                   50.0)
BEACON2 = BeaconV1("2", "1", BeaconTypeV1.iBeacon, "00002", "TestBeacon2", {"type": 'Point', "coordinates": [2, 2]},
                   70.0)
BEACON3 = BeaconV1("3", "2", BeaconTypeV1.AltBeacon, "00003", "TestBeacon3", {"type": 'Point', "coordinates": [10, 10]},
                   50.0)


class TestBeaconsHttpServiceV1:
    _persistence: BeaconsMemoryPersistence
    _controller: BeaconsController
    _service: BeaconsHttpServiceV1

    @classmethod
    def setup_class(cls):
        cls._persistence = BeaconsMemoryPersistence()
        cls._controller = BeaconsController()
        cls._service = BeaconsHttpServiceV1()

        cls._service.configure(ConfigParams.from_tuples(
            'connection.protocol', 'http',
            'connection.port', 3002,
            'connection.host', 'localhost'))

        references = References.from_tuples(Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'),
                                            cls._persistence,
                                            Descriptor('beacons', 'controller', 'default', 'default', '1.0'),
                                            cls._controller,
                                            Descriptor('beacons', 'service', 'http', 'default', '1.0'),
                                            cls._service)
        cls._controller.set_references(references)
        cls._service.set_references(references)

        cls._persistence.open(None)
        cls._service.open(None)

    @classmethod
    def teardown_class(cls):
        cls._persistence.close(None)
        cls._service.close(None)

    def test_crud_operations(self):
        time.sleep(2)
        # Create the first beacon
        beacon1 = self.invoke("/v1/beacons/create_beacon",
                              Parameters.from_tuples("beacon", PropertyReflector.get_properties(BEACON1)))

        assert beacon1 is not None
        assert beacon1['id'] == BEACON1.id
        assert beacon1['site_id'] == BEACON1.site_id
        assert beacon1['udi'] == BEACON1.udi
        assert beacon1['type'] == BEACON1.type
        assert beacon1['label'] == BEACON1.label
        assert beacon1['center'] is not None

        # Create the second beacon
        beacon2 = self.invoke("/v1/beacons/create_beacon",
                              Parameters.from_tuples("beacon", PropertyReflector.get_properties(BEACON2)))

        assert beacon2 is not None
        assert beacon2['id'] == BEACON2.id
        assert beacon2['site_id'] == BEACON2.site_id
        assert beacon2['udi'] == BEACON2.udi
        assert beacon2['type'] == BEACON2.type
        assert beacon2['label'] == BEACON2.label
        assert beacon2['center'] is not None

        # Get all beacons
        page = self.invoke("/v1/beacons/get_beacons", Parameters.from_tuples("beacons"))
        assert page is not None
        assert len(page['data']) == 2

        beacon1 = page['data'][0]

        # Update the beacon
        beacon1['label'] = "ABC"
        beacon = self.invoke("/v1/beacons/update_beacon", Parameters.from_tuples("beacon", beacon1))
        assert beacon is not None
        assert beacon1['id'] == beacon['id']
        assert "ABC" == beacon['label']

        # Get beacon by udi
        beacon = self.invoke("/v1/beacons/get_beacon_by_udi", Parameters.from_tuples("udi", beacon1['udi']))
        assert beacon is not None
        assert beacon['id'] == beacon1['id']

        # Calculate position for one beacon
        position = self.invoke("/v1/beacons/calculate_position",
                               Parameters.from_tuples("site_id", '1', "udis", ['00001']))
        assert position is not None
        assert "Point" == position["type"]
        assert 2 == len(position["coordinates"])
        assert 0 == position["coordinates"][0]
        assert 0 == position["coordinates"][1]

        # Delete beacon
        self.invoke("/v1/beacons/delete_beacon_by_id", Parameters.from_tuples("id", beacon1['id']))

        # Try to get deleted beacon
        beacon = self.invoke("/v1/beacons/get_beacon_by_id", Parameters.from_tuples("id", beacon1['id']))
        assert beacon is False

    def invoke(self, route, entity) -> Union[bool, dict]:
        params = {}
        route = "http://localhost:3002" + route
        response = None
        timeout = 10000
        # Call the service
        data = json.dumps(entity)
        try:
            response = requests.request('POST', route, params=params, json=data, timeout=timeout)
            return response.json()
        except JSONDecodeError:
            if response.status_code == 404:
                return False

Not available

Run the python test.py command and make sure that all of the tests pass successfully.

Congratulations! This step finishes off the development of our microservice! However, before we can start our service up as a fully fledged microservice, we’ll first need to compose all of its components using a process container. And that’s exactly what we’ll be doing in Step 7. Wrapping microservice into container.

Step 7. Wrapping the microservice into a container.