README
¶
Introduction
stripe playback
is a prototype feature for the Stripe CLI. It is still under development. This project is inspired by the VCR approach to testing popularized in Ruby.
For context, when using the Ruby VCR gem, you record any HTTP interactions made in your test suite (request & response). These recordings are stored in serialized format, and can be replayed in future tests to make them "fast, deterministic, and accurate".
While Ruby VCR is implemented as a Gem that you can directly use and configure in your test code - stripe playback
runs as a separate HTTP proxy server on your machine. That means any configuration happens either at startup via the CLI, or via interprocess HTTP calls.
This WIP document aims to give a new user enough information to begin using stripe playback
.
Usage
You start and configure the server via the stripe playback
command. See stripe playback --help
for a description of the flags, how to control the server once it is running (via HTTP endpoints).
go run cmd/stripe/main.go playback --help
When running in both record/replay mode, the server will print out interactions with it (exact formatting of output may have changed).
### When in recordmode (playing responses from the remote API)
--> POST to /v1/customers
<-- 200 OK from HTTPS://API.STRIPE.COM
--> GET to /v1/customers
<-- 200 OK from HTTPS://API.STRIPE.COM
--> GET to /v1/balance
<-- 200 OK from HTTPS://API.STRIPE.COM
### When in replaymode (playing responses from CASSETTE)
--> POST to /v1/customers
<-- 200 OK from CASSETTE
--> GET to /v1/customers
<-- 200 OK from CASSETTE
--> GET to /v1/balance
<-- 200 OK from CASSETTE
Controlling the playback server
Besides the command line flags at startup, there are also HTTP endpoints that allow you to control and modify the server's behavior while it is running.
POST:
/playback/mode/[mode]
: Sets the server mode to one of ["auto", "record", "replay"].
POST:
/playback/cassette/setroot?dir=[path_to_directory]
: Set the root directory for reading/writing cassettes. All cassette paths are relative to this directory.
POST:
/playback/cassette/load?filepath=[filepath]
: Load the cassette file at the given filepath, relative to the cassette root directory.
POST:
/playback/casette/eject
: Eject (unload) the current cassette and do any teardown. In record
mode or auto
mode when recording to a new file, this writes the recorded interactions to the cassette file. When replaying (whether in replay
or auto
modes) this is a no-op.
Example
In Window 1:
go run cmd/stripe/main.go playback
(Start a recordmode HTTP server at localhost:13111, writing to the default cassette)
In Window 2:
Record some test interactions using the stripe CLI, but proxy through the stripe playback
server:
stripe customers create --api-base="http://localhost:13111"
stripe customers list --api-base="http://localhost:13111"
stripe balance retrieve --api-base="http://localhost:13111"
Stop recording:
curl http://localhost:13111/playback/stop
In Window 1:
Ctrl-C the record server to shut it down.
Then, start the replay server, which should read from the same default cassette.
go run cmd/stripe/main.go playback --replaymode
In Window 2:
Replay the same sequence of interactions using the stripe CLI, and notice that we are now replaying from the cassette:
stripe customers create --api-base="http://localhost:13111"
stripe customers list --api-base="http://localhost:13111"
stripe balance retrieve --api-base="http://localhost:13111"
[WIP] Webhooks
Webhooks are a WIP feature, but currently have basic functionality working. If you don't plan to make use of webhook recording/replaying, you can ignore this section.
Skeleton demo of functionality:
# Terminal 1
# Start the playback server using the default settings. (in record mode, and using the default ports)
> go run cmd/stripe/main.go playback
Seting up playback server...
/playback/mode/: Setting mode to record
/playback/cassette/load: Loading cassette [default_cassette.yaml]
------ Server Running ------
Recording...
Using cassette: "default_cassette.yaml".
Listening via HTTP on localhost:13111
Forwarding webhooks to http://localhost:13112
-----------------------------
# Terminal 2
# Use stripe listen to forward webhooks to the playback server's webhook endpoint
> stripe listen --forward-to localhost:13111/playback/webhooks
# Terminal 3
# This effectively sets up a basic HTTP server on localhost:13112 that will echo out all requests
# and always respond with a 200 status code.
> socat -v -s tcp-listen:13112,reuseaddr,fork "exec:printf \'HTTP/1.0 200 OK\r\n\r\n\'"
Finally, use the Stripe CLI to send requests and trigger webhooks to the playback server.
# Terminal 4
# Send a normal request
stripe balance retrieve --api-base "http://localhost:13111"
# Trigger webhooks afterwards
stripe trigger payment_intent.created
You should see the socat
server in Terminal 3 receive the forwarded webhooks. You should also see the playback
server logging (and recording) all interactions (outbound API requests and inbound webhooks).
After all this, you can re-run the server in replay mode:
go run cmd/stripe/main.go playback --replaymode
Then, rerun the same commands in the same order in Terminal 4, and you should see recorded responses and webhooks being returned to your Stripe CLI client and to your socat
server.
Testing
Some of the tests require a .env
file to be present at /pkg/playback/.env
containing
STRIPE_SECRET_KEY="sk_test_..."
. To run the tests, create this file and define your own secret testmode key in it.
Documentation
¶
Index ¶
Constants ¶
const (
Auto string = "auto"
Record string = "record"
Replay string = "replay"
)
These constants define the different playback modes Auto mode: If a cassette exists, replays from the cassette or else records a new cassette.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cassette ¶ added in v1.5.5
type Cassette []interaction
Cassette is used to store a sequence of interactions that happened part of a single action
type Recorder ¶ added in v1.5.5
type Recorder struct {
// contains filtered or unexported fields
}
An Recorder proxies requests to a remote host and records all interactions.
type Replayer ¶ added in v1.5.5
type Replayer struct {
// contains filtered or unexported fields
}
An Replayer receives incoming requests and returns recorded responses from the provided cassette.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
A Server implements the full functionality of `stripe playback` as a HTTP server. Acting as a proxy server for the Stripe API, it can both record and replay interactions using cassette files.
func NewServer ¶
func NewServer(remoteURL string, webhookURL string, absCassetteDirectory string, mode string, initialCassetteFilepath string) (server *Server, err error)
NewServer instantiates a Server struct, representing the configuration and current state of a playback proxy server The cassetteDirectory param must be an absolute path initialCasssetteFilepath can be a relative path (interpreted relative to cassetteDirectory) or an absolute path
func (*Server) InitializeServer ¶
func (rr *Server) InitializeServer(address string) *http.Server
InitializeServer sets up and returns a http.Server that acts as a playback proxy
func (*Server) OnSwitchMode ¶ added in v1.5.4
func (rr *Server) OnSwitchMode(f func(string))
OnSwitchMode listens to the switchModeChan and calls f when it receives a mode string
type YAMLCassette ¶ added in v1.5.5
type YAMLCassette []YAMLInteraction
YAMLCassette is a playback.cassette interface encoded to YAML
type YAMLInteraction ¶ added in v1.5.5
type YAMLInteraction struct {
Type interactionType
Request YAMLRequest
Response YAMLResponse
}
YAMLInteraction is a playback.interaction interface encoded to YAML
type YAMLRequest ¶ added in v1.5.5
type YAMLRequest struct {
Method string `yaml:"method"`
Body string `yaml:"body"`
Headers http.Header `yaml:"headers"`
URL url.URL `yaml:"url"`
}
YAMLRequest is a playback.httpRequest interface encoded to YAML
type YAMLResponse ¶ added in v1.5.5
type YAMLResponse struct {
Headers http.Header `yaml:"headers"`
Body string `yaml:"body"`
StatusCode int `yaml:"status_code"`
}
YAMLResponse is a playback.httpResponse interface encoded to YAML
type YAMLSerializer ¶ added in v1.5.5
type YAMLSerializer struct{}
YAMLSerializer encodes/persists cassettes to files and decodes yaml cassette files.
func (YAMLSerializer) DecodeCassette ¶ added in v1.5.5
func (s YAMLSerializer) DecodeCassette(data []byte) (Cassette, error)
DecodeCassette takes in a []byte of YAML and returns a playback.cassette of it
func (YAMLSerializer) EncodeCassette ¶ added in v1.5.5
func (s YAMLSerializer) EncodeCassette(cassette Cassette) ([]byte, error)
EncodeCassette takes in a playback.cassette and returns an []byte of the YAML-encoded cassette