Testing a Custom Synapse Locally

Rok Kovač
Rok Kovač
  • Updated

Before either importing the synapse for the first time or making updates to it later on, we strongly recommend to first test it locally. Testing can be done using the pytest framework and mocking the platform requests that each method would receive.

Importing dependancies

In the test file we first recommend to include the following scripts and libraries (bellow examples are taken from the sample file provided with the Pipedrive Synapse):

import pytest
import flask
import json
import main
from syncari.models import *

Helper Methods

To ease the configuration of each method we recommend to set up helper methods that return mock request data, that Syncari’s platform would provide as arguments in methods:

  • __execute_request() -> executes the synapse.
  • __route() -> wrapper on the __execute_request() to add logging and assert that the synapse always outputs a response.
  • __get_entity() -> to mock a describe request that provides an entity schema for the entity when a CRUD method is triggered.
  • get_connection() -> creates the framework persisting connection object which includes authentication and metadata.
  • get_raw_connection() -> mocks test data that the user would input in the UI.
def __execute_request(synapse_request):
    with flask.Flask(__name__).test_request_context(data=synapse_request):
        return main.execute(flask.request)

def __route(synapse_request):
    resp = __execute_request(synapse_request)
    print(resp)
    print(type(resp))
    assert resp is not None
    return json.loads(resp

def __get_entity(entity_name):
    synapse_request = Request(type=RequestType.DESCRIBE,
        connection=get_connection(),
        body=DescribeRequest(entities=[entity_name])).json()
    synapse_response = __route(synapse_request)
    print("response is: " + str(synapse_response))
    return json.loads(synapse_response[0])

def get_connection():
    synapse_request = Request(type=RequestType.TEST,
        connection=get_raw_connection(),
        body=None).json()
    resp = __execute_request(synapse_request)
    print(type(resp))
    print(resp)
    return Connection.parse_raw(resp)  

def get_raw_connection():
    authConfig=AuthConfig(accessToken="")
    connection = Connection(id='1', name='name', endpoint='https://syncariinc-sandbox.pipedrive.com/v1/', 
        authConfig=authConfig, idFieldName='idfield', watermarkFieldName='watermarkfield', 
        createdAtFieldName='createdfield', updatedAtFieldName='updatedfield', oAuthRedirectUrl='http://redirect.com', extraField='extraField')
    return connection

Testing Connection and Describe Method

Once we have the helper methods set we can test the mandatory methods which include - synapse_info, test and describe method. When it comes to testing describe methods we recommend writing two tests - one for all entities and one for a single one. This is to ensure the synapse will function for both cases: resyncing and initial setup as well as invocations during the live sync of pipelines. Describe tests are especially recommended when you are using a seeded schema and are making changes to it, since it ensures that future updates doesn’t break the existing synapse.

def test_connect_request():
    connection = get_connection()
    assert connection is not None
    assert connection.authConfig.accessToken is not None

def test_synapse_info_request():
    synapse_request = Request(type=RequestType.SYNAPSE_INFO,
        connection=get_connection(),
        body=None).json()
    synapse_response = __route(synapse_request)
    assert synapse_response is not None
    assert synapse_response['name'] == 'pipedrive'

def test_describe_request():
    entity = __get_entity('person')
    assert entity['apiName'] == 'person'
    attribute_names = [attribute['apiName'] for attribute in entity['attributes']]
    assert 'id' in attribute_names

def test_describe_all_request():
    synapse_request = Request(type=RequestType.DESCRIBE,
        connection=get_connection(),
        body=DescribeRequest(entities=[])).json()
    entities = __route(synapse_request)
    # currently 12 entities are supported
    assert len(entities) == 12
    for ent in entities:
        entity = json.loads(ent)
        assert entity['apiName'] is not None
        attribute_names = [attribute['apiName'] for attribute in entity['attributes']]

Testing Read Method

Once we have the required methods working we can move on to CRUD methods. Here we recommend to have at least one test that covers all entities on the read method (if used) - this could be on either incremental sync or a historical resync example:

def test_read_request():
    objects = ['stage','pipeline','role','activity','activityType','person','user','organization','deal','product'] #,'lead','leadLabel']

    for object in objects:
        synapse_request = Request(type=RequestType.READ,
            connection=get_connection(),
            body=SyncRequest(
                entity=__get_entity(object), 
                watermark=Watermark(start=0, end=0, isResync=True))).json()
        synapse_response = __route(synapse_request)
        assert synapse_response['watermark'] is not None
        assert len(synapse_response['data']) = 1
        for resp_dict in synapse_response['data']:
            assert resp_dict['name'] == object
            assert resp_dict['id'] is not None
            assert resp_dict['values']['id'] is not None
            assert resp_dict['lastModified']  0
            assert resp_dict['createdAt']  0

Testing CRUD Methods

For the rest of the CRUD methods you can implement them one of the two ways - you can have separate methods as we have a separate Ge By ID method that tries to fetch a single record or have them grouped together, like Create, Update, Delete.

def test_crud_request():
    entity = __get_entity('person')
    watermark = Watermark(start=0, end=0)
    data = [Record(name='person', id='', syncariEntityId='',deleted=False,
        values={'name':'Test Lead1','email':'testlead1@test.com'}, lastModified=0,createdAt=0),
            Record(name='person', id='', syncariEntityId='',deleted=False,
        values={'name':'Test Lead2','email':'testlead2@test.com'}, lastModified=0,createdAt=0)]
    try:
        synapse_request = Request(type=RequestType.CREATE, connection=get_connection(),
            body=SyncRequest(entity=entity,watermark=watermark,data=data)).json()
        synapse_response = __route(synapse_request)
        assert len(synapse_response) == 2
        cnt = 0
        for resp in synapse_response:
            resp_dict = json.loads(resp)
            assert resp_dict['id'] is not None
            assert resp_dict['success'] is True
            data[cnt].id = resp_dict['id']
            data[cnt].values['first_name'] = 'Test First Name'
            cnt += 1
        synapse_request = Request(type=RequestType.UPDATE, connection=get_connection(),
            body=SyncRequest(entity=entity,watermark=watermark,data=data)).json()
        synapse_response = __route(synapse_request)
        assert len(synapse_response) == 2
        for resp in synapse_response:
            resp_dict = json.loads(resp)
            assert resp_dict['id'] is not None
            assert resp_dict['success'] is True
        synapse_request = Request(type=RequestType.GET_BY_ID, connection=get_connection(),
            body=SyncRequest(entity=entity,watermark=watermark,data=data)).json()
        synapse_response = __route(synapse_request)
        assert len(synapse_response) == 2
        for resp in synapse_response:
            resp_dict = json.loads(resp)
            assert resp_dict['id'] is not None
            assert resp_dict['values']['id'] is not None
            assert resp_dict['values']['first_name'] == 'Test First Name'

    finally:
        synapse_request = Request(type=RequestType.DELETE, connection=get_connection(),
            body=SyncRequest(entity=entity,watermark=watermark,data=data)).json()
        synapse_response = __route(synapse_request)
        assert len(synapse_response) == 2

Testing Webhook Methods

For webhook methods the extract_webhook_identifier_request tests the logic for returning the webhookIdentifier value, while the test_process_webook_request tests processing of a single webhook payload. For both cases we recommend example payload requests.

def test_extract_webhook_identifier_request():
    synapse_request = Request(type=RequestType.EXTRACT_WEBHOOK_IDENTIFIER,
        connection=get_connection(),
        body=WebhookRequest(body=json.dumps(test_webhook_request_body))).json()
    synapse_response = __route(synapse_request)
    assert synapse_response == 2881333

def test_process_webook_request():
    synapse_request = Request(type=RequestType.PROCESS_WEBHOOK,
        connection=get_connection(),
        body=WebhookRequest(body=json.dumps(test_webhook_request_body))).json()
    synapse_response = __route(synapse_request)
    for resp in synapse_response:
        resp_dict = json.loads(resp)
        assert resp_dict is not None
        assert resp_dict['id'] == '1'
        assert resp_dict['name'] == 'person'
        assert resp_dict['values']['first_name'] == 'Syncari'
        assert resp_dict['values']['owner_id'] == 14434274
Share this

Was this article helpful?

0 out of 0 found this helpful