Discount Codes in Open Event Server

The Open Event System allows usage of discount codes with tickets and events. This blogpost describes what types of discount codes are present and what endpoints can be used to fetch and update details.

In Open Event API Server, each event can have two types of discount codes. One is ‘event’ discount code, while the other is ‘ticket’ discount code. As the name suggests, the event discount code is an event level discount code and the ticket discount code is ticket level.

Now each event can have only one ‘event’ discount code and is accessible only to the server admin. The Open Event server admin can create, view and update the ‘event’ discount code for an event. The event discount code followsDiscountCodeEvent Schema. This schema is inherited from the parent class DiscountCodeSchemaPublic. To save the unique discount code associated with an event, the event model’s discount_code_id field is used.

The ‘ticket’ discount is accessible by the event organizer and co-organizer. Each event can have any number of ‘ticket’ discount codes. This follows the DiscountCodeTicket schema, which is also inherited from the same base class ofDiscountCodeSchemaPublic. The use of the schema is decided based on the value of the field ‘used_for’ which can have the value either ‘events’ or ‘tickets’. Both the schemas have different relationships with events and marketer respectively.

We have the following endpoints for Discount Code events and tickets:
‘/events/<int:event_id>/discount-code’
‘/events/<int:event_id>/discount-codes’

The first endpoint is based on the DiscountCodeDetail class. It returns the detail of one discount code which in this case is the event discount code associated with the event.

The second endpoint is based on the DiscountCodeList class which returns a list of discount codes associated with an event. Note that this list also includes the ‘event’ discount code, apart from all the ticket discount codes.

class DiscountCodeFactory(factory.alchemy.SQLAlchemyModelFactory):
   class Meta:
       model = DiscountCode
       sqlalchemy_session = db.session
event_id = None
user = factory.RelatedFactory(UserFactory)
user_id = 1


Since each discount code belongs to an event(either directly or through the ticket), the factory for this has event as related factory, but to check for 
/events/<int:event_id>/discount-code endpoint we first need the event and then pass the discount code id to be 1 for dredd to check this. Hence, event is not included as a related factory, but added as a different object every time a discount code object is to be used.

@hooks.before("Discount Codes > Get Discount Code Detail of an Event > Get Discount Code Detail of an Event")
def event_discount_code_get_detail(transaction):
   """
   GET /events/1/discount-code
   :param transaction:
   :return:
   """
   with stash['app'].app_context():
       discount_code = DiscountCodeFactory()
       db.session.add(discount_code)
       db.session.commit()
       event = EventFactoryBasic(discount_code_id=1)
       db.session.add(event)
       db.session.commit()


The other tests and extended documentation can be found 
here.

References:

Stripe Authorization In Open Event API Server

The Open Event System supports payments through stripe. Stripe is a suite of APIs that powers commerce for businesses of all sizes. This blogpost covers testing of Stripe Authorization Schema and endpoints in the API Server.

The Stripe Authorization class provides the following endpoints:

'/stripe-authorization'
'/stripe-authorization/<int:id>'
'/events/<int:event_id>/stripe-authorization'
'/events/<event_identifier>/stripe-authorization'


In the pull request made for adding documentation and tests, these two endpoints were removed:

'stripe_authorization_list',
'/events/<int:event_id>/stripe-authorization',
'/events/<event_identifier>/stripe-authorization'

This is because each event can have only one stripe authorization, so there can not exist a list of stripe authorization objects related to an event.

The ‘stripe_authorization_list’ endpoint is made POST only. This is because Open Event does not allow individual resources’ list to be accessible. Since, there is no endpoint which returns a list of Stripe Authorizations the StripeAuthorizationList(ResourceListis removed.

The ResourceDetail class was modified to add a query to support  results from ‘/events/<int:event_id>/stripe-authorization’ endpoint suThe view_kwargs for the detail endpoint has to contain the resource id, so event_id from view_kwags is used to get the id for stripe authorization.

stripe_authorization = self.session.query(StripeAuthorization).filter_by(event_id=view_kwargs['event_id']).one()
view_kwargs['id'] = stripe_authorization.id

Writing Test for Documentation

(Tests for the /events/1/stripe-authorization  is described here, for others please refer to links in additional references.)

To test the  /events/1/stripe-authorization endpoint for GET, we first insert a Stripe Authorization object into the database which will then be retrieved by the GET request and then compared with the expected response body from the documentation file.

Since stripe-auth has a required relationship with event class, an event must also exist for strie auth object to be created. The event is also required because the endpoint ‘events/’ expects an event object to exist. The StripeAuthorizationFactory takes care of this with event as a RelatedFactory. So when a StripeAuthorization object is inserted, an event is created first and passed as the required relationship to stripe_list_post endpoint.

The event is related to the stripe object by setting event_id = 1 in the factory.

Adding the pre-test hook for GET:

@hooks.before("StripeAuthorization > Stripe Authorization for an Event > Get Stripe Authorization Details of an Event")
def event_stripe_authorization_get_detail(transaction):
   """
   GET /events/1/stripe-authorization
   :param transaction:
   :return:
   """
   with stash['app'].app_context():
       stripe = StripeAuthorizationFactory()
       db.session.add(stripe)
       db.session.commit()


The expected response for this request can be found
 here.

Additional References:

Writing Dredd Test for Event Topic-Event Endpoint in Open Event API Server

The API Server exposes a large set of endpoints which are well documented using apiary’s API Blueprint. Ton ensure that these documentations describe exactly what the API does, as in the response made to a request, testing them is crucial. This testing is done through Dredd Documentation testing with the help of FactoryBoy for faking objects.

In this blogpost I describe how to use FactoryBoy to write Dredd tests for the Event Topic- Event endpoint of Open Event API Server.

The endpoint for which tests are described here is this: For testing this endpoint, we need to simulate the API GET request by making a call to our database and then compare the response received to the expected response written in the api_blueprint.apib file. For GET to return some data we need to insert an event with some event topic in the database.

The documentation for this endpoint is the following:

To add the event topic and event objects for GET events-topics/1/events, we use a hook. This hook is written in hook_main.py file and is run before the request is made.

We add this decorator on the function which will add objects to the database. This decorator basically traverses the APIB docs following level with number of ‘#’ in the documentation to ‘>’ in the decorator. So for
 we have,

Now let’s write the method itself. In the method here, we first add the event topic object using EventTopic Factory defined in the factories/event-topic.py file, the code for which can be found here.

Since the endpoint also requires some event to be created in order to fetch events related to an event topic, we add an event object too based on the EventFactoryBasic class in factories/event.py  file. [Code]

To fetch the event related to a topic, the event must be referenced in that particular event topic. This is achieved by passing event_topic_id=1 when creating the event object, so that for the event that is created by the constructor, event topic is set as id = 1.
event = EventFactoryBasic(event_topic_id=1)
In the EventFactoryBasic class, the event_topic_id is set as ‘None’, so that we don’t have to create event topic for creating events in other endpoints testing also. This also lets us to not add event-topic as a related factory. To add event_topic_id=1 as the event’s attribute, an event topic with id = 1 must be already present, hence event_topic object is added first.
After adding the event object also, we commit both of these into the database. Now that we have an event topic object with id = 1, an event object with id = 1 , and the event is related to that event topic, we can make a call to GET event-topics/1/events and get the correct response.

Related:

Working with Activity API in Open Event API Server


Recently, I added the Activities API with documentation and dredd tests for the same in
Open Event API Server. The Activity Model in the Open Event Server is basically a log of all the things that happen while the server is running, like – event updates, speaker additions, invoice generations and other similar things. This blogpost explains how to implement Activity API in the Open Event API Server’s nextgen branch. In the Open Event Server, we first add the endpoints, then document these so that the consumers ( Open Event Orga App, Open Event Frontend) find it easy to work with all the endpoints.

We also test the documentation against backend implementation to ensure that a end-developer who is working with the APIs is not misled to believe what each endpoint actually does in the server.

We also test the documentation against backend implementation to ensure that a end-developer who is working with the APIs is not misled to believe what each endpoint actually does in the server.
The Activities API endpoints are based on the Activity database model. The Activity table has four columns – 
id, actor, time, action, the names are self-explanatory. Now for the API schema, we need to make fields corresponding these columns.
Since id is auto generated, we do not need to add it as a field for API. Also the activity model’s __init__ method stamps time with the current system time. So this field is also not required in the API fields. We are left with two fields- actor and action.

Defining API Schema

Next, we define the API Schema class for Activities model. This will involve a Meta class and fields for the class.The Meta class contains the metadata of the class. This includes details about type_,

self_view, self_view_kwargs and an inflect parameter to dasherize the input fields from request body.

We define the four fields – id, actor, time and action according to marshmallow fields based on the data type and parameters from the activities model. Since id, actor and action are string columns and time is a DateTime column, the fields are written as following:

The id field is marked as dump only because it is a read-only type field. The other fields are marked with allow_none as they are all non-required field.

ActivityList Class:
The activity list class will provide us with the endpoint: “/v1/activities”

This endpoint will list all the activities. Since we wanted only GET requests to be working for this, so defined method = [‘GET’, ] for ResourceList. The activities are to be internally created based on different actions like creating an event, updating an event, adding speakers to sessions and likewise. Since the activities are to be shown only to the server admin, 
is_admin permission is used from the permission manager.

ActivityDetail Class:

The activity detail gives methods to work with an activity based on the activity id.
The endpoint provided is :  ‘/v1/activity/<int:activity_id>’

Since this is also an admin-only accessible GET only endpoint the following was written:

Writing Documentation:

The documentation is written using API Blueprint. Since we have two endpoints to document : /v1/activities and /v1/activities/<int:activity_id> both GET only.

So we begin by defining the ‘Group Activities’ , under which we first list  ‘Activity Collection’ which essentially is the Activity List class.

For this class, we have the endpoint:  /v1/activities. This is added for GET request. The parameters – actor, time and action are described along with description, type and whether they are required or not.

The request headers are written as part of the docs followed by the expected response.

Next we write the ‘Activity Details’ which represents the ActivityDetail class. Here the endpoint /v1/activities/<int:activity_id> is documented for GET. The parameter here is activity_id, which is the id of the activity to get the details of.

Writing DREDD Test for Documentation

To imitate the request responses, we need a faker script which creates an object of the the class we are testing the docs for then makes the request. For this we use FactoryBoy and dredd hooks to insert data into the database.

Defining Factory Model for Activity db model

The above is the factory model for activity model. It is derived from

factory.alchemy.SQLAlchemyModelFactory. The meta class defines the db model and sqlalchemy session to be used. The actor and action have dummy strings as part of the request body.

Writing Hooks
Now to test these endpoints we need to add objects to the database so that the GET requests have an object to fetch. This is done by dredd hooks. Before each request, an object of the corresponding factory class is initialised and committed into the database. Thus a dummy object is available for dredd to test on. The request is made and the real output is compared with the expected output written in the API Blueprint documentation.

This is what the hooks look like for  this endpoint: GET /activities

Now if the expected responses and actual responses match, the dredd test successfully passes. This dredd test in run on each build of the project on Travis to ensure that documented code does exactly what is says!

This concludes the process to write an API right from Schema to Resources and Documentation and Dredd tests.

Additional Resources:

Running Dredd Hooks as a Flask App in the Open Event Server

The Open Event Server is based on the micro-framework Flask from its initial phases. After implementing API documentation, we decided to implement the Dredd testing in the Open Event API.

After isolating each request in Dredd testing, the real challenge is now to bind the database engine to the Dredd Hooks. And as we have been using Flask-SQLAlchemy db.Model Baseclass for building all the models and Flask, being a micro framework itself, came to our rescue as we could easily bind the database engine to the Flask app. Conventionally dredd hooks are written in pure Python, but we will running them as a self contained Flask app itself.

How to initialise this flask app in our dredd hooks. The Flask app can be initialised in the before_all hook easily as shown below:

def before_all(transaction):
    app = Flask(__name__)
    app.config.from_object('config.TestingConfig')

 

The database can be binded to the app as follows:

def before_all(transaction):
app = Flask(__name__)
app.config.from_object('config.TestingConfig')
db.init_app(app)
Migrate(app, db)

 

The challenge now is how to bind the application context when applying the database fixtures. In a normal Flask application this can be done as following:

with app.app_context():
#perform your operation

 

While for unit tests in python:

with app.test_request_context():
#perform tests

 

But as all the hooks are separate from each other, Dredd-hooks-python supports idea of a single stash list where you can store all the desired variables(a list or the name stash is not necessary).

The app and db can be added to stash as shown below:

@hooks.before_all
def before_all(transaction):
app = Flask(__name__)
app.config.from_object('config.TestingConfig')
db.init_app(app)
Migrate(app, db)
stash['app'] = app
stash['db'] = db

 

These variables stored in the stash can be used efficiently as below:

@hooks.before_each
def before_each(transaction):
with stash['app'].app_context():
db.engine.execute("drop schema if exists public cascade")
db.engine.execute("create schema public")
db.create_all()

 

and many other such examples.

Related Links:
1. Testing Your API Documentation With Dredd: https://matthewdaly.co.uk/blog/2016/08/08/testing-your-api-documentation-with-dredd/
2. Dredd Tutorial: https://help.apiary.io/api_101/dredd-tutorial/
3. Dredd Docs: http://dredd.readthedocs.io/