stitches 3.5.0 → 3.6.0.RC1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 62009fed84cc0754e785017cc928284f840b63fd
4
- data.tar.gz: 4284223ca7d0b02b50de0ec7aed0027a85cd9f20
2
+ SHA256:
3
+ metadata.gz: 9af000ef971218e036d2b46e0bc508749ed8e834f216e857a57c6fb194928fe5
4
+ data.tar.gz: b39481868efbe1c8e373c265d9561d09989e1be3230b4716adf14176d5de63af
5
5
  SHA512:
6
- metadata.gz: b69a8d5c5bc964c70902d0becb072d69af02ed587e9b593ae972bc5df32baa3d9203aadf425791047527f68a9ced87d4f02b0f02eba51871e46604dbf63969fd
7
- data.tar.gz: e375f477cf060d106d42ba8a5ff3644e70dd75c515c7840b68c1010e58aaeedcd16ac73089ccf45479abbbc623c6aa64c4b97ddb66247c2234407051b6c8807f
6
+ metadata.gz: b6eb58de02836b34c9ddda0170c4d0b53f64d4b95e01ca4726213af7ae701978fe772e50d038c46cceb6c78b47de46029f04a9043b08fb4d77e3abe8eb9ba79a
7
+ data.tar.gz: 553079dcb2c338bff86d978db574634802db678344c30655d1db3c67b0625b576034b2f410dae975e04accd7514681df7aee6c0b5c58be2e1a48cb6a3b877a7c
@@ -0,0 +1,102 @@
1
+ # DO NOT MODIFY - this is managed by Git Reduce in goro
2
+ #
3
+ ---
4
+ version: 2
5
+ jobs:
6
+ ruby-2.5.1-rails-5.2:
7
+ docker:
8
+ - image: circleci/ruby:2.5.1
9
+ environment:
10
+ BUNDLE_GEMFILE: Gemfile.rails-5.2
11
+ working_directory: "~/stitches"
12
+ steps:
13
+ - checkout
14
+ - run: bundle install --full-index
15
+ - run: bundle exec rspec --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
16
+ --format=doc
17
+ - run:
18
+ name: Notify Pager Duty
19
+ command: bundle exec y-notify eng-platform
20
+ when: on_fail
21
+ - store_test_results:
22
+ path: "/tmp/test-results"
23
+ ruby-2.4.4-rails-5.2:
24
+ docker:
25
+ - image: circleci/ruby:2.4.4
26
+ environment:
27
+ BUNDLE_GEMFILE: Gemfile.rails-5.2
28
+ working_directory: "~/stitches"
29
+ steps:
30
+ - checkout
31
+ - run: bundle install --full-index
32
+ - run: bundle exec rspec --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
33
+ --format=doc
34
+ - run:
35
+ name: Notify Pager Duty
36
+ command: bundle exec y-notify eng-platform
37
+ when: on_fail
38
+ - store_test_results:
39
+ path: "/tmp/test-results"
40
+ ruby-2.5.1-rails-5.1:
41
+ docker:
42
+ - image: circleci/ruby:2.5.1
43
+ environment:
44
+ BUNDLE_GEMFILE: Gemfile.rails-5.1
45
+ working_directory: "~/stitches"
46
+ steps:
47
+ - checkout
48
+ - run: bundle install --full-index
49
+ - run: bundle exec rspec --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
50
+ --format=doc
51
+ - run:
52
+ name: Notify Pager Duty
53
+ command: bundle exec y-notify eng-platform
54
+ when: on_fail
55
+ - store_test_results:
56
+ path: "/tmp/test-results"
57
+ ruby-2.4.4-rails-5.1:
58
+ docker:
59
+ - image: circleci/ruby:2.4.4
60
+ environment:
61
+ BUNDLE_GEMFILE: Gemfile.rails-5.1
62
+ working_directory: "~/stitches"
63
+ steps:
64
+ - checkout
65
+ - run: bundle install --full-index
66
+ - run: bundle exec rspec --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
67
+ --format=doc
68
+ - run:
69
+ name: Notify Pager Duty
70
+ command: bundle exec y-notify eng-platform
71
+ when: on_fail
72
+ - store_test_results:
73
+ path: "/tmp/test-results"
74
+ workflows:
75
+ version: 2
76
+ on-commit:
77
+ jobs:
78
+ - ruby-2.5.1-rails-5.2:
79
+ context: org-global
80
+ - ruby-2.4.4-rails-5.2:
81
+ context: org-global
82
+ - ruby-2.5.1-rails-5.1:
83
+ context: org-global
84
+ - ruby-2.4.4-rails-5.1:
85
+ context: org-global
86
+ scheduled:
87
+ triggers:
88
+ - schedule:
89
+ cron: 53 20 * * 1,2,3,4,5
90
+ filters:
91
+ branches:
92
+ only:
93
+ - master
94
+ jobs:
95
+ - ruby-2.5.1-rails-5.2:
96
+ context: org-global
97
+ - ruby-2.4.4-rails-5.2:
98
+ context: org-global
99
+ - ruby-2.5.1-rails-5.1:
100
+ context: org-global
101
+ - ruby-2.4.4-rails-5.1:
102
+ context: org-global
data/.gitignore CHANGED
@@ -8,3 +8,5 @@ config/database.yml
8
8
  .DS_Store
9
9
  .jhw-cache
10
10
  **.orig
11
+ Gemfile.lock
12
+ .projections.json
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.3.1
1
+ 2.5.1
data/Gemfile.rails-4.2 ADDED
@@ -0,0 +1,8 @@
1
+ # DO NOT MODIFY - this is managed by Git Reduce in goro
2
+ #
3
+ source 'https://gem.fury.io/me/'
4
+ source 'https://www.rubygems.org'
5
+
6
+ gemspec
7
+
8
+ gem 'rails', '~> 4.2.0'
data/Gemfile.rails-5.0 ADDED
@@ -0,0 +1,8 @@
1
+ # DO NOT MODIFY - this is managed by Git Reduce in goro
2
+ #
3
+ source 'https://gem.fury.io/me/'
4
+ source 'https://www.rubygems.org'
5
+
6
+ gemspec
7
+
8
+ gem 'rails', '~> 5.0.0'
data/Gemfile.rails-5.1 ADDED
@@ -0,0 +1,8 @@
1
+ # DO NOT MODIFY - this is managed by Git Reduce in goro
2
+ #
3
+ source 'https://gem.fury.io/me/'
4
+ source 'https://www.rubygems.org'
5
+
6
+ gemspec
7
+
8
+ gem 'rails', '~> 5.1.0'
data/Gemfile.rails-5.2 ADDED
@@ -0,0 +1,8 @@
1
+ # DO NOT MODIFY - this is managed by Git Reduce in goro
2
+ #
3
+ source 'https://gem.fury.io/me/'
4
+ source 'https://www.rubygems.org'
5
+
6
+ gemspec
7
+
8
+ gem 'rails', '~> 5.2.0'
data/README.md CHANGED
@@ -35,6 +35,21 @@ Then, set it up:
35
35
  > bundle exec rake db:migrate
36
36
  ```
37
37
 
38
+ ### Upgrading from an older version
39
+
40
+ * If you have a version lower than 3.3.0, you need to run two generators, one of which creates a new database migration on your
41
+ `api_clients` table:
42
+
43
+ ```
44
+ > bin/rails generate stitches:add_enabled_to_api_clients
45
+ > bin/rails generate stitches:add_deprecation
46
+ ```
47
+ * If you have a version lower than 3.6.0, you need to run one generator:
48
+
49
+ ```
50
+ > bin/rails generate stitches:add_deprecation
51
+ ```
52
+
38
53
  ## Example Microservice Endpoint
39
54
 
40
55
  Suppose we wish to allow our consumers to create Widgets
@@ -47,7 +62,7 @@ class Api::V1::WidgetsController < ApiController
47
62
  head 201
48
63
  else
49
64
  render json: {
50
- errors: Stitches::Errors.from_active_record(widget)
65
+ errors: Stitches::Errors.from_active_record_object(widget)
51
66
  }, status: 422
52
67
  end
53
68
  end
@@ -82,6 +97,7 @@ See [the wiki](https://github.com/stitchfix/stitches/wiki/Setup) for how to setu
82
97
  - Versioned requests via HTTP content types
83
98
  - Structured Errors
84
99
  - ISO 8601-formatted dates
100
+ - Deprecation using the `Sunset` header
85
101
  * The [Generator](https://github.com/stitchfix/stitches/wiki/Generator) sets up some code in your app, so you can start writing
86
102
  APIs using vanilla Rails idioms:
87
103
  - a "ping" controller that can vaidate your app is working
@@ -95,6 +111,10 @@ APIs using vanilla Rails idioms:
95
111
 
96
112
  Although `Stitches.configuration` is global, do not depend directly on that in your logic. Instead, allow all classes to receive a configuration object in their constructor. This makes the classes easier to deal with and change, without incurring much of a real cost to development. Global symbols suck, but are convienient. This is how you make the most of it.
97
113
 
114
+ Also, the integration test does a lot of "testing the implementation", but since Rails generators are notorious for silently
115
+ failing with a successful result, we have to make sure that the various `inject_into_file` calls are actually working. Do not do
116
+ any fancy refactors here, just keep it up to date.
117
+
98
118
  ---
99
119
 
100
120
  Provided with love by your friends at [Stitch Fix Engineering](http://technology.stitchfix.com)
data/build-matrix.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "build_matrix": {
3
+ }
4
+ }
data/lib/stitches.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'stitches_norailtie'
2
2
  require 'stitches/railtie'
3
- require 'apitome'
3
+ # Add new stitches ruby files to the stitches_norailtie file instead of here
4
+ require 'apitome'
@@ -0,0 +1,15 @@
1
+ require 'rails/generators'
2
+
3
+ module Stitches
4
+ class AddDeprecationGenerator < Rails::Generators::Base
5
+ source_root(File.expand_path(File.join(File.dirname(__FILE__),"generator_files")))
6
+
7
+ desc "Adds deprecation support to an app creates with an older version of stitches"
8
+ def add_deprecation
9
+ inject_into_file "app/controllers/api/api_controller.rb", after: /^class.*$/ do<<-CODE
10
+ include Stitches::Deprecation
11
+ CODE
12
+ end
13
+ end
14
+ end
15
+ end
@@ -12,10 +12,12 @@ module Stitches
12
12
 
13
13
  desc "Bootstraps your API service with a basic ping controller and spec to ensure everything is setup properly"
14
14
  def bootstrap_api
15
- inject_into_file "Gemfile", after: /^gem ["']pg['"].*$/ do<<-GEM
15
+ inject_into_file "Gemfile", after: /^gem ['"]rails['"].*$/ do<<-GEM
16
16
 
17
17
  gem "apitome"
18
18
  gem "responders"
19
+ gem "rspec_api_documentation", group: [ :development, :test ]
20
+ gem "capybara", group: [ :development, :test ]
19
21
  GEM
20
22
  end
21
23
  inject_into_file "config/routes.rb", before: /^end/ do<<-ROUTES
@@ -51,12 +53,6 @@ mount api_docs, at: "docs"
51
53
  template "spec/features/api_spec.rb.erb", "spec/features/api_spec.rb"
52
54
  copy_file "spec/acceptance/ping_v1_spec.rb", "spec/acceptance/ping_v1_spec.rb"
53
55
 
54
- inject_into_file "Gemfile", after: /^group :test, :development do.*$/ do<<-GEM
55
-
56
- gem "rspec_api_documentation"
57
- gem "capybara"
58
- GEM
59
- end
60
56
  run 'bundle install'
61
57
 
62
58
  migration_template "db/migrate/enable_uuid_ossp_extension.rb", "db/migrate/enable_uuid_ossp_extension.rb"
@@ -0,0 +1,26 @@
1
+ module Stitches
2
+ module Deprecation
3
+ # Indicate that a previously-deprecated endpoint is now gone
4
+ def gone!
5
+ head 410
6
+ end
7
+
8
+ # Indicate that this endpoint is deprecated and will go away on the given date.
9
+ #
10
+ # gon_on: - date, as a string, when this endpoint will go away
11
+ # block - the contents of the endpoint
12
+ #
13
+ # Example:
14
+ #
15
+ # def show
16
+ # deprecated gone_on: "2019-04-09" do
17
+ # render widgets: { Widget.find(params[:id]) }
18
+ # end
19
+ # end
20
+ def deprecated(gone_on:,&block)
21
+ response.set_header("Sunset",Date.parse(gone_on).in_time_zone("GMT").midnight.strftime("%a, %e %b %Y %H:%M:%S %Z"))
22
+ Rails.logger.info("Deprecated endpoint #{request.method} #{request.fullpath} requested by #{current_user.id}")
23
+ block.()
24
+ end
25
+ end
26
+ end
@@ -1,4 +1,5 @@
1
1
  class Api::ApiController < ActionController::Base
2
+ include Stitches::Deprecation
2
3
  rescue_from ActiveRecord::RecordNotFound do |exception|
3
4
  respond_to do |type|
4
5
  type.json { render json: { errors: Stitches::Errors.new([ Stitches::Error.new(code: "not_found", message: exception.message) ]) }, status: 404 }
data/lib/stitches/spec.rb CHANGED
@@ -2,3 +2,5 @@ require 'stitches/spec/be_iso_8601_utc_encoded'
2
2
  require 'stitches/spec/have_api_error'
3
3
  require 'stitches/spec/api_clients'
4
4
  require 'stitches/spec/test_headers'
5
+ require 'stitches/spec/be_gone'
6
+ require 'stitches/spec/show_deprecation'
@@ -0,0 +1,19 @@
1
+ # Use this to test that an HTTP response is properly "410/Gone"
2
+ #
3
+ # The object you expect on is generally `self`, because this is the object on which
4
+ # rspec_api_documentation allows you to call `status`
5
+ #
6
+ # get "/api/widgets" do
7
+ # it "has been removed" do
8
+ # expect(self).to be_gone
9
+ # end
10
+ # end
11
+ RSpec::Matchers.define :be_gone do
12
+ match do |rspec_api_documentation_context|
13
+ rspec_api_documentation_context.status == 410
14
+ end
15
+ failure_message do |rspec_api_documentation_context|
16
+ "Expected HTTP status to be 410/Gone, but it was #{rspec_api_documentation_context.status}"
17
+ end
18
+ end
19
+
@@ -0,0 +1,46 @@
1
+ class DeprecationAnalysis
2
+ attr_reader :expected_sunset_value, :status, :sunset_value
3
+ def initialize(rspec_api_documentation_context, gone_on)
4
+ gone_on_date = Date.parse(gone_on)
5
+ if gone_on_date > Date.today
6
+ @expecting_sunset = true
7
+ @expected_sunset_value = gone_on_date.in_time_zone("GMT").midnight.strftime("%a, %e %b %Y %H:%M:%S %Z")
8
+ @sunset_header_set = rspec_api_documentation_context.response_headers["Sunset"].present?
9
+ @sunset_value = rspec_api_documentation_context.response_headers["Sunset"]
10
+ @sunset_header_match = @expected_sunset_value == @sunset_value
11
+ else
12
+ @expecting_gone = true
13
+ @status = rspec_api_documentation_context.status
14
+ @gone = @status == 410
15
+ end
16
+ end
17
+
18
+ def expecting_sunset?; !!@expecting_sunset; end
19
+ def sunset_header_set?; !!@sunset_header_set; end
20
+ def sunset_header_match?; !!@sunset_header_match; end
21
+ def gone?; !!@gone; end
22
+ end
23
+
24
+ RSpec::Matchers.define :show_deprecation do |gone_on:|
25
+ match do |rspec_api_documentation_context|
26
+ analysis = DeprecationAnalysis.new(rspec_api_documentation_context,gone_on)
27
+ if analysis.expecting_sunset?
28
+ analysis.sunset_header_match?
29
+ else
30
+ analysis.gone?
31
+ end
32
+ end
33
+ failure_message do |rspec_api_documentation_context|
34
+ analysis = DeprecationAnalysis.new(rspec_api_documentation_context,gone_on)
35
+ if analysis.expecting_sunset?
36
+ if analysis.sunset_header_set?
37
+ "Expected the Sunset header to be #{analysis.expected_sunset_value}, but was '#{analysis.sunset_value}'"
38
+ else
39
+ "No Sunset header was set. Use the deprecation method from Stitches::Deprecation in the controller method to deprecate this action"
40
+ end
41
+ else
42
+ "Since the deprecation date has passed, expected HTTP status to be 410/Gone, but it was #{analysis.status}. Re-implement the endpoint using `gone!` and then change the test to expect it to `be_gone`"
43
+ end
44
+ end
45
+ end
46
+
@@ -1,3 +1,3 @@
1
1
  module Stitches
2
- VERSION = '3.5.0'
2
+ VERSION = '3.6.0.RC1'
3
3
  end
@@ -12,7 +12,9 @@ require 'stitches/render_timestamps_in_iso8601_in_json'
12
12
  require 'stitches/error'
13
13
  require 'stitches/errors'
14
14
  require 'stitches/api_generator'
15
+ require 'stitches/add_deprecation_generator'
15
16
  require 'stitches/add_enabled_to_api_clients_generator'
16
17
  require 'stitches/api_version_constraint'
17
18
  require 'stitches/api_key'
19
+ require 'stitches/deprecation'
18
20
  require 'stitches/valid_mime_type'
data/owners.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "owners": [
3
+ {
4
+ "team": "platform"
5
+ }
6
+ ]
7
+ }
@@ -0,0 +1,49 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Stitches::Deprecation do
4
+ let(:response) {
5
+ double("response").tap { |r|
6
+ allow(r).to receive(:set_header)
7
+ }
8
+ }
9
+ let(:request) { double("request", method: "PUT", fullpath: "/foo/bar?blah") }
10
+ let(:api_client) { double("ApiClient", id: 99) }
11
+ let(:fake_controller) {
12
+ double("Controller", response: response, request: request, current_user: api_client).extend(described_class).tap { |controller|
13
+ allow(controller).to receive(:head)
14
+ }
15
+ }
16
+ describe "#gone" do
17
+ it "sends an HTTP 410" do
18
+ fake_controller.gone!
19
+ expect(fake_controller).to have_received(:head).with(410)
20
+ end
21
+ end
22
+
23
+ describe "#deprecated" do
24
+ let(:logger) { double("logger") }
25
+ before do
26
+ allow(Rails).to receive(:logger).and_return(logger)
27
+ allow(logger).to receive(:info)
28
+ end
29
+ it "sets the Sunset header to the date given in GMT" do
30
+ fake_controller.deprecated(gone_on: "2018-01-01") {}
31
+ expect(response).to have_received(:set_header).with("Sunset","Mon, 1 Jan 2018 00:00:00 GMT")
32
+ end
33
+ it "logs about the request and current API key id" do
34
+ fake_controller.deprecated(gone_on: "2018-01-01") {}
35
+ expect(logger).to have_received(:info).with(/deprecated.*#{Regexp.escape(request.method)}.*#{Regexp.escape(request.fullpath)}.*#{Regexp.escape(api_client.id.to_s)}/i)
36
+ end
37
+ it "executes and returns the block" do
38
+ block_executed = false
39
+ result = fake_controller.deprecated(gone_on: "2018-01-01") do
40
+ block_executed = true
41
+ 42
42
+ end
43
+ aggregate_failures do
44
+ expect(result).to eq(42)
45
+ expect(block_executed).to eq(true)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,200 @@
1
+ require "spec_helper"
2
+ require "fileutils"
3
+ require "open3"
4
+
5
+ RSpec.describe "Adding Stitches to a New Rails App" do
6
+ let(:work_dir) { Dir.mktmpdir }
7
+ let(:rails_app_name) { "swamp-thing" }
8
+
9
+ def run(command)
10
+ stdout, stderr, stat = Open3.capture3(command)
11
+ success = stat.success? && stdout !~ /Could not find generator/im
12
+
13
+ if ENV["DEBUG"] == 'true' || !success
14
+ $stdout.puts stdout
15
+ $stderr.puts stderr
16
+ end
17
+ unless success
18
+ raise "'#{command}' failed"
19
+ end
20
+ end
21
+
22
+ around(:each) do |example|
23
+ rails_new = [
24
+ "rails new #{rails_app_name}",
25
+ "--skip-yarn",
26
+ "--skip-git",
27
+ "--skip-keeps",
28
+ "--skip-action-mailer",
29
+ "--skip-active-storage",
30
+ "--skip-action-cable",
31
+ "--skip-spring",
32
+ "--skip-listen",
33
+ "--skip-coffee",
34
+ "--skip-javascript",
35
+ "--skip-turbolinks",
36
+ "--skip-bootsnap",
37
+ "--no-rc",
38
+ "--skip-bundle",
39
+ ].join(" ")
40
+ FileUtils.chdir work_dir do
41
+ run rails_new
42
+ FileUtils.chdir rails_app_name do
43
+ example.run
44
+ end
45
+ end
46
+ end
47
+
48
+ it "works as described in the README" do
49
+ run "bin/rails generate rspec:install"
50
+ run "bin/rails generate apitome:install"
51
+ run "bin/rails generate stitches:api"
52
+
53
+ rails_root = Pathname(work_dir) / rails_app_name
54
+
55
+ # Yuck! So much duplication! BUT: Rails app templates have a notoriously silent failure mode, so mostly
56
+ # what this is doing is ensuring that the generator inserted stuff when asked and that the very basics of what happens
57
+ # during generation are there. It's gross, and I'm sorry.
58
+ #
59
+ # It's also in one big block because making a new rails app and running the generator multiple times seems bad.
60
+ aggregate_failures do
61
+ expect(File.exist?(rails_root / "app" / "controllers" / "api" / "api_controller.rb")).to eq(true)
62
+ expect(rails_root / "Gemfile").to contain_gem("apitome")
63
+ expect(rails_root / "Gemfile").to contain_gem("responders")
64
+ expect(rails_root / "Gemfile").to contain_gem("rspec_api_documentation")
65
+ expect(rails_root / "Gemfile").to contain_gem("capybara")
66
+ expect(rails_root / "config" / "routes.rb").to have_route(namespace: :api, module_scope: :v1, resource: 'ping')
67
+ expect(rails_root / "config" / "routes.rb").to have_route(namespace: :api, module_scope: :v2, resource: 'ping')
68
+ expect(rails_root / "config" / "routes.rb").to have_mounted_engine("Apitome::Engine")
69
+ migrations = Dir["#{rails_root}/db/migrate/*.rb"].sort
70
+ expect(migrations.size).to eq(2)
71
+ expect(migrations[0]).to match(/\/\d+_enable_uuid_ossp_extension.rb/)
72
+ expect(migrations[1]).to match(/\/\d+_create_api_clients.rb/)
73
+ expect(File.read(rails_root / "spec" / "rails_helper.rb")).to include("config.include RSpec::Rails::RequestExampleGroup, type: :feature")
74
+ expect(File.read(rails_root / "spec" / "rails_helper.rb")).to include("require 'stitches/spec'")
75
+ expect(File.read(rails_root / "spec" / "rails_helper.rb")).to include("require 'rspec_api_documentation'")
76
+ expect(File.read(rails_root / "config" / "initializers" / "apitome.rb")).to include("config.mount_at = nil")
77
+ expect(File.read(rails_root / "config" / "initializers" / "apitome.rb")).to include("config.title = 'Service Documentation'")
78
+ end
79
+ end
80
+
81
+ it "inserts the deprecation module into ApiController" do
82
+ run "bin/rails generate rspec:install"
83
+ run "bin/rails generate apitome:install"
84
+ run "bin/rails generate stitches:api"
85
+
86
+ rails_root = Pathname(work_dir) / rails_app_name
87
+ api_controller = rails_root / "app" / "controllers" / "api" / "api_controller.rb"
88
+
89
+ api_controller_contents = File.read(api_controller).split(/\n/)
90
+ File.open(api_controller,"w") do |file|
91
+ api_controller_contents.each do |line|
92
+ file.puts line unless line =~ /Stitches::Deprecation/
93
+ end
94
+ end
95
+
96
+ run "bin/rails generate stitches:add_deprecation"
97
+
98
+ aggregate_failures do
99
+ expect(File.read(api_controller)).to include("include Stitches::Deprecation")
100
+ end
101
+ end
102
+
103
+ class RoutesFileAnalysis
104
+ attr_reader :routes_file
105
+ def initialize(routes_file, namespace: nil, module_scope: nil, resource: nil, mounted_engine: nil)
106
+ @routes_file = File.read(routes_file).split(/\n/)
107
+ @found_namespace = false
108
+ @found_module = false
109
+ @found_resource = false
110
+ @found_engine = false
111
+ @engine_mounted = false
112
+
113
+ @routes_file.each do |line|
114
+ if line =~ /namespace :#{namespace} do/
115
+ @found_namespace = true
116
+ end
117
+ if @found_namespace && line =~ /^\s*scope module: :#{module_scope}, constraints: Stitches::ApiVersionConstraint/
118
+ @found_module = true
119
+ end
120
+ if @found_module && line =~ /^\s*resource\s+['"]#{resource}["']/
121
+ @found_resource = true
122
+ end
123
+ if line =~ /api_docs = Rack::Auth::Basic.new\(#{mounted_engine}/
124
+ @found_engine = true
125
+ end
126
+ if @found_engine && line =~ /mount api_docs/
127
+ @engine_mounted = true
128
+ end
129
+ end
130
+ end
131
+
132
+ def found_namespace?
133
+ @found_namespace
134
+ end
135
+
136
+ def found_module?
137
+ @found_module
138
+ end
139
+
140
+ def found_resource?
141
+ @found_resource
142
+ end
143
+
144
+ def found_engine?
145
+ @found_engine
146
+ end
147
+
148
+ def engine_mounted?
149
+ @engine_mounted
150
+ end
151
+ end
152
+
153
+ RSpec::Matchers.define :have_mounted_engine do |engine_name|
154
+ match do |routes_file|
155
+ analysis = RoutesFileAnalysis.new(routes_file, mounted_engine: engine_name)
156
+ analysis.engine_mounted?
157
+ end
158
+ failure_message do |routes_file|
159
+ analysis = RoutesFileAnalysis.new(routes_file, mounted_engine: engine_name)
160
+ error = if analysis.found_engine?
161
+ "Found engine #{engine_name}, but it's not mounted"
162
+ else
163
+ "Didn't find engine #{engine_name}"
164
+ end
165
+
166
+ error + "\n#{File.read(analysis.routes_file.join("\n"))}"
167
+ end
168
+ end
169
+
170
+ RSpec::Matchers.define :have_route do |namespace:, module_scope:, resource:|
171
+ match do |routes_file|
172
+ analysis = RoutesFileAnalysis.new(routes_file, namespace: namespace, module_scope: module_scope, resource: resource)
173
+ analysis.found_resource? && analysis.found_module? && analysis.found_namespace?
174
+ end
175
+
176
+ failure_message do |routes_file|
177
+ analysis = RoutesFileAnalysis.new(routes_file, namespace: namespace, module_scope: module_scope, resource: resource)
178
+ error = if analysis.found_namespace?
179
+ if analysis.found_module?
180
+ "Could not find resource '#{resource}'"
181
+ else
182
+ "Could not find module '#{scope_module}'"
183
+ end
184
+ else
185
+ "Could not find namespace '#{namespace}'"
186
+ end
187
+ error + "\n#{File.read(analysis.routes_file.join("\n"))}"
188
+ end
189
+ end
190
+ RSpec::Matchers.define :contain_gem do |gem_name|
191
+ match do |gemfile|
192
+ File.read(gemfile).split(/\n/).any? { |line|
193
+ line =~ /^\s*gem [\"\']#{gem_name}[\"\']/
194
+ }
195
+ end
196
+ failure_message do |gemfile|
197
+ "#{gem_name} not found in #{gemfile}:\n#{File.read(gemfile)}"
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper.rb'
2
+ require 'stitches/spec'
3
+
4
+ describe "be_gone" do
5
+ it "passes the test if the http status was 410" do
6
+ rspec_api_documentation_context = double("a test using rspec_api_documentation", status: 410)
7
+ expect(rspec_api_documentation_context).to be_gone
8
+ end
9
+ it "fails the test if the http status was not 401" do
10
+ rspec_api_documentation_context = double("a test using rspec_api_documentation", status: 200)
11
+ begin
12
+ expect(rspec_api_documentation_context).to be_gone
13
+ fail "Expected the matcher to fail"
14
+ rescue RSpec::Expectations::ExpectationNotMetError => e
15
+ expect(e.message).to match(/expected http status.*410.*200/i)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper.rb'
2
+ require 'stitches/spec'
3
+
4
+ describe "show_deprecation" do
5
+ context "when the expiration date is in the future" do
6
+ it "passes if the sunset header is set properly" do
7
+ rspec_api_documentation_context = double(
8
+ "a test using rspec_api_documentation",
9
+ status: 200,
10
+ response_headers: { "Sunset" => "Mon, 13 Dec 2038 00:00:00 GMT" }
11
+ )
12
+
13
+ expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2038-12-13")
14
+ end
15
+ it "fails if the sunset header is not set" do
16
+ begin
17
+ rspec_api_documentation_context = double( "a test using rspec_api_documentation", status: 200, response_headers: {})
18
+ expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2038-12-13")
19
+ fail "Expected matcher to fail"
20
+ rescue RSpec::Expectations::ExpectationNotMetError => e
21
+ expect(e.message).to match(/No Sunset header was set/i)
22
+ end
23
+ end
24
+ it "fails if the sunset header is the wrong date" do
25
+ begin
26
+ rspec_api_documentation_context = double(
27
+ "a test using rspec_api_documentation",
28
+ status: 200,
29
+ response_headers: { "Sunset" => "Tuesday, 14 Dec 2038 00:00:00 GMT" }
30
+ )
31
+ expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2038-12-13")
32
+ fail "Expected matcher to fail"
33
+ rescue RSpec::Expectations::ExpectationNotMetError => e
34
+ expect(e.message).to match(/Expected the Sunset header to be Mon, 13 Dec 2038 00:00:00 GMT, but was 'Tuesday, 14 Dec 2038 00:00:00 GMT/i)
35
+ end
36
+ end
37
+ end
38
+ context "when the expiration date is in the past" do
39
+ it "passes if the http status is 410/gone" do
40
+ rspec_api_documentation_context = double("a test using rspec_api_documentation", status: 410)
41
+ expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2011-01-01")
42
+ end
43
+
44
+ it "fails if the status is not 410/gone" do
45
+ rspec_api_documentation_context = double("a test using rspec_api_documentation", status: 200)
46
+ begin
47
+ expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2011-01-01")
48
+ fail "Expected matcher to fail"
49
+ rescue RSpec::Expectations::ExpectationNotMetError => e
50
+ expect(e.message).to match(/deprecation date has passed.*410.*using `gone\!`/i)
51
+ end
52
+ end
53
+ end
54
+ end
data/stitches.gemspec CHANGED
@@ -17,10 +17,13 @@ Gem::Specification.new do |s|
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.executables = `git ls-files -- exe/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
+
20
21
  s.add_runtime_dependency("rails")
21
22
  s.add_runtime_dependency("pg")
22
- s.add_development_dependency("rake")
23
23
  s.add_runtime_dependency("rspec", ">= 3")
24
24
  s.add_runtime_dependency("rspec-rails", "~> 3")
25
25
  s.add_runtime_dependency("apitome")
26
+
27
+ s.add_development_dependency("rake")
28
+ s.add_development_dependency("rspec_junit_formatter")
26
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stitches
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.6.0.RC1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stitch Fix Engineering
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2017-08-24 00:00:00.000000000 Z
14
+ date: 2018-06-11 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rails
@@ -41,20 +41,6 @@ dependencies:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
43
  version: '0'
44
- - !ruby/object:Gem::Dependency
45
- name: rake
46
- requirement: !ruby/object:Gem::Requirement
47
- requirements:
48
- - - ">="
49
- - !ruby/object:Gem::Version
50
- version: '0'
51
- type: :development
52
- prerelease: false
53
- version_requirements: !ruby/object:Gem::Requirement
54
- requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- version: '0'
58
44
  - !ruby/object:Gem::Dependency
59
45
  name: rspec
60
46
  requirement: !ruby/object:Gem::Requirement
@@ -97,6 +83,34 @@ dependencies:
97
83
  - - ">="
98
84
  - !ruby/object:Gem::Version
99
85
  version: '0'
86
+ - !ruby/object:Gem::Dependency
87
+ name: rake
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ - !ruby/object:Gem::Dependency
101
+ name: rspec_junit_formatter
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
100
114
  description: You'll be in stitches at how easy it is to create a service at Stitch
101
115
  Fix
102
116
  email:
@@ -108,6 +122,7 @@ executables: []
108
122
  extensions: []
109
123
  extra_rdoc_files: []
110
124
  files:
125
+ - ".circleci/config.yml"
111
126
  - ".gitignore"
112
127
  - ".ruby-gemset"
113
128
  - ".ruby-version"
@@ -115,16 +130,22 @@ files:
115
130
  - CODE_OF_CONDUCT.md
116
131
  - CONTRIBUTING.md
117
132
  - Gemfile
118
- - Gemfile.lock
133
+ - Gemfile.rails-4.2
134
+ - Gemfile.rails-5.0
135
+ - Gemfile.rails-5.1
136
+ - Gemfile.rails-5.2
119
137
  - LICENSE.txt
120
138
  - README.md
121
139
  - Rakefile
140
+ - build-matrix.json
122
141
  - lib/stitches.rb
142
+ - lib/stitches/add_deprecation_generator.rb
123
143
  - lib/stitches/add_enabled_to_api_clients_generator.rb
124
144
  - lib/stitches/api_generator.rb
125
145
  - lib/stitches/api_key.rb
126
146
  - lib/stitches/api_version_constraint.rb
127
147
  - lib/stitches/configuration.rb
148
+ - lib/stitches/deprecation.rb
128
149
  - lib/stitches/error.rb
129
150
  - lib/stitches/errors.rb
130
151
  - lib/stitches/generator_files/app/controllers/api.rb
@@ -145,19 +166,26 @@ files:
145
166
  - lib/stitches/render_timestamps_in_iso8601_in_json.rb
146
167
  - lib/stitches/spec.rb
147
168
  - lib/stitches/spec/api_clients.rb
169
+ - lib/stitches/spec/be_gone.rb
148
170
  - lib/stitches/spec/be_iso_8601_utc_encoded.rb
149
171
  - lib/stitches/spec/have_api_error.rb
172
+ - lib/stitches/spec/show_deprecation.rb
150
173
  - lib/stitches/spec/test_headers.rb
151
174
  - lib/stitches/valid_mime_type.rb
152
175
  - lib/stitches/version.rb
153
176
  - lib/stitches/whitelisting_middleware.rb
154
177
  - lib/stitches_norailtie.rb
178
+ - owners.json
155
179
  - spec/api_key_spec.rb
156
180
  - spec/api_version_constraint_spec.rb
157
181
  - spec/configuration_spec.rb
182
+ - spec/deprecation_spec.rb
158
183
  - spec/error_spec.rb
159
184
  - spec/errors_spec.rb
185
+ - spec/integration/add_to_rails_app_spec.rb
186
+ - spec/spec/be_gone_spec.rb
160
187
  - spec/spec/have_api_error_spec.rb
188
+ - spec/spec/show_deprecation_spec.rb
161
189
  - spec/spec_helper.rb
162
190
  - spec/valid_mime_type_spec.rb
163
191
  - stitches.gemspec
@@ -176,12 +204,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
204
  version: '0'
177
205
  required_rubygems_version: !ruby/object:Gem::Requirement
178
206
  requirements:
179
- - - ">="
207
+ - - ">"
180
208
  - !ruby/object:Gem::Version
181
- version: '0'
209
+ version: 1.3.1
182
210
  requirements: []
183
211
  rubyforge_project:
184
- rubygems_version: 2.5.1
212
+ rubygems_version: 2.7.6
185
213
  signing_key:
186
214
  specification_version: 4
187
215
  summary: You'll be in stitches at how easy it is to create a service at Stitch Fix
@@ -189,8 +217,12 @@ test_files:
189
217
  - spec/api_key_spec.rb
190
218
  - spec/api_version_constraint_spec.rb
191
219
  - spec/configuration_spec.rb
220
+ - spec/deprecation_spec.rb
192
221
  - spec/error_spec.rb
193
222
  - spec/errors_spec.rb
223
+ - spec/integration/add_to_rails_app_spec.rb
224
+ - spec/spec/be_gone_spec.rb
194
225
  - spec/spec/have_api_error_spec.rb
226
+ - spec/spec/show_deprecation_spec.rb
195
227
  - spec/spec_helper.rb
196
228
  - spec/valid_mime_type_spec.rb
data/Gemfile.lock DELETED
@@ -1,154 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- stitches (3.5.0)
5
- apitome
6
- pg
7
- rails
8
- rspec (>= 3)
9
- rspec-rails (~> 3)
10
-
11
- GEM
12
- remote: https://www.rubygems.org/
13
- specs:
14
- actioncable (5.1.3)
15
- actionpack (= 5.1.3)
16
- nio4r (~> 2.0)
17
- websocket-driver (~> 0.6.1)
18
- actionmailer (5.1.3)
19
- actionpack (= 5.1.3)
20
- actionview (= 5.1.3)
21
- activejob (= 5.1.3)
22
- mail (~> 2.5, >= 2.5.4)
23
- rails-dom-testing (~> 2.0)
24
- actionpack (5.1.3)
25
- actionview (= 5.1.3)
26
- activesupport (= 5.1.3)
27
- rack (~> 2.0)
28
- rack-test (~> 0.6.3)
29
- rails-dom-testing (~> 2.0)
30
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
31
- actionview (5.1.3)
32
- activesupport (= 5.1.3)
33
- builder (~> 3.1)
34
- erubi (~> 1.4)
35
- rails-dom-testing (~> 2.0)
36
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
37
- activejob (5.1.3)
38
- activesupport (= 5.1.3)
39
- globalid (>= 0.3.6)
40
- activemodel (5.1.3)
41
- activesupport (= 5.1.3)
42
- activerecord (5.1.3)
43
- activemodel (= 5.1.3)
44
- activesupport (= 5.1.3)
45
- arel (~> 8.0)
46
- activesupport (5.1.3)
47
- concurrent-ruby (~> 1.0, >= 1.0.2)
48
- i18n (~> 0.7)
49
- minitest (~> 5.1)
50
- tzinfo (~> 1.1)
51
- apitome (0.1.0)
52
- kramdown
53
- railties
54
- rspec_api_documentation
55
- arel (8.0.0)
56
- builder (3.2.3)
57
- concurrent-ruby (1.0.5)
58
- diff-lcs (1.3)
59
- erubi (1.6.1)
60
- globalid (0.4.0)
61
- activesupport (>= 4.2.0)
62
- i18n (0.8.6)
63
- kramdown (1.14.0)
64
- loofah (2.0.3)
65
- nokogiri (>= 1.5.9)
66
- mail (2.6.6)
67
- mime-types (>= 1.16, < 4)
68
- method_source (0.8.2)
69
- mime-types (3.1)
70
- mime-types-data (~> 3.2015)
71
- mime-types-data (3.2016.0521)
72
- mini_portile2 (2.2.0)
73
- minitest (5.10.3)
74
- mustache (1.0.5)
75
- nio4r (2.1.0)
76
- nokogiri (1.8.0)
77
- mini_portile2 (~> 2.2.0)
78
- pg (0.21.0)
79
- rack (2.0.3)
80
- rack-test (0.6.3)
81
- rack (>= 1.0)
82
- rails (5.1.3)
83
- actioncable (= 5.1.3)
84
- actionmailer (= 5.1.3)
85
- actionpack (= 5.1.3)
86
- actionview (= 5.1.3)
87
- activejob (= 5.1.3)
88
- activemodel (= 5.1.3)
89
- activerecord (= 5.1.3)
90
- activesupport (= 5.1.3)
91
- bundler (>= 1.3.0)
92
- railties (= 5.1.3)
93
- sprockets-rails (>= 2.0.0)
94
- rails-dom-testing (2.0.3)
95
- activesupport (>= 4.2.0)
96
- nokogiri (>= 1.6)
97
- rails-html-sanitizer (1.0.3)
98
- loofah (~> 2.0)
99
- railties (5.1.3)
100
- actionpack (= 5.1.3)
101
- activesupport (= 5.1.3)
102
- method_source
103
- rake (>= 0.8.7)
104
- thor (>= 0.18.1, < 2.0)
105
- rake (12.0.0)
106
- rspec (3.6.0)
107
- rspec-core (~> 3.6.0)
108
- rspec-expectations (~> 3.6.0)
109
- rspec-mocks (~> 3.6.0)
110
- rspec-core (3.6.0)
111
- rspec-support (~> 3.6.0)
112
- rspec-expectations (3.6.0)
113
- diff-lcs (>= 1.2.0, < 2.0)
114
- rspec-support (~> 3.6.0)
115
- rspec-mocks (3.6.0)
116
- diff-lcs (>= 1.2.0, < 2.0)
117
- rspec-support (~> 3.6.0)
118
- rspec-rails (3.6.0)
119
- actionpack (>= 3.0)
120
- activesupport (>= 3.0)
121
- railties (>= 3.0)
122
- rspec-core (~> 3.6.0)
123
- rspec-expectations (~> 3.6.0)
124
- rspec-mocks (~> 3.6.0)
125
- rspec-support (~> 3.6.0)
126
- rspec-support (3.6.0)
127
- rspec_api_documentation (5.0.0)
128
- activesupport (>= 3.0.0)
129
- mustache (~> 1.0, >= 0.99.4)
130
- rspec (~> 3.0)
131
- sprockets (3.7.1)
132
- concurrent-ruby (~> 1.0)
133
- rack (> 1, < 3)
134
- sprockets-rails (3.2.0)
135
- actionpack (>= 4.0)
136
- activesupport (>= 4.0)
137
- sprockets (>= 3.0.0)
138
- thor (0.20.0)
139
- thread_safe (0.3.6)
140
- tzinfo (1.2.3)
141
- thread_safe (~> 0.1)
142
- websocket-driver (0.6.5)
143
- websocket-extensions (>= 0.1.0)
144
- websocket-extensions (0.1.2)
145
-
146
- PLATFORMS
147
- ruby
148
-
149
- DEPENDENCIES
150
- rake
151
- stitches!
152
-
153
- BUNDLED WITH
154
- 1.15.3