diff --git a/.travis.yml b/.travis.yml index 7a6f6cf..f973227 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,4 @@ -before_install: - - git submodule update --init --recursive -language: python -python: - - 2.7 -install: - - pip install -r tester/requirements.txt -script: - - cd tester && ./travis.sh +language: ruby +rvm: + - 2.0.0 +script: bin/rake cruise diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..42a4a6b --- /dev/null +++ b/Gemfile @@ -0,0 +1,8 @@ +source "https://rubygems.org" + +gem "cucumber" +gem "httparty" +gem "json-schema" +gem "uri_template" +gem "rake" +gem 'debugger' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..e050348 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,41 @@ +GEM + remote: https://rubygems.org/ + specs: + builder (3.2.2) + columnize (0.3.6) + cucumber (1.3.10) + builder (>= 2.1.2) + diff-lcs (>= 1.1.3) + gherkin (~> 2.12) + multi_json (>= 1.7.5, < 2.0) + multi_test (>= 0.0.2) + debugger (1.6.5) + columnize (>= 0.3.1) + debugger-linecache (~> 1.2.0) + debugger-ruby_core_source (~> 1.3.1) + debugger-linecache (1.2.0) + debugger-ruby_core_source (1.3.1) + diff-lcs (1.2.5) + gherkin (2.12.2) + multi_json (~> 1.3) + httparty (0.12.0) + json (~> 1.8) + multi_xml (>= 0.5.2) + json (1.8.1) + json-schema (2.1.3) + multi_json (1.8.2) + multi_test (0.0.2) + multi_xml (0.5.5) + rake (10.1.0) + uri_template (0.6.0) + +PLATFORMS + ruby + +DEPENDENCIES + cucumber + debugger + httparty + json-schema + rake + uri_template diff --git a/README.md b/README.md index 112155b..7c4da23 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,53 @@ +## Balanced API Specification + [![Build Status](https://travis-ci.org/balanced/balanced-api.png?branch=revision1)](https://travis-ci.org/balanced/balanced-api) -Balanced API Spec -================= - -Online marketplaces enable a new form of commerce at a scale that has never -existed before. The success of online marketplaces has the potential to -materially effect the global economy by creating new jobs and economic activity -that previously did not exist. Airbnb, Kickstarter, Etsy, and other -marketplaces have created new forms of income for businesses and individuals. -That's what gets the [Balanced](https://www.balancedpayments.com/) team excited -and what the [Balanced API](https://www.balancedpayments.com/docs/integration) -hopes to support. - -Payments for marketplaces is unfortunately painful because of unique -requirements including paying sellers, payments aggregation policies, tax -ramifications, and fraud. Balanced lets you charge cards, escrow funds, and pay -sellers the next business day without the pain of building a payments system. - -The primary goal of this repo is to create more openness behind the decisions -driving the designs and functionality of the Balanced API. We reached out to -existing and potential customers when designing the API, but that was a limited -set of people we already knew. We've received tremendous growth in the last few -months, and our new customers have great feedback or at least want to -understand the reasoning behind the original decisions. - -We're going to automate validation of our API code against the specifications -in the repo. Any changes to the API can't be deployed unless they've been -merged into master first. Any merge to master will happen concurrently with -deploys of the API and the [docs](https://www.balancedpayments.com/docs). That -means the specs need to be auto generated (except for comments) instead of -handwritten to make sure the code and specs can never go out of sync. - -We'll do our best to have even internal discussions online. All changes (even -by the Balanced team) to the specs must be submitted via pull request and can -only be merged in by @matin after giving the community a chance to comment on -the changes. - - -Running Tests -============= - -Create a new python2 virtual environment. -``` - $ cd tester - $ pip install -r requirements.txt - $ python fixture_data.py > fixtures.json - $ python runner.py ../scenarios/[scenario name] +This repository contains an executable specification of the Balanced API. The +goal of this repository is two-fold. First, to ensure that the API is working +according to the spec. Second, to provide a place to have discussions around +the API as a product. + +### An Executable Specification + +Specifications don't matter if they're not followed. Therefore, this +specification is executable, so we can ensure conformance to the spec. There are +more details in the README file inside the `features` directory, but to run these +tests yourself, simply: + +```bash +$ git clone https://github.com/balanced/balanced-api.git +$ cd balanced-api +$ bundle +$ bin/rake cucumber ``` + +This requires Ruby. We run the changes with Ruby 2.0, but other versions will +probably work as well. [Let us +know](https://github.com/balanced/balanced-api/issues/new) if you have any +problems running these specs, and we'd be happy to help. + +If you're working on a new scenario, the 'focus' task is useful. Tag a scenario +with `@focus`, and then run `bin/rake focus`. It will only run that single scenario. + +### Discussions around changes + +Speaking of issues, that's the second purpose of this repository. If you would +like to see a new feature implemented in the Balanced API, please [open an +issue](https://github.com/balanced/balanced-api/issues/new) and we'll discuss +it. + +For example, one of our biggest requests is to support non-USD currencies. +[Here](https://github.com/balanced/balanced-api/issues/23) is the issue with +the discussion, and when we support this feature, we close the issue via a pull +request that implements the specification, and then everyone on the issue gets +notified. + +Sometimes, Issues are great for collecting feedback, as well. For example, +[all implementation of the current framework was done via +PR](https://github.com/balanced/balanced-api/pull/431), and some issues need +more explanation around use cases by those who want the feature, like [Bitcoin +support](https://github.com/balanced/balanced-api/issues/204). + +We try to do as much 'internal' discussion in these issues as well, it's not +just for public feedback. If you want to know what we're thinking, just search +for a relevant issue! diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..701b883 --- /dev/null +++ b/Rakefile @@ -0,0 +1,52 @@ +require 'cucumber/rake/task' + +class BuildFailure < Exception; + def initialize(message = nil) + message ||= "Build failed" + super(message) + end +end; + +Cucumber::Rake::Task.new do |t| + t.cucumber_opts = "--format progress --tags ~@failing" +end + +Cucumber::Rake::Task.new(:focus) do |t| + t.cucumber_opts = "--format progress --tags @focus" +end + +namespace :features do + desc "Run all features" + Cucumber::Rake::Task.new(:all) do |t| + t.cucumber_opts = "--format progress" + end + + desc "Run in-progress features" + Cucumber::Rake::Task.new(:in_progress) do |t| + t.cucumber_opts = "--require formatters/ --format Cucumber::Formatter::InProgress --tags @failing" + end +end + +desc "Run complete feature build" +task :cruise do + finished_successful = run_and_check_for_exception("cucumber") + in_progress_successful = run_and_check_for_exception("features:in_progress") + + unless finished_successful && in_progress_successful + puts + puts("Finished features had failing steps") unless finished_successful + puts("In-progress Scenario/s passed when they should fail or be pending") unless in_progress_successful + puts + raise BuildFailure + end +end + +def run_and_check_for_exception(task_name) + puts "*** Running #{task_name} features ***" + begin + Rake::Task["#{task_name}"].invoke + rescue Exception => e + return false + end + true +end diff --git a/bin/cucumber b/bin/cucumber new file mode 100755 index 0000000..caa0654 --- /dev/null +++ b/bin/cucumber @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# +# This file was generated by Bundler. +# +# The application 'cucumber' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('cucumber', 'cucumber') diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000..26c7a2d --- /dev/null +++ b/bin/rake @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# +# This file was generated by Bundler. +# +# The application 'rake' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('rake', 'rake') diff --git a/features/README.md b/features/README.md new file mode 100644 index 0000000..91a554b --- /dev/null +++ b/features/README.md @@ -0,0 +1,81 @@ +## Balanced API Spec: Technical details + +Here's where the rubber hits the road. + +### Background + +We use [Cucumber](http://cukes.info/) for features. What that means is that the +descriptions are in psuedo-English, which then gets mapped to code. + +You can check out the specifications by looking at any of the files that end +with `.feature` in this directory. They look like this: + +``` +Feature: API Keys + API Keys are what customers use to authenticate against the Balanced API. + Generally speaking, this will be the first step that is needed to be taken by + the customer to get started with the API. + + Scenario: List all API keys + Customers can make as many keys as they'd like. Being able to see all of + them is a good thing. + + Given I have created more than one API keys + When I GET to /api_keys + Then I should get a 200 OK status code +``` + +As you can see by the indentation, each file describes a 'feature.' There's +then some descriptive text about what that feature is and why it's important. +Features are a collection of 'scenarios,' which describe different things the +API can do. These scenarios also have some descriptive text as well. Scenarios +are made up of 'steps,' which start with "Given," "When," "Then," "And," and a +few others. These steps are actually executed by Cucumber: they run actual code. + +That code is determined by the `step\_definitions` files, which are in that +directory. They map these feature lines to actual Ruby code. For example, + +``` +When I GET to /api_keys +``` + +is mapped to + +``` +When(/^I (\w+) to (\/\S*?)$/) do |verb, url| + options = { + headers: { + "Accept" => "application/vnd.api+json;revision=1.1", + }, + basic_auth: { + username: $api_secret, + password: "", + } + } + response = HTTParty.send(verb.downcase, "https://api.balancedpayments.com#{url}", options) + @response_code = response.code + @response_body = JSON.parse(response.body) +end +``` + +This is illustrative of why we're using Cucumber; if you're not a Rubyist, you +can probably follow the second bit, but it's full of details you just don't +care about. You care that you're making a GET to a certain URL, the rest of the stuff +is just noise. + +There is also a `support` directory with some background code that's used to +set things up. You probably don't care about it. + +### Running the tests + +The tests are run via `rake`. Simply go to the root level of the repository, and run + +``` +$ bin/rake cucumber +``` + +to run the specs that are currently passing. To see all the tasks, + +``` +$ bin/rake -T +``` diff --git a/features/checkout_flow.feature b/features/checkout_flow.feature new file mode 100644 index 0000000..7d54335 --- /dev/null +++ b/features/checkout_flow.feature @@ -0,0 +1,351 @@ +Feature: Credit cards + + Scenario: Canceling an order + Given I have an order with a debit + When I POST to /debits/:debit_id/refunds + Then I should get a 201 CREATED status code + And the response is valid according to the "refunds" schema + + Then I make a GET request to /orders/:order_id + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount": 0, + "amount_escrowed": 0 + } + """ + + Scenario: Existing buyer makes a purchase with a new card + Given I have created a customer + When I make a GET request to /customers/:customer_id + Then I should get a 200 OK status code + And the response is valid according to the "customers" schema + + Then I make a GET request to the link "customers.source" + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + + Then I POST to /cards with the JSON API body: + """ + { + "name": "Darius the Great", + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016, + "cvv": "123", + "address": { + "line1": "965 Mission St", + "line2": "Suite 425", + "city": "San Francisco", + "state": "CA", + "postal_code": "94103" + } + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + + When I make a GET request to /cards/:card_id + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_street_match": "yes", + "avs_postal_match": "yes", + "cvv_match": "yes" + } + """ + + When I PATCH to /customers/:customer_id with the JSON API body: + """ + [{ + "op": "replace", + "path": "/customers/0/links/source", + "value": ":card_id" + }] + """ + Then I should get a 200 OK status code + And the response is valid according to the "customers" schema + And the fields on this customer match: + """ + { + "links": { "source": ":card_id" } + } + """ + + When I make a POST request to the link "customers.orders" with the body: + """ + { + "description": "Catherine Malandrino Black Top", + "delivery_address": { + "line1": "965 Mission St", + "line2": "Suite 425", + "city": "San Francisco", + "state": "CA", + "postal_code": "94103" + }, + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42" + } + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "links":{ "merchant": ":customer_id" } + } + """ + + When I make a POST request to /customers/:customer_id/debits with the body: + """ + { + "amount": 10000, + "order": ":order_id", + "appears_on_statement_as": "Vaunte-Alice Ryan" + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "debits" schema + And the fields on this debit match: + """ + { + "description": "Catherine Malandrino Black Top", + "appears_on_statement_as": "BAL*Vaunte-Alice Ryan", + "status": "succeeded", + "links": { + "order": ":order_id" + } + } + """ + + When I PUT to /orders/:order_id with the JSON API body: + """ + { + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", + "courier": "usps", + "tracking_number": "9405510899359008595488" + } + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", + "courier": "usps", + "tracking_number": "9405510899359008595488" + } + } + """ + + Scenario: Existing buyer makes a purchase with an existing card + Given I have created a customer + When I make a GET request to /customers/:customer_id + Then I should get a 200 OK status code + And the response is valid according to the "customers" schema + + Then I make a GET request to the link "customers.source" + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + + When I make a POST request to /customers/:customer_id/orders with the body: + """ + { + "description": "Catherine Malandrino Black Top", + "delivery _address": { + "line1": "965 Mission St", + "line2": "Suite 425", + "city": "San Francisco", + "state": "CA", + "postal_code": "94103" + }, + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42" + } + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "links":{ "merchant": ":customer_id" } + } + """ + + When I make a POST request to the link "customers.debits" with the body: + """ + { + "amount": 10000, + "order": ":order_id", + "appears_on_statement_as": "Vaunte-Alice Ryan" + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "debits" schema + And the fields on this debit match: + """ + { + "description": "Catherine Malandrino Black Top", + "appears_on_statement_as": "BAL*Vaunte-Alice Ryan", + "status": "succeeded", + "links": { + "order": ":order_id" + } + } + """ + + When I PUT to /orders/:order_id with the JSON API body: + """ + { + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", + "courier": "usps", + "tracking_number": "9405510899359008595488" + } + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", + "courier": "usps", + "tracking_number": "9405510899359008595488" + } + } + """ + + Scenario: New buyer makes a purchase + When I POST to /cards with the JSON API body: + """ + { + "name": "Darius the Great", + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016, + "cvv": "123", + "address": { + "line1": "965 Mission St", + "line2": "Suite 425", + "city": "San Francisco", + "state": "CA", + "postal_code": "94103" + } + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_street_match": "yes", + "avs_postal_match": "yes", + "cvv_match": "yes" + } + """ + + When I make a POST request to /customers with the body: + """ + { + "name": "Darius the Great", + "email": "darius.great@gmail.com", + "source": ":card_id", + "meta": { + "ip_address": "174.240.15.249" + } + } + """ + + Then I should get a 201 OK status code + And the response is valid according to the "customers" schema + And the fields on this customer match: + """ + { + "links": { + "source": ":card_id", + "destination": null + } + } + """ + + When I make a POST request to /customers/:customer_id/orders with the body: + """ + { + "description": "Catherine Malandrino Black Top", + "delivery_address": { + "line1": "965 Mission St", + "line2": "Suite 425", + "city": "San Francisco", + "state": "CA", + "postal_code": "94103" + }, + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42" + } + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "links":{ "merchant": ":customer_id" } + } + """ + + When I make a POST request to the link "customers.debits" with the body: + """ + { + "amount": 10000, + "order": ":order_id", + "appears_on_statement_as": "Vaunte-Alice Ryan" + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "debits" schema + And the fields on this debit match: + """ + { + "description": "Catherine Malandrino Black Top", + "appears_on_statement_as": "BAL*Vaunte-Alice Ryan", + "status": "succeeded", + "links": { + "order": ":order_id" + } + } + """ + + + When I PUT to /orders/:order_id with the JSON API body: + """ + { + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", + "courier": "usps", + "tracking_number": "9405510899359008595488" + } + } + """ + + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "meta": { + "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", + "courier": "usps", + "tracking_number": "9405510899359008595488" + } + } + """ diff --git a/features/credit_cards.feature b/features/credit_cards.feature new file mode 100644 index 0000000..85129af --- /dev/null +++ b/features/credit_cards.feature @@ -0,0 +1,605 @@ +Feature: Credit cards + + Scenario: Add a card to a customer + Given I have tokenized a card + And I have created a customer + When I make a PATCH request to /cards/:card_id with the body: + """ + [{ + "op": "replace", + "path": "/cards/0/links/customer", + "value": ":customer_id" + }] + """ + Then I should get a 200 OK status code + And I make a GET request to /cards/:card_id + Then the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "links": { "customer": ":customer_id" } + } + """ + + Scenario: AVS Postal code matches + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016, + "address": { + "postal_code": "94301" + } + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_postal_match": "yes" + } + """ + + Scenario: AVS Postal code does not match + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016, + "address": { + "postal_code": "90210" + } + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_postal_match": "no" + } + """ + + Scenario: AVS Postal code is unsupported + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016, + "address": { + "postal_code": "90211" + } + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_postal_match": "unsupported" + } + """ + + Scenario: AVS Postal code is unused + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_postal_match": null + } + """ + + Scenario: AVS street matches + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016, + "address": { + "line1": "965 Mission St", + "postal_code": "94103" + } + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_street_match": "yes" + } + """ + + Scenario: AVS street does not match + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016, + "address": { + "line1": "21 Jump St", + "postal_code": "90210" + } + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_street_match": "no" + } + """ + + Scenario: AVS street match is null + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "avs_street_match": null + } + """ + + Scenario: Detect a Visa card brand + When I make a POST request to /cards with the body: + """ + { + "number": "4111 1111 1111 1111", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "brand": "Visa" + } + """ + + Scenario: Detect a Mastercard card brand + When I make a POST request to /cards with the body: + """ + { + "number": "5105 1051 0510 5100", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "brand": "MasterCard" + } + """ + + Scenario: Detect an American Express card brand + When I make a POST request to /cards with the body: + """ + { + "number": "3782 822463 10005", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "brand": "American Express" + } + """ + + Scenario: Detect a Discover card brand + When I make a POST request to /cards with the body: + """ + { + "number": "6011 1111 1111 1117", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "brand": "Discover" + } + """ + + Scenario: Retrieving a card + Given I have tokenized a card + When I make a GET request to /cards/:card_id + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "name": null, + "number": "xxxxxxxxxxxx1111", + "expiration_month": 12, + "expiration_year": 2016, + "cvv": "xxx", + "cvv_match": "yes", + "cvv_result": "Match", + "address": { + "line1": "965 Mission St", + "line2": null, + "city": "Balo Alto", + "state": null, + "postal_code": "94103", + "country_code": null + }, + "avs_street_match": "yes", + "avs_postal_match": "yes", + "avs_result": "Postal code matches, but street address not verified.", + "brand": "Visa", + "meta": {} + } + """ + + Scenario: Tokenizing a card + When I make a POST request to /cards with the body: + """ + { + "number": "4111 1111 1111 1111", + "expiration_month": "12", + "expiration_year": "2016" + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "expiration_month": 12, + "expiration_year": 2016 + } + """ + + When I make a POST request to /cards with the body: + """ + { + "name": "Frida Kahlo", + "number": "4111 1111 1111 1111", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "name": "Frida Kahlo" + } + """ + + When I make a POST request to /cards with the body: + """ + { + "number": "4111 1111 1111 1111", + "expiration_month": 12, + "expiration_year": 2016, + "address": { + "line1": "7 Bis Rue de l'Abbé de l'Épée", + "line2": "Apt 4", + "city": "Versailles", + "postal_code": "78000", + "country_code": "FR" + } + } + """ + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "address": { + "line1": "7 Bis Rue de l'Abbé de l'Épée", + "line2": "Apt 4", + "city": "Versailles", + "state": null, + "postal_code": "78000", + "country_code": "FR" + } + } + """ + + Scenario: Tokenization fails luhn test + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111112", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + # the api returns a 400, while running it locally returns a 409 + Then I should get a 409 status code + And the response is valid according to the "errors" schema + And the fields on this error match: + """ + { + "category_code": "card-not-validated" + } + """ + + Scenario: Unstore a card + Given I have tokenized a card + When I make a DELETE request to /cards/:card_id + Then I should get a 204 status code + + Scenario: CVV matches + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016, + "cvv": "123" + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "cvv_match": "yes" + } + """ + + Scenario: CVV does not match + When I make a POST request to /cards with the body: + """ + { + "number": "5112000200000002", + "expiration_month": 12, + "expiration_year": 2016, + "cvv": "200" + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "cvv_match": "no" + } + """ + + Scenario: CVV is unsupported + When I make a POST request to /cards with the body: + """ + { + "number": "4457000300000007 ", + "expiration_month": 12, + "expiration_year": 2016, + "cvv": "901" + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "cvv_match": "unsupported" + } + """ + + Scenario: CVV is unused + When I make a POST request to /cards with the body: + """ + { + "number": "4111111111111111", + "expiration_month": 12, + "expiration_year": 2016 + } + """ + + Then I should get a 201 CREATED status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "cvv_match": null + } + """ + + Scenario: Adding card metadata + Given I have tokenized a card + When I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "add", + "path": "/cards/0/meta/asdf", + "value": "the value to be added" + }] + """ + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { "meta": { "asdf": "the value to be added" } } + """ + + Scenario: Updating card metadata + Given I have tokenized a card + When I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "add", + "path": "/cards/0/meta/asdf", + "value": "the value to be added" + }] + """ + + Then I fetch the card + And I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "replace", + "path": "/cards/0/meta/asdf", + "value": "new value" + }] + """ + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { "meta": { "asdf": "new value" } } + """ + + Scenario: Safely updating the metadata + Given I have tokenized a card + When I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "add", + "path": "/cards/0/meta/asdf", + "value": "the value to be tested" + }] + """ + + Then I fetch the card + And I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "test", + "path": "/cards/0/meta/asdf", + "value": "the value to be tested" + },{ + "op": "replace", + "path": "/cards/0/meta/asdf", + "value": "after checking the value" + }] + """ + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { "meta": { "asdf": "after checking the value" } } + """ + + Scenario: Failing to safely update the metadata + Given I have tokenized a card + When I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "add", + "path": "/cards/0/meta/asdf", + "value": "the value to be tested" + }] + """ + + Then I fetch the card + And I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "test", + "path": "/cards/0/meta/asdf", + "value": "not the right value" + },{ + "op": "replace", + "path": "/cards/0/meta/asdf", + "value": "after checking the value" + }] + """ + Then I should get a 409 Conflict status code + And the response is valid according to the "errors" schema + + Scenario: Moving metadata + Given I have tokenized a card + When I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "add", + "path": "/cards/0/meta/asdf", + "value": "the value to be moved" + }] + """ + + Then I fetch the card + And I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "move", + "from": "/cards/0/meta/asdf", + "path": "/cards/0/meta/zxcv" + }] + """ + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { "meta": { "zxcv": "the value to be moved" } } + """ + + Scenario: Removing metadata + Given I have tokenized a card + When I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "add", + "path": "/cards/0/meta/asdf", + "value": "the value to be moved" + }] + """ + + Then I fetch the card + And I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "remove", + "path": "/cards/0/meta/asdf" + }] + """ + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { "meta": { } } + """ diff --git a/features/credits.feature b/features/credits.feature new file mode 100644 index 0000000..4ae6975 --- /dev/null +++ b/features/credits.feature @@ -0,0 +1,46 @@ +Feature: Credits + Credits are used for sending money to a customer + + Scenario: Crediting a deleted card leads to failure + Given I have tokenized a card + When I make a DELETE request to the href "href" + Then I should get a 204 status code + + When I make a POST request to /cards/:card_id/credits + Then I should get a 404 status code + + And the response is valid according to the "errors" schema + And the fields on this error match: + """ + { + "category_code": "not-found" + } + """ + + Scenario: Credit a customer + Given I have a customer with a tokenized bank account + When I POST to /customers/:customer_id/credits with the JSON API body: + """ + { + "amount": 500 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + + Scenario: Crediting without a funding source leads to failure + Given I have created a customer + When I POST to /customers/:customer_id/credits with the JSON API body: + """ + { + "amount": 1234 + } + """ + Then I should get a 409 status code + And the response is valid according to the "errors" schema + And the fields on this error match: + """ + { + "category_code": "no-funding-destination" + } + """ diff --git a/features/customers.feature b/features/customers.feature new file mode 100644 index 0000000..5e9030c --- /dev/null +++ b/features/customers.feature @@ -0,0 +1,73 @@ +Feature: Customers + + Scenario: Creating a customer + When I POST to /customers with the JSON API body: + """ + { + "name": "Balanced Testing" + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "customers" schema + And the fields on this customer match: + """ + { + "name": "Balanced Testing" + } + """ + + Scenario: Set the default destination + Given I have created a customer without a card and bank account + And I have tokenized a bank account + When I make a PATCH request to /customers/:customer_id with the body: + """ + [{ + "op": "replace", + "path": "/customers/0/links/destination", + "value": ":bank_account_id" + }] + """ + Then I should get a 200 OK status code + And the response is valid according to the "customers" schema + And the fields on this customer match: + """ + { + "links": { "destination": ":bank_account_id" } + } + """ + + Scenario: Underwrite a customer + When I POST to /customers with the body: + """ + { + "name": "Henry Ford" + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "customers" schema + And the fields on this customer match: + """ + { + "merchant_status": "no-match" + } + """ + + When I make a PUT request to the href "href" with the body: + """ + { + "name": "Henry Ford", + "dob_month": 7, + "dob_year": 1963, + "address": { + "postal_code": "48120" + } + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "customers" schema + And the fields on this customer match: + """ + { + "merchant_status": "underwritten" + } + """ diff --git a/features/debits.feature b/features/debits.feature new file mode 100644 index 0000000..d00c9cd --- /dev/null +++ b/features/debits.feature @@ -0,0 +1,78 @@ +Feature: Debit cards + + Scenario: Debit a card + Given I have tokenized a card + When I make a POST request to the link "cards.debits" with the body: + """ + { "amount": 2000 } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + + Scenario: Debit a customer card + Given I have tokenized a customer card + When I make a POST request to the link "cards.debits" with the body: + """ + { "amount": 2000 } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + + Scenario: Debit a verified bank account + Given I have a verified bank account + When I make a POST request to the link "bank_accounts.debits" with the body: + """ + { "amount": 20000 } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + And the fields on these debits match: + """ + { "status": "succeeded" } + """ + + Scenario: Debits to unverified bank accounts fail + Given I have attempted to verify a bank account and failed + When I make a POST request to the link "bank_accounts.debits" with the body: + """ + { "amount": 20000 } + """ + Then I should get a 409 Conflict status code + And the response is valid according to the "debits" schema + And the fields on these debits match: + """ + { "status": "failed" } + """ + + Scenario: Debit a customer + Given I have created a customer + When I make a POST request to the link "customers.debits" with the body: + """ + { "amount": 1234 } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + + Scenario: Failing to debit a customer + Given I have created a customer without a card and bank account + When I make a POST request to the link "customers.debits" with the body: + """ + { "amount": 8979 } + """ + Then I should get a 409 Conflict status code + And the response is valid according to the "debits" schema + + Scenario: Debiting a card directly + When I POST to /debits with the body: + """ + { + "amount": 1234, + "source": { + "number": "4111111111111111", + "expiration_year": "2018", + "expiration_month": 12 + } + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema diff --git a/features/orders.feature b/features/orders.feature new file mode 100644 index 0000000..8415ec1 --- /dev/null +++ b/features/orders.feature @@ -0,0 +1,305 @@ +Feature: Orders + + Scenario: Create an order + Given I have created a customer + When I make a POST request to /customers/:customer_id/orders + + Then I should get a 201 Created status code + And the response is valid according to the "orders" schema + + Scenario: Basic order flow + Given I have a customer with a tokenized bank account + When I make a POST request to /customers/:customer_id/orders + Then I should get a 201 Created status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "links": { "merchant": ":customer_id" } + } + """ + + When I fetch the customer + And I make a POST request to the link "customers.debits" with the body: + """ + { + "amount": 10000, + "order": ":order_id" + } + """ + + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + And the fields on this debit match: + """ + { + "links": { + "order": ":order_id" + } + } + """ + + When I fetch the order + And I make a GET request to /orders/:order_id + + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount": 10000, + "amount_escrowed": 10000 + } + """ + + When I fetch the customer + And I make a POST request to the link "customers.credits" with the body: + """ + { + "amount": 10000, + "order": ":order_id" + } + """ + + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + And the fields on this credit match: + """ + { + "links": { + "order": ":order_id" + } + } + """ + + When I fetch the order + And I make a GET request to /orders/:order_id + + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount": 10000, + "amount_escrowed": 0 + } + """ + + Scenario: Checking escrow of order after creating a debit + Given I have created an order + And I have tokenized a customer card + And I make a POST request to the link "cards.debits" with the body: + """ + { + "order": ":order_id", + "amount": 1234 + } + """ + + When I make a GET request to /orders/:order_id + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount_escrowed": 1234 + } + """ + + Scenario: Checking escrow of order after creating a credit + Given I have an order with a debit + And I have tokenized a bank account and associated with the merchant + And I POST to /bank_accounts/:bank_account_id/credits with the JSON API body: + """ + { + "order": ":order_id", + "amount": 12345 + } + """ + When I make a GET request to /orders/:order_id + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount": 12345, + "amount_escrowed": 0 + } + """ + + Scenario: Orders cannot be credited more than escrow balance + Given I have created an order + And I have tokenized a bank account and associated with the merchant + And I have tokenized a customer card + And I make a POST request to the link "cards.debits" with the body: + """ + { + "order": ":order_id", + "amount": 1234 + } + """ + When I make a GET request to /orders/:order_id + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount_escrowed": 1234 + } + """ + And I POST to /bank_accounts/:bank_account_id/credits with the JSON API body: + """ + { + "order": ":order_id", + "amount": 2000 + } + """ + Then I should get a 409 status code + And the response is valid according to the "errors" schema + And the fields on this error match: + """ + { + "category_code": "insufficient-funds" + } + """ + + Scenario: Create a refund + Given I have created an order + And I have tokenized a customer card + And I have debited that card + + When I make a POST request to the link "debits.refunds" + And I make a GET request to /orders/:order_id + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount_escrowed": 0 + } + """ + + Scenario: Create a reversal + Given I have an order with a debit + And I make a POST request to /customers/:merchant_id/bank_accounts with the body: + """ + { + "name": "Michael Jordan", + "account_number": "9900000002", + "routing_number": "021000021", + "account_type": "checking" + } + """ + And I make a POST request to the link "bank_accounts.credits" with the body: + """ + { + "order": ":order_id", + "amount": 12345 + } + """ + When I make a GET request to /orders/:order_id + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount_escrowed": 0 + } + """ + + When I make a POST request to the link "credits.reversals" + Then I should get a 201 Created status code + And the response is valid according to the "reversals" schema + + When I make a GET request to /orders/:order_id + Then I should get a 200 OK status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "amount_escrowed": 12345 + } + """ + + Scenario: Create a failed refund when insufficient funds are in order escrow + Given I have created an order + And I have tokenized a card + When I make a POST request to the link "cards.debits" with the body: + """ + { + "order": ":order_id", + "amount": 1234 + } + """ + And I have tokenized a bank account and associated with the merchant + Then I make a POST request to the link "bank_accounts.credits" with the body: + """ + { + "order": ":order_id", + "amount": 1234 + } + """ + + When I make a POST request to the link "debits.refunds" + Then I should get a 409 status code + And the response is valid according to the "errors" schema + And the fields on this error match: + """ + { + "category_code": "insufficient-funds" + } + """ + + Scenario: Transactions should inherit the description of the order by default + Given I have a merchant with an order with the body: + """ + { + "description": "Beats by Dr. Dre" + } + """ + + Then I should get a 201 Created status code + And the response is valid according to the "orders" schema + And the fields on this order match: + """ + { + "description": "Beats by Dr. Dre" + } + """ + + Given I have another customer with a card + Then I make a POST request to the link "customers.debits" with the body: + """ + { + "amount": 10000, + "order": ":order_id" + } + """ + + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + And the fields on this debit match: + """ + { + "description": "Beats by Dr. Dre" + } + """ + + # this scenario becomes complicted, as there are two different customers + # that we need to reference. The first customer is the merchant which the order + # is created under. The second is the one doing the buying. + Then I make a GET request to /customers/:merchant_id + And I make a POST request to the link "customers.credits" with the body: + """ + { + "amount": 10000, + "order": ":order_id" + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + And the fields on this credit match: + """ + { + "description": "Beats by Dr. Dre" + } + """ diff --git a/features/refunds.feature b/features/refunds.feature new file mode 100644 index 0000000..5a1c387 --- /dev/null +++ b/features/refunds.feature @@ -0,0 +1,28 @@ +Feature: Refunds + + Scenario: Create a refund + Given I have tokenized a customer card + And I have debited that card + + When I make a POST request to the link "debits.refunds" + Then I should get a 201 Created status code + And the response is valid according to the "refunds" schema + + Scenario: Create a failed refund + Given I have tokenized a customer card + And I have debited that card + + When I make a POST request to the link "debits.refunds" with the body: + """ + { + "amount": 200000000 + } + """ + Then I should get a 400 status code + And the response is valid according to the "errors" schema + + Scenario: Refund after changed source + Given I have created a debit + When I change the default funding source + And I create a refund + Then the refund should go to the original card diff --git a/features/resources.feature b/features/resources.feature new file mode 100644 index 0000000..147e602 --- /dev/null +++ b/features/resources.feature @@ -0,0 +1,16 @@ +Feature: The resources resource. + The resources resource is interesting. Basically, it's a global lookup + system. It allows you to take the unique ID of any Balanced entity and + get a representation. + + Such meta. + + Scenario: Look up a resource. + As an example, maybe a Balanced user has stored a Customer ID rather than a + Customer URL. This allows them to retrieve a Customer representation without + worrying about the actual structure of our usual URLs. + + Given I have created a customer + When I GET to /resources/:customer_id giving the customer_id + Then I should get a 200 OK status code + And the response is valid according to the "customers" schema diff --git a/features/rest/api_keys.feature b/features/rest/api_keys.feature new file mode 100644 index 0000000..c1bd7c2 --- /dev/null +++ b/features/rest/api_keys.feature @@ -0,0 +1,108 @@ +Feature: API Keys + API Keys are what customers use to authenticate against the Balanced API. + Generally speaking, this will be the first step that is needed to be taken by + the customer to get started with the API. + + API keys are used to make authenticated requests by sending an HTTP Basic + Auth header, using the key as the username, with no password. + + Scenario: Create an API Key + To obtain a key, one must be created. This is done through an + unauthenticated API request. + + When I POST to /api_keys without my secret key + Then I should get a 201 Created status code + And the response has this schema: + """ + { + "api_keys": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "properties": { + "href": { "type": "string" }, + "id": { "type": "string" }, + "secret": { "type": "string" }, + "links": { "type": "object" }, + "meta": { "type": "object" } + } + }, + "required": ["api_keys"] + } + """ + + Scenario: Retrieve information about an existing API key + Right now, there's not a whole lot of extra information, but we support it + anyway. + + Given I have created an API key + When I GET to /api_keys/:api_key giving the key + Then I should get a 200 OK status code + And the response has this schema: + """ + { + "api_keys": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "properties": { + "href": { "type": "string" }, + "id": { "type": "string" }, + "secret": { "type": "string" }, + "links": { "type": "object" }, + "meta": { "type": "object" } + } + }, + "required": ["api_keys"] + } + """ + + Scenario: List all API keys + Customers can make as many keys as they'd like. Being able to see all of + them is a good thing. + + Given I have created more than one API keys + When I GET to /api_keys + Then I should get a 200 OK status code + And the response has this schema: + """ + { + "api_keys": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "properties": { + "href": { "type": "string" }, + "id": { "type": "string" }, + "secret": { "type": "string" }, + "links": { "type": "object" }, + "meta": { "type": "object" } + } + }, + "properties": { + "meta": { + "type": "object", + "properties": { + "first": { "type": "string" }, + "href": { "type": "string" }, + "last": { "type": "string" }, + "limit": { "type": "integer" }, + "next": { "type": ["string", "null"] }, + "offset": { "type": "integer" }, + "previous": { "type": ["string", "null"] }, + "total": { "type": "integer" } + } + } + }, + "required": ["api_keys"] + } + """ + + Scenario: Remove an API key + Sometimes, a customer just doesn't want an API key any more. Deleting that + key will get rid of it. + + Given I have created an API key + When I DELETE to /api_keys/:api_key giving the key + Then I should get a 204 OK status code + And there should be no response body diff --git a/features/rest/bank_account_verifications.feature b/features/rest/bank_account_verifications.feature new file mode 100644 index 0000000..10c8656 --- /dev/null +++ b/features/rest/bank_account_verifications.feature @@ -0,0 +1,51 @@ +Feature: Bank account verifications + Bank account verifications are used when you want to debit from a bank account. + In a production marketplace, you will create a bank account verification, + at which point Balanced will create two micro deposists into your customers + bank account, the amounts will be less than one dollar. You can then verify + the bank account by asking your customer for the value of the micro deposists + and then submitting the values to Balanced. + + In a test marketplace, the micro deposit amounts will always be 1 cent + + Scenario: Creating a bank account verification + Given I have tokenized a bank account + When I POST to /bank_accounts/:bank_account_id/verifications + Then I should get a 201 Created status code + And the response is valid according to the "bank_account_verifications" schema + + Scenario: Get verification for a bank account + Given I have a bank account with a verification + When I GET to /bank_accounts/:bank_account_id/verifications + Then I should get a 200 OK status code + And the response is valid according to the "bank_account_verifications" schema + + Scenario: Confirm a verification + Given I have a bank account with a verification + When I PUT to /verifications/:bank_account_verification_id with the JSON API body: + """ + { + "amount_1": 1, + "amount_2": 1 + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "bank_account_verifications" schema + And the fields on this bank_account_verification match: + """ + { + "verification_status": "succeeded" + } + """ + + Scenario: Fail to confirm a verification + Given I have a bank account with a verification + When I PUT to /verifications/:bank_account_verification_id with the JSON API body: + """ + { + "amount_1": 2, + "amount_2": 2 + } + """ + Then I should get a 409 Conflict status code + And the response is valid according to the "errors" schema diff --git a/features/rest/bank_accounts.feature b/features/rest/bank_accounts.feature new file mode 100644 index 0000000..b4f49bf --- /dev/null +++ b/features/rest/bank_accounts.feature @@ -0,0 +1,175 @@ +Feature: Bank accounts + The bank account resource respresents a funding source or + destination that is backed by a bank account. Marketplaces + are able to credit out to bank accounts without doing any verifications + but to debit from a bank account, micro deposit verifications are + required. + + Scenario: Tokenize a bank account + When I POST to /bank_accounts without my secret key with the JSON API body: + """ + { + "name": "That guy over there", + "routing_number": "321174851", + "account_number": "9900000001", + "account_type": "checking" + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "bank_account_tokens" schema + + When I GET "bank_accounts.href" from the previous response + Then I should get a 200 OK status code + And the response is valid according to the "bank_accounts" schema + + Scenario: Tokenize a savings account + When I make a POST request to /bank_accounts with the body: + """ + { + "name": "Jack Lalanne", + "account_number": "200938202", + "routing_number": "121042882", + "account_type": "savings" + } + """ + + Then I should get a 201 Created status code + And the response is valid according to the "bank_accounts" schema + And the fields on this bank_account match: + """ + { "account_type": "savings" } + """ + + Scenario: Tokenize a bank account with a country code + When I make a POST request to /bank_accounts with the body: + """ + { + "name": "Mahmoud Abdelkader", + "account_number": "200938202", + "routing_number": "121042882", + "account_type": "checking", + "address": { + "country_code": "US" + } + } + """ + + Then I should get a 201 Created status code + And the response is valid according to the "bank_accounts" schema + And the fields on this bank_account match: + """ + { + "address": { + "country_code": "US" + } + } + """ + + Scenario: Retrieve a bank account + Given I have tokenized a bank account + When I GET to /bank_accounts/:bank_account_id + Then I should get a 200 OK status code + And the response is valid according to the "bank_accounts" schema + + Scenario: List bank accounts + Given I have tokenized more than one bank account + When I GET to /bank_accounts + Then I should get a 200 OK status code + And the response is valid according to the "bank_accounts" schema + + Scenario: Unstore a bank account + Given I have tokenized a bank account + When I DELETE to /bank_accounts/:bank_account_id + Then I should get a 204 OK status code + And there should be no response body + + Scenario: Update a bank account + Given I have tokenized a bank account + When I PUT to /bank_accounts/:bank_account_id with the JSON API body: + """ + { + "meta": { + "random": "hello world" + } + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "bank_accounts" schema + + Scenario: Credit a bank account + Given I have sufficient funds in my marketplace + And I have tokenized a bank account + When I POST to /bank_accounts/:bank_account_id/credits with the JSON API body: + """ + { + "amount": 1234 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + + Scenario: Debit a bank account + Given I have a verified bank account + When I POST to /bank_accounts/:bank_account_id/debits with the JSON API body: + """ + { + "amount": 1234 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + + Scenario: Infer bank names + When I POST to /bank_accounts with the JSON API body: + """ + { + "name": "Michael Johnson", + "account_number": "982379283", + "routing_number": "121000358", + "account_type": "checking" + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "bank_accounts" schema + And the fields on this bank_account match: + """ + {"bank_name": "BANK OF AMERICA, N.A."} + """ + + When I POST to /bank_accounts with the JSON API body: + """ + { + "name": "Maurice Green", + "account_number": "33727930", + "routing_number": "322271627", + "account_type": "checking" + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "bank_accounts" schema + And the fields on this bank_account match: + """ + {"bank_name": "J.P. MORGAN CHASE BANK, N.A."} + """ + + Scenario: Add a bank account to a customer + Given I have created a customer + And I have tokenized a bank account + When I make a PATCH request to the href "href" with the body: + """ + [{ + "op": "replace", + "path": "/bank_accounts/0/links/customer", + "value": ":customer_id" + }] + """ + Then I should get a 200 OK status code + And the response is valid according to the "bank_accounts" schema + And the fields on this bank_account match: + """ + { + "links": { + "customer": ":customer_id" + } + } + """ diff --git a/features/rest/callbacks.feature b/features/rest/callbacks.feature new file mode 100644 index 0000000..910a5de --- /dev/null +++ b/features/rest/callbacks.feature @@ -0,0 +1,29 @@ +Feature: Callbacks + + Scenario: Create a callback + When I POST to /callbacks with the JSON API body: + """ + { + "url":"http://www.example.com/callback" + } + """ + Then I should get a 201 Created status code + # TODO: Callback schema? + + Scenario: Retrieve information about an existing callback + Given I have created a callback + When I GET to /callbacks/:callback_id giving the callback_id + Then I should get a 200 OK status code + # TODO: Callback schema? + + Scenario: List all callbacks + Given I have created more than one callback + When I GET to /callbacks + Then I should get a 200 OK status code + # TODO: Callback schema? + + Scenario: Remove a callback + Given I have created a callback + When I DELETE to /callbacks/:callback_id giving the callback_id + Then I should get a 204 OK status code + And there should be no response body diff --git a/features/rest/cards.feature b/features/rest/cards.feature new file mode 100644 index 0000000..cc261f4 --- /dev/null +++ b/features/rest/cards.feature @@ -0,0 +1,70 @@ +Feature: Tokenize a credit card + "Tokenizing" a credit card is the process of sending Balanced a credit card + number for storage. The API will then give the customer back a token that the + customer will store. The idea is that if a customer stored the credit card data + directly, the customer would legally be required to take extra precautions for safely storing the information, whereas with the token, Balanced worries about + that, and the customer just charges against the token rather than the card. + + For more on tokenization as a concept, see ['tokenization' on + Wikipedia.](http://en.wikipedia.org/wiki/Tokenization_%28data_security%29) + + Scenario: Tokenize a card without a secret key + Cards are able to be tokenized without sending along a secret key. When + this happens, the customer gets less information than if the key was sent. + + When I POST to /cards without my secret key with the JSON API body: + """ + { + "number": "4111111111111111", + "expiration_month": "12", + "expiration_year": 2016 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "card_tokens" schema + + When I GET "cards.href" from the previous response + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + + Scenario: Retrieve a card + Given I have tokenized a card + When I GET to /cards/:card_id giving the card_id + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + + Scenario: List cards + Given I have tokenized more than one card + When I GET to /cards + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + + Scenario: Remove a card + Given I have tokenized a card + When I DELETE to /cards/:card_id + Then I should get a 204 OK status code + And there should be no response body + + Scenario: Update a card + Given I have tokenized a card + When I PUT to /cards/:card_id giving the card_id, with the JSON API body: + """ + { + "number": "4111111111111111", + "expiration_month": "12", + "expiration_year": 2016 + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + + Scenario: Debit a card + Given I have tokenized a card + When I POST to /cards/:card_id/debits giving the card_id, with the JSON API body: + """ + { + "amount": "50" + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema diff --git a/features/rest/credits.feature b/features/rest/credits.feature new file mode 100644 index 0000000..203caf0 --- /dev/null +++ b/features/rest/credits.feature @@ -0,0 +1,37 @@ +Feature: Credits + Credit is the action of moving money out of the marketplace. + The money will come from either the marketplace's escrow balance + or an order and can be sent to a bank account. If sent to a customer + then that customer's default bank account will be used instead. + + Scenario: Credit a bank account + Given I have sufficient funds in my marketplace + And I have tokenized a bank account + When I POST to /bank_accounts/:bank_account_id/credits with the JSON API body: + """ + { + "amount": 1234 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + + Scenario: List credits + Given I have more than two credits + When I GET to /credits + Then I should get a 200 OK status code + And the response is valid according to the "credits" schema + + Scenario: Retrieving credits for a bank account + Given I have a bank account with a credit + When I GET to /bank_accounts/:bank_account_id/credits + Then I should get a 200 OK status code + And the response is valid according to the "credits" schema + And the fields on these credits match: + """ + { + "links": { + "destination": ":bank_account_id" + } + } + """ diff --git a/features/rest/customers.feature b/features/rest/customers.feature new file mode 100644 index 0000000..786efa9 --- /dev/null +++ b/features/rest/customers.feature @@ -0,0 +1,101 @@ +Feature: Customers + Customers are used for representing both buyers on a marketplace. + Customers have cards and bank accounts associated to themselves. + All debit and credits can be tracked for a specific customer. + + Scenario: Creating a customer + When I POST to /customers with the JSON API body: + """ + { + "name": "Customer name", + "email": "email@example.com" + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "customers" schema + And the fields on this customer match: + """ + { + "name": "Customer name", + "email": "email@example.com" + } + """ + + Scenario: Get a customer + Given I have created a customer + When I GET to /customers/:customer_id giving the customer_id + Then I should get a 200 OK status code + And the response is valid according to the "customers" schema + + Scenario: List all customers + Given I have created more than one customer + When I GET to /customers + Then I should get a 200 OK status code + And there should be more than two customers paged + + Scenario: Remove a customer + Given I have created a customer + When I DELETE to /customers/:customer_id giving the customer_id + Then I should get a 204 OK status code + And there should be no response body + + Scenario: Update a customer + Given I have created a customer + When I make a PUT request to the href "href" with the body: + """ + { + "email": "asdf@balancedpayments.com" + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "customers" schema + And the fields on this customer match: + """ + { + "email": "asdf@balancedpayments.com" + } + """ + + Scenario: Add a card to a customer + Given I have tokenized a card + And I have created a customer + When I PUT to /cards/:card_id with the JSON API body: + """ + { + "links": { + "customer": ":customer_id" + } + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "cards" schema + And the fields on this card match: + """ + { + "links": { + "customer": ":customer_id" + } + } + """ + + Scenario: Add a bank account to a customer + Given I have tokenized a bank account + And I have created a customer + When I PUT to /bank_accounts/:bank_account_id with the JSON API body: + """ + { + "links": { + "customer": ":customer_id" + } + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "bank_accounts" schema + And the fields on this bank_account match: + """ + { + "links": { + "customer": ":customer_id" + } + } + """ diff --git a/features/rest/debits.feature b/features/rest/debits.feature new file mode 100644 index 0000000..86fbbd1 --- /dev/null +++ b/features/rest/debits.feature @@ -0,0 +1,91 @@ +Feature: Debit a card or bank account + Debits is the action of charging a customers card or bank account. + Upon a debit succeeding, the value will be reflected in the marketplace's escrow balance + or in the escrow for an order. + + Scenario: Debiting a card + Given I have tokenized a card + When I POST to /cards/:card_id/debits with the JSON API body: + """ + { + "amount": 20000 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + And the fields on this debit match: + """ + { + "amount": 20000 + } + """ + + Scenario: Retrieving debits for a card + Given I have debited a card + When I GET to /cards/:card_id/debits + Then I should get a 200 OK status code + And the response is valid according to the "debits" schema + And the fields on these debits match: + """ + { + "links": { + "source": ":card_id" + } + } + """ + + Scenario: Debiting a customer + If the debit is done directly on the customer resource + then that customers default "funding source" will be used + when preforming the debit. + Given I have a customer with a card + When I POST to /customers/:customer_id/debits with the JSON API body: + """ + { + "amount": 2000 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "debits" schema + And the fields on this debit match: + """ + { + "amount": 2000, + "links": { + "customer": ":customer_id" + } + } + """ + + Scenario: List debits + Given I have more than one debit + When I GET to /debits + Then I should get a 200 OK status code + And the response is valid according to the "debits" schema + + Scenario: Update a debit + Given I have debited a card + When I PUT to /debits/:debit_id with the JSON API body: + """ + { + "meta": { + "order.status": "shipped" + } + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "debits" schema + And the fields on this debit match: + """ + { + "meta": { + "order.status": "shipped" + } + } + """ + + Scenario: Refund a debit + Given I have debited a card + When I POST to /debits/:debit_id/refunds + Then I should get a 201 Created status code + And the response is valid according to the "refunds" schema diff --git a/features/rest/events.feature b/features/rest/events.feature new file mode 100644 index 0000000..7a671bd --- /dev/null +++ b/features/rest/events.feature @@ -0,0 +1,9 @@ +Feature: Events + Events represent action that occue inside the Balanced system. + They can not be created by the customer, and instead are automatically created + when actions are completed. + + Scenario: List events + When I GET to /events + Then I should get a 200 OK status code + And the response is valid according to the "events" schema diff --git a/features/rest/refunds.feature b/features/rest/refunds.feature new file mode 100644 index 0000000..9fe8b0f --- /dev/null +++ b/features/rest/refunds.feature @@ -0,0 +1,57 @@ +Feature: Refunds + Refunds are used to cancel a debit. The marketplace can either refund the whole amount + of the debit or a partial amount of the debit using "amount" when creating the refund. + + Scenario: Creating a full refund + Given I have debited a card + When I POST to /debits/:debit_id/refunds + Then I should get a 201 Created status code + And the response is valid according to the "refunds" schema + + Scenario: Creating a partial refund + Given I have debited a card + When I POST to /debits/:debit_id/refunds with the JSON API body: + """ + { + "amount": 100 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "refunds" schema + And the fields on this refund match: + """ + { + "amount": 100 + } + """ + + Scenario: Listing refunds for a debit + Given I have created a refund for a debit + When I GET to /debits/:debit_id/refunds + Then I should get a 200 OK status code + And the response is valid according to the "refunds" schema + And the fields on these refunds match: + """ + { + "links": { + "debit": ":debit_id" + } + } + """ + + Scenario: Retrieving a refund + Given I have created a refund for a debit + When I GET to /refunds/:refund_id + Then I should get a 200 OK status code + And the response is valid according to the "refunds" schema + + Scenario: Update a refund + Given I have created a refund for a debit + When I PUT to /refunds/:refund_id with the JSON API body: + """ + { + "description": "The customer cancel the order" + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "refunds" schema diff --git a/features/rest/reversals.feature b/features/rest/reversals.feature new file mode 100644 index 0000000..342c300 --- /dev/null +++ b/features/rest/reversals.feature @@ -0,0 +1,55 @@ +Feature: Reversal + Reversals allow for canceling a credit to a bank account. + A reversals can be partial or the full amount. + + Scenario: Creating a full reversal + Given I have a bank account with a credit + When I POST to /credits/:credit_id/reversals + Then I should get a 201 Created status code + And the response is valid according to the "reversals" schema + + Scenario: Creating a partial reversal + Given I have a bank account with a credit + When I POST to /credits/:credit_id/reversals with the JSON API body: + """ + { + "amount": 100 + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "reversals" schema + And the fields on this reversal match: + """ + { + "amount": 100 + } + """ + + Scenario: List reversals + Given I have a bank account with a reversal + When I GET to /credits/:credit_id/reversals + Then I should get a 200 OK status code + And the response is valid according to the "reversals" schema + + Scenario: Retrieving a reversal + Given I have a bank account with a reversal + When I GET to /reversals/:reversal_id + Then I should get a 200 OK status code + And the response is valid according to the "reversals" schema + + Scenario: Update a reversal + Given I have a bank account with a reversal + When I PUT to /reversals/:reversal_id with the JSON API body: + """ + { + "description": "merchant did not ship item, taking my money back" + } + """ + Then I should get a 200 OK status code + And the response is valid according to the "reversals" schema + And the fields on this reversal match: + """ + { + "description": "merchant did not ship item, taking my money back" + } + """ diff --git a/features/reversals.feature b/features/reversals.feature new file mode 100644 index 0000000..4c1b15d --- /dev/null +++ b/features/reversals.feature @@ -0,0 +1,83 @@ +Feature: Reversals + + Scenario: Reverse a credit. + + Given I have tokenized a bank account + When I make a POST request to the link "bank_accounts.credits" with the body: + """ + { "amount": 2000 } + """ + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + + When I make a POST request to the link "credits.reversals" + Then I should get a 201 Created status code + And the response is valid according to the "reversals" schema + + Scenario: Failed reversal + You can't reverse a particular amount. + + Given I have tokenized a bank account + When I make a POST request to the link "bank_accounts.credits" with the body: + """ + { "amount": 2000 } + """ + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + + When I make a POST request to the link "credits.reversals" with the body: + """ + { "amount": 10000000 } + """ + Then I should get a 400 status code + And the response is valid according to the "errors" schema + + Scenario: Reverse a bank account credit successfully + + When I POST to /credits with the JSON API body: + """ + { + "amount": 1234, + "destination": { + "name": "Michael Jordan", + "account_number": "9900000095", + "routing_number": "021000021", + "account_type": "checking" + } + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + + When I make a POST request to the link "credits.reversals" + Then I should get a 201 Created status code + And the response is valid according to the "reversals" schema + And the fields on this reversal match: + """ + { "status": "succeeded" } + """ + + Scenario: Reverse a bank account credit unsuccessfully + + When I POST to /credits with the JSON API body: + """ + { + "amount": 1234, + "destination": { + "name": "Michael Jordan", + "account_number": "9900000004", + "routing_number": "021000021", + "account_type": "checking" + } + } + """ + Then I should get a 201 Created status code + And the response is valid according to the "credits" schema + + When I make a POST request to the link "credits.reversals" + Then I should get a 409 Conflict status code + And the response is valid according to the "reversals" schema + And the fields on this reversal match: + """ + { "status": "failed" } + """ diff --git a/features/root.feature b/features/root.feature new file mode 100644 index 0000000..0c270b4 --- /dev/null +++ b/features/root.feature @@ -0,0 +1,12 @@ +Feature: Root resource + The root resource is kind of funny, because while it's one of the simplest, + it's also one of the most important. + + This is where all hypermedia style interaction begins from. Right now it's + not that way, but it will be at some point. + + Scenario: Request the root resource + The primary interaction with the root is to fetch it. + + When I make a GET request to / + Then I should get a 200 status code diff --git a/features/step_definitions/api_keys.rb b/features/step_definitions/api_keys.rb new file mode 100644 index 0000000..23af468 --- /dev/null +++ b/features/step_definitions/api_keys.rb @@ -0,0 +1,43 @@ +Given(/^I have created an API key$/) do + options = { + headers: { + "Accept" => $accept_header, + }, + } + response = HTTParty.post("#{$root_url}/api_keys", options) + @client.add_response(response) + @api_secret = @client['secret'] + @api_key = @client['id'] +end + +When(/^I GET to \/api_keys\/:api_key giving the key$/) do + options = { + headers: { + "Accept" => $accept_header, + }, + basic_auth: { + username: @api_secret, + password: "", + } + } + response = HTTParty.get("#{@client.root_url}/api_keys/#{@api_key}", options) + @client.add_response(response) +end + +When(/^I DELETE to \/api_keys\/:api_key giving the key$/) do + options = { + headers: { + "Accept" => $accept_header, + }, + basic_auth: { + username: @api_secret, + password: "", + } + } + response = HTTParty.delete("#{@client.root_url}/api_keys/#{@api_key}", options) + @client.add_response(response) +end + +Given(/^I have created more than one API keys$/) do + 2.times { step "I have created an API key" } +end diff --git a/features/step_definitions/bank_accounts.rb b/features/step_definitions/bank_accounts.rb new file mode 100644 index 0000000..2d80251 --- /dev/null +++ b/features/step_definitions/bank_accounts.rb @@ -0,0 +1,110 @@ +Given(/^I have tokenized a bank account$/) do + @client.post('/bank_accounts', { + name: "Hhenry Ford", + routing_number: "321174851", + account_number: "9900000001", + account_type: "checking", + }) + @bank_account_id = @client['bank_accounts']['id'] + @client.add_hydrate(:bank_account_id, @bank_account_id) + @client.add_hydrate(:bank_accounts_id, @bank_account_id) +end + + +Given(/^I have tokenized more than one bank account$/) do + 2.times { step 'I have tokenized a bank account' } +end + + +Given(/^I have a verified bank account$/) do + step 'I have tokenized a bank account' + @client.post("/bank_accounts/#{@bank_account_id}/verifications", {}) + @client.put(@client['bank_account_verifications']['href'], { + amount_1: 1, + amount_2: 1 + }) + # TODO: fix hax + # client has mutable state, we care about the account, not the verification + + @client.get("/bank_accounts/#{@bank_account_id}") +end + +Given(/^I have attempted to verify a bank account and failed$/) do + @client.post('/bank_accounts', { + name: "Karl Malone", + account_number: "9900000004", + routing_number: "021000021", + account_type: "checking", + }) + @bank_account_id = @client['bank_accounts']['id'] + @client.add_hydrate(:bank_account_id, @bank_account_id) +end + +Given(/^I have a bank account with a verification$/) do + step 'I have tokenized a bank account' + @client.post("/bank_accounts/#{@bank_account_id}/verifications", {}) + @bank_account_verification_id = @client['bank_account_verifications']['id'] + @client.add_hydrate :bank_account_verification_id, @bank_account_verification_id +end + +Given(/^I have a customer with a tokenized bank account$/) do + step 'I have created a customer' + step 'I have tokenized a bank account' + @client.put("/bank_accounts/#{@bank_account_id}", { + links: { + customer: @customer_id + } + }) +end + +Given(/^I have created a non-underwritten customer with a tokenized bank account$/) do + @client.post('/customers', {}) + @customer_id = @client['id'] + @client.add_hydrate :customer_id, @customer_id + + @customer_url = @client['customers']['href'] + + # tokenize a card for them + @client.post('/cards', + { + number: "4111 1111 1111 1111", + expiration_month: 12, + expiration_year: 2016, + cvv: "123", + address: { + line1: "965 Mission St", + postal_code: "94103" + } + } + ) + card_url = @client['cards']['href'] + @card_id = @client['cards']['id'] + @client.patch(card_url, + [{ + op: "replace", + path: "/cards/0/links/customer", + value: @customer_id, + }] + ) + + # associate their card so that they have a funding source + @client.patch(@customer_url, + [{ + op: "replace", + path: "/customers/0/links/source", + value: @card_id + }] + ) + + step 'I have tokenized a bank account' + @client.put("/bank_accounts/#{@bank_account_id}", { + links: { + customer: @customer_id + } + }) + + ## TODO: fix hax + # Right now, we rely on last_body in places becuase the client is mutable + # this is bad and we should stop it. + @client.get(@customer_url) +end diff --git a/features/step_definitions/callbacks.rb b/features/step_definitions/callbacks.rb new file mode 100644 index 0000000..24d41c1 --- /dev/null +++ b/features/step_definitions/callbacks.rb @@ -0,0 +1,26 @@ +Given(/^I have created a callback$/) do + @client.post('/callbacks', {url: "http://example.com/callback"}) + @callback_id = @client.last_body['callbacks'].first['id'] +end + +Given(/^I have created more than one callback$/) do + 2.times { step "I have created a callback" } +end + +When(/^I GET to \/callbacks\/:callback_id giving the callback_id$/) do + @client.get("/callbacks/#{@callback_id}") +end + +When(/^I DELETE to \/callbacks\/:callback_id giving the callback_id$/) do + options = { + headers: { + "Accept" => $accept_header, + }, + basic_auth: { + username: $api_secret, + password: "", + } + } + response = HTTParty.delete("#{@client.root_url}/callbacks/#{@callback_id}", options) + @client.add_response(response) +end diff --git a/features/step_definitions/cards.rb b/features/step_definitions/cards.rb new file mode 100644 index 0000000..72aa936 --- /dev/null +++ b/features/step_definitions/cards.rb @@ -0,0 +1,77 @@ +Given(/^I have tokenized a card$/) do + @client.post('/cards', + { + number: "4111 1111 1111 1111", + expiration_month: 12, + expiration_year: 2016, + cvv: "123", + address: { + line1: "965 Mission St", + postal_code: "94103" + } + } + ) + @card_id = @client['cards']['id'] + @client.add_hydrate(:card_id, @card_id) +end + +Given(/^I have tokenized a customer card$/) do + @client.post('/cards', + { + number: "4111111111111111", + expiration_month: "12", + expiration_year: 2016 + } + ) + @card_id = @client['cards']['id'] + @client.add_hydrate(:card_id, @card_id) +end + +Given(/^I have debited that card$/) do + @client.post(@client.hydrater(@client.last_body["links"]["cards.debits"]), + { + amount: 2000, + }, + env + ) + @debit_id = @client['debits']['id'] + @client.add_hydrate(:debit_id, @debit_id) +end + + +Given(/^I have tokenized more than one card$/) do + 2.times { step "I have tokenized a card" } +end + +When(/^I GET to \/cards\/:card_id giving the card_id$/) do + @client.get("/cards/#{@card_id}") +end + +When(/^I DELETE to \/cards\/:card_id giving the card_id$/) do + options = { + headers: { + "Accept" => $accept_header, + }, + basic_auth: { + username: $api_secret, + password: "", + } + } + response = HTTParty.delete("#{@client.root_url}/cards/#{@card_id}", options) + @client.add_response(response) +end + +When(/^I PUT to \/cards\/:card_id giving the card_id, with the JSON API body:$/) do |body| + @client.put("/cards/#{@card_id}", body) +end + +When(/^I POST to \/cards\/:card_id\/debits giving the card_id, with the JSON API body:$/) do |body| + @client.post("/cards/#{@card_id}/debits", body) +end + +Given(/^I have sufficient funds in my marketplace$/) do + step 'I have tokenized a card' + @client.post("/cards/#{@card_id}/debits", { + amount: 50000 + }) +end diff --git a/features/step_definitions/credits.rb b/features/step_definitions/credits.rb new file mode 100644 index 0000000..5b65401 --- /dev/null +++ b/features/step_definitions/credits.rb @@ -0,0 +1,14 @@ +Given(/^I have a bank account with a credit$/) do + step 'I have sufficient funds in my marketplace' + step 'I have tokenized a bank account' + @client.post("/bank_accounts/#{@bank_account_id}/credits", { + amount: 2000 + }) + @credit_id = @client['credits']['id'] + @client.add_hydrate :credit_id, @credit_id +end + + +Given(/^I have more than two credits$/) do + 2.times { step 'I have a bank account with a credit' } +end diff --git a/features/step_definitions/customers.rb b/features/step_definitions/customers.rb new file mode 100644 index 0000000..1dcb3ad --- /dev/null +++ b/features/step_definitions/customers.rb @@ -0,0 +1,92 @@ +Given(/^I have created a customer$/) do + @client.post('/customers', + { + name: 'Henry Ford', + dob: '1863-07', + address: { + postal_code: '48120' + } + } + ) + @customer_id = @client['id'] + @client.add_hydrate :customer_id, @customer_id + + @customer_url = @client['customers']['href'] + + # tokenize a card for them + @client.post('/cards', + { + number: "4111 1111 1111 1111", + expiration_month: 12, + expiration_year: 2016, + cvv: "123", + address: { + line1: "965 Mission St", + postal_code: "94103" + } + } + ) + card_url = @client['cards']['href'] + @card_id = @client['cards']['id'] + @client.patch(card_url, + [{ + op: "replace", + path: "/cards/0/links/customer", + value: @customer_id, + }] + ) + + # associate their card so that they have a funding source + @client.patch(@customer_url, + [{ + op: "replace", + path: "/customers/0/links/source", + value: @card_id + }] + ) + + ## TODO: fix hax + # Right now, we rely on last_body in places becuase the client is mutable + # this is bad and we should stop it. + @client.get(@customer_url) +end + +Given(/^I have created a customer without a card and bank account$/) do + @client.post('/customers', {}) + @customer_id = @client['id'] + @client.add_hydrate :customer_id, @customer_id + + @customer_url = @client['customers']['href'] +end + + +Given(/^I have created more than one customer$/) do + 2.times { step "I have created a customer" } +end + +When(/^I GET to \/customers\/:customer_id giving the customer_id$/) do + @client.get("/customers/#{@customer_id}") +end + +When(/^I GET to \/resources\/:customer_id giving the customer_id$/) do + @client.get("/resources/#{@customer_id}") +end + +When(/^I DELETE to \/customers\/:customer_id giving the customer_id$/) do + options = { + headers: { + "Accept" => $accept_header, + }, + basic_auth: { + username: $api_secret, + password: "", + } + } + response = HTTParty.delete("#{@client.root_url}/customers/#{@customer_id}", options) + @client.add_response(response) +end + + +Given(/^I have another customer with a card$/) do + step 'I have created a customer' +end diff --git a/features/step_definitions/debits.rb b/features/step_definitions/debits.rb new file mode 100644 index 0000000..7ea80f8 --- /dev/null +++ b/features/step_definitions/debits.rb @@ -0,0 +1,30 @@ +Given(/^I have debited a card$/) do + step "I have tokenized a card" + @client.post("/cards/#{@card_id}/debits", { + 'amount' => 1234 + }) + @debit_id = @client['debits']['id'] + @client.add_hydrate :debit_id, @debit_id +end + +Given(/^I have a customer with a card$/) do + step "I have created a customer" + step "I have tokenized a card" + @client.put("/cards/#{@card_id}", { + 'links' => { + 'customer' => @customer_id + } + }) + +end + +Given(/^I have more than one debit$/) do + 2.times { step 'I have debited a card' } +end + +Given(/^I have created a debit$/) do + step 'I have created a customer' + step 'I make a POST request to the link "customers.debits" with the body:', '{ "amount": 1234 }' + step 'I should get a 201 Created status code' + step 'the response is valid according to the "debits" schema' +end diff --git a/features/step_definitions/debugging.rb b/features/step_definitions/debugging.rb new file mode 100644 index 0000000..7f960d2 --- /dev/null +++ b/features/step_definitions/debugging.rb @@ -0,0 +1,13 @@ +Then(/^debug$/) do + puts "HTTP status code: #{@client.last_code}" + puts "HTTP body: #{@client.last_body}" + puts "hydrate tokens: #{@client.hydrate_tokens}" +end + +require 'logger' +$logger = Logger.new(STDOUT) +$logger.level = Logger::FATAL + +When(/^logging is enabled$/) do + $logger.level = Logger::DEBUG +end diff --git a/features/step_definitions/http_steps.rb b/features/step_definitions/http_steps.rb new file mode 100644 index 0000000..80e4ade --- /dev/null +++ b/features/step_definitions/http_steps.rb @@ -0,0 +1,194 @@ +require 'erb' + +When(/^I (\w+) to (\/\S*?)$/) do |verb, url| + $logger.debug("Making request to #{url}") + $logger.debug("hydrated: #{@client.hydrater(url)}") + @client.verb(verb, @client.hydrater(url), env) + @order_id = @client['orders']['id'] rescue nil + @client.add_hydrate(:order_id, @order_id) if @order_id + @customer_source = @client['customers']['links']['source'] rescue nil + @client.add_hydrate(:customers_source, @customer_source) if @customer_source +end + +When(/^I (\w+) to (\/\S*?) with the body:$/) do |verb, url, body| + body = ERB.new(body).result(binding) + $logger.debug("Making request to #{url}") + $logger.debug("hydrated: #{@client.hydrater(url)}") + body = @client.hydrater body + $logger.debug("body: #{body}") + @client.verb(verb, @client.hydrater(url), env, body) + @customer_id = @client['customers']['id'] rescue nil + @client.add_hydrate(:customer_id, @customer_id) if @customer_id + @client.add_hydrate(:order_id, @client['orders']['id']) rescue nil +end + +When(/^I make a (\w+) request to (\/\S*?)$/) do |verb, url| + step "I #{verb} to #{url}" +end + +When(/^I make a (\w+) request to (\/\S*?) with the body:$/) do |verb, url, body| + step "I #{verb} to #{url} with the body:", body +end + +def env + { + "bank_accounts_id" => @bank_account_id, + "credits_id" => @credit_id, + "cards_id" => @card_id, + "debits_id" => @debit_id, + "customers_id" => @customer_id, + "customers_source" => @customer_source, + "orders_id" => @order_id, + } +end + + +When(/^I make a (\w+) request to the href "(.*?)"$/) do |verb, keys| + link = @client[keys] || @client.inject(keys) + @client.send(verb.downcase, link, {}, env) +end + +When(/^I make a (\w+) request to the href "(.*?)" with the body:$/) do |verb, keys, body| + body = ERB.new(@client.hydrater body).result(binding) + link = @client[keys] || @client.inject(keys) + @client.send(verb.downcase, link, body, env) +end + +When(/^I make a (\w+) request to the link "(.*?)" with the body:$/) do |verb, keys, body| + body = ERB.new(@client.hydrater(body)).result(binding) + href = @client.get_link(keys) + #$logger.debug("Requesting hydrated: #{@client.hydrater(@client.last_body["links"][keys])}") + body = @client.send(verb.downcase, @client.get_link(keys), JSON.parse(body), env) + @credit_id = @client['credits']['id'] rescue nil + @cards_id = @client['cards']['id'] rescue nil + @client.add_hydrate(:card_id, @cards_id) if @cards_id + @client.add_hydrate(:order_id, @client['orders']['id']) if @client['orders'] + @debit_url = @client['debits']['href'] rescue nil + body +end + +When(/^I make a (\w+) request to the link "(.*?)" of that (\w+)$/) do |verb, keys, resource| + id = instance_variable_get("@#{resource}_id") + @client.get("/resources/#{id}") + step %Q{I make a #{verb} request to the link "#{keys}"} +end + +When(/^I fetch the (.*+)$/) do |resource| + resource = resource.gsub(/\s/, "_") + id = instance_variable_get("@#{resource}_id") + @client.get("/resources/#{id}") +end + +When(/^I make a (\w+) request to the link "(.*?)"$/) do |verb, keys| + #$logger.debug("Requesting hydrated: #{@client.hydrater(@client.last_body["links"][keys])}") + body = @client.send(verb.downcase, @client.get_link(keys), {}, env) + @cards_id = @client['cards']['id'] rescue nil + @client.add_hydrate(:card_id, @cards_id) if @cards_id + @credit_id = @client['credits']['id'] rescue nil + @client.add_hydrate(:order_id, @client['orders']['id']) rescue nil + body +end + +When(/^I POST to (\/.*) without my secret key with the JSON API body:$/) do |url, body| + # use for tokenizing cards and bank accounts + options = { + headers: { + "Accept" => $accept_header, + }, + body: JSON.parse(body) + } + + response = HTTParty.post("#{$root_url}#{url}", options) + @client.add_response(response) +end + +When(/^I POST to (\/\S*) without my secret key$/) do |url| + # used for creating api keys for new marketplaces + options = { + headers: { + "Accept" => $accept_header, + }, + } + + response = HTTParty.post("#{$root_url}#{url}", options) + @client.add_response(response) +end + +When(/^I GET "(.*?)" from the previous response$/) do |keys| + @client.get(@client.inject keys) +end + +When(/^I POST to (\/\S*) with the JSON API body:$/) do |url, body| + body = @client.post(@client.hydrater(url), @client.hydrater(body)) + @credit_id = @client['credits']['id'] rescue nil + @card_id = @client['cards']['id'] rescue nil + @client.add_hydrate(:card_id, @card_id) if @card_id + body +end + +When(/^I PUT to (\/\S*) with the JSON API body:$/) do |url, body| + @client.put(@client.hydrater(url), @client.hydrater(body)) +end + +When(/^I PATCH to (\/\S*) with the JSON API body:$/) do |url, body| + body = ERB.new(@client.hydrater(body)).result(binding) + @client.patch(@client.hydrater(url), body) +end + +require 'json-schema' +Then(/^the response has this schema:$/) do |schema| + @client.validate(schema) +end + +Then(/^the response is valid according to the "(.*?)" schema$/) do |filename| + @client.validate(filename) +end + +Then(/^I should get a (.+) status code$/) do |code| + message = @client.last_body["errors"][0]["description"] rescue "" + assert_equal code.to_i, @client.last_code, message +end + +Then(/^there should be no response body$/) do + assert_nil @client.last_body +end + +def checker(from, of, nesting) + assert_not_nil from, nesting + + from.each_pair do |key, val| + if val.is_a? String or val.is_a? Integer + assert_equal val, of[key], "#{nesting}>#{key}" + elsif val.nil? + assert_nil of[key] + else + checker val, of[key], "#{nesting}>#{key}" + end + end +end + +Then(/^the fields on this (.*) match:$/) do |resource, against| + against = ERB.new(against).result(binding) + checker JSON.parse(@client.hydrater against), @client["#{resource}s"], '' + assert_equal @client.last_body["#{resource}s"].size, 1 +end + +Then(/^the fields on these (.*) match:$/) do |resource, against| + against = ERB.new(against).result(binding) + against = JSON.parse(@client.hydrater against) + @client.last_body[resource].each do |body| + checker against, body, '' + end +end + +Then(/^there should be more than two (.*) paged$/) do |name| + assert @client.last_body[name].size >= 2, "There were not more than two #{name}" +end + + +# TODO: move? + +Before do |scenario| + @client = Balanced::TinyClient.new($api_secret, $accept_header, $root_url) + @client.running = scenario +end diff --git a/features/step_definitions/orders.rb b/features/step_definitions/orders.rb new file mode 100644 index 0000000..30e2b33 --- /dev/null +++ b/features/step_definitions/orders.rb @@ -0,0 +1,42 @@ +Given(/^I have created an order$/) do + step 'I have created a customer' + @merchant_id = @customer_id + @client.add_hydrate :merchant_id, @merchant_id + @client.post(@client.hydrater('/customers/:customer_id/orders'), {}) + @client.add_hydrate :order_id, @client['id'] +end + +Given(/^I have an order with a debit$/) do + step 'I have created a customer' + @client.post('/customers', {}) + @merchant_id = @client['id'] + @client.add_hydrate :merchant_id, @client['id'] + @client.post("/customers/#{@client['id']}/orders", {}) + @order_id = @client['id'] + @client.add_hydrate :order_id, @order_id + @client.post("/cards/#{@card_id}/debits", { + amount: 12345, + order: @order_id + }) + @client.add_hydrate :debit_id, @client['id'] +end + + +When(/^I have tokenized a bank account and associated with the merchant$/) do + step 'I have tokenized a bank account' + @client.put("/bank_accounts/#{@bank_account_id}", { + links: { + customer: @merchant_id + } + }) +end + + +Given(/^I have a merchant with an order with the body:$/) do |body| + step 'I have created a customer' + @merchant_id = @customer_id + @client.add_hydrate :merchant_id, @merchant_id + step 'I have tokenized a bank account and associated with the merchant' + @client.post("/customers/#{@merchant_id}/orders", @client.hydrater(body)) + @client.add_hydrate :order_id, @client['id'] +end diff --git a/features/step_definitions/refund.rb b/features/step_definitions/refund.rb new file mode 100644 index 0000000..f59325a --- /dev/null +++ b/features/step_definitions/refund.rb @@ -0,0 +1,38 @@ +Given(/^I have created a refund for a debit$/) do + step 'I have debited a card' + @client.post("/debits/#{@debit_id}/refunds", {}) + @refund_id = @client['refunds']['id'] + @client.add_hydrate :refund_id, @refund_id +end + +When(/^I change the default funding source$/) do + @client.post('/cards', + { + number: "4111 1111 1111 1111", + expiration_month: 12, + expiration_year: 2016, + } + ) + @other_card_id = @client['cards']['id'] + + @client.patch(@customer_url, + [{ + op: "replace", + path: "/customers/0/links/source", + value: @other_card_id + }] + ) +end + +When(/^I create a refund$/) do + @original_card_id = @card_id # creating the refund will make a new card + step 'I have created a refund for a debit' +end + +Then(/^the refund should go to the original card$/) do + @client.get(@debit_url) + step "I should get a 200 OK status code" + step 'the response is valid according to the "debits" schema' + step "the fields on these debits match:", %Q/{ "links": { "source": "#{@original_card_id}" } }/ +end + diff --git a/features/step_definitions/reversals.rb b/features/step_definitions/reversals.rb new file mode 100644 index 0000000..0e616ab --- /dev/null +++ b/features/step_definitions/reversals.rb @@ -0,0 +1,8 @@ +Given(/^I have a bank account with a reversal$/) do + step 'I have a bank account with a credit' + + @client.post("/credits/#{@credit_id}/reversals", {}) + @reversal_id = @client['reversals']['id'] rescue nil + + @client.add_hydrate :reversal_id, @reversal_id +end diff --git a/features/support/initial_setup.rb b/features/support/initial_setup.rb new file mode 100644 index 0000000..4c33994 --- /dev/null +++ b/features/support/initial_setup.rb @@ -0,0 +1,51 @@ +require 'httparty' +require 'json' + +# TODO: move this into the lib +$root_url = ENV['BALANCED_ROOT'] || 'https://api.balancedpayments.com' +$accept_header = 'application/vnd.api+json;revision=1.1' + +# First, we need to create an API key. This is as easy as making a POST request. + +options = { + headers: { + "Accept" => $accept_header, + }, +} +response = HTTParty.post("#{$root_url}/api_keys", options) +$api_secret = JSON.parse(response.body)["api_keys"][0]["secret"] +puts "SECRET: #{$api_secret}" + +# Now that we have our key, we need to make a marketplace. Lots of our scenarios +# will fail unless we've made at least one. + +options = { + headers: { + "Accept" => $accept_header, + }, + basic_auth: { + username: $api_secret, + password: "" + }, +} + +response = HTTParty.post("#{$root_url}/marketplaces", options) +marketplace_id = response["marketplaces"][0]["id"] +puts "MARKETPLACE: #{marketplace_id}" + +# create an initial amount of money in the marketplace +# Sorry, whoever owns this card. ;) +HTTParty.post("#{$root_url}/debits", options.merge(body: { + amount: 10_000_000, # WE"RE RICH!!!! + source: { + number: "4111111111111111", + expiration_year: "2018", + expiration_month: 12 + } +})) + +$:.unshift(File.dirname(__FILE__)+'/../../lib') + +require 'balanced/tiny_client' + +$client = Balanced::TinyClient.new($api_secret, $accept_header, $root_url) diff --git a/responses/_models/address.json b/fixtures/_models/address.json similarity index 95% rename from responses/_models/address.json rename to fixtures/_models/address.json index 99f020d..d8cfcc2 100644 --- a/responses/_models/address.json +++ b/fixtures/_models/address.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-03/schema#", "type": "object", "properties": { "line1": { diff --git a/responses/_models/bank_account.json b/fixtures/_models/bank_account.json similarity index 59% rename from responses/_models/bank_account.json rename to fixtures/_models/bank_account.json index f925162..559d0f4 100644 --- a/responses/_models/bank_account.json +++ b/fixtures/_models/bank_account.json @@ -4,72 +4,58 @@ "properties": { "id": { "type": "string", - "required": true, "pattern": "BA[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "created_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "updated_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "name": { "type": [ "string", "null" - ], - "required": true + ] }, "account_number": { "type": "string", - "pattern": "x*[0-9]{4}", - "required": true + "pattern": "x*[0-9]{4}" }, "routing_number": { "type": "string", - "pattern": "[0-9]{9}", - "required": true + "pattern": "[0-9]{9}" }, "account_type": { "type": "string", "enum": [ "checking", "savings" - ], - "required": true + ] }, "bank_name": { - "type": "string", - "required": true + "type": "string" }, "address": { - "$ref": "address.json", - "required": true + "$ref": "address.json" }, "fingerprint": { - "type": "string", - "required": true + "type": "string" }, "can_debit": { - "type": "boolean", - "required": true + "type": "boolean" }, "can_credit": { - "type": "boolean", - "required": true + "type": "boolean" }, "meta": { - "type": "object", - "required": true + "type": "object" }, "links": { "type": "object", @@ -79,21 +65,39 @@ "null", "string" ], - "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}" }, "bank_account_verification": { "type": [ "null", "string" ], - "pattern": "BZ[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "BZ[a-zA-Z0-9]{16,32}" } }, - "required": true, + "required": [ + "customer", + "bank_account_verification" + ], "additionalProperties": false } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "id", + "href", + "created_at", + "updated_at", + "name", + "account_number", + "routing_number", + "account_type", + "bank_name", + "address", + "fingerprint", + "can_debit", + "can_credit", + "meta", + "links" + ] } \ No newline at end of file diff --git a/responses/_models/bank_account_verification.json b/fixtures/_models/bank_account_verification.json similarity index 55% rename from responses/_models/bank_account_verification.json rename to fixtures/_models/bank_account_verification.json index 249fe75..b990672 100644 --- a/responses/_models/bank_account_verification.json +++ b/fixtures/_models/bank_account_verification.json @@ -4,60 +4,64 @@ "properties": { "id": { "type": "string", - "pattern": "BZ[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "BZ[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "created_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "updated_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "attempts": { "type": "integer", "minimum": 0, - "maximum": 3, - "required": true + "maximum": 3 }, "attempts_remaining": { "type": "integer", "minimum": 0, - "maximum": 3, - "required": true + "maximum": 3 }, "deposit_status": { - "$ref": "status.json", - "required": true + "$ref": "status.json" }, "verification_status": { - "$ref": "status.json", - "required": true + "$ref": "status.json" }, "meta": { - "type": "object", - "required": true + "type": "object" }, "links": { "type": "object", "properties": { "bank_account": { "type": "string", - "pattern": "BA[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "BA[a-zA-Z0-9]{16,32}" } }, - "required": true, + "required": [ + "bank_account" + ], "additionalProperties": false } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "id", + "href", + "created_at", + "updated_at", + "attempts", + "attempts_remaining", + "deposit_status", + "verification_status", + "meta", + "links" + ] } \ No newline at end of file diff --git a/responses/_models/card.json b/fixtures/_models/card.json similarity index 99% rename from responses/_models/card.json rename to fixtures/_models/card.json index d05a5d5..769403c 100644 --- a/responses/_models/card.json +++ b/fixtures/_models/card.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-03/schema#", "type": "object", "properties": { "id": { diff --git a/responses/_models/credit.json b/fixtures/_models/credit.json similarity index 61% rename from responses/_models/credit.json rename to fixtures/_models/credit.json index f1d4e27..e1b3827 100644 --- a/responses/_models/credit.json +++ b/fixtures/_models/credit.json @@ -4,69 +4,61 @@ "properties": { "id": { "type": "string", - "required": true, "pattern": "CR[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "created_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "updated_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "amount": { "type": "integer", - "minimum": 1, - "required": true + "minimum": 1 }, "currency": { "type": "string", "enum": [ "USD" - ], - "required": true + ] }, "appears_on_statement_as": { "type": "string", - "pattern": "[a-zA-Z0-9 \\.\\-]{1,18}", - "required": true + "pattern": "[a-zA-Z0-9 \\.\\-]{1,18}" }, "description": { "type": [ "string", "null" - ], - "required": true + ] }, "status": { - "$ref": "status.json", - "required": true + "$ref": "status.json" }, "failure_reason_code": { "type": [ "string", "null" - ], - "required": true + ] }, "failure_reason": { "type": [ "string", "null" - ], - "required": true + ] + }, + "transaction_number": { + "type": "string", + "pattern": "CR[0-9]{3}-[0-9]{3}-[0-9]{4}" }, "meta": { - "type": "object", - "required": true + "type": "object" }, "links": { "type": "object", @@ -76,26 +68,42 @@ "null", "string" ], - "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}" }, "destination": { "type": "string", - "pattern": "BA[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "BA[a-zA-Z0-9]{16,32}" }, "order": { "type": [ "null", "string" ], - "pattern": "OR[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "OR[a-zA-Z0-9]{16,32}" } }, - "required": true, + "required": [ + "customer", + "destination", + "order" + ], "additionalProperties": false } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "id", + "href", + "created_at", + "updated_at", + "amount", + "currency", + "appears_on_statement_as", + "description", + "status", + "failure_reason_code", + "failure_reason", + "meta", + "links" + ] } \ No newline at end of file diff --git a/responses/_models/customer.json b/fixtures/_models/customer.json similarity index 66% rename from responses/_models/customer.json rename to fixtures/_models/customer.json index 8f063a1..11b4b72 100644 --- a/responses/_models/customer.json +++ b/fixtures/_models/customer.json @@ -4,69 +4,59 @@ "properties": { "id": { "type": "string", - "required": true, "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "created_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "updated_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "name": { "type": [ "null", "string" - ], - "required": true + ] }, "business_name": { "type": [ "null", "string" - ], - "required": true + ] }, "email": { "type": [ "null", "string" ], - "format": "email", - "required": true + "format": "email" }, "phone": { "type": [ "null", "string" ], - "format": "phone", - "required": true + "format": "phone" }, "ssn_last4": { "type": [ "null", "string" ], - "pattern": "x{4}", - "required": true + "pattern": "x{4}" }, "ein": { "type": [ "null", "string" ], - "pattern": "[0-9]{9}", - "required": true + "pattern": "[0-9]{9}" }, "dob_month": { "type": [ @@ -74,32 +64,28 @@ "integer" ], "minimum": 1, - "maximum": 12, - "required": true + "maximum": 12 }, "dob_year": { "type": [ "null", "integer" - ], - "required": true + ] }, "merchant_status": { "type": "string", "enum": [ "need-more-information", "underwritten", - "rejected" - ], - "required": true + "rejected", + "no-match" + ] }, "address": { - "$ref": "address.json", - "required": true + "$ref": "address.json" }, "meta": { - "type": "object", - "required": true + "type": "object" }, "links": { "type": "object", @@ -109,21 +95,40 @@ "null", "string" ], - "pattern": "(CC|BA)[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "(CC|BA)[a-zA-Z0-9]{16,32}" }, "destination": { "type": [ "null", "string" ], - "pattern": "BA[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "BA[a-zA-Z0-9]{16,32}" } }, - "required": true, + "required": [ + "source", + "destination" + ], "additionalProperties": false } }, + "required": [ + "id", + "href", + "created_at", + "updated_at", + "name", + "business_name", + "email", + "phone", + "ssn_last4", + "ein", + "dob_month", + "dob_year", + "merchant_status", + "address", + "meta", + "links" + ], "additionalProperties": false } \ No newline at end of file diff --git a/responses/_models/debit.json b/fixtures/_models/debit.json similarity index 60% rename from responses/_models/debit.json rename to fixtures/_models/debit.json index f414f4b..561500f 100644 --- a/responses/_models/debit.json +++ b/fixtures/_models/debit.json @@ -4,98 +4,106 @@ "properties": { "id": { "type": "string", - "required": true, "pattern": "WD[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "created_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "updated_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "amount": { "type": "integer", - "minimum": 1, - "required": true + "minimum": 1 }, "currency": { "type": "string", "enum": [ "USD" - ], - "required": true + ] }, "appears_on_statement_as": { "type": "string", - "pattern": "BAL\\*[a-zA-Z0-9 \\.\\-]{1,18}", - "required": true + "pattern": "BAL\\*[a-zA-Z0-9 \\.\\-]{1,18}" }, "description": { "type": [ "string", "null" - ], - "required": true + ] }, "status": { - "$ref": "status.json", - "required": true + "$ref": "status.json" }, "failure_reason_code": { "type": [ "string", "null" - ], - "required": true + ] }, "failure_reason": { "type": [ "string", "null" - ], - "required": true + ] + }, + "transaction_number": { + "type": "string", + "pattern": "W[0-9]{3}-[0-9]{3}-[0-9]{4}" }, "meta": { - "type": "object", - "required": true + "type": "object" }, "links": { "type": "object", "properties": { "source": { "type": "string", - "pattern": "(CC|BA)[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "(CC|BA)[a-zA-Z0-9]{16,32}" }, "customer": { "type": [ "null", "string" ], - "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}" }, "order": { "type": [ "null", "string" ], - "pattern": "OR[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "OR[a-zA-Z0-9]{16,32}" } }, - "required": true, + "required": [ + "source", + "customer", + "order" + ], "additionalProperties": false } }, - "additionalProperties": false -} + "additionalProperties": false, + "required": [ + "id", + "href", + "created_at", + "updated_at", + "amount", + "currency", + "appears_on_statement_as", + "description", + "status", + "failure_reason_code", + "transaction_number", + "meta", + "links" + ] +} \ No newline at end of file diff --git a/responses/errors.json b/fixtures/_models/error.json similarity index 95% rename from responses/errors.json rename to fixtures/_models/error.json index 2487a5d..7c4fb3c 100644 --- a/responses/errors.json +++ b/fixtures/_models/error.json @@ -2,8 +2,7 @@ "type": "object", "properties": { "status": { - "type": "string", - "required": true + "type": "string" }, "category_code": { "type": "string", @@ -75,8 +74,7 @@ "request", "order-kyc", "method-not-allowed" - ], - "required": true + ] }, "description": { "__example": "Human readable error message about what went wrong. Your request id is OHMasdfasdfasdfasdf", @@ -149,15 +147,10 @@ { "type": "null" } - ], - "required": true - }, - "additional": { - "required": false - }, - "extras": { - "required": false + ] }, + "additional": {}, + "extras": {}, "status_code": { "type": "integer", "enum": [ @@ -167,8 +160,7 @@ 405, 404, 409 - ], - "required": true + ] }, "category_type": { "type": "string", @@ -176,14 +168,20 @@ "request", "banking", "logical" - ], - "required": true + ] }, "request_id": { "type": "string", - "pattern": "OHM[a-zA-Z0-9]{16,34}", - "required": true + "pattern": "OHM[a-zA-Z0-9]{16,34}" } }, + "required": [ + "status", + "category_code", + "description", + "status_code", + "category_type", + "request_id" + ], "additionalProperties": false } \ No newline at end of file diff --git a/fixtures/_models/event.json b/fixtures/_models/event.json new file mode 100644 index 0000000..fb958ad --- /dev/null +++ b/fixtures/_models/event.json @@ -0,0 +1,112 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "EV[a-zA-Z0-9]{16,32}" + }, + "href": { + "type": "string", + "format": "uri" + }, + "occurred_at": { + "type": "string", + "format": "date-time" + }, + "type": { + "type": "string" + }, + "entity": { + "anyOf": [ + { + "bank_accounts": { + "type": "array", + "item": { + "$ref": "_models/bank_accounts.json" + }, + "minItems": 1, + "uniqueItems": true + } + }, + { + "bank_account_verifications": { + "type": "array", + "item": { + "$ref": "_models/bank_account_verifications.json" + }, + "minItems": 1, + "uniqueItems": true + } + }, + { + "card_holds": { + "type": "array", + "item": { + "$ref": "_models/card_holds.json" + }, + "minItems": 1, + "uniqueItems": true + } + }, + { + "credits": { + "type": "array", + "item": { + "$ref": "_models/credits.json" + }, + "minItems": 1, + "uniqueItems": true + } + }, + { + "customers": { + "type": "array", + "item": { + "$ref": "_models/customers.json" + }, + "minItems": 1, + "uniqueItems": true + } + }, + { + "debits": { + "type": "array", + "item": { + "$ref": "_models/debits.josn" + }, + "minItems": 1, + "uniqueItems": true + } + }, + { + "debits": { + "type": "array", + "item": { + "$ref": "_models/refunds.json" + }, + "minItems": 1, + "uniqueItems": true + } + }, + { + "reversals": { + "type": "array", + "item": { + "$ref": "_models/reversals.json" + }, + "minItems": 1, + "uniqueItems": true + } + } + ] + } + }, + "required": [ + "id", + "href", + "occurred_at", + "type", + "entity" + ] +} \ No newline at end of file diff --git a/responses/_models/marketplace.json b/fixtures/_models/marketplace.json similarity index 100% rename from responses/_models/marketplace.json rename to fixtures/_models/marketplace.json diff --git a/responses/_models/order.json b/fixtures/_models/order.json similarity index 59% rename from responses/_models/order.json rename to fixtures/_models/order.json index 4b1cfd5..e6d5f01 100644 --- a/responses/_models/order.json +++ b/fixtures/_models/order.json @@ -4,68 +4,58 @@ "properties": { "id": { "type": "string", - "pattern": "OR[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "OR[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "created_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "updated_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "currency": { "type": "string", "enum": [ "USD" - ], - "required": true + ] }, "amount": { "type": "integer", - "minimum": 0, - "required": true + "minimum": 0 }, "amount_escrowed": { "type": "integer", - "minimum": 0, - "required": true + "minimum": 0 }, "description": { "type": [ "string", "null" - ], - "required": true + ] }, "delivery_address": { - "$ref": "address.json", - "required": true + "$ref": "address.json" }, "meta": { - "type": "object", - "required": true + "type": "object" }, "links": { "type": "object", "properties": { "merchant": { "type": "string", - "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "(CU|AC)[a-zA-Z0-9]{16,32}" } }, - "required": true, + "required": [ "merchant" ], "additionalProperties": false } }, + "required": ["id", "href", "created_at", "updated_at", "currency", "amount", "amount_escrowed", "description", "delivery_address", "meta", "links"], "additionalProperties": false } diff --git a/responses/_models/refund.json b/fixtures/_models/refund.json similarity index 59% rename from responses/_models/refund.json rename to fixtures/_models/refund.json index 37f7ad7..8dc42b3 100644 --- a/responses/_models/refund.json +++ b/fixtures/_models/refund.json @@ -4,71 +4,79 @@ "properties": { "id": { "type": "string", - "required": true, "pattern": "RF[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "created_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "updated_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "amount": { "type": "integer", - "minimum": 1, - "required": true + "minimum": 1 }, "currency": { "type": "string", "enum": [ "USD" - ], - "required": true + ] }, "description": { "type": [ "string", "null" - ], - "required": true + ] }, "status": { - "$ref": "status.json", - "required": true + "$ref": "status.json" + }, + "transaction_number": { + "type": "string", + "pattern": "RF[0-9]{3}-[0-9]{3}-[0-9]{4}" }, "meta": { - "type": "object", - "required": true + "type": "object" }, "links": { "type": "object", "properties": { "debit": { "type": "string", - "pattern": "WD[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "WD[a-zA-Z0-9]{16,32}" }, "order": { "type": [ "null", "string" ], - "pattern": "OR[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "OR[a-zA-Z0-9]{16,32}" } }, - "required": true, + "required": [ + "debit", + "order" + ], "additionalProperties": false } }, - "additionalProperties": false -} + "additionalProperties": false, + "required": [ + "id", + "href", + "created_at", + "updated_at", + "amount", + "currency", + "description", + "status", + "meta", + "links" + ] +} \ No newline at end of file diff --git a/responses/_models/reversal.json b/fixtures/_models/reversal.json similarity index 61% rename from responses/_models/reversal.json rename to fixtures/_models/reversal.json index cc38a12..f4f59e6 100644 --- a/responses/_models/reversal.json +++ b/fixtures/_models/reversal.json @@ -4,85 +4,93 @@ "properties": { "id": { "type": "string", - "required": true, "pattern": "RV[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "created_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "updated_at": { "type": "string", - "format": "date-time", - "required": true + "format": "date-time" }, "amount": { "type": "integer", - "minimum": 1, - "required": true + "minimum": 1 }, "currency": { "type": "string", "enum": [ "USD" - ], - "required": true + ] }, "description": { "type": [ "string", "null" - ], - "required": true + ] }, "status": { - "$ref": "status.json", - "required": true + "$ref": "status.json" }, "failure_reason_code": { "type": [ "string", "null" - ], - "required": true + ] }, "failure_reason": { "type": [ "string", "null" - ], - "required": true + ] + }, + "transaction_number": { + "type": "string", + "pattern": "RV[0-9]{3}-[0-9]{3}-[0-9]{4}" }, "meta": { - "type": "object", - "required": true + "type": "object" }, "links": { "type": "object", "properties": { "credit": { "type": "string", - "pattern": "CR[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "CR[a-zA-Z0-9]{16,32}" }, "order": { "type": [ "null", "string" ], - "pattern": "OR[a-zA-Z0-9]{16,32}", - "required": true + "pattern": "OR[a-zA-Z0-9]{16,32}" } }, - "required": true, + "required": [ + "credit", + "order" + ], "additionalProperties": false } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "id", + "href", + "created_at", + "updated_at", + "amount", + "currency", + "description", + "status", + "failure_reason_code", + "failure_reason", + "meta", + "links" + ] } \ No newline at end of file diff --git a/responses/_models/status.json b/fixtures/_models/status.json similarity index 100% rename from responses/_models/status.json rename to fixtures/_models/status.json diff --git a/responses/_models/token.json b/fixtures/_models/token.json similarity index 71% rename from responses/_models/token.json rename to fixtures/_models/token.json index 6ac6513..fd766c2 100644 --- a/responses/_models/token.json +++ b/fixtures/_models/token.json @@ -4,20 +4,22 @@ "properties": { "id": { "type": "string", - "required": true, "pattern": "(CC|BA)[a-zA-Z0-9]{16,32}" }, "href": { "type": "string", - "format": "uri", - "required": true + "format": "uri" }, "links": { "type": "object", "properties": {}, - "additionalProperties": false, - "required": true + "additionalProperties": false } }, + "required": [ + "id", + "href", + "links" + ], "additionalProperties": false } \ No newline at end of file diff --git a/responses/bank_account_tokens.json b/fixtures/bank_account_tokens.json similarity index 76% rename from responses/bank_account_tokens.json rename to fixtures/bank_account_tokens.json index 48c2fe2..689029c 100644 --- a/responses/bank_account_tokens.json +++ b/fixtures/bank_account_tokens.json @@ -5,12 +5,10 @@ "type": "object", "properties": { "links": { - "type": "object", - "required": false + "type": "object" }, "meta": { - "type": "object", - "required": false + "type": "object" }, "bank_accounts": { "items": { @@ -19,8 +17,10 @@ "type": "array", "minItems": 1, "maxItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "bank_accounts" + ] } \ No newline at end of file diff --git a/responses/bank_account_verifications.json b/fixtures/bank_account_verifications.json similarity index 72% rename from responses/bank_account_verifications.json rename to fixtures/bank_account_verifications.json index 04aebb7..3aed7bc 100644 --- a/responses/bank_account_verifications.json +++ b/fixtures/bank_account_verifications.json @@ -8,15 +8,15 @@ "bank_account_verifications.bank_account": { "type": "string", "format": "uri", - "pattern": "/bank_accounts/{bank_account_verifications.bank_account}", - "required": true + "pattern": "/bank_accounts/{bank_account_verifications.bank_account}" } }, - "required": false + "required": [ + "bank_account_verifications.bank_account" + ] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "bank_account_verifications": { "items": { @@ -24,8 +24,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "bank_account_verifications" + ] } \ No newline at end of file diff --git a/responses/bank_accounts.json b/fixtures/bank_accounts.json similarity index 72% rename from responses/bank_accounts.json rename to fixtures/bank_accounts.json index b6954a2..96def46 100644 --- a/responses/bank_accounts.json +++ b/fixtures/bank_accounts.json @@ -8,39 +8,39 @@ "bank_accounts.bank_account_verifications": { "type": "string", "format": "uri", - "pattern": "/bank_accounts/{bank_accounts.id}/bank_account_verifications", - "required": true + "pattern": "/bank_accounts/{bank_accounts.id}/verifications" }, "bank_accounts.credits": { "type": "string", "format": "uri", - "pattern": "/bank_accounts/{bank_accounts.id}/credits", - "required": true + "pattern": "/bank_accounts/{bank_accounts.id}/credits" }, "bank_accounts.debits": { "type": "string", "format": "uri", - "pattern": "/bank_accounts/{bank_accounts.id}/debits", - "required": true + "pattern": "/bank_accounts/{bank_accounts.id}/debits" }, "bank_accounts.customer": { "type": "string", "format": "uri", - "pattern": "/customers/{bank_accounts.customer}", - "required": true + "pattern": "/customers/{bank_accounts.customer}" }, "bank_accounts.bank_account_verification": { "type": "string", "format": "uri", - "pattern": "/bank_account_verifications/{bank_accounts.bank_account_verification}", - "required": true + "pattern": "/verifications/{bank_accounts.bank_account_verification}" } }, - "required": false + "required": [ + "bank_accounts.bank_account_verifications", + "bank_accounts.credits", + "bank_accounts.debits", + "bank_accounts.customer", + "bank_accounts.bank_account_verification" + ] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "bank_accounts": { "items": { @@ -48,8 +48,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "bank_accounts" + ] } \ No newline at end of file diff --git a/responses/card_tokens.json b/fixtures/card_tokens.json similarity index 75% rename from responses/card_tokens.json rename to fixtures/card_tokens.json index 7712d25..bd40485 100644 --- a/responses/card_tokens.json +++ b/fixtures/card_tokens.json @@ -5,12 +5,10 @@ "type": "object", "properties": { "links": { - "type": "object", - "required": false + "type": "object" }, "meta": { - "type": "object", - "required": false + "type": "object" }, "cards": { "items": { @@ -19,8 +17,10 @@ "type": "array", "minItems": 1, "maxItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "cards" + ] } \ No newline at end of file diff --git a/responses/cards.json b/fixtures/cards.json similarity index 58% rename from responses/cards.json rename to fixtures/cards.json index 3ac1019..4130b3f 100644 --- a/responses/cards.json +++ b/fixtures/cards.json @@ -1,5 +1,4 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "links": { @@ -8,27 +7,27 @@ "cards.card_holds": { "type": "string", "format": "uri", - "pattern": "/cards/{cards.id}/card_holds", - "required": true + "pattern": "/cards/{cards.id}/card_holds" }, "cards.debits": { "type": "string", "format": "uri", - "pattern": "/cards/{cards.id}/debits", - "required": true + "pattern": "/cards/{cards.id}/debits" }, "cards.customer": { "type": "string", "format": "uri", - "pattern": "/customers/{cards.customer}", - "required": true + "pattern": "/customers/{cards.customer}" } }, - "required": false + "required": [ + "cards.customer", + "cards.debits", + "cards.card_holds" + ] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "cards": { "items": { @@ -36,8 +35,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "cards" + ] } \ No newline at end of file diff --git a/responses/credits.json b/fixtures/credits.json similarity index 62% rename from responses/credits.json rename to fixtures/credits.json index bfa3c3e..c2a3a73 100644 --- a/responses/credits.json +++ b/fixtures/credits.json @@ -8,33 +8,39 @@ "credits.reversals": { "type": "string", "format": "uri", - "pattern": "/credits/{credits.id}/reversals", - "required": true + "pattern": "/credits/{credits.id}/reversals" }, "credits.customer": { "type": "string", "format": "uri", - "pattern": "/customers/{credits.customer}", - "required": true + "pattern": "/customers/{credits.customer}" }, "credits.order": { "type": "string", "format": "uri", - "pattern": "/orders/{credits.order}", - "required": true + "pattern": "/orders/{credits.order}" }, "credits.destination": { "type": "string", "format": "uri", - "pattern": "/resources/{credits.destination}", - "required": true + "pattern": "/resources/{credits.destination}" + }, + "credits.events": { + "type": "string", + "format": "uri", + "pattern": "/credits/{credits.id}/events" } }, - "required": false + "required": [ + "credits.reversals", + "credits.customer", + "credits.order", + "credits.destination", + "credits.events" + ] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "credits": { "items": { @@ -42,8 +48,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "credits" + ] } \ No newline at end of file diff --git a/responses/customers.json b/fixtures/customers.json similarity index 78% rename from responses/customers.json rename to fixtures/customers.json index 7c87f4d..818b8dd 100644 --- a/responses/customers.json +++ b/fixtures/customers.json @@ -8,64 +8,64 @@ "customers.cards": { "type": "string", "format": "uri", - "pattern": "/customers/{customers.id}/cards", - "required": true + "pattern": "/customers/{customers.id}/cards" }, "customers.bank_accounts": { "type": "string", "format": "uri", - "pattern": "/customers/{customers.id}/bank_accounts", - "required": true + "pattern": "/customers/{customers.id}/bank_accounts" }, "customers.credits": { "type": "string", "format": "uri", - "pattern": "/customers/{customers.id}/credits", - "required": true + "pattern": "/customers/{customers.id}/credits" }, "customers.debits": { "type": "string", "format": "uri", - "pattern": "/customers/{customers.id}/debits", - "required": true + "pattern": "/customers/{customers.id}/debits" }, "customers.refunds": { "type": "string", "format": "uri", - "pattern": "/customers/{customers.id}/refunds", - "required": true + "pattern": "/customers/{customers.id}/refunds" }, "customers.reversals": { "type": "string", "format": "uri", - "pattern": "/customers/{customers.id}/reversals", - "required": true + "pattern": "/customers/{customers.id}/reversals" }, "customers.orders": { "description": "Orders in which the customer is either a buyer or merchant", "type": "string", "format": "uri", - "pattern": "/customers/{customers.id}/orders", - "required": true + "pattern": "/customers/{customers.id}/orders" }, "customers.source": { "type": "string", "format": "uri", - "pattern": "/resources/{customers.source}", - "required": true + "pattern": "/resources/{customers.source}" }, "customers.destination": { "type": "string", "format": "uri", - "pattern": "/resources/{customers.destination}", - "required": true + "pattern": "/resources/{customers.destination}" } }, - "required": false + "required": [ + "customers.cards", + "customers.bank_accounts", + "customers.credits", + "customers.debits", + "customers.refunds", + "customers.reversals", + "customers.orders", + "customers.source", + "customers.destination" + ] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "customers": { "items": { @@ -73,8 +73,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "customers" + ] } \ No newline at end of file diff --git a/responses/debits.json b/fixtures/debits.json similarity index 55% rename from responses/debits.json rename to fixtures/debits.json index 239f61a..0e3d417 100644 --- a/responses/debits.json +++ b/fixtures/debits.json @@ -8,33 +8,39 @@ "debits.refunds": { "type": "string", "format": "uri", - "pattern": "/debits/{debits.id}/refunds", - "required": true + "pattern": "/debits/{debits.id}/refunds" }, "debits.customer": { "type": "string", "format": "uri", - "pattern": "/customers/{debits.customer}", - "required": true + "pattern": "/customers/{debits.customer}" }, "debits.order": { "type": "string", "format": "uri", - "pattern": "/orders/{debits.order}", - "required": true + "pattern": "/orders/{debits.order}" }, "debits.source": { "type": "string", "format": "uri", - "pattern": "/resources/{debits.source}", - "required": true + "pattern": "/resources/{debits.source}" + }, + "debits.events": { + "type": "string", + "format": "uri", + "pattern": "/debits/{debits.id}/events" } }, - "required": false + "required": [ + "debits.refunds", + "debits.customer", + "debits.order", + "debits.source", + "debits.events" + ] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "debits": { "items": { @@ -42,8 +48,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "require": [ + "debits" + ] } \ No newline at end of file diff --git a/fixtures/errors.json b/fixtures/errors.json new file mode 100644 index 0000000..c0135da --- /dev/null +++ b/fixtures/errors.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "properties": { + "errors": { + "items": { + "$ref": "_models/error.json" + }, + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "required": [ + "errors" + ] +} diff --git a/responses/events.json b/fixtures/events.json similarity index 65% rename from responses/events.json rename to fixtures/events.json index 9e884a2..7baa290 100644 --- a/responses/events.json +++ b/fixtures/events.json @@ -4,12 +4,10 @@ "properties": { "links": { "type": "object", - "properties": {}, - "required": false + "properties": {} }, "meta": { - "type": "object", - "required": false + "type": "object" }, "events": { "items": { @@ -17,8 +15,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "events" + ] } \ No newline at end of file diff --git a/responses/orders.json b/fixtures/orders.json similarity index 61% rename from responses/orders.json rename to fixtures/orders.json index fa61f33..cb37dbe 100644 --- a/responses/orders.json +++ b/fixtures/orders.json @@ -8,45 +8,38 @@ "orders.debits": { "type": "string", "format": "uri", - "pattern": "/orders/{orders.id}/debits", - "required": true + "pattern": "/orders/{orders.id}/debits" }, "orders.refunds": { "type": "string", "format": "uri", - "pattern": "/orders/{orders.id}/refunds", - "required": true + "pattern": "/orders/{orders.id}/refunds" }, "orders.credits": { "type": "string", "format": "uri", - "pattern": "/orders/{orders.id}/credits", - "required": true + "pattern": "/orders/{orders.id}/credits" }, "orders.reversals": { "type": "string", "format": "uri", - "pattern": "/orders/{orders.id}/reversals", - "required": true + "pattern": "/orders/{orders.id}/reversals" }, "orders.buyers": { "type": "string", "format": "uri", - "pattern": "/orders/{orders.id}/buyers", - "required": true + "pattern": "/orders/{orders.id}/buyers" }, "orders.merchant": { "type": "string", "format": "uri", - "pattern": "/customers/{orders.merchant}", - "required": true + "pattern": "/customers/{orders.merchant}" } }, - "required": false + "required": ["orders.debits", "orders.refunds", "orders.credits", "orders.reversals", "orders.buyers", "orders.merchant"] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "orders": { "items": { @@ -54,8 +47,8 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } -} \ No newline at end of file + }, + "required": ["orders"] +} diff --git a/responses/refunds.json b/fixtures/refunds.json similarity index 54% rename from responses/refunds.json rename to fixtures/refunds.json index 395a9ae..7831eb3 100644 --- a/responses/refunds.json +++ b/fixtures/refunds.json @@ -8,21 +8,27 @@ "refunds.debit": { "type": "string", "format": "uri", - "pattern": "/debits/{refunds.debit}", - "required": true + "pattern": "/debits/{refunds.debit}" }, "refunds.order": { "type": "string", "format": "uri", - "pattern": "/orders/{refunds.order}", - "required": true + "pattern": "/orders/{refunds.order}" + }, + "refunds.events": { + "type": "string", + "format": "uri", + "pattern": "/refunds/{refunds.id}/events" } }, - "required": false + "required": [ + "refunds.debit", + "refunds.order", + "refunds.events" + ] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "refunds": { "items": { @@ -30,8 +36,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "refunds" + ] } \ No newline at end of file diff --git a/responses/reversals.json b/fixtures/reversals.json similarity index 63% rename from responses/reversals.json rename to fixtures/reversals.json index 2a2f9b8..93f1ae7 100644 --- a/responses/reversals.json +++ b/fixtures/reversals.json @@ -16,13 +16,22 @@ "format": "uri", "pattern": "/orders/{reversals.order}", "required": true + }, + "reversals.events": { + "type": "string", + "format": "uri", + "pattern": "/reversals/{reversals.id}/events", + "required": true } }, - "required": false + "required": [ + "reversals.credit", + "reversals.order", + "reversals.events" + ] }, "meta": { - "type": "object", - "required": false + "type": "object" }, "reversals": { "items": { @@ -30,8 +39,10 @@ }, "type": "array", "minItems": 1, - "uniqueItems": true, - "required": true + "uniqueItems": true } - } + }, + "required": [ + "reversals" + ] } \ No newline at end of file diff --git a/formatters/in_progress.rb b/formatters/in_progress.rb new file mode 100644 index 0000000..4a24a92 --- /dev/null +++ b/formatters/in_progress.rb @@ -0,0 +1,60 @@ +require 'cucumber/formatter/progress' + +module Cucumber + module Formatter + class InProgress < Progress + FAILURE_CODE = 1 + SUCCESS_CODE = 0 + + FORMATS[:invalid_pass] = Proc.new{ |string| ::Term::ANSIColor.blue(string) } + + def initialize(step_mother, io, options) + super(step_mother, io, options) + @scenario_passed = true + @passing_scenarios = [] + @feature_element_count = 0 + end + + def visit_feature_element(feature_element) + super + + @passing_scenarios << feature_element if @scenario_passed + @scenario_passed = true + @feature_element_count += 1 + + @io.flush + end + + def visit_exception(exception, status) + @scenario_passed = false + super + end + + private + + def print_summary(features) + unless @passing_scenarios.empty? + @io.puts format_string("(::) Scenarios passing which should be failing or pending (::)", :invalid_pass) + @io.puts + @passing_scenarios.each do |element| + @io.puts(format_string(element.backtrace_line, :invalid_pass)) + end + @io.puts + end + + unless @passing_scenarios.empty? + override_exit_code(FAILURE_CODE) + else + override_exit_code(SUCCESS_CODE) + end + end + + def override_exit_code(status_code) + at_exit do + Kernel.exit(status_code) + end + end + + end + end +end diff --git a/lib/balanced/tiny_client.rb b/lib/balanced/tiny_client.rb new file mode 100644 index 0000000..74a1ded --- /dev/null +++ b/lib/balanced/tiny_client.rb @@ -0,0 +1,189 @@ +require 'json-schema' + +module Balanced + class TinyClient + attr_reader :api_secret, :root_url + attr_reader :responses + attr_reader :hydrate_tokens + attr_writer :running + + def initialize(api_secret, accept_header, root_url) + @api_secret = api_secret + @accept_header = accept_header + @root_url = root_url + + @responses = [] + @hydrate_tokens = {} + end + + def post(endpoint, body, env={}) + body = JSON.parse(body) if body.is_a? String + options = { + headers: { + 'Accept' => @accept_header, + }, + body: body, + basic_auth: { + username: @api_secret, + password: '', + } + } + + url = expand_url(endpoint, env) + + response = HTTParty.post(url, options) + @responses << response + response + end + + def expand_url(endpoint, env) + require 'uri_template' + # I am so sorry + template = "#{@root_url}#{endpoint}".gsub(/{(.*?)}/) do + "{#{$1.gsub(".", "_")}}" + end + $logger.debug("expanding: #{template} with env: #{env}") + # TODO: does using . in uri variables make sense? http://tools.ietf.org/html/rfc6570#section-3.2.1 + template = URITemplate.new(template) + url = template.expand(env) + $logger.debug("expanded: #{url}") + url + end + + def put(endpoint, body, env={}) + body = JSON.parse(body) if body.is_a? String + options = { + headers: { + 'Accept' => @accept_header, + }, + body: body, + basic_auth: { + username: @api_secret, + password: '', + } + } + + response = HTTParty.put("#{@root_url}#{endpoint}", options) + @responses << response + response + end + + def patch(endpoint, body, env={}) + body = JSON.parse(body) if body.is_a? String + options = { + headers: { + 'Accept' => @accept_header, + 'Content-Type' => "application/json", # github: https://github.com/balanced/balanced-api/issues/458 + }, + body: JSON.dump(body), # sometimes we send arrays and then it gets confused + basic_auth: { + username: @api_secret, + password: '', + } + } + + response = HTTParty.patch("#{@root_url}#{endpoint}", options) + @responses << response + response + end + + def get(endpoint, body=nil, env={}) + verb 'GET', endpoint, env + end + + def delete(endpoint, body=nil, env={}) + verb 'DELETE', endpoint + end + + def add_response(response) + @responses << response + end + + def verb(verb, url, env={}, body=nil) + options = { + headers: { + 'Accept' => @accept_header, + 'Content-Type' => "application/json", # github: https://github.com/balanced/balanced-api/issues/458 + }, + body: JSON.dump(body), # sometimes we send arrays and then it gets confused + basic_auth: { + username: @api_secret, + password: '', + } + } + + options[:body] = body if body + + url = expand_url(url, env) + + response = HTTParty.send(verb.downcase, url, options) + @responses << response + response + end + + def last_code + @responses.last.code + end + + def last_body + if @responses.last.body + JSON.parse(@responses.last.body) + end + end + + def get_link(keys) + @responses.reverse.each do |response| + #require 'debugger'; debugger + body = JSON.parse(response.body) + if body and body['links'] and body['links'][keys] + key = body['links'][keys] + kk = key.gsub(/\{(\w+)\.(\w+)\}/) do |match| + a = match[1...-1].split('.') + body[a[0]][0][a[1]] or body[a[0]][0]['links'][a[1]] + end + #require 'debugger'; debugger + return kk + #return keys.split('.').inject(body)json['links'][keys] + end + end + '/boom' + end + + def inject(key) + # hax to access a Ruby hash like dot notation + key.split('.').inject(last_body) {|o, k| Array(o[k])[0] } + end + + def hydrater(what) + @hydrate_tokens.each_pair do |key, value| + if key.class == Symbol + key = ":#{key}" + end + what = what.gsub(key, value) + end + what + end + + def add_hydrate(key, value) + @hydrate_tokens[key] = value + end + + def validate(against) + file_name = File.join(File.dirname(__FILE__), "../..", 'fixtures', "#{against}.json") + if File.exists?(file_name) and not against.is_a? Hash + JSON::Validator.validate!(file_name, last_body) + else + JSON::Validator.validate!(against, last_body) + end + end + + def [](name) + # either access some type by name, or access a field on an object if there was only one + if last_body.has_key? name + last_body[name][0] + else + last_body.select { |x| x != 'links' and x != 'meta' }.values.first.first[name] + end + end + end +end diff --git a/requests/README.md b/requests/README.md deleted file mode 100644 index 325a5a6..0000000 --- a/requests/README.md +++ /dev/null @@ -1,9 +0,0 @@ -## Balanced-api requests directory - -The directory defines all the possible requests to the balanced api. - -### layout - * files in this directory are requests that can be done at the top level. Eg: the file `card.json` represents request to `/cards` - * files contained in directories represent nested requests. Eg: `bank_accounts/credit.json` represents requests to `/bank_accounts/:bank_account_id/credits` - * the folder _models contains schemas that are commonly used through requests such as address field - * the _patch.json file represents the schema for a PATCH request to updated an object diff --git a/requests/_models/address.json b/requests/_models/address.json deleted file mode 100644 index ace45b8..0000000 --- a/requests/_models/address.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "line1": { - "type": [ - "string", - "null" - ] - }, - "line2": { - "type": [ - "string", - "null" - ] - }, - "city": { - "type": [ - "string", - "null" - ] - }, - "state": { - "type": [ - "string", - "null" - ] - }, - "postal_code": { - "type": [ - "string", - "null" - ] - }, - "country_code": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "string", - "pattern": "[A-Z]{2}" - } - ] - } - }, - "additionalProperties": false -} \ No newline at end of file diff --git a/requests/_models/credit.json b/requests/_models/credit.json deleted file mode 100644 index 66e7580..0000000 --- a/requests/_models/credit.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "amount": { - "type": "integer", - "minimum": 1, - "required": true - }, - "currency": { - "type": "string", - "enum": [ - "USD" - ] - }, - "order": { - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "null" - } - ] - }, - "appears_on_statement_as": { - "type": [ - "string", - "null" - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "meta": { - "type": "object" - } - } -} \ No newline at end of file diff --git a/requests/_models/debit.json b/requests/_models/debit.json deleted file mode 100644 index f08c3d7..0000000 --- a/requests/_models/debit.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "amount": { - "type": "integer", - "minimum": 1, - "required": true - }, - "currency": { - "type": "string", - "enum": [ - "USD" - ], - "default": "USD" - }, - "order": { - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "null" - } - ] - }, - "appears_on_statement_as": { - "anyOf": [ - { - "type": "string", - "pattern": "[a-zA-Z0-9 \\.\\-]{1,18}" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "meta": { - "type": "object" - } - } -} diff --git a/requests/_models/month.json b/requests/_models/month.json deleted file mode 100644 index 6217c92..0000000 --- a/requests/_models/month.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "anyOf": [ - { - "type": "integer", - "minimum": 1, - "maximum": 12 - }, - { - "type": "string", - "enum": [ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12" - ] - } - ] -} \ No newline at end of file diff --git a/requests/_models/year.json b/requests/_models/year.json deleted file mode 100644 index 350f214..0000000 --- a/requests/_models/year.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "anyOf": [ - { - "type": "integer", - "minimum": 1900, - "maximum": 2100 - }, - { - "type": "string", - "pattern": "[12][901][0-9]{2}" - } - ] -} \ No newline at end of file diff --git a/requests/_patch.json b/requests/_patch.json deleted file mode 100644 index 1691599..0000000 --- a/requests/_patch.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Patch_requests", - "type": "array", - "items": { - "type": "object", - "properties": { - "op": { - "type": "string", - "enum": [ - "add", - "remove", - "replace", - "move", - "copy", - "test" - ], - "required": true - }, - "path": { - "type": "string", - "pattern": "/\\w+s/0/.+", - "required": true - }, - "from": { - "type": "string", - "pattern": "/\\w+s/0/.+", - "required": false - }, - "value": { - "required": false - } - }, - "additionalProperties": false - }, - "required": true, - "uniqueItems": true, - "minItems": 1 -} \ No newline at end of file diff --git a/requests/bank_account.json b/requests/bank_account.json deleted file mode 100644 index bfe16cf..0000000 --- a/requests/bank_account.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/bank_accounts", - "type": "object", - "properties": { - "name": { - "type": "string", - "required": true - }, - "account_number": { - "type": "string", - "pattern": "[0-9]+", - "required": true - }, - "routing_number": { - "type": "string", - "pattern": "[0-9]{9}", - "required": true - }, - "account_type": { - "type": "string", - "enum": [ - "checking", - "savings" - ], - "required": true - }, - "country_code": { - "type": "string", - "pattern": "[A-Z]{2}", - "default": "US" - }, - "meta": { - "type": "object" - } - } -} \ No newline at end of file diff --git a/requests/bank_accounts/bank_account_verification.json b/requests/bank_accounts/bank_account_verification.json deleted file mode 100644 index 08ea074..0000000 --- a/requests/bank_accounts/bank_account_verification.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/bank_accounts/:bank_account_id/bank_account_verifications", - "anyof": [ - { - "type": "object", - "properties": {} - }, - { - "type": "null" - } - ] -} \ No newline at end of file diff --git a/requests/bank_accounts/credit.json b/requests/bank_accounts/credit.json deleted file mode 100644 index e12f336..0000000 --- a/requests/bank_accounts/credit.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/bank_accounts/:bank_account_id/credits", - "$ref": "../_models/credit.json" -} \ No newline at end of file diff --git a/requests/bank_accounts/debit.json b/requests/bank_accounts/debit.json deleted file mode 100644 index 73ff7da..0000000 --- a/requests/bank_accounts/debit.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/bank_accounts/:bank_account_id/debits", - "$ref": "../_models/debit.json" -} \ No newline at end of file diff --git a/requests/card.json b/requests/card.json deleted file mode 100644 index 48b3e6e..0000000 --- a/requests/card.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/cards", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "number": { - "type": "string", - "pattern": "([0-9][ \\-]*){15,16}", - "required": true - }, - "expiration_month": { - "$ref": "_models/month.json", - "required": true - }, - "expiration_year": { - "$ref": "_models/year.json", - "required": true - }, - "cvv": { - "type": "string", - "pattern": "[0-9]{3,4}" - }, - "address": { - "$ref": "_models/address.json" - }, - "meta": { - "type": "object" - } - } -} \ No newline at end of file diff --git a/requests/cards/debit.json b/requests/cards/debit.json deleted file mode 100644 index 87db869..0000000 --- a/requests/cards/debit.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/cards/:card_id/debits", - "$ref": "../_models/debit.json" -} \ No newline at end of file diff --git a/requests/credit.json b/requests/credit.json deleted file mode 100644 index bd6ca83..0000000 --- a/requests/credit.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/credits", - "allOf": [ - { - "$ref": "_models/credit.json" - }, - { - "type": "object", - "properties": { - "destination": { - "$ref": "bank_account.json" - } - } - } - ] -} \ No newline at end of file diff --git a/requests/credits/reversal.json b/requests/credits/reversal.json deleted file mode 100644 index afeeb49..0000000 --- a/requests/credits/reversal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/credits/:credit_id/reversals", - "anyOf": [ - { - "type": "object", - "properties": {} - }, - { - "type": "null" - } - ] -} \ No newline at end of file diff --git a/requests/customer.json b/requests/customer.json deleted file mode 100644 index 709a3ce..0000000 --- a/requests/customer.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/customers", - "type": "object", - "properties": { - "name": { - "type": [ - "string", - "null" - ] - }, - "business_name": { - "type": [ - "string", - "null" - ] - }, - "email": { - "type": [ - "string", - "null" - ], - "format": "email" - }, - "phone": { - "type": [ - "string", - "null" - ], - "format": "phone" - }, - "ssn_last4": { - "type": [ - "string", - "null" - ], - "pattern": "[0-9]{4}" - }, - "ein": { - "type": [ - "string", - "null" - ], - "pattern": "[0-9]{9}" - }, - "dob_month": { - "anyOf": [ - { - "$ref": "_models/month.json" - }, - { - "type": "null" - } - ] - }, - "dob_year": { - "anyOf": [ - { - "$ref": "_models/year.json" - }, - { - "type": "null" - } - ] - }, - "address": { - "$ref": "_models/address.json" - }, - "source": { - "description": "The default financial instrument used when debiting a customer", - "anyOf": [ - { - "$ref": "card.json" - }, - { - "$ref": "bank_account.json" - }, - { - "description": "Either the href or id of a card or bank account", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "destination": { - "description": "The default financial instrument used when crediting a customer", - "anyOf": [ - { - "$ref": "bank_account.json" - }, - { - "description": "Either the href or the id of a bank account", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "meta": { - "type": "object" - } - } -} \ No newline at end of file diff --git a/requests/customers/bank_accounts.json b/requests/customers/bank_accounts.json deleted file mode 100644 index 1d79cfe..0000000 --- a/requests/customers/bank_accounts.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/customers/:customer_id/bank_accounts", - "anyOf": [ - { - "$ref": "../bank_account.json" - }, - { - "properties": { - "href": { - "type": "string" - } - } - } - ] -} \ No newline at end of file diff --git a/requests/customers/cards.json b/requests/customers/cards.json deleted file mode 100644 index ef0586d..0000000 --- a/requests/customers/cards.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/customers/:customer_id/cards", - "anyOf": [ - { - "$ref": "../card.json" - }, - { - "properties": { - "href": { - "type": "string" - } - } - } - ] -} \ No newline at end of file diff --git a/requests/customers/credit.json b/requests/customers/credit.json deleted file mode 100644 index 8fb0732..0000000 --- a/requests/customers/credit.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/customers/:customer_id/credits", - "$ref": "../_models/credit.json" -} \ No newline at end of file diff --git a/requests/customers/debit.json b/requests/customers/debit.json deleted file mode 100644 index ce1d016..0000000 --- a/requests/customers/debit.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/customers/:customer_id/debits", - "type": "object", - "$ref": "../_models/debit.json" -} \ No newline at end of file diff --git a/requests/customers/order.json b/requests/customers/order.json deleted file mode 100644 index 65786db..0000000 --- a/requests/customers/order.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/customers/:customer_id/orders", - "properties": { - "description": { - "type": "string" - }, - "delivery_address": { - "$ref": "../_models/address.json" - }, - "meta": { - "type": "object" - } - } -} \ No newline at end of file diff --git a/requests/debit.json b/requests/debit.json deleted file mode 100644 index d1d5d40..0000000 --- a/requests/debit.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/debits", - "allOf": [ - { - "$ref": "_models/debit.json" - }, - { - "type": "object", - "properties": { - "source": { - "$ref": "card.json" - } - } - } - ] -} \ No newline at end of file diff --git a/requests/debits/refund.json b/requests/debits/refund.json deleted file mode 100644 index 0f5200c..0000000 --- a/requests/debits/refund.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "/debits/:debit_id/refunds", - "anyOf": [ - { - "type": "object", - "properties": { - "amount": { - "type": "integer", - "minimum": 1 - } - } - }, - { - "type": "null" - } - ] -} diff --git a/responses/README.md b/responses/README.md deleted file mode 100644 index 4d0496a..0000000 --- a/responses/README.md +++ /dev/null @@ -1,56 +0,0 @@ -## Balanced-api responses directory - -This directory defines the form of all the possible responses from the balanced api. - -### Example response -``` json - { - "customers": [ - { - "name": "Balanced testing", - "links": { - "source": null, - "destination": null - }, - "created_at": "2013-08-16T03:19:00.112349Z", - "dob_month": null, - "merchant_status": "need-more-information", - "updated_at": "2013-08-16T03:19:00.112350Z", - "phone": null, - "href": "/customers/CU4CQ5MkeOIuxwL2IGI4I2RG", - "meta": {}, - "dob_year": null, - "email": null, - "address": { - "city": null, - "line2": null, - "line1": null, - "state": null, - "postal_code": null, - "country_code": null - }, - "business_name": null, - "ssn_last4": null, - "id": "CU4CQ5MkeOIuxwL2IGI4I2RG", - "ein": null - } - ], - "links": { - "customers.source": "/cards/{customers.source}", - "customers.card_holds": "/customers/{customers.id}/card_holds", - "customers.cards": "/customers/{customers.id}/cards", - "customers.debits": "/customers/{customers.id}/debits", - "customers.destination": "/cards/{customers.destination}", - "customers.bank_accounts": "/customers/{customers.id}/bank_accounts", - "customers.transactions": "/customers/{customers.id}/transactions", - "customers.refunds": "/customers/{customers.id}/refunds", - "customers.reversals": "/customers/{customers.id}/reversals", - "customers.orders": "/customers/{customers.id}/orders", - "customers.credits": "/customers/{customers.id}/credits" - } - } -``` - -### Layout of files - * The files in this directory are for validating the full response with the response envelope - * The files in the _models directory are for validating the individual object contained in the `[resource]s` array diff --git a/responses/_models/event.json b/responses/_models/event.json deleted file mode 100644 index a73aee3..0000000 --- a/responses/_models/event.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "href": { - "type": "string", - "format": "uri", - "required": true - }, - "id": { - "type": "string", - "pattern": "EV[a-zA-Z0-9]{16,32}", - "required": true - }, - "occurred_at": { - "type": "string", - "format": "date-time", - "required": true - }, - "type": { - "type": "string", - "required": true - }, - "entity": { - "anyOf": [ - { - "$ref": "_models/bank_accounts.json" - }, - { - "$ref": "_models/bank_account_verifications.json" - }, - { - "$ref": "_models/card_holds.json" - }, - { - "$ref": "_models/credits.json" - }, - { - "$ref": "_models/customers.json" - }, - { - "$ref": "_models/debits.josn" - }, - { - "$ref": "_models/refunds.json" - }, - { - "$ref": "_models/reversals.json" - } - ] - } - } -} \ No newline at end of file diff --git a/responses/callback.json b/responses/callback.json deleted file mode 100644 index 21e0870..0000000 --- a/responses/callback.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "_models/event.json" -} \ No newline at end of file diff --git a/responses/marketplaces.json b/responses/marketplaces.json deleted file mode 100644 index 011052c..0000000 --- a/responses/marketplaces.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "links": { - "type": "object", - "properties": { - "marketplaces.debits": { - "type": "string", - "format": "uri", - "pattern": "/debits", - "required": true - }, - "marketplaces.reversals": { - "type": "string", - "format": "uri", - "pattern": "/reversals", - "required": true - }, - "marketplaces.customers": { - "type": "string", - "format": "uri", - "pattern": "/customers", - "required": true - }, - "marketplaces.credits": { - "type": "string", - "format": "uri", - "pattern": "/credits", - "required": true - }, - "marketplaces.cards": { - "type": "string", - "format": "uri", - "pattern": "/cards", - "required": true - }, - "marketplaces.card_holds": { - "type": "string", - "format": "uri", - "pattern": "/card_holds", - "required": true - }, - "marketplaces.refunds": { - "type": "string", - "format": "uri", - "pattern": "/refunds", - "required": true - }, - "marketplaces.transactions": { - "type": "string", - "format": "uri", - "pattern": "/transactions", - "required": true - }, - "marketplaces.bank_accounts": { - "type": "string", - "format": "uri", - "pattern": "/bank_accounts", - "required": true - }, - "marketplaces.callbacks": { - "type": "string", - "format": "uri", - "pattern": "/callbacks", - "required": true - }, - "marketplaces.events": { - "type": "string", - "format": "uri", - "pattern": "/events", - "required": true - }, - "marketplaces.owner_customer": { - "type": "string", - "format": "uri", - "pattern": "/customers/{marketplaces.owner_customer}", - "required": true - } - }, - "required": false - }, - "meta": { - "type": "object", - "required": false - }, - "marketplaces": { - "items": { - "$ref": "_models/marketplace.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "required": true - } - } -} \ No newline at end of file diff --git a/responses/response.json b/responses/response.json deleted file mode 100644 index 0ed9c8c..0000000 --- a/responses/response.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "links": { - "type": "object", - "required": false - }, - "meta": { - "type": "object", - "required": false - }, - "bank_accounts": { - "items": { - "$ref": "_models/bank_accounts.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "bank_account_verifications": { - "items": { - "$ref": "_models/bank_account_verifications.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "card_holds": { - "items": { - "$ref": "_models/card_holds.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "cards": { - "items": { - "$ref": "_models/cards.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "credits": { - "items": { - "$ref": "_models/credits.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "customers": { - "items": { - "$ref": "_models/customers.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "debits": { - "items": { - "$ref": "_models/debits.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "orders": { - "items": { - "$ref": "_models/orders.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "refunds": { - "items": { - "$ref": "_models/refunds.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "reversals": { - "items": { - "$ref": "_models/reversals.json" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true - } - }, - "additionalProperties": false -} \ No newline at end of file diff --git a/scenarios/.gitignore b/scenarios/.gitignore deleted file mode 100644 index a6c57f5..0000000 --- a/scenarios/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.json diff --git a/scenarios/README.md b/scenarios/README.md deleted file mode 100644 index b98f39d..0000000 --- a/scenarios/README.md +++ /dev/null @@ -1,55 +0,0 @@ -## Balanced-api scenarios directory - -This directory contains all the scenarios that defined how the api is suppose to interact. - -### Simple example scenario -``` yaml -scenarios: - - name: customer # Every scenario has a name that can be used to reference it in other scenarios - request: - method: POST - href: /customers - schema: # The request is validated against the request json schema - "$ref": "../requests/customer.json" - body: { # The contents of the the request - "name": "Balanced testing" - } - response: - status_code: 201 # status_code validated the returned status code - schema: # response schema to validate response against - "$ref": "../responses/customers.json" -``` - - -### More complex scenario -``` yaml -require: # scenarios can require the result from other files - - ../card_fixtures.yml - - ../customer_fixtures.yml -scenarios: - - name: add_card_to_customer - request: - method: PATCH - href: "{card,cards.href}" # the results of the other scenarios can be referenced using {scenario_name,resource_name.field_on_resource} - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/cards/0/links/customer", - "value": "{customer,customers.id}" # referencing other scenarios can also be used in request bodies - }] - response: - schema: - "$ref": "responses/cards.json" - matches: { "cards": [ { "links": { "customer": "{customer,customers.id}" } } ] } # matches assert that a field in the response is equal -``` - - -## Additional notes about scenarios - * required scenarios only run once at the beginning of the file - * scenarios in a file are run sequentially, the file will stop on the first scenario to fail - * when referencing other scenarios by name, the most recent scenario with that name is references (you can overwrite the result of a scenario) - * all required files are isolated from each other (eg, if both files require a customer scenario, then two different customers will be created. Additionally "{customer,customers.href}" will reference the last customer scenario) - * matches in the response will assert that the field in the response and the value given are equal - * assertRegex in the response will assert that the field in the response matches the regular expression - * all paths are relative or from the root of this repository diff --git a/scenarios/bank_account_fixtures.yml b/scenarios/bank_account_fixtures.yml deleted file mode 100644 index c6446e0..0000000 --- a/scenarios/bank_account_fixtures.yml +++ /dev/null @@ -1,192 +0,0 @@ -require: - - ./customer_fixtures.yml -scenarios: - - name: bank_account - request: - method: POST - href: /bank_accounts - schema: - "$ref": "requests/bank_account.json" - body: { - "name": "Kareem Abdul-Jabbar", - "account_number": "9900000000", - "routing_number": "021000021", - "account_type": "checking" - } - response: - status_code: 201 - schema: - "$ref": "responses/bank_accounts.json" - - - name: verified_bank_account - request: - method: POST - href: /bank_accounts - body: { - "name": "Wit Chamberlain", - "account_number": "9900000001", - "routing_number": "321174851", - "account_type": "checking" - } - response: - status_code: 201 - schema: - "$ref": "responses/bank_accounts.json" - - - name: associate_verified_bank_account_with_customer - request: - method: PUT - href: "{verified_bank_account,bank_accounts.href}" - body: { - "links": { - "customer": "{customer,customers.id}" - } - } - response: - schema: - "$ref": "responses/bank_accounts.json" - - - name: verified_bank_account_create_verification - request: - method: POST - href: "{verified_bank_account,bank_accounts.bank_account_verifications}" - response: - status_code: 201 - - - name: verified_bank_account_verify_verification - request: - method: PUT - href: "{verified_bank_account_create_verification,bank_account_verifications.href}" - body: { - "amount_1": 1, - "amount_2": 1 - } - response: - status_code: 200 - matches: { "bank_account_verifications": [ { "verification_status": "succeeded" } ] } - - - name: bank_account_successful - request: - method: POST - href: /bank_accounts - body: { - "name": "Michael Jordan", - "account_number": "9900000002", - "routing_number": "021000021", - "account_type": "checking" - } - response: - status_code: 201 - - - name: verified_bank_account_successful - request: - method: POST - href: /bank_accounts - body: { - "name": "Larry Bird", - "account_number": "9900000003", - "routing_number": "321174851", - "account_type": "checking" - } - response: - status_code: 201 - - - name: associate_verified_bank_account_successful_with_customer - request: - method: PATCH - href: "{verified_bank_account_successful,bank_accounts.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/bank_accounts/0/links/customer", - "value": "{customer,customers.id}" - }] - response: - status_code: 200 - schema: - "$ref": "responses/bank_accounts.json" - matches: {"bank_accounts": [{ - "links": { - "customer": "{customer,customers.id}" - } - }]} - - - name: verified_bank_account_successful_create_verification - request: - method: POST - href: "{verified_bank_account_successful,bank_accounts.bank_account_verifications}" - response: - status_code: 201 - - - name: verified_bank_account_successful_verify_verification - request: - method: PUT - href: "{verified_bank_account_successful_create_verification,bank_account_verifications.href}" - body: { - "amount_1": 1, - "amount_2": 1 - } - response: - status_code: 200 - matches: { "bank_account_verifications": [ { "verification_status": "succeeded" } ] } - - - name: bank_account_failed - request: - method: POST - href: /bank_accounts - body: { - "name": "Karl Malone", - "account_number": "9900000004", - "routing_number": "021000021", - "account_type": "checking" - } - response: - status_code: 201 - - - name: verified_bank_account_failed - request: - method: POST - href: /bank_accounts - body: { - "name": "Karl Malone", - "account_number": "9900000004", - "routing_number": "021000021", - "account_type": "checking" - } - response: - status_code: 201 - - - name: associate_verified_bank_account_failed_with_customer - request: - method: PATCH - href: "{verified_bank_account_failed,bank_accounts.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/bank_accounts/0/links/customer", - "value": "{customer,customers.id}" - }] - response: - schema: - "$ref": "responses/bank_accounts.json" - - - name: verified_bank_account_failed_create_verification - request: - method: POST - href: "{verified_bank_account_failed,bank_accounts.bank_account_verifications}" - response: - status_code: 201 - - - name: verified_bank_account_failed_verify_verification - request: - method: PUT - href: "{verified_bank_account_failed_create_verification,bank_account_verifications.href}" - body: { - "amount_1": 1, - "amount_2": 1 - } - response: - status_code: 200 - matches: { "bank_account_verifications": [ { "verification_status": "succeeded" } ] } diff --git a/scenarios/bank_accounts/add_to_customer.yml b/scenarios/bank_accounts/add_to_customer.yml deleted file mode 100644 index 3de3970..0000000 --- a/scenarios/bank_accounts/add_to_customer.yml +++ /dev/null @@ -1,36 +0,0 @@ -require: - - ../customer_fixtures.yml - - ../bank_account_fixtures.yml -scenarios: - - - name: add_bank_account_to_customer_1 - request: - method: PATCH - href: "{bank_account,bank_accounts.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/bank_accounts/0/links/customer", - "value": "{customer,customers.id}" - }] - response: - schema: - "$ref": "responses/bank_accounts.json" - matches: { "bank_accounts": [ { "links": { "customer": "{customer,customers.id}" } } ] } - - - name: add_bank_account_to_customer - request: - method: PATCH - href: "{verified_bank_account_successful,bank_accounts.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/bank_accounts/0/links/customer", - "value": "{customer,customers.id}" - }] - response: - schema: - "$ref": "responses/bank_accounts.json" - matches: { "bank_accounts": [ { "links": { "customer": "{customer,customers.id}" } } ] } \ No newline at end of file diff --git a/scenarios/bank_accounts/bank_account_tokenization.yml b/scenarios/bank_accounts/bank_account_tokenization.yml deleted file mode 100644 index c1eea5e..0000000 --- a/scenarios/bank_accounts/bank_account_tokenization.yml +++ /dev/null @@ -1,69 +0,0 @@ -require: - - ../bank_account_fixtures.yml -scenarios: - - name: retrieve_bank_account - request: - method: GET - href: "{bank_account,bank_accounts.href}" - response: - schema: - "$ref": "responses/bank_accounts.json" - status_code: 200 - matches: {"bank_accounts": [{ - "name": "Kareem Abdul-Jabbar", - "account_number": "xxxxxx0000", - "routing_number": "021000021", - "account_type": "checking", - "bank_name": "JPMORGAN CHASE BANK", - "meta": {} - }]} - - - name: savings_account - request: - method: POST - href: /bank_accounts - schema: - "$ref": "requests/bank_account.json" - body: { - "name": "Jack Lalanne", - "account_number": "200938202", - "routing_number": "121042882", - "account_type": "savings" - } - response: - schema: - "$ref": "responses/bank_accounts.json" - status_code: 201 - matches: {"bank_accounts": [{"account_type": "savings"}]} - - - name: bank_account_address - request: - method: POST - href: /bank_accounts - schema: - "$ref": "requests/bank_account.json" - body: { - "name": "Mahmoud Abdelkader", - "account_number": "200938202", - "routing_number": "121042882", - "account_type": "checking", - "address": { - "country_code": "US", - } - } - response: - schema: - "$ref": "responses/bank_accounts.json" - status_code: 201 - matches: {"bank_accounts": [{ - "address": { - "country_code": "US" - } - }]} - - - name: unstore_bank_account - request: - method: DELETE - href: "{bank_account,bank_accounts.href}" - response: - status_code: 204 diff --git a/scenarios/bank_accounts/bank_name.yml b/scenarios/bank_accounts/bank_name.yml deleted file mode 100644 index b3cb9ae..0000000 --- a/scenarios/bank_accounts/bank_name.yml +++ /dev/null @@ -1,54 +0,0 @@ -scenarios: - - name: bank_name_wells_fargo - request: - method: POST - href: /bank_accounts - schema: - "$ref": "requests/bank_account.json" - body: { - "name": "Jack Lalanne", - "account_number": "200938202", - "routing_number": "121042882", - "account_type": "checking" - } - response: - schema: - "$ref": "responses/bank_accounts.json" - status_code: 201 - matches: {"bank_accounts": [{"bank_name": "WELLS FARGO BANK NA"}]} - - - name: bank_name_boa - request: - method: POST - href: /bank_accounts - schema: - "$ref": "requests/bank_account.json" - body: { - "name": "Michael Johnson", - "account_number": "982379283", - "routing_number": "121000358", - "account_type": "checking" - } - response: - schema: - "$ref": "responses/bank_accounts.json" - status_code: 201 - matches: {"bank_accounts": [{"bank_name": "BANK OF AMERICA, N.A."}]} - - - name: bank_name_jpmc - request: - method: POST - href: /bank_accounts - schema: - "$ref": "requests/bank_account.json" - body: { - "name": "Maurice Green", - "account_number": "33727930", - "routing_number": "322271627", - "account_type": "checking" - } - response: - schema: - "$ref": "responses/bank_accounts.json" - status_code: 201 - matches: {"bank_accounts": [{"bank_name": "J.P. MORGAN CHASE BANK, N.A."}]} diff --git a/scenarios/bank_accounts/tokenize_without_secret.yml b/scenarios/bank_accounts/tokenize_without_secret.yml deleted file mode 100644 index 986d556..0000000 --- a/scenarios/bank_accounts/tokenize_without_secret.yml +++ /dev/null @@ -1,28 +0,0 @@ -scenarios: - - name: tokenize - options: - - no-secret-key - request: - method: POST - href: /bank_accounts - schema: - "$ref": "requests/bank_account.json" - body: { - "name": "That guy over there", - "routing_number": "321174851", - "account_number": "9900000001", - "account_type": "checking" - } - response: - status_code: 201 - schema: - "$ref": "responses/bank_account_tokens.json" - - - name: associated_bank_account_to_marketplace - request: - method: GET - href: "{tokenize,bank_accounts.href}" - response: - status_code: 200 - schema: - "$ref": "responses/bank_accounts.json" \ No newline at end of file diff --git a/scenarios/bank_accounts/verification_failed.yml b/scenarios/bank_accounts/verification_failed.yml deleted file mode 100644 index 3ba5b16..0000000 --- a/scenarios/bank_accounts/verification_failed.yml +++ /dev/null @@ -1,88 +0,0 @@ -require: - - ../bank_account_fixtures.yml -scenarios: - - name: associate_bank_account_with_customer - request: - method: PATCH - href: "{bank_account,bank_accounts.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/bank_accounts/0/links/customer", - "value": "{customer,customers.id}" - }] - response: - schema: - "$ref": "responses/bank_accounts.json" - - - name: create_verification - request: - method: POST - href: "{bank_account,bank_accounts.bank_account_verifications}" - schema: - "$ref": "requests/bank_accounts/bank_account_verification.json" - response: - status_code: 201 - schema: - "$ref": "responses/bank_account_verifications.json" - matches: { "bank_account_verifications": [ { "attempts_remaining": 3, "verification_status": "pending" } ] } - - - name: submit_verification1 - request: - method: PUT - href: "{create_verification,bank_account_verifications.href}" - schema: - "$ref": "requests/bank_accounts/bank_account_verification.json" - body: { - "amount_1": 10, - "amount_2": 15 - } - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": "Authentication amounts do not match" } - - - name: submit_verification2 - request: - method: PUT - href: "{create_verification,bank_account_verifications.href}" - schema: - "$ref": "requests/bank_accounts/bank_account_verification.json" - body: { - "amount_1": 33, - "amount_2": 42 - } - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": "Authentication amounts do not match" } - - - name: submit_verification3 - request: - method: PUT - href: "{create_verification,bank_account_verifications.href}" - schema: - "$ref": "requests/bank_accounts/bank_account_verification.json" - body: { - "amount_1": 99, - "amount_2": 21 - } - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": "Authentication amounts do not match" } - - - - name: check_verification - request: - method: GET - href: "{create_verification,bank_account_verifications.href}" - response: - status_code: 200 - schema: - "$ref": "responses/bank_account_verifications.json" - matches: { "bank_account_verifications": [ { "attempts_remaining": 0, "verification_status": "failed" } ] } diff --git a/scenarios/bank_accounts/verification_success.yml b/scenarios/bank_accounts/verification_success.yml deleted file mode 100644 index f07c639..0000000 --- a/scenarios/bank_accounts/verification_success.yml +++ /dev/null @@ -1,46 +0,0 @@ -require: - - ../bank_account_fixtures.yml -scenarios: - - - name: associate_bank_account_to_verify_with_customer - request: - method: PATCH - href: "{bank_account,bank_accounts.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/bank_accounts/0/links/customer", - "value": "{customer,customers.id}" - }] - response: - schema: - "$ref": "responses/bank_accounts.json" - - - name: create_verification - request: - method: POST - href: "{bank_account,bank_accounts.bank_account_verifications}" - schema: - "$ref": "requests/bank_accounts/bank_account_verification.json" - response: - status_code: 201 - schema: - "$ref": "responses/bank_account_verifications.json" - matches: { "bank_account_verifications": [ { "attempts_remaining": 3, "verification_status": "pending" } ] } - - - name: submit_verification - request: - method: PUT - href: "{create_verification,bank_account_verifications.href}" - schema: - "$ref": "requests/bank_accounts/bank_account_verification.json" - body: { - "amount_1": 1, - "amount_2": 1 - } - response: - status_code: 200 - schema: - "$ref": "responses/bank_account_verifications.json" - matches: { "bank_account_verifications": [ { "verification_status": "succeeded" } ] } diff --git a/scenarios/card_fixtures.yml b/scenarios/card_fixtures.yml deleted file mode 100644 index 002b936..0000000 --- a/scenarios/card_fixtures.yml +++ /dev/null @@ -1,68 +0,0 @@ -require: - - ./customer_fixtures.yml -scenarios: - - name: card - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111 1111 1111 1111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "123", - "address": { - "line1": "965 Mission St", - "postal_code": "94103" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/cards.json" - - - name: customer_card - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111 1111 1111 1111", - "expiration_month": 12, - "expiration_year": 2016, - } - response: - status_code: 201 - schema: - "$ref": "responses/cards.json" - - - name: associate_customer_card_with_customer - request: - method: PUT - href: "{customer,customers.href}" - schema: - "$ref": "requests/customer.json" - body: { - "card_uri": "{customer_card,cards.href}" - } - response: - schema: - "$ref": "responses/customers.json" - - - name: card_processor_failure - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4444444444444448", - "expiration_month": 12, - "expiration_year": 2018 - } - response: - status_code: 201 - schema: - "$ref": "responses/cards.json" diff --git a/scenarios/cards/add_to_customer.yml b/scenarios/cards/add_to_customer.yml deleted file mode 100644 index 5dd3b9a..0000000 --- a/scenarios/cards/add_to_customer.yml +++ /dev/null @@ -1,19 +0,0 @@ -require: - - ../card_fixtures.yml - - ../customer_fixtures.yml -scenarios: - - name: add_card_to_customer - request: - method: PATCH - href: "{card,cards.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/cards/0/links/customer", - "value": "{customer,customers.id}" - }] - response: - schema: - "$ref": "responses/cards.json" - matches: { "cards": [ { "links": { "customer": "{customer,customers.id}" } } ] } diff --git a/scenarios/cards/avs_postal_match.yml b/scenarios/cards/avs_postal_match.yml deleted file mode 100644 index 390419f..0000000 --- a/scenarios/cards/avs_postal_match.yml +++ /dev/null @@ -1,77 +0,0 @@ -scenarios: - - name: avs_postal_match_yes - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "address": { - "postal_code": "94301" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"avs_postal_match": "yes"}]} - - - name: avs_postal_match_no - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "address": { - "postal_code": "90210" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"avs_postal_match": "no"}]} - - - name: avs_postal_match_unsupported - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "address": { - "postal_code": "90211" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"avs_postal_match": "unsupported"}]} - - - name: avs_postal_match_unused - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"avs_postal_match": null}]} diff --git a/scenarios/cards/avs_street_match.yml b/scenarios/cards/avs_street_match.yml deleted file mode 100644 index 6b81379..0000000 --- a/scenarios/cards/avs_street_match.yml +++ /dev/null @@ -1,80 +0,0 @@ -scenarios: - - name: avs_street_match_yes - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "address": { - "line1": "965 Mission St", - "postal_code": "94103" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"avs_street_match": "yes"}]} - - - name: avs_street_match_no - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "address": { - "line1": "21 Jump St", - "postal_code": "90210" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"avs_street_match": "no"}]} - - - name: avs_street_match_unsupported - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "address": { - "line1": "1984 Elm St", - "postal_code": "90210" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"avs_street_match": "unsupported"}]} - - - name: avs_street_match_null - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"avs_street_match": null}]} diff --git a/scenarios/cards/card_brand.yml b/scenarios/cards/card_brand.yml deleted file mode 100644 index e80a36f..0000000 --- a/scenarios/cards/card_brand.yml +++ /dev/null @@ -1,68 +0,0 @@ -scenarios: - - name: card_brand_visa - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111 1111 1111 1111", - "expiration_month": 12, - "expiration_year": 2016, - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"brand": "Visa"}]} - - - name: card_brand_mc - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "5105 1051 0510 5100", - "expiration_month": 12, - "expiration_year": 2016, - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"brand": "MasterCard"}]} - - - name: card_brand_amex - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "3782 822463 10005", - "expiration_month": 12, - "expiration_year": 2016, - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"brand": "American Express"}]} - - - name: card_brand_discover - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "6011 1111 1111 1117", - "expiration_month": 12, - "expiration_year": 2016, - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"brand": "Discover"}]} \ No newline at end of file diff --git a/scenarios/cards/card_tokenization.yml b/scenarios/cards/card_tokenization.yml deleted file mode 100644 index 609f62e..0000000 --- a/scenarios/cards/card_tokenization.yml +++ /dev/null @@ -1,131 +0,0 @@ -require: - - ../card_fixtures.yml -scenarios: - - name: retrieve_card - request: - method: GET - href: "{card,cards.href}" - response: - schema: - "$ref": "responses/cards.json" - status_code: 200 - matches: {"cards": [{ - "name": null, - "number": "xxxxxxxxxxxx1111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": xxx, - "cvv_match": "yes", - "cvv_result": "Match", - "address": { - "line1": "965 Mission St", - "line2": null, - "city": null, - "state": null, - "postal_code": "94103", - "country_code": null - }, - "avs_street_match": "yes", - "avs_postal_match": "yes", - "avs_result": "Postal code matches, but street address not verified.", - "brand": "Visa", - "meta": {} - }]} - - - name: expiration_string - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111 1111 1111 1111", - "expiration_month": "12", - "expiration_year": "2016" - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{ - "expiration_month": 12, - "expiration_year": 2016 - }]} - - - name: cardholder_name - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "name": "Frida Kahlo", - "number": "4111 1111 1111 1111", - "expiration_month": 12, - "expiration_year": 2016 - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"name": "Frida Kahlo"}]} - - - name: cardholder_address - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111 1111 1111 1111", - "expiration_month": 12, - "expiration_year": 2016, - "address": { - "line1": "7 Bis Rue de l'Abbé de l'Épée", - "line2": "Apt 4", - "city": "Versailles", - "postal_code": "78000", - "country_code": "FR" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: { - "cards": [{ - "address": { - "line1": "7 Bis Rue de l'Abbé de l'Épée", - "line2": "Apt 4", - "city": "Versailles", - "state": null, - "postal_code": "78000", - "country_code": "FR" - } - }] - } - - - name: fails_luhn - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111 1111 1111 1112", - "expiration_month": 12, - "expiration_year": 2016 - } - response: - schema: - "$ref": "responses/errors.json" - status_code: 409 - matches: { "category_code": "card-not-validated" } - assertRegex: { "description": "^Card cannot be validated" } - - - name: unstore_card - request: - method: DELETE - href: "{card,cards.href}" - response: - status_code: 204 diff --git a/scenarios/cards/cvv_match.yml b/scenarios/cards/cvv_match.yml deleted file mode 100644 index 5e0f751..0000000 --- a/scenarios/cards/cvv_match.yml +++ /dev/null @@ -1,71 +0,0 @@ -scenarios: - - name: cvv_match_yes - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "123" - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"cvv_match": "yes"}]} - - - name: cvv_match_no - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "902" - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"cvv_match": "no"}]} - - - name: cvv_match_unsupported - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "901" - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"cvv_match": "unsupported"}]} - - - name: cvv_match_unused - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016 - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - matches: {"cards": [{"cvv_match": null}]} diff --git a/scenarios/cards/tokenize_without_secret.yml b/scenarios/cards/tokenize_without_secret.yml deleted file mode 100644 index 991d025..0000000 --- a/scenarios/cards/tokenize_without_secret.yml +++ /dev/null @@ -1,27 +0,0 @@ -scenarios: - - name: tokenize - options: - - no-secret-key - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111111111111111", - "expiration_month": "12", - "expiration_year": 2016 - } - response: - status_code: 201 - schema: - "$ref": "responses/card_tokens.json" - - - name: associated_card_to_marketplace - request: - method: GET - href: "{tokenize,cards.href}" - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" \ No newline at end of file diff --git a/scenarios/cards/update_meta.yml b/scenarios/cards/update_meta.yml deleted file mode 100644 index 0635fd2..0000000 --- a/scenarios/cards/update_meta.yml +++ /dev/null @@ -1,106 +0,0 @@ -require: - - ../card_fixtures.yml -scenarios: - - name: update_meta_add - request: - href: "{card,cards.href}" - method: PATCH - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "add", - "path": "/cards/0/meta/asdf", - "value": "the value to be added" - }] - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - matches: { "cards": [ { "meta": { "asdf": "the value to be added" } } ] } - - - name: update_meta_replace - request: - href: "{card,cards.href}" - method: PATCH - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/cards/0/meta/asdf", - "value": "new value" - }] - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - matches: { "cards": [ { "meta": { "asdf": "new value" } } ] } - - - name: update_meta_safe - request: - href: "{card,cards.href}" - method: PATCH - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "test", - "path": "/cards/0/meta/asdf", - "value": "new value" - },{ - "op": "replace", - "path": "/cards/0/meta/asdf", - "value": "after checking the value" - }] - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - matches: { "cards": [ { "meta": { "asdf": "after checking the value" } } ] } - - - name: update_meta_test_fail - request: - href: "{card,cards.href}" - method: PATCH - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "test", - "path": "/cards/0/meta/asdf", - "value": "not the right value" - }] - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - - - name: update_meta_move - request: - href: "{card,cards.href}" - method: PATCH - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "move", - "from": "/cards/0/meta/asdf", - "path": "/cards/0/meta/zxcv" - }] - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - matches: { "cards": [ { "meta": { "zxcv": "after checking the value" } } ] } - - - name: update_meta_remove - request: - href: "{card,cards.href}" - method: PATCH - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "remove", - "path": "/cards/0/meta/zxcv" - }] - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - matches: { "cards": [ { "meta": {} } ] } \ No newline at end of file diff --git a/scenarios/checkout_flow/cancel_an_order.yml b/scenarios/checkout_flow/cancel_an_order.yml deleted file mode 100644 index d4e6526..0000000 --- a/scenarios/checkout_flow/cancel_an_order.yml +++ /dev/null @@ -1,57 +0,0 @@ -require: - - new_buyer_purchases_an_item.yml -scenarios: - # The buyer or seller cancel the order prior to the seller being paid - - name: fetch_order - request: - method: GET - href: "{new_order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "description": "Catherine Malandrino Black Top" - }]} - - # Refund the debit, which leaves the order empty - - name: fetch_debits - request: - method: GET - href: "{fetch_order,orders.debits}" - response: - status_code: 200 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{ - "amount": 10000, - "description": "Catherine Malandrino Black Top" - }]} - - - name: refund_debit - request: - method: POST - href: "{fetch_debits,debits.refunds}" - schema: - "$ref": "requests/debits/refund.json" - response: - status_code: 201 - matches: {"refunds": [{ - "amount": 10000, - "description": "Catherine Malandrino Black Top" - }]} - - - name: confirm_order_is_canceled - request: - method: GET - href: "{new_order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "amount": 0, - "amount_escrowed": 0, - "currency": "USD", - "description": "Catherine Malandrino Black Top" - }]} diff --git a/scenarios/checkout_flow/existing_buyer_purchases_with_a_new_card.yml b/scenarios/checkout_flow/existing_buyer_purchases_with_a_new_card.yml deleted file mode 100644 index ec936e5..0000000 --- a/scenarios/checkout_flow/existing_buyer_purchases_with_a_new_card.yml +++ /dev/null @@ -1,171 +0,0 @@ -require: - - ../customer_fixtures.yml -scenarios: - - name: fetch_customer - request: - method: GET - href: "{customer_with_card,customers.href}" - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - - # Fetch default card for a buyer - - name: fetch_source - request: - method: GET - href: "{fetch_customer,customers.source}" - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - - # Fetch list of all cards if the buyer wants to use another card - - name: fetch_all_cards - request: - method: GET - href: "{fetch_customer,customers.cards}" - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - - # Tokenize a new card, check AVS, and add to customer as the new default - - name: tokenize_new_card - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "name": "Darius the Great", - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "123", - "address": { - "line1": "965 Mission St", - "line2": "Suite 425", - "city": "San Francisco", - "state": "CA", - "postal_code": "94103" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - - # Verify that address and CVV match - - name: verify_avs_match - request: - method: GET - href: "{tokenize_new_card,cards.href}" - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - matches: {"cards": [{ - "avs_street_match": "yes", - "avs_postal_match": "yes", - "cvv_match": "yes" - }]} - - - name: update_default_card - request: - method: PATCH - href: "{fetch_customer,customers.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/customers/0/links/source", - "value": "{tokenize_new_card,cards.href}" - }] - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - matches: {"customers": [{ - "links": { - "source": "{tokenize_new_card,cards.id}" - } - }]} - - # Create the new order and debit the buyer - - name: new_order - request: - method: POST - href: "{underwritten_merchant,customers.orders}" - schema: - "$ref": "requests/customers/order.json" - body: { - "description": "Catherine Malandrino Black Top", - "delivery_address": { - "line1": "965 Mission St", - "line2": "Suite 425", - "city": "San Francisco", - "state": "CA", - "postal_code": "94103" - }, - "meta": { - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "links":{ - "merchant": "{underwritten_merchant,customers.id}" - } - }]} - - - name: debit_buyer - request: - method: POST - href: "{customer_with_card,customers.debits}" - schema: - "$ref": "requests/customers/debit.json" - body: { - "amount": 10000, - "order": "{new_order,orders.href}", - "appears_on_statement_as": "Vaunte-Alice Ryan" - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{ - "description": "Catherine Malandrino Black Top", - "appears_on_statement_as": "BAL*Vaunte-Alice Ryan", - "status": "succeeded", - "links": { - "order": "{new_order,orders.id}" - } - }]} - - # Add the shipping information once the item has been shipped using PATCH - - name: update_meta_on_order - request: - method: PUT - href: "{new_order,orders.href}" - body: { - "meta": { - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", - "courier": "usps", - "tracking_number": "9405510899359008595488" - } - } - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "meta": { - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", - "courier": "usps", - "tracking_number": "9405510899359008595488" - } - }]} diff --git a/scenarios/checkout_flow/existing_buyer_purchases_with_an_existing_card.yml b/scenarios/checkout_flow/existing_buyer_purchases_with_an_existing_card.yml deleted file mode 100644 index f8a5476..0000000 --- a/scenarios/checkout_flow/existing_buyer_purchases_with_an_existing_card.yml +++ /dev/null @@ -1,99 +0,0 @@ -require: - - ../customer_fixtures.yml -scenarios: - - name: fetch_customer - request: - method: GET - href: "{customer_with_card,customers.href}" - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - - # Fetch default card for a buyer - - name: fetch_source - request: - method: GET - href: "{fetch_customer,customers.source}" - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - - # Create the new order and debit the buyer - - name: new_order - request: - method: POST - href: "{underwritten_merchant,customers.orders}" - schema: - "$ref": "requests/customers/order.json" - body: { - "description": "Catherine Malandrino Black Top", - "delivery_address": { - "line1": "965 Mission St", - "line2": "Suite 425", - "city": "San Francisco", - "state": "CA", - "postal_code": "94103" - }, - "meta": { - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "links":{ - "merchant": "{underwritten_merchant,customers.id}" - } - }]} - - - name: debit_buyer - request: - method: POST - href: "{customer_with_card,customers.debits}" - schema: - "$ref": "requests/customers/debit.json" - body: { - "amount": 10000, - "order": "{new_order,orders.href}", - "appears_on_statement_as": "Vaunte-Alice Ryan" - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{ - "description": "Catherine Malandrino Black Top", - "appears_on_statement_as": "BAL*Vaunte-Alice Ryan", - "status": "succeeded", - "links": { - "order": "{new_order,orders.id}" - } - }]} - - # Add the shipping information once the item has been shipped - - name: update_meta_on_order - request: - method: PUT - href: "{new_order,orders.href}" - body: { - "meta": { - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", - "courier": "usps", - "tracking_number": "9405510899359008595488" - } - } - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "meta": { - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42", - "courier": "usps", - "tracking_number": "9405510899359008595488" - } - }]} diff --git a/scenarios/checkout_flow/new_buyer_purchases_an_item.yml b/scenarios/checkout_flow/new_buyer_purchases_an_item.yml deleted file mode 100644 index 4e94bc2..0000000 --- a/scenarios/checkout_flow/new_buyer_purchases_an_item.yml +++ /dev/null @@ -1,192 +0,0 @@ -require: - - ../customer_fixtures.yml -scenarios: - - # Tokenize card using balanced.js or by POSTing directly from mobile app - - name: new_card_failing_avs - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "name": "Darius the Great", - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "123", - "address": { - "line1": "21 Jump St", - "city": "San Francisco", - "state": "CA", - "postal_code": "90210" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - - # Verify that address and CVV match - - name: verify_avs_match_fails - request: - method: GET - href: "{new_card_failing_avs,cards.href}" - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - matches: {"cards": [{ - "avs_street_match": "no", - "avs_postal_match": "no", - "cvv_match": "yes" - }]} - - # Tokenize another card using balanced.js or by POSTing directly from mobile app - - name: new_card - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "name": "Darius the Great", - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "123", - "address": { - "line1": "965 Mission St", - "line2": "Suite 425", - "city": "San Francisco", - "state": "CA", - "postal_code": "94103" - } - } - response: - schema: - "$ref": "responses/cards.json" - status_code: 201 - - # Verify that address and CVV match - - name: verify_avs_match - request: - method: GET - href: "{new_card,cards.href}" - response: - status_code: 200 - schema: - "$ref": "responses/cards.json" - matches: {"cards": [{ - "avs_street_match": "yes", - "avs_postal_match": "yes", - "cvv_match": "yes" - }]} - - # Create the new customer and add the new card as their default payment method - - name: new_buyer - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Darius the Great", - "email": "darius.great@gmail.com", - "source": "{new_card,cards.href}", - "meta": { - "ip_address": "174.240.15.249" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - matches: { - "customers": [{ - "links": { - "source": "{new_card,cards.id}", - "destination": null, - } - }] - } - - # Create the new order and debit the buyer - - name: new_order - request: - method: POST - href: "{underwritten_merchant,customers.orders}" - schema: - "$ref": "requests/customers/order.json" - body: { - "description": "Catherine Malandrino Black Top", - "delivery_address": { - "line1": "965 Mission St", - "line2": "Suite 425", - "city": "San Francisco", - "state": "CA", - "postal_code": "94103" - }, - "meta": { - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "links":{ - "merchant": "{underwritten_merchant,customers.id}" - } - }]} - - - name: debit_buyer - request: - method: POST - href: "{new_buyer,customers.debits}" - schema: - "$ref": "requests/customers/debit.json" - body: { - "amount": 10000, - "order": "{new_order,orders.href}", - "appears_on_statement_as": "Vaunte-Alice Ryan" - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{ - "appears_on_statement_as": "BAL*Vaunte-Alice Ryan", - "description": "Catherine Malandrino Black Top", - "status": "succeeded", - "links": { - "order": "{new_order,orders.id}" - } - }]} - - # Add the shipping information once the item has been shipped - - name: update_meta_on_order - request: - method: PUT - href: "{new_order,orders.href}" - body: { - "meta": { - "courier": "usps", - "tracking_number": "9405510899359008595488", - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42" - } - } - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { - "orders": [{ - "meta": { - "courier": "usps", - "tracking_number": "9405510899359008595488", - "listing": "https://www.vaunte.com/items/catherine-malandrino-black-top-42" - } - }] - } diff --git a/scenarios/credits/create_new_bank_account.yml b/scenarios/credits/create_new_bank_account.yml deleted file mode 100644 index 833a397..0000000 --- a/scenarios/credits/create_new_bank_account.yml +++ /dev/null @@ -1,22 +0,0 @@ -require: - - ../debits/create_new_card.yml -scenarios: - - name: create_root_credit - request: - method: POST - href: /credits - schema: - "$ref": "requests/credit.json" - body: { - "amount": 1234, - "destination": { - "name": "Kareem Abdul-Jabbar", - "account_number": "9900000000", - "routing_number": "021000021", - "account_type": "checking" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" diff --git a/scenarios/credits/credit_bank_account.yml b/scenarios/credits/credit_bank_account.yml deleted file mode 100644 index aa6bb8e..0000000 --- a/scenarios/credits/credit_bank_account.yml +++ /dev/null @@ -1,89 +0,0 @@ -require: - - ../bank_account_fixtures.yml -scenarios: - - name: load_balance_80000 - request: - method: POST - href: "{verified_bank_account_successful,bank_accounts.debits}" - body: {"amount": 80000} - response: - status_code: 201 - - - name: credit_new_bank_account - request: - method: POST - href: /credits - schema: - "$ref": "requests/credit.json" - body: { - "amount": 20000, - "appears_on_statement_as": "Rent My Bike", - "destination": { - "name": "Kareem Abdul-Jabbar", - "account_number": "9900000000", - "routing_number": "021000021", - "account_type": "checking" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - matches: {"credits": [{ - "amount": 20000, - "appears_on_statement_as": "Rent My Bike", - "status": "pending" - }]} - - - name: credit_existing_bank_account - request: - method: POST - href: "{bank_account,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: {"amount": 20000} - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - matches: {"credits": [{"status": "pending"}]} - - - name: credit_bank_account_successful - request: - method: POST - href: "{bank_account_successful,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: {"amount": 20000} - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - matches: {"credits": [{"status": "succeeded"}]} - - - name: credit_bank_account_failed - request: - method: POST - href: "{bank_account_failed,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: {"amount": 20000} - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - matches: {"credits": [{"status": "failed"}]} - - - name: credit_bank_account_insufficient_escrow - request: - method: POST - href: "{bank_account,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: {"amount": 99999999 } - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": ".*s insufficient to cover transfer of 99999999.*" } - matches: { "category_code": "account-insufficient-funds" } diff --git a/scenarios/credits/credit_card_fail.yml b/scenarios/credits/credit_card_fail.yml deleted file mode 100644 index b0b0b12..0000000 --- a/scenarios/credits/credit_card_fail.yml +++ /dev/null @@ -1,28 +0,0 @@ -require: - - ../card_fixtures.yml -scenarios: - - name: delete_card - request: - method: DELETE - href: "{card,cards.href}" - response: - status_code: 204 - - - name: credit_invalid_card - request: - method: POST - # Do not construct uri with id like this - # doing this can lead to unexpected cases - # there is no credits end point on a card - - href: "/cards/{card,cards.id}/credits" - body: { - "amount": 1234 - } - response: - status_code: 404 - schema: - "$ref": "responses/errors.json" - matches: { - "category_code": "not-found" - } diff --git a/scenarios/credits/credit_customer.yml b/scenarios/credits/credit_customer.yml deleted file mode 100644 index 5a71f20..0000000 --- a/scenarios/credits/credit_customer.yml +++ /dev/null @@ -1,16 +0,0 @@ -require: - - ../bank_accounts/add_to_customer.yml -scenarios: - - name: credit_customer - request: - method: POST - href: "{customer,customers.credits}" - schema: - "$ref": "requests/customers/credit.json" - body: { - "amount": 500 - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" diff --git a/scenarios/credits/credit_customer_fail.yml b/scenarios/credits/credit_customer_fail.yml deleted file mode 100644 index 7d060e3..0000000 --- a/scenarios/credits/credit_customer_fail.yml +++ /dev/null @@ -1,17 +0,0 @@ -require: - - ../customer_fixtures.yml -scenarios: - - name: fail_credit_customer - request: - method: POST - href: "{customer,customers.credits}" - schema: - "$ref": "requests/customers/credit.json" - body: { - "amount": 1234 - } - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - matches: { "category_code": "no-funding-destination" } diff --git a/scenarios/customer_fixtures.yml b/scenarios/customer_fixtures.yml deleted file mode 100644 index 7700c4c..0000000 --- a/scenarios/customer_fixtures.yml +++ /dev/null @@ -1,92 +0,0 @@ -scenarios: - - name: customer - request: - method: POST - href: /customers - schema: - "$ref": "../requests/customer.json" - body: { - "name": "Balanced testing" - } - response: - status_code: 201 - schema: - "$ref": "../responses/customers.json" - - - name: underwritten_merchant - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Henry Ford", - "dob_month": 7, - "dob_year": 1963, - "address": { - "postal_code": "48120" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "merchant_status": "underwritten" } ] } - - - name: customer_with_card - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Darius the Great", - "email": "darius.great@gmail.com", - "source": { - "name": "Darius the Great", - "number": "4111111111111111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "123", - "address": { - "line1": "965 Mission St", - "line2": "Suite 425", - "city": "San Francisco", - "state": "CA", - "postal_code": "94103" - } - }, - "meta": { - "ip_address": "174.240.15.249" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - - - name: merchant_with_bank_account - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Henry Ford", - "dob_month": 7, - "dob_year": 1963, - "address": { - "postal_code": "48120" - }, - "destination": { - "name": "Kareem Abdul-Jabbar", - "account_number": "9900000000", - "routing_number": "021000021", - "account_type": "checking" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "merchant_status": "underwritten" } ] } \ No newline at end of file diff --git a/scenarios/customers/create.yml b/scenarios/customers/create.yml deleted file mode 100644 index 3dc748e..0000000 --- a/scenarios/customers/create.yml +++ /dev/null @@ -1,15 +0,0 @@ -scenarios: - - name: customer_create - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Balanced Testing", - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "name": "Balanced Testing" } ] } diff --git a/scenarios/customers/set_default_destination.yml b/scenarios/customers/set_default_destination.yml deleted file mode 100644 index aa05c0d..0000000 --- a/scenarios/customers/set_default_destination.yml +++ /dev/null @@ -1,17 +0,0 @@ -require: - - ../bank_accounts/add_to_customer.yml -scenarios: - - name: set_default_source - request: - method: PATCH - href: "{customer,customers.href}" - body: [{ - "op": "replace", - "path": "/customers/0/links/destination", - "value": "{bank_account,bank_accounts.id}" - }] - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "links": { "destination": "{bank_account,bank_accounts.id}" } } ] } \ No newline at end of file diff --git a/scenarios/customers/set_default_source.yml b/scenarios/customers/set_default_source.yml deleted file mode 100644 index 057591d..0000000 --- a/scenarios/customers/set_default_source.yml +++ /dev/null @@ -1,54 +0,0 @@ -require: - - ../cards/add_to_customer.yml -scenarios: - - - name: set_default_source - request: - method: PATCH - href: "{customer,customers.href}" - body: [{ - "op": "replace", - "path": "/customers/0/links/source", - "value": "{card,cards.id}" - }] - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "links": { "source": "{card,cards.id}" } } ] } - - - name: other_card - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111 1111 1111 1111", - "expiration_month": 12, - "expiration_year": 2016, - "cvv": "123", - "address": { - "line1": "965 Mission St", - "postal_code": "94103" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/cards.json" - - - name: set_default_source_using_PUT - request: - method: PUT - href: "{customer,customers.href}" - body: { - "links": { - "source": "{other_card,cards.id}", - } - } - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "links": { "source": "{other_card,cards.id}" } } ] } diff --git a/scenarios/customers/underwrite.yml b/scenarios/customers/underwrite.yml deleted file mode 100644 index 2dde15f..0000000 --- a/scenarios/customers/underwrite.yml +++ /dev/null @@ -1,54 +0,0 @@ -scenarios: - - name: customer_create - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Henry Ford" - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "merchant_status": "need-more-information" } ] } - - - name: add_information - request: - method: PUT - href: "{customer_create,customers.href}" - schema: - "$ref": "requests/customer.json" - body: { - "dob_month": 7, - "dob_year": 1963, - "address": { - "postal_code": "48120" - } - } - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "merchant_status": "underwritten" } ] } - - - name: create_underwritten - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Henry Ford", - "dob_month": 7, - "dob_year": 1963, - "address": { - "postal_code": "48120" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "merchant_status": "underwritten" } ] } diff --git a/scenarios/customers/underwrite_with_destination.yml b/scenarios/customers/underwrite_with_destination.yml deleted file mode 100644 index 1556595..0000000 --- a/scenarios/customers/underwrite_with_destination.yml +++ /dev/null @@ -1,77 +0,0 @@ -require: - - ../bank_account_fixtures.yml -scenarios: - - name: create_underwritten - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Henry Ford", - "dob_month": 7, - "dob_year": 1963, - "address": { - "postal_code": "48120" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "merchant_status": "underwritten" } ] } - - - name: add_bank_account - request: - method: PATCH - href: "{bank_account,bank_accounts.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/bank_accounts/0/links/customer", - "value": "{create_underwritten,customers.id}" - }] - response: - status_code: 200 - schema: - "$ref": "responses/bank_accounts.json" - matches: { "bank_accounts": [ { "links": { "customer": "{create_underwritten,customers.id}" } } ] } - - - - name: create_underwritten_successful - request: - method: POST - href: /customers - schema: - "$ref": "requests/customer.json" - body: { - "name": "Henry Ford-successful", - "dob_month": 7, - "dob_year": 1963, - "address": { - "postal_code": "48120" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "merchant_status": "underwritten" } ] } - - - name: add_bank_account_successful - request: - method: PATCH - href: "{bank_account_successful,bank_accounts.href}" - schema: - "$ref": "requests/_patch.json" - body: [{ - "op": "replace", - "path": "/bank_accounts/0/links/customer", - "value": "{create_underwritten_successful,customers.id}" - }] - response: - status_code: 200 - schema: - "$ref": "responses/bank_accounts.json" - matches: { "bank_accounts": [ { "links": { "customer": "{create_underwritten_successful,customers.id}" } } ] } diff --git a/scenarios/customers/update.yml b/scenarios/customers/update.yml deleted file mode 100644 index 8c45af8..0000000 --- a/scenarios/customers/update.yml +++ /dev/null @@ -1,17 +0,0 @@ -require: - - create.yml -scenarios: - - name: update_customer - request: - method: PUT - href: "{customer_create,customers.href}" - schema: - "$ref": "requests/customer.json" - body: { - "email": "asdf@balancedpayments.com" - } - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "email": "asdf@balancedpayments.com" } ] } \ No newline at end of file diff --git a/scenarios/debits/create_new_card.yml b/scenarios/debits/create_new_card.yml deleted file mode 100644 index 143f523..0000000 --- a/scenarios/debits/create_new_card.yml +++ /dev/null @@ -1,19 +0,0 @@ -scenarios: - - name: create_root_debit - request: - method: POST - href: /debits - schema: - "$ref": "requests/debit.json" - body: { - "amount": 1234, - "source": { - "number": "4111111111111111", - "expiration_year": "2018", - "expiration_month": 12 - } - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" diff --git a/scenarios/debits/debit_bank_account.yml b/scenarios/debits/debit_bank_account.yml deleted file mode 100644 index 24d4283..0000000 --- a/scenarios/debits/debit_bank_account.yml +++ /dev/null @@ -1,66 +0,0 @@ -# TODO: -# - attempt to debit new bank account. will fail -# - attempt to debit unverified bank account. will fail - -require: - - ../bank_account_fixtures.yml -scenarios: - - name: debit_verified_bank_account - request: - method: POST - href: "{verified_bank_account,bank_accounts.debits}" - schema: - "$ref": "requests/bank_accounts/debit.json" - body: { - "amount": 20000 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{"status": "pending"}]} - - - name: debit_verified_bank_account_successful - request: - method: POST - href: "{verified_bank_account_successful,bank_accounts.debits}" - schema: - "$ref": "requests/bank_accounts/debit.json" - body: { - "amount": 20000 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{"status": "succeeded"}]} - - - name: debit_verified_bank_account_failed - request: - method: POST - href: "{verified_bank_account_failed,bank_accounts.debits}" - schema: - "$ref": "requests/bank_accounts/debit.json" - body: { - "amount": 20000 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{"status": "failed"}]} - - - name: debit_unverified_bank_account - request: - method: POST - href: "{bank_account,bank_accounts.debits}" - schema: - "$ref": "requests/bank_accounts/debit.json" - body: { - "amount": 123 - } - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": "^Funding instrument cannot be debited" } diff --git a/scenarios/debits/debit_card.yml b/scenarios/debits/debit_card.yml deleted file mode 100644 index c9d14c9..0000000 --- a/scenarios/debits/debit_card.yml +++ /dev/null @@ -1,28 +0,0 @@ -require: - - ../card_fixtures.yml -scenarios: - - name: debit_card - request: - method: POST - href: "{card,cards.debits}" - schema: - "$ref": "requests/cards/debit.json" - body: {"amount": 20000} - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{"status": "succeeded"}]} - - - name: debit_customer_card - request: - method: POST - href: "{customer_card,cards.debits}" - schema: - "$ref": "requests/cards/debit.json" - body: {"amount": 20000} - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{"status": "succeeded"}]} diff --git a/scenarios/debits/debit_customer.yml b/scenarios/debits/debit_customer.yml deleted file mode 100644 index fd58e8b..0000000 --- a/scenarios/debits/debit_customer.yml +++ /dev/null @@ -1,17 +0,0 @@ -require: - - ../cards/add_to_customer.yml -scenarios: - - name: debit_customer - request: - method: POST - href: "{customer,customers.debits}" - schema: - "$ref": "requests/customers/debit.json" - body: { - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: { "debits": [ { "links": { "customer": "{customer,customers.id}", "source": "{add_card_to_customer,cards.id}" } } ] } \ No newline at end of file diff --git a/scenarios/debits/debit_customer_fail.yml b/scenarios/debits/debit_customer_fail.yml deleted file mode 100644 index 847c687..0000000 --- a/scenarios/debits/debit_customer_fail.yml +++ /dev/null @@ -1,17 +0,0 @@ -require: - - ../customer_fixtures.yml -scenarios: - - name: fail_debit_customer - request: - method: POST - href: "{customer,customers.debits}" - schema: - "$ref": "requests/customers/debit.json" - body: { - "amount": 8979 - } - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": ".*has no funding source" } diff --git a/scenarios/orders/basic_usage.yml b/scenarios/orders/basic_usage.yml deleted file mode 100644 index a44c065..0000000 --- a/scenarios/orders/basic_usage.yml +++ /dev/null @@ -1,56 +0,0 @@ -require: - - ../cards/add_to_customer.yml - - create.yml -scenarios: - - name: create_debit - request: - method: POST - href: "{add_card_to_customer,cards.debits}" - schema: - "$ref": "requests/cards/debit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - - - name: check_order_escrow_1 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 1234 } ] } - - - name: create_credit - request: - method: POST - href: "{verified_bank_account_successful,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - shcema: - "$ref": "responses/credits.json" - matches: { - "credits": [{"status": "succeeded", "links": {"destination": "{verified_bank_account_successful,bank_accounts.id}"}}] - } - - - name: check_order_escrow_2 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 0 } ] } diff --git a/scenarios/orders/cannot_over_credit.yml b/scenarios/orders/cannot_over_credit.yml deleted file mode 100644 index 0272a23..0000000 --- a/scenarios/orders/cannot_over_credit.yml +++ /dev/null @@ -1,56 +0,0 @@ -require: - - ../cards/add_to_customer.yml - - create.yml -# - ../customers/underwrite_with_destination.yml -scenarios: - - name: create_debit - request: - method: POST - href: "{add_card_to_customer,cards.debits}" - schema: - "$ref": "requests/cards/debit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - - - name: check_order_escrow_1 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 1234 } ] } - - - name: create_credit_fail - request: - method: POST - href: "{customer,customers.credits}" - schema: - "$ref": "requests/customers/credit.json" - body: { - "order": "{order,orders.href}", - "amount": 2000 - } - response: - status_code: 409 - shcema: - "$ref": "responses/errors.json" - assertRegex: { "description": ".*is insufficient to cover transfer of 2000.*" } - matches: { "category_code": "account-insufficient-funds" } - - - name: check_order_escrow_2 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 1234 } ] } diff --git a/scenarios/orders/create.yml b/scenarios/orders/create.yml deleted file mode 100644 index 8fcdf69..0000000 --- a/scenarios/orders/create.yml +++ /dev/null @@ -1,31 +0,0 @@ -require: - - ../bank_accounts/add_to_customer.yml -scenarios: - - name: underwrite_customer - request: - method: PUT - href: "{customer,customers.href}" - schema: - "$ref": "requests/customer.json" - body: { - "dob_month": 7, - "dob_year": 1963, - "name": "Henry Ford", - "address": { - "postal_code": "48120" - } - } - response: - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "merchant_status": "underwritten" } ] } - - name: order - request: - method: POST - href: "{customer,customers.orders}" - schema: - "$ref": "requests/customers/order.json" - response: - status_code: 201 - schema: - "$ref": "responses/orders.json" diff --git a/scenarios/orders/create_refund.yml b/scenarios/orders/create_refund.yml deleted file mode 100644 index fcf94ef..0000000 --- a/scenarios/orders/create_refund.yml +++ /dev/null @@ -1,97 +0,0 @@ -require: - - ../cards/add_to_customer.yml - - create.yml -# - ../customers/underwrite_with_destination.yml -scenarios: - - name: create_debit - request: - method: POST - href: "{add_card_to_customer,cards.debits}" - schema: - "$ref": "requests/cards/debit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - - - name: check_order_escrow_1 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 1234 } ] } - - - name: create_credit - request: - method: POST - href: "{add_bank_account_to_customer,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - shcema: - "$ref": "responses/credits.json" - - - name: check_order_escrow_2 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 0 } ] } - - - name: create_reversal - request: - method: POST - href: "{create_credit,credits.reversals}" - schema: - "$ref": "requests/credits/reversal.json" - response: - status_code: 201 - schema: - "$ref": "responses/reversals.json" - matches: { "reversals": [ { "status": "succeeded" } ] } - - - name: check_order_escrow_3 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 1234 } ] } - - - name: create_refund - request: - method: POST - href: "{create_debit,debits.refunds}" - schema: - "$ref": "requests/debits/refund.json" - response: - status_code: 201 - schema: - "$ref": "responses/refunds.json" - - - name: check_order_escrow_4 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 0 } ] } diff --git a/scenarios/orders/failed_refund_pending.yml b/scenarios/orders/failed_refund_pending.yml deleted file mode 100644 index f32134b..0000000 --- a/scenarios/orders/failed_refund_pending.yml +++ /dev/null @@ -1,108 +0,0 @@ -require: - - ../cards/add_to_customer.yml - - create.yml -# - ../customers/underwrite_with_destination.yml -scenarios: - - - name: associate_failed_bank_account_with_customer - request: - method: PUT - href: "{bank_account_failed,bank_accounts.href}" - body: { - "links": { - "customer": "{customer,customers.id}" - } - } - response: - schema: - "$ref": "responses/bank_accounts.json" - - - name: create_debit - request: - method: POST - href: "{add_card_to_customer,cards.debits}" - schema: - "$ref": "requests/cards/debit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - - - name: check_order_escrow_1 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 1234 } ] } - - - name: create_failed_credit - request: - method: POST - href: "{bank_account_failed,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - martches: { "credits": [ { "status": "failed" } ] } - - - name: check_order_escrow_2 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 1234 } ] } - - - name: create_pending_credit - request: - method: POST - href: "{bank_account,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - martches: { "credits": [ { "status": "pending" } ] } - - - name: check_order_escrow_2 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 0 } ] } - - - name: create_refund_failure - request: - method: POST - href: "{create_debit,debits.refunds}" - schema: - "$ref": "requests/debits/refund.json" - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": ".*is insufficient to cover transfer of 1234.*" } - matches: { "category_code": "account-insufficient-funds" } \ No newline at end of file diff --git a/scenarios/orders/simple_order.yml b/scenarios/orders/simple_order.yml deleted file mode 100644 index f406d01..0000000 --- a/scenarios/orders/simple_order.yml +++ /dev/null @@ -1,98 +0,0 @@ -require: - - ../bank_account_fixtures.yml - - ../customer_fixtures.yml - - ../cards/add_to_customer.yml -scenarios: - - name: add_bank_account - request: - method: PUT - href: "{bank_account,bank_accounts.href}" - body: { - "links": { - "customer": "{underwritten_merchant,customers.id}" - } - } - response: - status_code: 200 - - - name: order - request: - method: POST - href: "{underwritten_merchant,customers.orders}" - schema: - "$ref": "requests/customers/order.json" - response: - status_code: 201 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "links":{ - "merchant": "{underwritten_merchant,customers.id}" - } - }]} - - - name: debit_buyer - request: - method: POST - href: "{customer,customers.debits}" - schema: - "$ref": "requests/customers/debit.json" - body: { - "amount": 10000, - "order": "{order,orders.href}" - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits": [{ - "links": { - "order": "{order,orders.id}" - } - }]} - - - name: check_escrow_after_debit - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "amount": 10000, - "amount_escrowed": 10000, - }]} - - - name: pay_merchant - request: - method: POST - href: "{underwritten_merchant,customers.credits}" - schema: - "$ref": "requests/customers/credit.json" - body: { - "amount": 10000, - "order": "{order,orders.href}" - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - matches: {"credits": [{ - "links": { - "order": "{order,orders.id}" - } - }]} - - - name: check_escrow_after_credit - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: {"orders": [{ - "amount": 10000, - "amount_escrowed": 0, - }]} \ No newline at end of file diff --git a/scenarios/orders/transaction_inherit_description.yml b/scenarios/orders/transaction_inherit_description.yml deleted file mode 100644 index b6ecaf8..0000000 --- a/scenarios/orders/transaction_inherit_description.yml +++ /dev/null @@ -1,55 +0,0 @@ -require: - - ../customer_fixtures.yml -scenarios: - - name: order_with_description - request: - method: POST - href: "{merchant_with_bank_account,customers.orders}" - schema: - "$ref": "requests/customers/order.json" - body: { - "description": "Beats by Dr. Dre" - } - response: - status_code: 201 - schema: - "$ref": "responses/orders.json" - matches: {"orders":[{ - "description": "Beats by Dr. Dre" - }]} - - - name: debit_without_description - request: - method: POST - href: "{customer_with_card,customers.debits}" - schema: - "$ref": "requests/customers/debit.json" - body: { - "amount": 10000, - "order": "{order_with_description,orders.href}" - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: {"debits":[{ - "description": "Beats by Dr. Dre" - }]} - - - name: credit_without_description - request: - method: POST - href: "{merchant_with_bank_account,customers.credits}" - schema: - "$ref": "requests/customers/credit.json" - body: { - "amount": 10000, - "order": "{order_with_description,orders.href}" - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - matches: {"credits":[{ - "description": "Beats by Dr. Dre" - }]} \ No newline at end of file diff --git a/scenarios/orders/unverified_merchant.yml b/scenarios/orders/unverified_merchant.yml deleted file mode 100644 index 96229c0..0000000 --- a/scenarios/orders/unverified_merchant.yml +++ /dev/null @@ -1,57 +0,0 @@ -require: - - ../cards/add_to_customer.yml - - ../bank_accounts/add_to_customer.yml -# - ../customer_fixtures.yml -scenarios: - - name: order - request: - method: POST - href: "{customer,customers.orders}" - response: - status_code: 201 - schema: - "$ref": "responses/orders.json" - - - name: create_debit - request: - method: POST - href: "{add_card_to_customer,cards.debits}" - schema: - "$ref": "requests/cards/debit.json" - body: { - "order": "{order,orders.href}", - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - - - name: check_order_escrow_1 - request: - method: GET - href: "{order,orders.href}" - response: - status_code: 200 - schema: - "$ref": "responses/orders.json" - matches: { "orders": [ { "amount_escrowed": 1234 } ] } - - - name: create_credit - request: - method: POST - href: "{verified_bank_account_successful,bank_accounts.credits}" - schema: - "$ref": "requests/bank_accounts/credit.json" - body: { - "amount": 1234, - "order": "{order,orders.href}" - } - response: - status_code: 409 - schema: - "$ref": "responses/errors.json" - assertRegex: { - "description": "Order requires that merchant CU[a-zA-Z0-9]{16,32} be underwritten.", - "category_code": "order-kyc" - } \ No newline at end of file diff --git a/scenarios/refunds/create.yml b/scenarios/refunds/create.yml deleted file mode 100644 index fadb0ee..0000000 --- a/scenarios/refunds/create.yml +++ /dev/null @@ -1,13 +0,0 @@ -require: - - ../debits/debit_card.yml -scenarios: - - name: create_refund - request: - method: POST - href: "{debit_customer_card,debits.refunds}" - schema: - "$ref": "requests/debits/refund.json" - response: - status_code: 201 - schema: - "$ref": "responses/refunds.json" \ No newline at end of file diff --git a/scenarios/refunds/create_after_change_default.yml b/scenarios/refunds/create_after_change_default.yml deleted file mode 100644 index 17696e1..0000000 --- a/scenarios/refunds/create_after_change_default.yml +++ /dev/null @@ -1,73 +0,0 @@ -require: - - ../customers/set_default_source.yml -scenarios: - - name: create_debit - request: - method: POST - href: "{customer,customers.debits}" - schema: - "$ref": "requests/customers/debit.json" - body: { - "amount": 1234 - } - response: - status_code: 201 - schema: - "$ref": "responses/debits.json" - matches: { "debits": [ { "links": { "source": "{other_card,cards.id}" } } ] } - - - name: new_card - request: - method: POST - href: /cards - schema: - "$ref": "requests/card.json" - body: { - "number": "4111 1111 1111 1111", - "expiration_month": 12, - "expiration_year": 2016, - } - response: - status_code: 201 - schema: - "$ref": "responses/cards.json" - - - name: change_default_source - request: - method: PATCH - href: "{customer,customers.href}" - body: [{ - "op": "replace", - "path": "/customers/0/links/source", - "value": "{new_card,cards.id}" - }] - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" - matches: { "customers": [ { "links": { "source": "{new_card,cards.id}" } } ] } - - # this refund should go back onto the card that - # this debit was create with - # not the new default source on the customer - - name: create_refund - request: - method: POST - href: "{create_debit,debits.refunds}" - schema: - "$ref": "requests/debits/refund.json" - response: - status_code: 201 - schema: - "$ref": "responses/refunds.json" - matches: { "refunds": [ { "links": { "debit": "{create_debit,debits.id}" } } ] } - - - name: check_debit - request: - method: GET - href: "{create_debit,debits.href}" - response: - status_code: 200 - schema: - "$ref": "responses/debits.json" - matches: { "debits": [ { "links": { "source": "{other_card,cards.id}" } } ] } diff --git a/scenarios/refunds/fail.yml b/scenarios/refunds/fail.yml deleted file mode 100644 index 8579dc9..0000000 --- a/scenarios/refunds/fail.yml +++ /dev/null @@ -1,17 +0,0 @@ -require: - - ../debits/debit_card.yml -scenarios: - - name: create_failed_refund - request: - method: POST - href: "{debit_customer_card,debits.refunds}" - schema: - "$ref": "requests/debits/refund.json" - body: { - "amount": 200000000 - } - response: - status_code: 400 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": ".*must be <= 20000" } diff --git a/scenarios/resources_redirect.yml b/scenarios/resources_redirect.yml deleted file mode 100644 index 829ddc1..0000000 --- a/scenarios/resources_redirect.yml +++ /dev/null @@ -1,11 +0,0 @@ -require: - - customer_fixtures.yml -scenarios: - - name: get_resouces - request: - method: GET - href: "/resources/{customer,customers.id}" - response: - status_code: 200 - schema: - "$ref": "responses/customers.json" \ No newline at end of file diff --git a/scenarios/reversals/create.yml b/scenarios/reversals/create.yml deleted file mode 100644 index c394394..0000000 --- a/scenarios/reversals/create.yml +++ /dev/null @@ -1,13 +0,0 @@ -require: - - ../credits/credit_bank_account.yml -scenarios: - - name: reversals_create - request: - method: POST - href: "{credit_existing_bank_account,credits.reversals}" - schema: - "$ref": "requests/credits/reversal.json" - response: - status_code: 201 - schema: - "$ref": "responses/reversals.json" \ No newline at end of file diff --git a/scenarios/reversals/fail_amount.yml b/scenarios/reversals/fail_amount.yml deleted file mode 100644 index bda2388..0000000 --- a/scenarios/reversals/fail_amount.yml +++ /dev/null @@ -1,15 +0,0 @@ -require: - - ../credits/credit_bank_account.yml -scenarios: - - name: failed_reversal - request: - method: POST - href: "{credit_new_bank_account,credits.reversals}" - schema: - "$ref": "requests/credits/reversal.json" - body: { "amount": 10000000 } - response: - status_code: 400 - schema: - "$ref": "responses/errors.json" - assertRegex: { "description": "^Invalid field \\[amount\\]" } diff --git a/scenarios/reversals/failed.yml b/scenarios/reversals/failed.yml deleted file mode 100644 index 3c3e934..0000000 --- a/scenarios/reversals/failed.yml +++ /dev/null @@ -1,31 +0,0 @@ -scenarios: - - name: credit_bank_account - request: - method: POST - href: /credits - schema: - "$ref": "requests/credit.json" - body: { - "amount": 1234, - "destination": { - "name": "Michael Jordan", - "account_number": "9900000004", - "routing_number": "021000021", - "account_type": "checking" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - - name: create_reversal - request: - method: POST - href: "{credit_bank_account,credits.reversals}" - schema: - "$ref": "requests/credits/reversal.json" - response: - status_code: 201 - schema: - "$ref": "responses/reversals.json" - matches: { "reversals": [ { "status": "failed" } ] } diff --git a/scenarios/reversals/succeeded.yml b/scenarios/reversals/succeeded.yml deleted file mode 100644 index 654994d..0000000 --- a/scenarios/reversals/succeeded.yml +++ /dev/null @@ -1,31 +0,0 @@ -scenarios: - - name: credit_bank_account - request: - method: POST - href: /credits - schema: - "$ref": "requests/credit.json" - body: { - "amount": 1234, - "destination": { - "name": "Michael Jordan", - "account_number": "9900000095", - "routing_number": "021000021", - "account_type": "checking" - } - } - response: - status_code: 201 - schema: - "$ref": "responses/credits.json" - - name: create_reversal - request: - method: POST - href: "{credit_bank_account,credits.reversals}" - schema: - "$ref": "requests/credits/reversal.json" - response: - status_code: 201 - schema: - "$ref": "responses/reversals.json" - matches: { "reversals": [ { "status": "succeeded" } ] } diff --git a/scenarios/root_resource.yml b/scenarios/root_resource.yml deleted file mode 100644 index ef395cb..0000000 --- a/scenarios/root_resource.yml +++ /dev/null @@ -1,7 +0,0 @@ -scenarios: - - name: get_root - request: - method: GET - href: / - response: - status_code: 200 diff --git a/tester/.gitignore b/tester/.gitignore deleted file mode 100644 index 734771b..0000000 --- a/tester/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -fixtures.json -*iml diff --git a/tester/Makefile b/tester/Makefile deleted file mode 100644 index 32f9465..0000000 --- a/tester/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -SCENARIOS= $(shell find ../scenarios/ -name "*.yml") -PYTHON= python - -JSON_SCENARIOS= $(SCENARIOS:.yml=.json) - -ROOT_URL=https://api-pmtest.balancedpayments.com - -all: scenarios.json - -clean: - rm -f fixtures.json - rm -rf $(JSON_SCENARIOS) scenarios.json - -fixtures.json: - @ROOT_URL=$(ROOT_URL) $(PYTHON) fixture_data.py > fixtures.json - -scenarios.json: $(JSON_SCENARIOS) - @ROOT_URL=$(ROOT_URL) $(PYTHON) combine.py $(JSON_SCENARIOS) > $@ - - -%.json: %.yml fixtures.json - @ROOT_URL=$(ROOT_URL) DUMP_JSON=1 $(PYTHON) runner.py $< > $@ 2> /dev/null diff --git a/tester/README.md b/tester/README.md deleted file mode 100644 index bda1211..0000000 --- a/tester/README.md +++ /dev/null @@ -1,65 +0,0 @@ -## Balanced-api test runner directory - -This directory contains all the code for running the scenarios against the api and validating the output. - - -## Files -| Name | Used for | -|--------------------|-------------------------------------------------------| -| `check_json.sh` | Automatically checks and styles all json files | -| `run_scenarios.sh` | Sequentially runs all scenarios against the api | -| `travis.sh` | Checks json and scenarios syntax | -| `Makefile` | Builds scenarios.json for building the docs scenarios | -| `requirements.txt` | All the python requirements for running the scenarios | -| `fixture_data.py` | Creates a new api_key for running the scenarios | -| `fix_json.js` | Checks the json syntax of a single file | -| `runner.py` | Sequentially runs the scenarios in a single file | -| `combine.py` | Combines the json output for building the docs | - - -### Running a single scenario -``` - $ cd tester - $ pip install -r requirements.txt - $ python fixture_data.py > fixtures.json - $ python runner.py ../scenarios/the_name_of_the_file.yml -``` - -### Running all scenarios -``` - $ cd tester - $ pip install -r requirements.txt - $ python fixture_data.py > fixtures.json - $ ./run_scenarios.sh -``` - -### Running scenarios against an alternate host -``` - $ cd tester - $ pip install -r requirements.txt - $ ROOT_URL="https://api.balancedpayments.com" python fixture_data.py > fixtures.json - $ ROOT_URL="https://api.balancedpayments.com" ./run_scenarios.sh -``` - -### Running scenarios against a different api version -``` - $ cd tester - $ pip install -r requirements.txt - $ API_VERSION=1.1 python fixture_data.py > fixtures.json - $ API_VERSION=1.1 ./run_scenarios.sh -``` - -### Checking the syntax of files -``` - $ cd tester - $ pip install -r requirements.txt - $ ./travis.sh -``` - -### Creating scenarios.json for the docs -``` - $ cd tester - $ pip install -r requirements.txt - $ python fixture_data.py > fixtures.json - $ make -``` diff --git a/tester/check_json.sh b/tester/check_json.sh deleted file mode 100755 index b4c4487..0000000 --- a/tester/check_json.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -FAILED="" -code=0 - -FILES=`find .. -name "*.json"` - -for i in $FILES -do - if node fix_json.js $i > /dev/null ; then - echo -e "\e[00;32mOK $i\e[00m" - else - echo -e "\e[00;31mFAILED $i\e[00m" - FAILED="$FAILED\t$i\n" - code=$(( $code + 1 )) - fi -done - -if [ $code -ne 0 ]; then - echo -e "\e[00;31mChecking of JSON has failed\e[00m" - echo -e "$FAILED" -else - echo "ALL passed" -fi - -exit $code diff --git a/tester/combine.py b/tester/combine.py deleted file mode 100755 index 0e47dd5..0000000 --- a/tester/combine.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python - -import json -import sys -import os - -def main(): - data = {} - for name in sys.argv: - if 'combine' in name: - continue - vers, _ = os.path.splitext(os.path.basename(name)) - data[vers] = json.loads(open(name).read()) - print(json.dumps(data, indent=1)) - - -if __name__ == "__main__": - main() diff --git a/tester/fix_json.js b/tester/fix_json.js deleted file mode 100644 index 9e6ac07..0000000 --- a/tester/fix_json.js +++ /dev/null @@ -1,12 +0,0 @@ -var fs = require('fs'); - -var inStr = fs.readFileSync(process.argv[2]).toString(); - -var json = JSON.parse(inStr); - -var str = JSON.stringify(json, null, 4); - -console.log(str); - -if(inStr != str) - fs.writeFileSync(process.argv[2], str); diff --git a/tester/fix_json.py b/tester/fix_json.py deleted file mode 100644 index 12f9a32..0000000 --- a/tester/fix_json.py +++ /dev/null @@ -1,17 +0,0 @@ -import sys -import json - -def main(): - try: - contents = json.load(open(sys.argv[1])) - except ValueError, e: - sys.stderr.write(e.message) - sys.exit(1) - result = json.dumps(contents, indent=4) - print(result) - if contents != result: - with open(sys.argv[1], 'w') as w: - w.write(result) - -if __name__ == '__main__': - main() diff --git a/tester/fixture_data.py b/tester/fixture_data.py deleted file mode 100755 index 9fa545f..0000000 --- a/tester/fixture_data.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -import os -import json -import requests - -import yaml - - -config = {} -if os.path.isfile('../config.yml'): - config = yaml.load(open('../config.yml').read()) - -ROOT_URL = os.environ.get('ROOT_URL', config.get('root_url', 'http://localhost:5000')) -API_VERSION = os.environ.get('API_VERSION', config.get('api_version', '1.1')) -ACCEPT_HEADERS = os.environ.get( - 'ACCEPT_HEADERS', - config.get('accept_headers', ('application/vnd.balancedpayments+json; version={version}, ' - 'application/vnd.api+json')) -) -ACCEPT_HEADERS = ACCEPT_HEADERS.replace('{version}', API_VERSION) - -cache = {} - -def make_api_key(): - - result = requests.post(ROOT_URL + '/api_keys', headers={'Accept': ACCEPT_HEADERS}, data={}) - - cache['secret'] = result.json()['api_keys'][0]['secret'] - -def make_marketplace(): - - result = requests.post(ROOT_URL + '/marketplaces', auth=(cache['secret'], ''), - headers={'Accept': ACCEPT_HEADERS}, data={}) - - cache['marketplace'] = result.json()['marketplaces'][0]['href'] - -def main(): - - make_api_key() - make_marketplace() - - print(json.dumps(cache, indent=2)) - - -if __name__ == '__main__': - main() diff --git a/tester/requirements.txt b/tester/requirements.txt deleted file mode 100644 index 7d3e349..0000000 --- a/tester/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -pyyaml -requests -jsonschema diff --git a/tester/run_scenarios.sh b/tester/run_scenarios.sh deleted file mode 100755 index ade7ec0..0000000 --- a/tester/run_scenarios.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -RED=\033[0;31m -ERED=\033[0m - -FAILED="" -code=0 - -FILES=`find ../scenarios -name "*.yml" | sort` - -for i in $FILES -do - if python runner.py $i; then - echo -e "\033[00;32mOK $i\033[00m" - else - echo -e "\033[00;31mFAILED $i\033[00m" - FAILED="$FAILED\t$i\n" - code=$(( $code + 1 )) - fi -done - -if [ $code -ne 0 ]; then - echo -e "\033[00;31mRunning of scenarios has failed\033[00m" - echo -e "$FAILED" -else - echo "ALL passed" -fi - -exit $code diff --git a/tester/runner.py b/tester/runner.py deleted file mode 100755 index 83a91cc..0000000 --- a/tester/runner.py +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/env python - -# This file is designed to run one scenario at a time -# scenarios can include in other scenario using a require block at the top -# the scenarios are run in the order that they are specified - -import os -import sys -import re -import json - -import yaml -import requests -import jsonschema - -config = {} -if os.path.isfile('../config.yml'): - config = yaml.load(open('../config.yml').read()) - -ROOT_URL = os.environ.get('ROOT_URL', config.get('root_url', 'http://localhost:5000')) -API_VERSION = os.environ.get('API_VERSION', config.get('api_version', '1.1')) -ACCEPT_HEADERS = os.environ.get( - 'ACCEPT_HEADERS', - config.get('accept_headers', ('application/vnd.balancedpayments+json; version={version}, ' - 'application/vnd.api+json')) -) -ACCEPT_HEADERS = ACCEPT_HEADERS.replace('{version}', API_VERSION) - -DRY_RUN = os.environ.get('DRY_RUN', '0') != '0' - -DUMP_JSON = os.environ.get('DUMP_JSON', '0') != '0' - - -def find_file(fromFile, fileName): - path = os.path.join(os.path.dirname(fromFile), fileName) - if os.path.isfile(path): - return path - path = os.path.join('..', fileName) - if os.path.isfile(path): - return path - sys.stderr.write('Could not find file {0}\n'.format(fileName)) - sys.exit(1) - - -# the $ref do not work with relative paths as specified in the jsonschema spec -def validator_fix_ref(contents, fileName): - if isinstance(contents, dict): - if '$ref' in contents: - path = find_file(fileName, contents['$ref']) - cont = open(path).read() - return validator_fix_ref(json.loads(cont), path) - else: - return dict( - (k, validator_fix_ref(v, fileName)) - for k, v in contents.iteritems() - ) - elif isinstance(contents, list): - return [validator_fix_ref(v, fileName) for v in contents] - else: - return contents - - -def validator_required(validator, required, instance, schema): - # the properties function in draft3 takes care of required - # this validator has an issue with the form of required that we are using - return - -validator = jsonschema.validators.extend(jsonschema.Draft4Validator, - { - "properties": jsonschema._validators.properties_draft3, - "required": validator_required, - }) - - -def validate(resp_json): - class Validator(object): - - def against(self, schema): - validator(schema).validate(resp_json) - return Validator() - - -class Runner(object): - - cache = {} - - session = requests.Session() - - def get_field(self, name, data): - - def resolve_link(match): - if match.group(2) in data[match.group(1)][0]: - return data[match.group(1)][0][match.group(2)] - else: - return data[match.group(1)][0]['links'][match.group(2)] - - controller, action = name.split('.') - try: - if action in data[controller][0]: - return data[controller][0][action] - except Exception as ex: - #sys.stderr.write(ex) - import ipdb; ipdb.set_trace() - if name in data['links']: - return re.sub( - r'\{(\w+)\.(\w+)\}', - resolve_link, - data['links'][name] - ) - return None - - def resolve_deps(self, scenario, data): - def fix_match(matchgroup): - if DRY_RUN: - gg = data[matchgroup.group(1)] - return 'asdf' - return self.get_field( - matchgroup.group(2), data[matchgroup.group(1)]['response'] - ) - if isinstance(scenario, str): - return re.sub(r'\{(\w+),(\w+\.\w+)\}', fix_match, scenario) - elif isinstance(scenario, list): - return [self.resolve_deps(v, data) for v in scenario] - elif isinstance(scenario, dict): - return dict( - (k, self.resolve_deps(v, data)) - for k, v in scenario.iteritems() - ) - else: - return scenario - - def equals(self, data, instance, assertRegex): - ret = 0 - if isinstance(data, dict): - if not isinstance(instance, dict): - return 1 - for k, v in data.iteritems(): - eq = self.equals(v, instance.get(k, None), assertRegex) - ret += eq - if eq: - sys.stderr.write('err: {}[{}] != {}\n'.format( - k, v, instance.get(k) - )) - return ret - elif isinstance(data, list): - if not isinstance(instance, list): - return 1 - if len(instance) < len(data): - return 1 - for i in xrange(len(data)): - ret += self.equals(data[i], instance[i], assertRegex) - return ret - else: - if assertRegex: - try: - if re.match(data, instance): - return 0 - except TypeError: - pass - return 1 - else: - return 0 if data == instance else 1 - - def run_scenario(self, scenario, data, path): - sys.stderr.write('Running scenario {0}\n'.format(scenario['name'])) - - scenario = self.resolve_deps(scenario, data) - - request_scenario = scenario['request'] - response = scenario['response'] - name = scenario['name'] - body = request_scenario.get('body', {}) - options = scenario.get('options', []) - - if 'schema' in request_scenario: - try: - schema = validator_fix_ref(request_scenario['schema'], path) - except Exception, e: - sys.stderr.write( - 'Error loading request schema for {0}'.format(name) - ) - sys.stderr.write(str(e)) - sys.exit(1) - validate(body).against(schema) - else: - if body: - sys.stderr.write( - 'Warning: {0} missing schema section for request\n'.format( - name - ) - ) - if not (isinstance(request_scenario['href'], str) - or isinstance(request_scenario['href'], unicode)): - sys.stderr.write( - 'href for scenario must be string' - ) - sys.exit(1) - - if not DRY_RUN: - sys.stderr.write('{0}: {2} {1}\n'.format( - name, - ROOT_URL + request_scenario['href'], - request_scenario['method'] - )) - kwargs = {} - if 'no-secret-key' not in options: - kwargs['auth'] = (self.cache['secret'], '') - if 'no-follow-redirects' in options: - kwargs['allow_redirects'] = False - resp = requests.request( - method=request_scenario['method'], - url=ROOT_URL + request_scenario['href'], - data=json.dumps(body), - headers={ - 'Accept': ACCEPT_HEADERS, - 'Content-type': 'application/json' - }, - **kwargs - ) - - if 'status_code' in response and not DRY_RUN: - if response['status_code'] != resp.status_code: - sys.stderr.write( - 'Scenario {0} failed with wrong status code {1} != {2}' - .format(name, response['status_code'], resp.status_code) - ) - sys.stderr.write( - str(resp.json()) - ) - sys.exit(1) - - resp_json = {} - if not DRY_RUN and resp.status_code != 204: - resp_json = resp.json() - - try: - schema = validator_fix_ref(response.get('schema', {}), path) - except: - sys.stderr.write( - 'could not parse response schema for {0}'.format(name) - ) - sys.exit(1) - - if not DRY_RUN: - if not schema: - sys.stderr.write( - 'Warning: no schema to validate response against for {0}\n' - .format(name) - ) - validate(resp_json).against(schema) - - if 'matches' in response: - if 0 != self.equals(response['matches'], resp_json, False) and not DRY_RUN: - sys.stderr.write( - 'Error validating equals for {0}'.format(name) - ) - sys.stderr.write(json.dumps(resp_json, indent=4)) - sys.stderr.write(json.dumps(response['matches'], indent=4)) - sys.exit(1) - - if 'assertRegex' in response: - if 0 != self.equals(response['assertRegex'], resp_json, True) and not DRY_RUN: - sys.stderr.write( - 'Error validating assertRegex for {0}'.format(name) - ) - sys.stderr.write(json.dumps(resp_json, indent=4)) - sys.stderr.write(json.dumps(response['assertRegex'], indent=4)) - sys.exit(1) - - return { - 'response': resp_json, - 'status_code': resp.status_code if not DRY_RUN else 0, - 'request': body - } - - def parse_file(self, name): - scenarios = {} - - data = yaml.load(open(name).read()) - - for other in data.get('require', []): - scenarios.update(**self.parse_file( - find_file(name, other) - )) - - for scenario in data.get('scenarios', []): - scenarios[scenario['name']] = self.run_scenario( - scenario, scenarios, name - ) - - return scenarios - - -def main(): - if len(sys.argv) == 2: - runner = Runner() - runner.cache = json.load(open('fixtures.json')) - result = runner.parse_file(sys.argv[1]) - if DUMP_JSON: - sys.stdout.write(json.dumps(result, indent=1)) - else: - print('python {0} [file of scenario to run]'.format(sys.argv[0])) - - -if __name__ == '__main__': - main() diff --git a/tester/travis.sh b/tester/travis.sh deleted file mode 100755 index 5ec7ad8..0000000 --- a/tester/travis.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -code=0 - -export ROOT_URL="https://api-pmtest.balancedpayments.com" - -./check_json.sh -code=$? - -echo '{}' > fixtures.json - -DRY_RUN=1 ./run_scenarios.sh - -code=$(( $code + $? )) - - -if [ $code -eq 0 ] -then - echo -e "\033[00;32m=================================================\n== ALL SCENARIOS HAVE PASSED THE INITIAL CHECK ==\n== WILL PROCEED TO TEST PUBLIC API ==\n=================================================\033[00m" - - echo "Creating new test marketplace" - ./fixture_data.py > fixtures.json - cat fixtures.json - echo -e "\n\nRunning scenarios against the api" - ./run_scenarios.sh - code=$? - -fi - -exit $code