Introducing Cukes

**Please note this is not merge-able yet, this is mostly for discussion purposes.**

Hey all! One of the first things I've been tasked with is improving the API specification. Over the weekend I asked around my network, did some research, and spiked out an idea, and I wanted to run it all past you.

## Requirements

As always, the first step is to gather requirements. Here's what I gather we need for the API spec:

1. A way to define what each aspect of the API does.
2. An automated way to turn those definitions into executable tests.
3. A way to run those tests regularly to ensure conformance.

All three of these are pretty straightforward, but might as well spell them out.

4. These specifications must be easy to understand by a large variety of people.

This one is complicated. There are two kinds of concerns here: {in,out}side of Balanced, and across software language groups.

The first is the in/out part. Basically, while we have a very clear idea of what everything does, people who are new to Balanced won't. Just today I got an email from someone, asking if Balanced was appropriate for them, when their marketplace was basically identical to Rent My Bike. I'm happy to answer those kinds of questions, but we need something that will be easy for people who don't live and breathe payments to grok.

Secondly, software language groups. If we write our specs in Python, Pythonistas will be able to understand it, but Haskellers might not. Well, they probably will, but you know what I mean. Everyone wants their preferred language, but our product is an API, and it's language agnostic. Therefore, we need something that's not incredibly closely tied to a particular software language.

5. The specification has to facilitate collaboration on what the specification actually does.

This is related to #4, but is a bit different. It's not enough that the spec is _legible_ to everyone, but that it's easy to bikeshed. ;)

## What we have

Right now, the API spec looks largely like this: https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/tokenize_without_secret.yml

It's a bunch of YAML files, with a totally custom, bash/Python runner. 

This situation is good in that it's language agnostic, but bad in that it's 100% custom, so it's not natively understood by _anyone_, and while it communicates to _some_ degree of what the specs do, it's almost 100% code. It took me some time to be able to actually understand what's going on, which will always happen, of course, but it's still hard to turn into actual _words_, which impedes #4 and #5.

## Solution

I've spiked out a temporary solution based on a tool called "Cucumber." Before I tell you more, let's compare:

Current:      https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/tokenize_without_secret.yml

Cucumber:  https://github.com/balanced/balanced-api/blob/cukes/features/cards/tokenize.feature

The 'current' has two features, and the cucumber only has one, so ignore past line 18 on 'current.'

Cucumber, if you haven't heard of it, is a tool for collaboration between stakeholders. The idea is that you write high level descriptions of what you want your software to do in psudo-English ("Gherkin"), and then that gets transformed into code to actually run and test those descriptions.

While I've implemented the scenarios in Ruby, you can write the 'code' aspect of the tests in a variety of languages. You can also write the features in a variety of languages, too. For example: https://github.com/cucumber/cucumber/blob/master/examples/i18n/el/features/addition.feature

As for how this gets turned into code, I have two versions: 

JSON Schema: https://github.com/balanced/balanced-api/blob/cukes/features/step_definitions/card_steps.rb

Assertions: https://github.com/balanced/balanced-api/blob/b91d4264eaace33373606191ef4d32ca1372ac47/features/step_definitions/card_steps.rb

Since we already assert against JSON Schema, I think sticking with that makes sense, as it'll be easier to port.

I actually think the "200 code" assertion may be too low level.

## Pros

Basically, this is the exact use case for Cucumber. Because the specifications are written in English, we help make it easier for people to understand, as well as making it easier from people to collaborate across language boundaries. I have some issue with English being the de facto human language that technologists communicate in, but it is what it is, at least for now. This solves #4 and #5, our biggest problems, nicely.

Furthermore, collaboration can be done on the high-level language, without worrying about the details. For example, Matin had some concern over my wording of the last part of that scenario, because 'that's not exactly what that does.' Excellent, we have some sort of communication error here. Cucumber's high-level descriptions bring that kind of thing out, and people can submit pull requests with just the Gherkin, and we can add the implementation ourselves.

I wonder if "elevator pitches for your code" is a good way of describing Cucumber. Maybe I've been in San Francisco too long. ;)

## Cons

Cucumber is... not a tool without some controversy. It was originally described as an 'acceptance testing' tool, and so people got into 'test mode' and started writing tests. Cucumber is very bad when used in this way. Eventually, even some helpers were removed [1] to discourage writing tests at a low level. Basically, if you're not writing very high-level descriptions, you are Cuking It Wrong. ;)

Cucumber can be slow. There's the whole Gherkin-code 'compilation' step, so it can take longer than other frameworks. This is mitigatable, but we're already planning on making HTTP requests against our API for our tests, so they'll be slow no matter what the framework is.

As I mentioned above, some people really, really dislike cukes. I think that's mostly due to having terrible Cucumber suites, people have developed some scars. This can be overcome, but there may be some initial 'eww.' There may be with our specs as of right now. ;)

## Other options

Basically, if this is a bad idea, we should just move to some sort of very basic unit testing library in a not-to-unusual language. I'd basically use Python and Requests to do this, since it's what we're most familiar with overall. I think that the downsides of being language specific is pretty big, though.

## Conclusion

I'm sure I've forgotten a few things, but I'd love to hear feedback, good and bad. I think this is the right move, but I don't always have all the right answers... I would especially love feedback from @mattwynne and @aslakhellesoy .

1: http://aslakhellesoy.com/post/11055981222/the-training-wheels-came-off


## Scenarios

### CRUD tests

Here's all the cases we need to cover to merge. From https://docs.balancedpayments.com/1.1/api/ :

API KEYS
- [x] Create a Key
- [x] Get a Key
- [x] List API Keys
- [x] Delete API Key

BANK ACCOUNT VERIFICATIONS
- [x] Create a Bank Account Verification
- [x] Get a Bank Account Verification
- [x] Confirm a Bank Account Verification

BANK ACCOUNTS
- [x] Tokenize a Bank Account (Direct)
- [x] Get a Bank Account
- [x] List Bank Accounts
- [x] Delete a Bank Account
- [x] Credit a Bank Account
- [x] Debit a Bank Account

CALLBACKS
- [x] Create a Callback
- [x] Get a Callback
- [x] List all Callbacks
- [x] Delete a Callback

CARDS
- [x] Tokenize a Card (Direct)
- [x] Get a Card
- [x] List All Cards
- [x] Update a Card
- [x] Deleting a Card
- [x] Debit a Card

CREDITS
- [x] Create a Credit
- [x] Get a Credit
- [x] List All Credits

CUSTOMERS
- [x] Create a Customer
- [x] Get a Customer
- [x] List Customers
- [x] Delete a Customer
- [x] Associate a Card
- [x] Associate a Bank Account

DEBITS
- [x] Create a Debit
- [x] Get a Debit
- [x] List All Debits
- [x] Update a Debit
- [x] Refund a Debit

EVENTS
- [x] Get an Event
- [x] List all Events

REFUNDS
- [x] Create a Refund
- [x] Get a Refund
- [x] List All Refunds
- [x] Update a Refund

REVERSALS
- [x] Create a Reversal
- [x] Get a Reversal
- [x] List All Reversals
- [x] Update a Reversal


### non-crud tests

- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/root_resource.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/resources_redirect.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/reversals/create.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/reversals/fail_amount.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/reversals/failed.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/reversals/succeeded.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/refunds/create.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/refunds/create_after_change_default.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/refunds/fail.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/orders/basic_usage.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/orders/cannot_over_credit.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/orders/create.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/orders/create_refund.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/orders/failed_refund_pending.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/orders/simple_order.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/orders/transaction_inherit_description.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/orders/unverified_merchant.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/debits/create_new_card.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/debits/debit_bank_account.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/debits/debit_card.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/debits/debit_customer.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/debits/debit_customer_fail.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/customers/create.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/customers/set_default_destination.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/customers/set_default_source.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/customers/underwrite.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/customers/underwrite_with_destination.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/customers/update.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/credits/create_new_bank_account.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/credits/credit_bank_account.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/credits/credit_card_fail.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/credits/credit_customer.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/credits/credit_customer_fail.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/checkout_flow/cancel_an_order.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/checkout_flow/existing_buyer_purchases_with_a_new_card.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/checkout_flow/existing_buyer_purchases_with_an_existing_card.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/checkout_flow/new_buyer_purchases_an_item.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/add_to_customer.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/avs_postal_match.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/avs_street_match.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/card_brand.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/card_tokenization.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/cvv_match.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/tokenize_without_secret.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/cards/update_meta.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/bank_accounts/add_to_customer.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/bank_accounts/bank_account_tokenization.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/bank_accounts/bank_name.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/bank_accounts/tokenize_without_secret.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/bank_accounts/verification_failed.yml
- [x] https://github.com/balanced/balanced-api/blob/revision1/scenarios/bank_accounts/verification_success.yml

That's exhaustive. Let's do this.