From 1a37cccee9701b24eaaef85b0d8fe4134dc95b07 Mon Sep 17 00:00:00 2001 From: Zee <50284+zspencer@users.noreply.github.com> Date: Sat, 11 Jul 2020 18:01:29 -0700 Subject: [PATCH] Wire in assertion that the appropriate products are created in Stripe See: https://github.com/zinc-collective/compensated/issues/79 This is a big commit because it conflates the formatting changes from https://github.com/zinc-collective/compensated/pull/89. That said, it's also significant in it's own right. The ClientSandbox now has the ability to query and assert against Stripe, so we will be able to write full integration tests. That said we _probably_ want to consider spinning out a fake Stripe API so that our tests don't _always_ perform a full end-to-end check. Perhaps in Coruru, if something like that doesn't exist already? --- .env.example | 3 ++ .gitignore | 3 +- bin/setup | 2 +- features/client-sandbox.js | 57 +++++++++++++++----------- features/parameter_types.js | 4 +- features/steps.js | 65 +++++++++++++++--------------- features/support/PaymentGateway.js | 20 +++++++++ 7 files changed, 95 insertions(+), 59 deletions(-) create mode 100644 .env.example create mode 100644 features/support/PaymentGateway.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..02aeaf3 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Get these from https://dashboard.stripe.com/test/apikeys +STRIPE_SECRET_KEY="" +STRIPE_PUBLISHABLE_KEY="" \ No newline at end of file diff --git a/.gitignore b/.gitignore index c2a80fa..31f782f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ node_modules package-lock.json # Probably don't want tmp files to persist between dev machines. -tmp \ No newline at end of file +tmp +.env \ No newline at end of file diff --git a/bin/setup b/bin/setup index a9f4b11..a252e88 100755 --- a/bin/setup +++ b/bin/setup @@ -35,4 +35,4 @@ gem install bundler # We use cucumber-js for system and integration testing # We use `uuid` to generate random strings -npm install --save-dev cucumber uuid child-process-promise \ No newline at end of file +npm install --save-dev cucumber uuid child-process-promise dotenv stripe \ No newline at end of file diff --git a/features/client-sandbox.js b/features/client-sandbox.js index 116b11c..15a1666 100644 --- a/features/client-sandbox.js +++ b/features/client-sandbox.js @@ -1,6 +1,8 @@ -const fs = require('fs') -const { execSync } = require('child_process') -const { v4: uuidv4 } = require('uuid') +const fs = require("fs"); +require('dotenv').config(); +const { execSync } = require("child_process"); +const { v4: uuidv4 } = require("uuid"); +const PaymentGateway = require("./support/PaymentGateway"); /* * A Sandbox lets us create and destroy our testing environment @@ -8,54 +10,61 @@ const { v4: uuidv4 } = require('uuid') * program, so the sandbox is going to be a local directory on * the filesystem that we can dump temporary files in. */ -module.exports = class ClientSandbox { - constructor (paymentGateway) { - this.paymentGateway = paymentGateway - this.runId = uuidv4() - this.createTempDirectory() + module.exports = class ClientSandbox { + constructor() { + this.runId = uuidv4(); - this.createFileSync('Gemfile', gemfileTemplate) - this.executeSync('bundle') + this.createTempDirectory(); + + this.createFileSync("Gemfile", gemfileTemplate); + this.executeSync("bundle"); } /* * The location where the Sandbox stores any files useful at runtime */ - get temporaryDirectory () { - return `${sandboxDir}/${this.paymentGateway}-${this.runId}` + get temporaryDirectory() { + return `${sandboxDir}/${this.runId}`; } /* * Create a file in the test sandbox synchronously. */ - createFileSync (fileName, contents) { - return fs.writeFileSync(`${this.temporaryDirectory}/${fileName}`, contents) + createFileSync(fileName, contents) { + return fs.writeFileSync(`${this.temporaryDirectory}/${fileName}`, contents); } /* * Create a file in the test sandbox synchronously. */ - executeSync (command) { - return execSync(command, { cwd: this.temporaryDirectory }) + executeSync(command) { + return execSync(command, { cwd: this.temporaryDirectory }); } - createTempDirectory () { + createTempDirectory() { if (!fs.existsSync(this.temporaryDirectory)) { - fs.mkdirSync(this.temporaryDirectory) + fs.mkdirSync(this.temporaryDirectory); } } -} + + productsWhere(type, filter) { + return new PaymentGateway({ type, secretKey: process.env.STRIPE_SECRET_KEY }) + .products() + .then((products) => + products.filter((product) => product.name === filter.name) + ); + } +}; // Location to store sandbox files -const sandboxDir = './tmp' +const sandboxDir = "./tmp"; if (!fs.existsSync(sandboxDir)) { - fs.mkdirSync(sandboxDir) + fs.mkdirSync(sandboxDir); } // Default gemfile for the compensated sandbox -const gemfileTemplate = -` +const gemfileTemplate = ` source "https://rubygems.org" gem "compensated", path: "../../compensated-ruby" -` +`; diff --git a/features/parameter_types.js b/features/parameter_types.js index 23ce353..198e015 100644 --- a/features/parameter_types.js +++ b/features/parameter_types.js @@ -30,10 +30,12 @@ defineParameterType({ defineParameterType({ name: 'paymentGateway', regexp: /Stripe/, - transformer: (pg) => pg + transformer: (type) => type }) + + /* * A Custom Parameter Type for our Compensated Packages * diff --git a/features/steps.js b/features/steps.js index 793950d..8364b10 100644 --- a/features/steps.js +++ b/features/steps.js @@ -1,59 +1,60 @@ -const { Given, When, Then } = require('cucumber') -const ClientSandbox = require('./client-sandbox') -const ContributorSandbox = require('./contributor-sandbox') -const assert = require('assert').strict +const { Given, When, Then } = require("cucumber"); +const ClientSandbox = require("./client-sandbox"); +const ContributorSandbox = require("./contributor-sandbox"); +const assert = require("assert").strict; Given( - 'Compensated is configured with a clean {paymentGateway} account', + "Compensated is configured with a clean {paymentGateway} account", function (paymentGateway) { - return (this.clientSandbox = new ClientSandbox(paymentGateway)) + return (this.clientSandbox = new ClientSandbox(paymentGateway)); } -) +); -Given('the following language test matrix:', function (languageMatrix) { - this.languageMatrix = languageMatrix.hashes() -}) +Given("the following language test matrix:", function (languageMatrix) { + this.languageMatrix = languageMatrix.hashes(); +}); -Given('there is a compensated.json with the following data:', function (json) { - return this.clientSandbox.createFileSync('compensated.json', json) -}) +Given("there is a compensated.json with the following data:", function (json) { + return this.clientSandbox.createFileSync("compensated.json", json); +}); -When('I run `compensated apply`', function () { - return this.clientSandbox.executeSync('bundle exec compensated apply') -}) +When("I run `compensated apply`", function () { + return this.clientSandbox.executeSync("bundle exec compensated apply"); +}); When( - 'I run the setup and test scripts for {compensatedPackage} on each version', + "I run the setup and test scripts for {compensatedPackage} on each version", { timeout: -1 }, async function (compensatedPackage) { this.commandResults = await Promise.all( this.languageMatrix.map((language) => new ContributorSandbox(language, compensatedPackage).spawn( - 'bin/setup && bin/test' + "bin/setup && bin/test" ) ) - ) + ); } -) +); Then( - 'the {string} Product has a {string} Price of {price} in {paymentGateway}', + "the {string} Product has a {string} Price of {price} in {paymentGateway}", function (productName, priceName, price, paymentGateway) { // Write code here that turns the phrase above into concrete actions - return 'pending' + return "pending"; } -) +); -Then('a {string} Product is created in {paymentGateway}', function ( - string, +Then("a {string} Product is created in {paymentGateway}", async function ( + name, paymentGateway ) { - // Write code here that turns the phrase above into concrete actions - return 'pending' -}) + return this.clientSandbox.productsWhere(paymentGateway, { name }).then( + (products) => assert(products[0], `Product "${name}" does not exist`) + ) +}); -Then('all the commands passed', function () { +Then("all the commands passed", function () { this.commandResults.forEach((result) => { - assert.strictEqual(result.code, 0) - }) -}) + assert.strictEqual(result.code, 0); + }); +}); diff --git a/features/support/PaymentGateway.js b/features/support/PaymentGateway.js new file mode 100644 index 0000000..60f8857 --- /dev/null +++ b/features/support/PaymentGateway.js @@ -0,0 +1,20 @@ +const Stripe = require('stripe') +const http = require('http') + +module.exports = class PaymentGateway { + constructor({ type, secretKey }) { + this.type = type; + if(this.type === 'Stripe') { + this.stripe = Stripe(secretKey); + } + } + + products() { + return new Promise((resolve, reject) => { + this.stripe.products.list({limit: 100}, (err, products) => { + if(err) { return reject(err) } + resolve(products.data) + }) + }) + } +}