rack-downtime 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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