stitches 3.5.0 → 3.6.0.RC1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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