doc_repo 0.1.1 → 1.0.0.pre.beta.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.
@@ -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