rack-downtime 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cdd5cb910c23cd91f064883334d4e56c25124deb
4
+ data.tar.gz: 106113c2a731759876a1aadc4c5aa9b031149862
5
+ SHA512:
6
+ metadata.gz: 2bd6d11b99494837a1b4f857a9894a4f38a9c839182ca78f41c3eae7ac1c62cad46275a97418a4a5f7d82b43a282b23915f7dba5da3f3ec8560dbad674c45045
7
+ data.tar.gz: cc874fd53780e82e4ec467f364aa2fa08b964a6af6370a9882301bfcc9aa123a7992a0ba0ec6003f705e05f7464d84c1d475596b336059518bec0787e7d0dcf4
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-19mode
5
+ - 2.0
6
+ - 2.1
7
+
8
+ notifications:
9
+ email: false
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 sshaw
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,190 @@
1
+ # Rack::Downtime
2
+
3
+ Planned downtime management for Rack applications
4
+
5
+ [![Build Status](https://travis-ci.org/sshaw/rack-downtime.svg?branch=master)](https://travis-ci.org/sshaw/rack-dowmtime)
6
+ [![Code Climate](https://codeclimate.com/github/sshaw/rack-downtime/badges/gpa.svg)](https://codeclimate.com/github/sshaw/rack-downtime)
7
+
8
+ ## Overview
9
+
10
+ `Rack::Dowtime` **does not** add a maintenance page -there are *plenty* of ways to do this already. Instead,
11
+ it provides one with a variety of simple ways to trigger and takedown planned maintenance notifications while a site
12
+ **is still up**.
13
+
14
+ ### Examples
15
+
16
+ ```ruby
17
+ require "rack/downtime"
18
+
19
+ use Rack::Downtime
20
+ ```
21
+
22
+ In your layout:
23
+
24
+ ```erb
25
+ <% if ENV.include?("rack.downtime") %>
26
+ <div>
27
+ <p>
28
+ We will be down for maintenance on
29
+ <%= ENV["rack.downtime"][0].strftime("%b %e") %> from
30
+ <%= ENV["rack.downtime"][0].strftime("%l:%M %p") %> to
31
+ <%= ENV["rack.downtime"][1].strftime("%l:%M %p") %> EST.
32
+ </p>
33
+ </div>
34
+ <% end %>
35
+ ```
36
+
37
+ Now set the downtime using the `:file` strategy:
38
+
39
+ ```
40
+ # 1 to 4 AM
41
+ > echo '2014-11-15T01:00/2014-11-15T04:00' > downtime.txt
42
+ ```
43
+
44
+ If you prefer, `Rack::Downtime` can insert the message for you:
45
+
46
+ ```ruby
47
+ # Inserts a downtime message
48
+ use Rack::Downtime, :insert => "my_template.erb"
49
+
50
+ # Specify where to insert message
51
+ use Rack::Downtime, :insert => "my_template.erb", :insert_at => "body #container"
52
+ ```
53
+
54
+ The downtime can be set various ways:
55
+
56
+ ```ruby
57
+ # From the HTTP header X-Downtime
58
+ use Rack::Downtime, :strategy => :header
59
+ use Rack::Downtime, :strategy => { :header => "X-MyHeader" }
60
+
61
+ # Or from the query string
62
+ use Rack::Downtime, :strategy => :query
63
+ use Rack::Downtime, :strategy => { :query => "dwn__" }
64
+ ```
65
+
66
+ Alternate configuration:
67
+
68
+ ```ruby
69
+ Rack::Downtime.strategy = :file
70
+ Rack::Downtime::Strategy::File.path = Rails.root.join("downtime.txt")
71
+ ```
72
+
73
+ Control its behavior via environment variables:
74
+
75
+ ```
76
+ SetEnv RACK_DOWNTIME 2014-11-15T01:00:00-05/2014-11-15T04:00:00-05
77
+
78
+ # Disable
79
+ SetEnv RACK_DOWNTIME_DISABLE 1
80
+
81
+ # Or, just turn of insertion
82
+ SetEnv RACK_DOWNTIME_INSERT 0
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ Downtime can be retrieved from various locations via a [downtime strategy](#downtime-strategies). When downtime is detected,
88
+ it's turned into 2 instances of `DateTime` and added to the Rack environment at `rack.downtime`. The 0th
89
+ element is the start time and the 1st element is the end time.
90
+
91
+ The dates must be given as an [ISO 8601 time interval](https://en.wikipedia.org/wiki/ISO_8601#Time_intervals)
92
+ in `start/end` format. If no dates are found `rack.downtime` will not be set.
93
+
94
+ Downtime messages can also be added to the response's body. See *[Inserting a Downtime Message](#inserting-a-downtime-message)*.
95
+
96
+ ### Downtime Strategies
97
+
98
+ Strategies are given via the `:strategy` option. If none is provided then `Rack::Downtime.strategy`
99
+ is used, which defaults to `:file`.
100
+
101
+ #### `:file`
102
+
103
+ Looks in the current directory for a file named `downtime.txt`.
104
+
105
+ ```ruby
106
+ use Rack::Downtime :strategy => :file
107
+ ```
108
+
109
+ To use a file named `my_file.txt`:
110
+
111
+ ```ruby
112
+ use Rack::Downtime :strategy => { :file => "my_file.txt" }
113
+ ```
114
+
115
+ #### `:query`
116
+
117
+ Looks for a query string parameter named `__dt__`.
118
+
119
+ ```ruby
120
+ use Rack::Downtime :strategy => :query
121
+ ```
122
+
123
+ To use a query string named `q`:
124
+
125
+ ```ruby
126
+ use Rack::Downtime :strategy => { :query => "q" }
127
+ ```
128
+
129
+ #### `:header`
130
+
131
+ Looks for an HTTP header named `X-Downtime`.
132
+
133
+ ```ruby
134
+ use Rack::Downtime :strategy => :header
135
+ ```
136
+ To look for a header named `X-DT-4SHO`:
137
+
138
+ ```ruby
139
+ use Rack::Downtime :strategy => { :header => "X-DT-4SHO" }
140
+ ```
141
+
142
+ Internally the header will be converted to match
143
+ [what rack generates](http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Environment), e.g., `HTTP_*`.
144
+
145
+ #### `:env`
146
+
147
+ Looks for an environment variable named `RACK_DOWNTIME`.
148
+
149
+ #### `:cookie`
150
+
151
+ Looks for cookie named `__dt__`.
152
+
153
+ ```ruby
154
+ use Rack::Downtime :strategy => :cookie
155
+ ```
156
+ To use a cookie named `oreo`:
157
+
158
+ ```ruby
159
+ use Rack::Downtime :strategy => { :cookie => "oreo" }
160
+ ```
161
+
162
+ #### Custom
163
+
164
+ Just pass in something that responds to `:call`, accepts a rack environment, and returns a [downtime array](#usage).
165
+
166
+ ```ruby
167
+ use Rack::Downtime :strategy => ->(env) { YourDownTimeConfig.find_dowmtime }
168
+ ```
169
+
170
+ ### Inserting a Downtime Message
171
+
172
+ A message can be inserted by `Rack::Downtime` into your response's body whenever downtime is scheduled.
173
+ Just provide a path to an ERB template to the `:insert` option. The downtime will be passed to the template
174
+ as `start_time` and `end_time`.
175
+
176
+ By default the template will be inserted after the `body` tag. This can be changed by providing the
177
+ desired location to the `:insert_at` option. The location can be given as a CSS selector or an XPath location.
178
+
179
+ Messages are only inserted into HTML responses with a `200` status code.
180
+
181
+ **Note that when `Rack::Downtime` inserts a message it will turn a streaming response into a buffered one**.
182
+ If this is a problem you can always check for and insert the downtime yourself in your application.
183
+
184
+ ## TODO
185
+
186
+ Don't invalidate XHTML responses when inserting a downtime message.
187
+
188
+ ## Author
189
+
190
+ Skye Shaw [sshaw AT gmail.com]
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => "spec"
7
+
@@ -0,0 +1,148 @@
1
+ require "rack/utils"
2
+ require "erb"
3
+ require "nokogiri"
4
+
5
+ require "rack/downtime/strategy"
6
+ require "rack/downtime/version"
7
+
8
+ module Rack
9
+ ##
10
+ # For the full documentation see README.md
11
+
12
+ class Downtime
13
+
14
+ DOWNTIME_DISABLE = "RACK_DOWNTIME_DISABLE".freeze
15
+ DOWNTIME_INSERT = "RACK_DOWNTIME_INSERT".freeze
16
+
17
+ ENV_KEY = "rack.downtime".freeze
18
+ DEFAULT_INSERT_AT = "html body".freeze
19
+
20
+ # Newer versions of Rack should have these
21
+ CONTENT_TYPE = "Content-Type".freeze
22
+ CONTENT_LENGTH = "Content-Length".freeze
23
+
24
+ class << self
25
+ attr_writer :strategy
26
+
27
+ ##
28
+ # Set the default downtime strategy
29
+ def strategy
30
+ @strategy ||= :file
31
+ end
32
+ end
33
+
34
+ ##
35
+ # Create an instance of the Rack middleware to manage downtime notifications
36
+ #
37
+ # === Arguments
38
+ #
39
+ # [options (Hash)] downtime detection and insertion options; optional
40
+ #
41
+ # === Options
42
+ #
43
+ # [:strategy (Symbol|Hash)] Set the downtime detection strategy; defaults to Rack::Downtime::strategy. If a +Hash+ its first value is passed as an argument to the strategy class.
44
+ # [:insert (String)] Path to an ERB template to insert when downtime is planned. Downtimes are given to the template as +start_time+ and +end_time+.
45
+ # [:insert_at (String)] Where to insert the ERB template given by +:insert+. Can be given as a CSS selector or an XPath location.
46
+ #
47
+ # === Errors
48
+ #
49
+ # [ArgumentError] If the strategy is unknown
50
+
51
+ def initialize(app, options = {})
52
+ @app = app
53
+
54
+ @strategy = options[:strategy] || self.class.strategy
55
+ @strategy = load_strategy(@strategy) unless @strategy.respond_to?(:call)
56
+
57
+ @insert = options[:insert]
58
+ @insert = load_template(@insert) if @insert
59
+
60
+ @insert_at = options[:insert_at] || DEFAULT_INSERT_AT
61
+ end
62
+
63
+ def call(env)
64
+ return @app.call(env) if env[DOWNTIME_DISABLE] == "1"
65
+
66
+ downtime = get_downtime(env)
67
+ env[ENV_KEY] = downtime if downtime
68
+
69
+ response = @app.call(env)
70
+ return response unless downtime && insert_downtime?(env, response)
71
+
72
+ old_body = response[2]
73
+ new_body = insert_downtime(old_body, downtime)
74
+
75
+ old_body.close if old_body.respond_to?(:close)
76
+ response[1][CONTENT_LENGTH] = Rack::Utils.bytesize(new_body).to_s
77
+ response[2] = [new_body]
78
+
79
+ response
80
+ end
81
+
82
+ private
83
+
84
+ def load_strategy(options)
85
+ config = nil
86
+ strategy = options
87
+ strategy, config = strategy.first if strategy.is_a?(Hash)
88
+
89
+ case strategy
90
+ when :cookie
91
+ Strategy::Cookie.new(config)
92
+ when :env, :environment
93
+ Strategy::Env.new(config)
94
+ when :file
95
+ Strategy::File.new(config)
96
+ when :header
97
+ Strategy::Header.new(config)
98
+ when :query
99
+ Strategy::Query.new(config)
100
+ else
101
+ raise ArgumentError, "unknown strategy: #{strategy}"
102
+ end
103
+ end
104
+
105
+ def load_template(template)
106
+ Class.new do
107
+ include ERB.new(::File.read(template), nil, "<>%-").def_module("render(start_time, end_time)")
108
+ end.new
109
+ end
110
+
111
+ def get_downtime(env)
112
+ @strategy.call(env)
113
+ rescue => e
114
+ message = "Rack::Downtime failed: #{e}"
115
+
116
+ if env["rack.logger"].respond_to?(:error)
117
+ env["rack.logger"].error(message)
118
+ else
119
+ env["rack.errors"].puts(message)
120
+ end
121
+
122
+ nil
123
+ end
124
+
125
+ def insert_downtime?(env, response)
126
+ @insert && env[DOWNTIME_INSERT] != "0" && response[0] == 200 && response[1][CONTENT_TYPE] =~ /html/
127
+ end
128
+
129
+ def insert_downtime(old_body, times)
130
+ new_body = ""
131
+ old_body.each { |line| new_body << line }
132
+
133
+ doc = Nokogiri::HTML(new_body)
134
+ e = doc.at(@insert_at)
135
+ return new_body unless e
136
+
137
+ message = @insert.render(*times)
138
+
139
+ if e.child
140
+ e.child.before(message)
141
+ else
142
+ e.add_child(message)
143
+ end
144
+
145
+ doc.to_html
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,111 @@
1
+ require "rack/request"
2
+ require "rack/utils"
3
+ require "rack/downtime/utils"
4
+
5
+ class Rack::Downtime
6
+ module Strategy
7
+ class Cookie
8
+ include Rack::Utils
9
+
10
+ class << self
11
+ attr_writer :named
12
+
13
+ def named
14
+ @named ||= "__dt__"
15
+ end
16
+ end
17
+
18
+ def initialize(named = nil)
19
+ @named = named || self.class.named
20
+ end
21
+
22
+ def call(env)
23
+ req = Rack::Request.new(env)
24
+ Rack::Downtime::Utils.parse_downtime(req.cookies[@named])
25
+ #delete_cookie_header!(env, @named) if downtime
26
+ #downtime
27
+ end
28
+ end
29
+
30
+ class Env
31
+ class << self
32
+ attr_writer :named
33
+
34
+ def named
35
+ @named ||= "RACK_DOWNTIME"
36
+ end
37
+ end
38
+
39
+ def initialize(named = nil)
40
+ @named = named || self.class.named
41
+ end
42
+
43
+ def call(env)
44
+ Rack::Downtime::Utils.parse_downtime(env[@named])
45
+ end
46
+ end
47
+
48
+ class Header < Env
49
+ def self.named
50
+ @named ||= "X-Downtime"
51
+ end
52
+
53
+ def initialize(named = nil)
54
+ @named = (named || self.class.named).upcase.tr!("-", "_")
55
+ @named.prepend "HTTP_" unless @named.start_with?("HTTP_")
56
+ end
57
+ end
58
+
59
+ class Query
60
+ class << self
61
+ attr_writer :param
62
+
63
+ def param
64
+ @param ||= "__dt__"
65
+ end
66
+ end
67
+
68
+ def initialize(param = nil)
69
+ @param = param || self.class.param
70
+ end
71
+
72
+ def call(env)
73
+ req = Rack::Request.new(env)
74
+ Rack::Downtime::Utils.parse_downtime(req[@param])
75
+ end
76
+ end
77
+
78
+ class File
79
+ class << self
80
+ attr_writer :path
81
+
82
+ def path
83
+ @path ||= "downtime.txt"
84
+ end
85
+ end
86
+
87
+ def initialize(path = nil)
88
+ @path = path || self.class.path
89
+ @mtime = 0
90
+ end
91
+
92
+ def call(env)
93
+ return unless ::File.exists?(@path)
94
+
95
+ new_mtime = ::File.mtime(@path).to_i
96
+ if new_mtime > @mtime
97
+ @downtime = parse_downtime(@path)
98
+ @mtime = new_mtime
99
+ end
100
+
101
+ @downtime
102
+ end
103
+
104
+ private
105
+
106
+ def parse_downtime(path)
107
+ Rack::Downtime::Utils.parse_downtime(::File.read(path))
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,14 @@
1
+ require "date"
2
+
3
+ class Rack::Downtime
4
+ module Utils
5
+ def parse_downtime(data)
6
+ return unless data
7
+
8
+ downtime = data.split("/", 2).map { |date| DateTime.iso8601(date) }
9
+ downtime.empty? ? nil : downtime
10
+ end
11
+
12
+ module_function :parse_downtime
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class Downtime
3
+ VERSION = "0.0.1".freeze
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack/downtime/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rack-downtime"
8
+ spec.version = Rack::Downtime::VERSION
9
+ spec.authors = ["Skye Shaw"]
10
+ spec.email = ["skye.shaw@gmail.com"]
11
+ spec.summary = %q{Planned downtime management for Rack applications}
12
+ spec.description =<<DOC
13
+ Rack::Downtime provides a variety of ways to easily trigger and display planned maintenance notifications to users
14
+ while a site is still up. Various strategies are provided that will work with sites of all sizes.
15
+ DOC
16
+
17
+ spec.homepage = "https://github.com/sshaw/rack-downtime"
18
+ spec.license = "MIT"
19
+
20
+ spec.files = `git ls-files -z`.split("\x0")
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency "nokogiri"
26
+ spec.add_dependency "rack", "~> 1"
27
+ spec.add_development_dependency "bundler", "~> 1.7"
28
+ spec.add_development_dependency "rack-test"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec"
31
+ end
@@ -0,0 +1,244 @@
1
+ require "spec_helper"
2
+ require "fileutils"
3
+
4
+ describe Rack::Downtime do
5
+
6
+ before do
7
+ @tmp = Dir.mktmpdir
8
+ Dir.chdir(@tmp)
9
+
10
+ @dates = [ DateTime.new(2014,11,11,0,0), DateTime.new(2014,11,11,2,0) ]
11
+ @downtime = time_interval(@dates)
12
+ File.write("downtime.txt", @downtime)
13
+ end
14
+
15
+ after { FileUtils.rm_rf(@tmp) }
16
+
17
+ it "sets the environment's rack.downtime to the downtime" do
18
+ set_dates = nil
19
+ app = new_app { |env| set_dates = env["rack.downtime"] }
20
+ req = Rack::Test::Session.new(described_class.new(app))
21
+ req.get "/"
22
+
23
+ expect(set_dates).to eq @dates
24
+ end
25
+
26
+ # context "when no downtime has been specified" do
27
+ # it "does not insert the alert"
28
+ # it "does not assign any dates to rack.downtime"
29
+ # end
30
+
31
+ context "given a downtime message template" do
32
+ before do
33
+ @template = File.join(@tmp, "alert.erb")
34
+ File.write(@template, "__HERE__")
35
+ end
36
+
37
+ #it "sets the environment's rack.downtime to the downtime"
38
+
39
+ context "with a non-200 response" do
40
+ it "does not insert the template" do
41
+ app = new_app(:code => 302)
42
+
43
+ req = Rack::Test::Session.new(described_class.new(app, :insert => @template))
44
+ req.get "/"
45
+
46
+ expect(req.last_response.body).to_not match("__HERE__")
47
+ end
48
+ end
49
+
50
+ context "with a non-HTML content type" do
51
+ it "does not insert the template" do
52
+ app = new_app(:headers => {"Content-Type" => "text/plain"})
53
+
54
+ req = Rack::Test::Session.new(described_class.new(app, :insert => @template))
55
+ req.get "/"
56
+
57
+ expect(req.last_response.body).to_not match("__HERE__")
58
+ end
59
+ end
60
+
61
+ context "when RACK_DOWNTIME_INSERT = 0" do
62
+ it "does not insert the template" do
63
+ req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template))
64
+ req.get "/", nil, "RACK_DOWNTIME_INSERT" => "0"
65
+
66
+ expect(req.last_response.body).to_not match("__HERE__")
67
+ end
68
+
69
+ #it "sets the environment's rack.downtime to the downtime"
70
+ end
71
+
72
+ context "when RACK_DOWNTIME_DISABLE = 1" do
73
+ it "does not insert the template" do
74
+ req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template))
75
+ req.get "/", nil, "RACK_DOWNTIME_DISABLE" => "1"
76
+
77
+ expect(req.last_response.body).to_not match("__HERE__")
78
+ end
79
+
80
+ it "does not set rack.downtime" do
81
+ set_dates = nil
82
+ app = new_app { |env| set_dates = env["rack.downtime"] }
83
+
84
+ req = Rack::Test::Session.new(described_class.new(new_app))
85
+ req.get "/", nil, "RACK_DOWNTIME_DISABLE" => "1"
86
+
87
+ expect(set_dates).to be_nil
88
+ end
89
+ end
90
+
91
+ it "inserts the template into the response" do
92
+ req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template))
93
+ req.get "/"
94
+
95
+ expect(req.last_response.body).to eq_html "<!doctype html><html><body>__HERE__<p>Content!</p></body></html>"
96
+ end
97
+
98
+ it "passes downtime times to the template" do
99
+ File.write(@template, "<%= start_time.hour %>/<%= end_time.hour %>")
100
+
101
+ req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template))
102
+ req.get "/"
103
+
104
+ expect(req.last_response.body).to eq_html "<!doctype html><html><body>0/2<p>Content!</p></body></html>"
105
+ end
106
+
107
+ describe "the :insert_at option" do
108
+ [["CSS", "body p"], ["xpath", "//body/p"]].each do |format, location|
109
+ it "inserts the template at the given #{format} location" do
110
+ req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template, :insert_at => location))
111
+ req.get "/"
112
+
113
+ expect(req.last_response.body).to match("<body><p>__HERE__Content!</p>")
114
+ end
115
+ end
116
+
117
+ context "given an invalid location" do
118
+ it "does not insert the template into response" do
119
+ req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template, :insert_at => "div span a"))
120
+ req.get "/"
121
+
122
+ expect(req.last_response.body).to_not match("__HERE__")
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ describe "strategies" do
129
+ before do
130
+ @app = lambda { |env|
131
+ body = env["rack.downtime"].map { |t| t.strftime("%s") }.join("/")
132
+ headers = {"Content-Type" => "text/plain"}
133
+ headers["Content-Length"] = body.size
134
+
135
+ [ 200, headers, [body] ]
136
+ }
137
+ @body = @dates.map { |d| d.strftime("%s") }.join("/")
138
+ end
139
+
140
+ it "raises an ArgumentError when the strategy is unknown" do
141
+ expect { described_class.new(@app, :strategy => :g_code) }.to raise_error(ArgumentError, /unknown/)
142
+ end
143
+
144
+ describe ":cookie" do
145
+ before { @downtime = Rack::Utils.escape(@downtime) }
146
+
147
+ it "sets the downtime from the default cookie name" do
148
+ req = Rack::Test::Session.new(described_class.new(@app, :strategy => :cookie))
149
+ req.set_cookie "__dt__=#@downtime"
150
+ req.get "/"
151
+
152
+ expect(req.last_response.body).to eq(@body)
153
+ end
154
+
155
+ it "sets the downtime from the given cookie name" do
156
+ req = Rack::Test::Session.new(described_class.new(@app, :strategy => { :cookie => "QWERTY" }))
157
+ req.set_cookie "QWERTY=#@downtime"
158
+ req.get "/"
159
+
160
+ expect(req.last_response.body).to eq(@body)
161
+ end
162
+
163
+
164
+ # context "with a new default name" do
165
+ # before { Rack::Downtime::Strategy::Cookie.named = "xxx" }
166
+ # after { Rack::Downtime::Strategy::Cookie.named = nil }
167
+
168
+
169
+ # req = Rack::Test::Session.new(described_class.new(@app, :strategy => :cookie))
170
+ # req.set_cookie "__dt__=#@downtime" # esc?
171
+ # req.get "/"
172
+
173
+ # expect(req.last_response.body).to eq(@body)
174
+ # end
175
+ # end
176
+ end
177
+
178
+ describe ":env" do
179
+ it "sets the downtime from the RACK_DOWNTIME environment variable" do
180
+ req = Rack::Test::Session.new(described_class.new(@app, :strategy => :env))
181
+ req.get "/", nil, "RACK_DOWNTIME" => @downtime
182
+
183
+ expect(req.last_response.body).to eq(@body)
184
+ end
185
+ end
186
+
187
+ describe ":header" do
188
+ it "sets the downtime from the X-Downtime HTTP header" do
189
+ req = Rack::Test::Session.new(described_class.new(@app, :strategy => :header))
190
+ req.header "X-Downtime", @downtime
191
+ req.get "/"
192
+
193
+ expect(req.last_response.body).to eq(@body)
194
+ end
195
+ end
196
+
197
+ describe ":query" do
198
+ it "sets the downtime from the default query string param" do
199
+ req = Rack::Test::Session.new(described_class.new(@app, :strategy => :query))
200
+ req.get "/", :__dt__ => @downtime
201
+
202
+ expect(req.last_response.body).to eq(@body)
203
+ end
204
+
205
+ it "sets the downtime from the given query string param" do
206
+ req = Rack::Test::Session.new(described_class.new(@app, :strategy => { :query => "param" }))
207
+ req.get "/", :param => @downtime
208
+
209
+ expect(req.last_response.body).to eq(@body)
210
+ end
211
+ end
212
+
213
+ describe ":file" do
214
+ it "sets the downtime from the given downtime file" do
215
+ path = File.join(@tmp, "im_goin_dooooown.txt")
216
+ File.write(path, @downtime)
217
+
218
+ req = Rack::Test::Session.new(described_class.new(@app, :strategy => { :file => path }))
219
+ req.get "/"
220
+
221
+ expect(req.last_response.body).to eq(@body)
222
+ end
223
+
224
+ describe "when the downtime is updated" do
225
+ it "detects the new downtime" do
226
+ req = Rack::Test::Session.new(described_class.new(@app, :strategy => { :file => "downtime.txt" }))
227
+ req.get "/"
228
+
229
+ @dates[1] = DateTime.new(2014,12,31)
230
+ new_body = @dates.map { |d| d.strftime("%s") }.join("/")
231
+ new_downtime = time_interval(@dates)
232
+
233
+ # So we have a different mtime
234
+ sleep 1
235
+ File.write("downtime.txt", new_downtime)
236
+
237
+ req.get "/"
238
+
239
+ expect(req.last_response.body).to eq(new_body)
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,34 @@
1
+ require "rspec"
2
+ require "nokogiri"
3
+ require "rack/test"
4
+ require "rack/downtime"
5
+
6
+ RSpec.configure do |c|
7
+ c.include Module.new {
8
+ def time_interval(times)
9
+ times.map { |d| d.strftime("%FT%X%Z") }.join("/")
10
+ end
11
+
12
+ def new_app(response = {})
13
+ code = response[:code] || 200
14
+ headers = response[:headers] || {}
15
+ body = response[:body] || "<!DOCTYPE html><html><body><p>Content!</p></body></html>"
16
+
17
+ headers["Content-Length"]||= body.size
18
+ headers["Content-Type"] ||= "text/html"
19
+
20
+ ->(env) do
21
+ yield env if block_given?
22
+
23
+ [code, headers, [body]]
24
+ end
25
+ end
26
+ }
27
+ end
28
+
29
+ # Poor man's TestXml
30
+ RSpec::Matchers.define :eq_html do |expect|
31
+ match do |actual|
32
+ Nokogiri::HTML(expect).to_html == Nokogiri::HTML(actual).to_html
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-downtime
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Skye Shaw
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack-test
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: |
98
+ Rack::Downtime provides a variety of ways to easily trigger and display planned maintenance notifications to users
99
+ while a site is still up. Various strategies are provided that will work with sites of all sizes.
100
+ email:
101
+ - skye.shaw@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".rspec"
108
+ - ".travis.yml"
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - lib/rack/downtime.rb
114
+ - lib/rack/downtime/strategy.rb
115
+ - lib/rack/downtime/utils.rb
116
+ - lib/rack/downtime/version.rb
117
+ - rack-downtime.gemspec
118
+ - spec/downtime_spec.rb
119
+ - spec/spec_helper.rb
120
+ homepage: https://github.com/sshaw/rack-downtime
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.2.2
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Planned downtime management for Rack applications
144
+ test_files:
145
+ - spec/downtime_spec.rb
146
+ - spec/spec_helper.rb