doc_repo 0.1.1 → 1.0.0.pre.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,25 +1,337 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe DocRepo::Repository do
4
4
 
5
- it "yields the file block for a jpg" do
6
- repo = DocRepo::Repository.new
5
+ def build_config(settings)
6
+ DocRepo::Configuration.new { |c|
7
+ settings.each do |name, value|
8
+ c.public_send "#{name}=", value
9
+ end
10
+ }
11
+ end
7
12
 
8
- repo.respond("sonic_screwdriver.png") do |r|
9
- expect { |b| r.redirect(&b) }.to yield_control
10
- expect { |b| r.html(&b) }.not_to yield_control
13
+ def any_handler
14
+ DocRepo::ResultHandler.new do |h|
15
+ h.complete {}
16
+ h.error {}
17
+ h.not_found {}
18
+ h.redirect {}
11
19
  end
12
20
  end
13
21
 
14
- it "determines the right mime type for a jpg" do
15
- repo = DocRepo::Repository.new
22
+ shared_examples "requesting a slug" do |slug_reader|
23
+ let(:any_slug) { send slug_reader }
16
24
 
17
- repo.respond("sonic_screwdriver.png") do |r|
18
- r.redirect do |url|
19
- expect( url ).to match %r{https://raw\..*/master/docs/sonic_screwdriver.png}
20
- end
25
+ it "requires a block for handling the result" do
26
+ expect {
27
+ subject.request any_slug
28
+ }.to raise_error(/no block given/)
29
+ end
30
+
31
+ it "yields a result handler" do
32
+ null_handler = any_handler
33
+ expect { |b|
34
+ subject.request any_slug, result_handler: null_handler, &b
35
+ }.to yield_with_args null_handler
21
36
  end
22
37
  end
23
38
 
24
- end
39
+ describe "creating a repository" do
40
+ it "requires a configuration" do
41
+ expect {
42
+ DocRepo::Repository.new
43
+ }.to raise_error ArgumentError
44
+ end
45
+
46
+ it "duplicates the configuration to preserve state", :aggregate_failures do
47
+ a_config = DocRepo::Configuration.new
48
+ a_repo = DocRepo::Repository.new(a_config)
49
+ expect(a_repo.config).not_to eq a_config
50
+ expect(a_repo.config.to_h).to eq a_config.to_h
51
+ end
52
+
53
+ it "freezes its configuration to preserve state", :aggregate_failures do
54
+ a_config = DocRepo::Configuration.new
55
+ a_repo = DocRepo::Repository.new(a_config)
56
+ expect(a_repo.config).to be_frozen
57
+ expect(a_config).not_to be_frozen
58
+ end
59
+ end
60
+
61
+ it "has a repository name" do
62
+ a_repo = DocRepo::Repository.new(build_config(repo: "Any Repo Name"))
63
+ expect(a_repo.repo).to eq "Any Repo Name"
64
+ end
65
+
66
+ it "belongs to an org" do
67
+ a_repo = DocRepo::Repository.new(build_config(org: "Any Org"))
68
+ expect(a_repo.org).to eq "Any Org"
69
+ end
70
+
71
+ it "has a target branch" do
72
+ a_repo = DocRepo::Repository.new(build_config(branch: "Target Branch"))
73
+ expect(a_repo.branch).to eq "Target Branch"
74
+ end
75
+
76
+ it "has a document root path" do
77
+ a_repo = DocRepo::Repository.new(build_config(doc_root: "any/doc/path"))
78
+ expect(a_repo.doc_root).to eq "any/doc/path"
79
+ end
80
+
81
+ it "has a fallback extension" do
82
+ a_repo = DocRepo::Repository.new(build_config(fallback_ext: ".anything"))
83
+ expect(a_repo.fallback_ext).to eq ".anything"
84
+ end
85
+
86
+ it "has a set of supported document format extensions" do
87
+ a_repo = DocRepo::Repository.new(build_config(doc_formats: %w[.anything]))
88
+ expect(a_repo.doc_formats).to eq %w[.anything]
89
+ end
90
+
91
+ describe "generating a URI for a slug" do
92
+ it "defines the full repo path" do
93
+ a_repo = DocRepo::Repository.new(
94
+ build_config(
95
+ org: "AnyOrg",
96
+ repo: "any-repo",
97
+ branch: "target-branch",
98
+ doc_root: "doc/root/path",
99
+ fallback_ext: ".fallback",
100
+ )
101
+ )
102
+ expect(a_repo.uri_for("any-file.ext")).to eq(
103
+ "/AnyOrg/any-repo/target-branch/doc/root/path/any-file.ext"
104
+ )
105
+ end
106
+
107
+ it "applies the fallback extension when necessary" do
108
+ a_repo = DocRepo::Repository.new(build_config(fallback_ext: ".fallback"))
109
+ expect(a_repo.uri_for("any-document")).to end_with "/any-document.fallback"
110
+ end
111
+
112
+ it "supports a root document path", :aggregate_failures do
113
+ root_conf = build_config(org: "o", repo: "r", branch: "b", doc_root: "/")
114
+ a_repo = DocRepo::Repository.new(root_conf)
115
+ expect(a_repo.uri_for("any-file.ext")).to eq "/o/r/b/any-file.ext"
116
+
117
+ root_conf.doc_root = "/root/"
118
+ a_repo = DocRepo::Repository.new(root_conf)
119
+ expect(a_repo.uri_for("any-file.ext")).to eq "/o/r/b/root/any-file.ext"
120
+ end
121
+ end
122
+
123
+ describe "requesting a redirectable slug" do
124
+ subject(:a_repo) {
125
+ DocRepo::Repository.new(
126
+ build_config(
127
+ org: "org",
128
+ repo: "repo",
129
+ branch: "branch",
130
+ doc_root: "root",
131
+ doc_formats: %w[ custom ],
132
+ )
133
+ )
134
+ }
135
+
136
+ let(:a_redirectable_slug) { "slug.non_document" }
137
+
138
+ include_examples "requesting a slug", :a_redirectable_slug
139
+
140
+ it "yields the redirect result to the handler" do
141
+ expect { |b|
142
+ a_repo.request a_redirectable_slug do |result|
143
+ result.redirect(&b)
144
+ end
145
+ }.to yield_with_args(
146
+ an_instance_of(
147
+ DocRepo::Redirect
148
+ ).and(
149
+ have_attributes(url:<<~URL.chomp)
150
+ https://raw.githubusercontent.com/org/repo/branch/root/slug.non_document
151
+ URL
152
+ )
153
+ )
154
+ end
155
+
156
+ it "raises when a handler is not configured for redirection" do
157
+ expect {
158
+ a_repo.request a_redirectable_slug do |result|
159
+ # No-Op
160
+ end
161
+ }.to raise_error(
162
+ DocRepo::UnhandledAction,
163
+ /no result redirect handler defined/,
164
+ )
165
+ end
166
+ end
25
167
 
168
+ describe "requesting a document slug" do
169
+ subject(:a_repo) {
170
+ DocRepo::Repository.new(
171
+ build_config(
172
+ org: "org",
173
+ repo: "repo",
174
+ branch: "branch",
175
+ doc_root: "root",
176
+ ),
177
+ http_adapter: successful_http,
178
+ )
179
+ }
180
+
181
+ let(:a_doc_slug) { "any-doc-slug.md" }
182
+
183
+ let(:successful_http) {
184
+ instance_double("DocRepo::NetHttpAdapter", retrieve: the_document)
185
+ }
186
+
187
+ let(:the_document) {
188
+ instance_double("DocRepo::Doc", redirect?: false, success?: true)
189
+ }
190
+
191
+ include_examples "requesting a slug", :a_doc_slug
192
+
193
+ it "fetches the document from the remote site" do
194
+ expect(successful_http).to receive(:retrieve).with(
195
+ "/org/repo/branch/root/any-doc-slug.md"
196
+ )
197
+ a_repo.request(a_doc_slug, result_handler: any_handler) { }
198
+ end
199
+
200
+ it "yields the document to the handler" do
201
+ expect { |b|
202
+ a_repo.request a_doc_slug do |result|
203
+ result.complete(&b)
204
+ end
205
+ }.to yield_with_args the_document
206
+ end
207
+
208
+ it "raises when a handler is not configured for completion" do
209
+ expect {
210
+ a_repo.request a_doc_slug do |result|
211
+ # No-Op
212
+ end
213
+ }.to raise_error(
214
+ DocRepo::UnhandledAction,
215
+ /no result completion handler defined/,
216
+ )
217
+ end
218
+ end
219
+
220
+ describe "requesting a non-existant document slug" do
221
+ subject(:a_repo) {
222
+ DocRepo::Repository.new(
223
+ build_config(
224
+ org: "org",
225
+ repo: "repo",
226
+ branch: "branch",
227
+ doc_root: "root",
228
+ ),
229
+ http_adapter: not_found_http,
230
+ )
231
+ }
232
+
233
+ let(:missing_doc_slug) { "missing-doc-slug.md" }
234
+
235
+ let(:not_found_http) {
236
+ instance_double("DocRepo::NetHttpAdapter", retrieve: not_found_result)
237
+ }
238
+
239
+ let(:not_found_result) {
240
+ http_response = instance_double(
241
+ "Net::HTTPNotFound",
242
+ code: "404",
243
+ message: "Any Message",
244
+ )
245
+ DocRepo::HttpError.new("uri", http_response.as_null_object)
246
+ }
247
+
248
+ include_examples "requesting a slug", :missing_doc_slug
249
+
250
+ it "attempts to fetch the document from the remote site" do
251
+ expect(not_found_http).to receive(:retrieve).with(
252
+ "/org/repo/branch/root/missing-doc-slug.md"
253
+ )
254
+ a_repo.request(missing_doc_slug, result_handler: any_handler) { }
255
+ end
256
+
257
+ it "yields the error result to the `not_found` handler" do
258
+ expect { |b|
259
+ a_repo.request missing_doc_slug do |result|
260
+ result.not_found(&b)
261
+ end
262
+ }.to yield_with_args not_found_result
263
+ end
264
+
265
+ it "falls back to yielding the error result to the `error` handler " \
266
+ "without a `not_found` handler" do
267
+ expect { |b|
268
+ a_repo.request missing_doc_slug do |result|
269
+ result.error(&b)
270
+ end
271
+ }.to yield_with_args not_found_result
272
+ end
273
+
274
+ it "raises the error result without a configured handler" do
275
+ expect {
276
+ a_repo.request missing_doc_slug do |result|
277
+ # No-Op
278
+ end
279
+ }.to raise_error(DocRepo::HttpError, '404 "Any Message"')
280
+ end
281
+ end
282
+
283
+ describe "requesting a document slug", "which causes an error" do
284
+ subject(:a_repo) {
285
+ DocRepo::Repository.new(
286
+ build_config(
287
+ org: "org",
288
+ repo: "repo",
289
+ branch: "branch",
290
+ doc_root: "root",
291
+ ),
292
+ http_adapter: error_http,
293
+ )
294
+ }
295
+
296
+ let(:any_doc_slug) { "any-doc-slug.md" }
297
+
298
+ let(:error_http) {
299
+ instance_double("DocRepo::NetHttpAdapter", retrieve: error_result)
300
+ }
301
+
302
+ let(:error_result) {
303
+ http_response = instance_double(
304
+ "Net::HTTPClientError",
305
+ code: "400",
306
+ message: "Any Message",
307
+ )
308
+ DocRepo::HttpError.new("uri", http_response.as_null_object)
309
+ }
310
+
311
+ include_examples "requesting a slug", :any_doc_slug
312
+
313
+ it "attempts to fetch the document from the remote site" do
314
+ expect(error_http).to receive(:retrieve).with(
315
+ "/org/repo/branch/root/any-doc-slug.md"
316
+ )
317
+ a_repo.request(any_doc_slug, result_handler: any_handler) { }
318
+ end
319
+
320
+ it "yields the error result to the `error` handler" do
321
+ expect { |b|
322
+ a_repo.request any_doc_slug do |result|
323
+ result.error(&b)
324
+ end
325
+ }.to yield_with_args error_result
326
+ end
327
+
328
+ it "raises the error result without a configured handler" do
329
+ expect {
330
+ a_repo.request any_doc_slug do |result|
331
+ # No-Op
332
+ end
333
+ }.to raise_error(DocRepo::HttpError, '400 "Any Message"')
334
+ end
335
+ end
336
+
337
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe DocRepo::ResultHandler do
4
+
5
+ shared_examples "handling results of type" do |type|
6
+ subject(:a_handler) { DocRepo::ResultHandler.new }
7
+
8
+ it "requires a block" do
9
+ expect {
10
+ a_handler.public_send type
11
+ }.to raise_error ArgumentError, "Result handler block required"
12
+ end
13
+
14
+ it "does not evaluate the block when defined" do
15
+ expect { |b| a_handler.public_send type, &b }.not_to yield_control
16
+ end
17
+
18
+ it "stores the block" do
19
+ handler_strategy = -> { "Does Anything" }
20
+ expect {
21
+ a_handler.public_send type, &handler_strategy
22
+ }.to change {
23
+ a_handler[type.to_sym]
24
+ }.from(nil).to(handler_strategy)
25
+ end
26
+ end
27
+
28
+ it "creating a new handler yields itself when given a block" do
29
+ an_action = -> { }
30
+ a_handler = DocRepo::ResultHandler.new { |h| h.complete(&an_action) }
31
+ expect(a_handler[:complete]).to be an_action
32
+ end
33
+
34
+ it "has no actions by default" do
35
+ expect(DocRepo::ResultHandler.new.each.to_a).to be_empty
36
+ end
37
+
38
+ include_behavior "handling results of type", :complete
39
+ include_behavior "handling results of type", :error
40
+ include_behavior "handling results of type", :not_found
41
+ include_behavior "handling results of type", :redirect
42
+
43
+ end
@@ -6,13 +6,35 @@ RSpec.describe DocRepo do
6
6
  # Save and Reset the configuration
7
7
  original_config = DocRepo.configuration
8
8
  ex.run
9
- DocRepo.instance_variable_set :@configuration, original_config
9
+ DocRepo.configuration = original_config
10
10
  end
11
11
 
12
- it "yields the configuration" do
12
+ it "always has a configuration" do
13
+ original_config = DocRepo.configuration
14
+ expect {
15
+ DocRepo.configuration = nil
16
+ }.to change {
17
+ DocRepo.configuration
18
+ }.from(original_config).to an_instance_of(DocRepo::Configuration)
19
+ end
20
+
21
+ describe "modifying the configuration" do
22
+ it "yields the configuration" do
13
23
  expect { |b|
14
24
  DocRepo.configure(&b)
15
- }.to yield_control
25
+ }.to yield_with_args DocRepo.configuration
26
+ end
27
+
28
+ it "reflects changes made by the block" do
29
+ DocRepo.configuration.branch = "Any Branch"
30
+ expect {
31
+ DocRepo.configure do |c|
32
+ c.branch = "Modified Branch"
33
+ end
34
+ }.to change {
35
+ DocRepo.configuration.branch
36
+ }.from("Any Branch").to "Modified Branch"
37
+ end
16
38
  end
17
39
 
18
40
  end
data/spec/spec_helper.rb CHANGED
@@ -1,22 +1,107 @@
1
+ # Conventionally, all specs live under a `spec` directory, which RSpec adds to
2
+ # the `$LOAD_PATH`. The generated `.rspec` file contains `--require
3
+ # spec_helper` which will cause this file to always be loaded, without a need
4
+ # to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, consider making
10
+ # a separate helper file that requires the additional dependencies and performs
11
+ # the additional setup, and require it from the spec files that actually need
12
+ # it.
13
+ #
14
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
15
+
1
16
  # This is a very warning riddled gem, load it before we enable warnings
2
17
  require 'rouge'
3
18
 
4
19
  RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
5
23
  config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
6
31
  expectations.include_chain_clauses_in_custom_matcher_descriptions = true
7
32
  end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
8
36
  config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
9
40
  mocks.verify_partial_doubles = true
10
41
  end
11
- config.filter_run :focus
12
- config.run_all_when_everything_filtered = true
42
+
43
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
44
+ # have no way to turn it off -- the option exists only for backwards
45
+ # compatibility in RSpec 3). It causes shared context metadata to be
46
+ # inherited by the metadata hash of host groups and examples, rather than
47
+ # triggering implicit auto-inclusion in groups with matching metadata.
48
+ config.shared_context_metadata_behavior = :apply_to_host_groups
49
+
50
+ # This allows you to limit a spec run to individual examples or groups
51
+ # you care about by tagging them with `:focus` metadata. When nothing
52
+ # is tagged with `:focus`, all examples get run. RSpec also provides
53
+ # aliases for `it`, `describe`, and `context` that include `:focus`
54
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
55
+ config.filter_run_when_matching :focus
56
+
57
+ # Allows RSpec to persist some state between runs in order to support
58
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
59
+ # you configure your source control system to ignore this file.
60
+ config.example_status_persistence_file_path = "spec/examples.txt"
61
+
62
+ # Limits the available syntax to the non-monkey patched syntax that is
63
+ # recommended. For more details, see:
64
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
65
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
66
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
13
67
  config.disable_monkey_patching!
68
+
69
+ # This setting enables warnings. It's recommended, but in some cases may
70
+ # be too noisy due to issues in dependencies.
14
71
  config.warnings = true
72
+
73
+ # Many RSpec users commonly either run the entire suite or an individual
74
+ # file, and it's useful to allow more verbose output when running an
75
+ # individual spec file.
15
76
  if config.files_to_run.one?
16
- config.default_formatter = 'doc'
77
+ # Use the documentation formatter for detailed output,
78
+ # unless a formatter has already been configured
79
+ # (e.g. via a command-line flag).
80
+ config.default_formatter = "doc"
17
81
  end
82
+
83
+ # Print the 10 slowest examples and example groups at the
84
+ # end of the spec run, to help surface which specs are running
85
+ # particularly slow.
86
+ #config.profile_examples = 10
87
+
88
+ # Run specs in random order to surface order dependencies. If you find an
89
+ # order dependency and want to debug it, you can fix the order by providing
90
+ # the seed, which is printed after each run.
91
+ # --seed 1234
92
+ config.order = :random
93
+
94
+ # Seed global randomization in this process using the `--seed` CLI option.
95
+ # Setting this allows you to use `--seed` to deterministically reproduce
96
+ # test failures related to randomization by passing the same `--seed` value
97
+ # as the one that triggered the failure.
18
98
  Kernel.srand config.seed
99
+
100
+ # Custom shared example aliases for better output grammar
101
+ config.alias_it_should_behave_like_to :include_behavior
19
102
  end
20
103
 
104
+ require 'webmock/rspec'
105
+
21
106
  # Load our lib after warnings are enabled so we can fix them
22
107
  require 'doc_repo'
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DocRepo
4
+ module Spec
5
+ class InMemoryCache
6
+ def initialize
7
+ @cache = {}
8
+ @options = {}
9
+ end
10
+
11
+ attr_reader :cache, :options
12
+
13
+ def clear
14
+ cache.clear
15
+ options.clear
16
+ end
17
+
18
+ def keys
19
+ cache.keys
20
+ end
21
+
22
+ def fetch(name, opts = nil, &block)
23
+ options[name] = opts
24
+ cache[name] = cache.fetch(name, &block)
25
+ end
26
+
27
+ def write(name, value, opts = nil)
28
+ options[name] = opts
29
+ cache[name] = value
30
+ end
31
+ end
32
+ end
33
+ end