webspicy 0.16.3 → 0.17.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -3
  3. data/examples/restful/Gemfile.lock +14 -13
  4. data/examples/restful/webspicy/config.rb +1 -0
  5. data/examples/restful/webspicy/{todo/deleteTodo.yml → formaldef/todo/_one/delete.yml} +3 -0
  6. data/examples/restful/webspicy/{todo/getTodoSingleServiceFormat.yml → formaldef/todo/_one/get.simpler.yml} +0 -0
  7. data/examples/restful/webspicy/{todo/getTodo.yml → formaldef/todo/_one/get.yml} +0 -0
  8. data/examples/restful/webspicy/{todo/patchTodo.yml → formaldef/todo/_one/patch.yml} +0 -0
  9. data/examples/restful/webspicy/{todo/putTodo.yml → formaldef/todo/_one/put.yml} +0 -0
  10. data/examples/restful/webspicy/{todo/getTodos.yml → formaldef/todo/get.yml} +0 -0
  11. data/examples/restful/webspicy/{todo → formaldef/todo}/options.yml +0 -0
  12. data/examples/restful/webspicy/{todo/postCsv.yml → formaldef/todo/post.csv.yml} +0 -0
  13. data/examples/restful/webspicy/{todo/postFile.yml → formaldef/todo/post.file.yml} +0 -0
  14. data/examples/restful/webspicy/{todo/postTodos.yml → formaldef/todo/post.yml} +0 -0
  15. data/examples/restful/webspicy/{todo → formaldef/todo}/todos.csv +0 -0
  16. data/examples/restful/webspicy/support/todo_not_removed.rb +21 -0
  17. data/examples/restful/webspicy/support/todo_removed.rb +5 -3
  18. data/lib/webspicy.rb +1 -0
  19. data/lib/webspicy/checker.rb +5 -20
  20. data/lib/webspicy/configuration.rb +9 -0
  21. data/lib/webspicy/configuration/scope.rb +0 -8
  22. data/lib/webspicy/formaldoc.fio +2 -0
  23. data/lib/webspicy/rspec/checker.rb +2 -0
  24. data/lib/webspicy/rspec/checker/rspec_checker.rb +24 -0
  25. data/lib/webspicy/rspec/support/rspec_runnable.rb +27 -0
  26. data/lib/webspicy/rspec/tester.rb +4 -0
  27. data/lib/webspicy/{tester → rspec/tester}/rspec_asserter.rb +23 -10
  28. data/lib/webspicy/{tester → rspec/tester}/rspec_matchers.rb +10 -0
  29. data/lib/webspicy/rspec/tester/rspec_tester.rb +63 -0
  30. data/lib/webspicy/specification.rb +5 -7
  31. data/lib/webspicy/specification/errcondition.rb +16 -0
  32. data/lib/webspicy/specification/service.rb +27 -19
  33. data/lib/webspicy/specification/test_case.rb +3 -9
  34. data/lib/webspicy/support.rb +1 -0
  35. data/lib/webspicy/support/data_object.rb +25 -0
  36. data/lib/webspicy/tester.rb +4 -78
  37. data/lib/webspicy/tester/asserter.rb +9 -4
  38. data/lib/webspicy/tester/assertions.rb +8 -9
  39. data/lib/webspicy/tester/failure.rb +6 -0
  40. data/lib/webspicy/tester/invocation.rb +8 -156
  41. data/lib/webspicy/version.rb +2 -2
  42. data/spec/unit/configuration/scope/test_each_service.rb +2 -2
  43. data/spec/unit/configuration/scope/test_each_specification.rb +7 -7
  44. data/spec/unit/test_configuration.rb +1 -1
  45. data/spec/unit/tester/test_asserter.rb +198 -3
  46. data/spec/unit/tester/test_assertions.rb +8 -6
  47. metadata +27 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cbfb644d2ae34f2e2e87aa2d69b50b762299770b9c0109fbc5281fdd3b80d22
4
- data.tar.gz: 6bf17b7423bb6ff33b53d8f12dc761094e613961487c146c66a87ce84b792bc7
3
+ metadata.gz: 63c15addd49634a62ba637d18cfee736f405dc0e144ec688e2fda6861764a09c
4
+ data.tar.gz: 903d3a0ca2f20a5dbac1fafc4a1a89981736d527cb69668a06b41acb5ef04696
5
5
  SHA512:
6
- metadata.gz: 403ddeb22ffccd059d8450a6290f7f190d97bc45ef12a04a43a97059d733085ce4fe2865f31a5452d43c18e4244a53129ac280cd37db09b8a6ce50e76506f8f6
7
- data.tar.gz: '095210fda4e1b4e903c1803a2cd75420a905894b06d4403e8f1019df0c43eeec10eb3952b8d06397bdb0d8a753361c01f1577f85f09ca34258386020276dcdaa'
6
+ metadata.gz: d54f59f003313e38dd37104e56038b6ede06c7339718498e6dda9730de97acdd0fae5499d45463ef790f78332eb0d337a3f068e3dd08bfec901edb8125a710c2
7
+ data.tar.gz: c43077a9023045856e726c0d2dbe39abc83abbd198df4a765758e6578664bcc6ec44ec7df75e164b436ae6a89cb6fe0ee89b4fc995afd3fe068bef2e5e484144
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # Webspicy
2
2
 
3
3
  A specification and test framework for web services seen as black-box software
4
- operations. Webspicy yields a better test coverage for a smaller testing effort,
5
- because software quality matters.
4
+ operations. Webspicy yields a better test coverage for a smaller testing effort.
6
5
 
7
6
  See webspicy in action and make the tutorial on https://yourbackendisbroken.dev
8
7
 
8
+ Have a look at `doc/*.md` for vocabulary and vision as well as `ROADMAP.md`.
9
+
9
10
  ## Features
10
11
 
11
12
  * Declarative specification of HTTP web services + their tests
@@ -19,7 +20,7 @@ See webspicy in action and make the tutorial on https://yourbackendisbroken.dev
19
20
  your API design better.
20
21
 
21
22
  * Formal and human-friendly data schema with strong data matching semantics,
22
- thanks to finitio.io
23
+ thanks to [http://finitio.io](http://finitio.io)
23
24
 
24
25
  * Test instrumentation and generation, based on PRE & POST contracts.
25
26
 
@@ -30,6 +31,10 @@ See webspicy in action and make the tutorial on https://yourbackendisbroken.dev
30
31
  * Extra goodies: when a specification is written, it can also be used for
31
32
  mocking the API, generating an openapi file, etc.
32
33
 
34
+ ## Is this used on real-world cases?
35
+
36
+ Yes, `webspicy` is currently used on a dozen production components. Our biggest specification has 324 specification files for thousands of tests, 35% of them being generated.
37
+
33
38
  ## Getting started with the commandline
34
39
 
35
40
  To install webspicy on your developer computer, install ruby then:
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- webspicy (0.16.3)
4
+ webspicy (0.17.0)
5
5
  colorize (~> 0.8.1)
6
6
  finitio (~> 0.9.0)
7
7
  http (>= 2)
@@ -11,7 +11,7 @@ PATH
11
11
  path (~> 2.0)
12
12
  rack-robustness (~> 1.1, >= 1.1.0)
13
13
  rack-test (~> 0.6.3)
14
- rspec (~> 3.7)
14
+ rspec (~> 3.10)
15
15
  rspec_junit_formatter (~> 0.4.1)
16
16
 
17
17
  GEM
@@ -27,11 +27,11 @@ GEM
27
27
  diff-lcs (1.4.4)
28
28
  domain_name (0.5.20190701)
29
29
  unf (>= 0.0.5, < 1.0.0)
30
- ffi (1.13.1)
30
+ ffi (1.14.2)
31
31
  ffi-compiler (1.0.1)
32
32
  ffi (>= 1.0.0)
33
33
  rake
34
- finitio (0.9.0)
34
+ finitio (0.9.1)
35
35
  citrus (>= 2.4, < 4.0)
36
36
  hansi (0.2.0)
37
37
  http (4.4.1)
@@ -42,9 +42,9 @@ GEM
42
42
  http-cookie (1.0.3)
43
43
  domain_name (~> 0.5)
44
44
  http-form_data (2.3.0)
45
- http-parser (1.2.2)
46
- ffi-compiler
47
- i18n (1.8.5)
45
+ http-parser (1.2.3)
46
+ ffi-compiler (>= 1.0, < 2.0)
47
+ i18n (1.8.7)
48
48
  concurrent-ruby (~> 1.0)
49
49
  mustermann (1.1.1)
50
50
  ruby2_keywords (~> 0.0.1)
@@ -55,7 +55,7 @@ GEM
55
55
  commonmarker (~> 0.17)
56
56
  psych (~> 3.1)
57
57
  path (2.0.1)
58
- psych (3.2.1)
58
+ psych (3.3.0)
59
59
  public_suffix (4.0.6)
60
60
  rack (2.2.3)
61
61
  rack-protection (2.1.0)
@@ -68,15 +68,15 @@ GEM
68
68
  rspec-core (~> 3.10.0)
69
69
  rspec-expectations (~> 3.10.0)
70
70
  rspec-mocks (~> 3.10.0)
71
- rspec-core (3.10.0)
71
+ rspec-core (3.10.1)
72
72
  rspec-support (~> 3.10.0)
73
- rspec-expectations (3.10.0)
73
+ rspec-expectations (3.10.1)
74
74
  diff-lcs (>= 1.2.0, < 2.0)
75
75
  rspec-support (~> 3.10.0)
76
- rspec-mocks (3.10.0)
76
+ rspec-mocks (3.10.1)
77
77
  diff-lcs (>= 1.2.0, < 2.0)
78
78
  rspec-support (~> 3.10.0)
79
- rspec-support (3.10.0)
79
+ rspec-support (3.10.1)
80
80
  rspec_junit_formatter (0.4.1)
81
81
  rspec-core (>= 2, < 4, != 2.12.0)
82
82
  ruby-enum (0.8.0)
@@ -95,6 +95,7 @@ GEM
95
95
  PLATFORMS
96
96
  ruby
97
97
  x86_64-darwin-15
98
+ x86_64-darwin-18
98
99
 
99
100
  DEPENDENCIES
100
101
  rake (~> 12)
@@ -102,4 +103,4 @@ DEPENDENCIES
102
103
  webspicy!
103
104
 
104
105
  BUNDLED WITH
105
- 2.2.1
106
+ 2.2.2
@@ -11,6 +11,7 @@ def webspicy_config(&bl)
11
11
  c.precondition Webspicy::Specification::Precondition::RobustToInvalidInput.new
12
12
 
13
13
  c.postcondition TodoRemoved
14
+ c.errcondition TodoNotRemoved
14
15
 
15
16
  c.instrument do |tc, client|
16
17
  role = tc.metadata[:role]
@@ -18,6 +18,9 @@ services:
18
18
  postconditions:
19
19
  - The todo has been removed
20
20
 
21
+ errconditions:
22
+ - If it existed, the todo has not been removed
23
+
21
24
  input_schema: |-
22
25
  {
23
26
  id: Integer
@@ -0,0 +1,21 @@
1
+ class TodoNotRemoved
2
+ include Webspicy::Specification::Postcondition
3
+
4
+ def self.match(service, descr)
5
+ return TodoNotRemoved.new if descr =~ /If it existed, the todo has not been removed/
6
+ end
7
+
8
+ def check(invocation)
9
+ client, scope, test_case = invocation.client,
10
+ invocation.client.scope,
11
+ invocation.test_case
12
+ return if invocation.response.status == 404
13
+ id = test_case.params['id']
14
+ url = scope.to_real_url("/todo/#{id}", test_case){|url| url }
15
+ response = client.api.get(url, {}, {
16
+ "Accept" => "application/json"
17
+ })
18
+ return nil if response.status == 200
19
+ "Todo `#{id}` was not supposed to be deleted, it was not found"
20
+ end
21
+ end
@@ -6,9 +6,11 @@ class TodoRemoved
6
6
  end
7
7
 
8
8
  def check(invocation)
9
- client = invocation.client
10
- id = invocation.test_case.params['id']
11
- url = "/todo/#{id}"
9
+ client, scope, test_case = invocation.client,
10
+ invocation.client.scope,
11
+ invocation.test_case
12
+ id = test_case.params['id']
13
+ url = scope.to_real_url("/todo/#{id}", test_case){|url| url }
12
14
  response = client.api.get(url, {}, {
13
15
  "Accept" => "application/json"
14
16
  })
@@ -62,6 +62,7 @@ module Webspicy
62
62
  raw = YAML.load(raw) if raw.is_a?(String)
63
63
  with_scope(scope) do
64
64
  r = FORMALDOC["Specification"].dress(raw)
65
+ r.config = scope.config
65
66
  r.located_at!(file) if file
66
67
  r
67
68
  end
@@ -1,25 +1,10 @@
1
1
  module Webspicy
2
2
  class Checker
3
3
 
4
- def initialize(config)
5
- @config = Configuration.dress(config)
4
+ def self.new(*args, &bl)
5
+ require_relative 'rspec/checker'
6
+ RSpecChecker.new(*args, &bl)
6
7
  end
7
- attr_reader :config
8
8
 
9
- def call
10
- config.each_scope do |scope|
11
- scope.each_specification_file do |file, folder|
12
- RSpec.describe file.relative_to(folder).to_s do
13
-
14
- it 'meets the formal doc data schema' do
15
- Webspicy.specification(file.load, file, scope)
16
- end
17
-
18
- end
19
- end
20
- end
21
- RSpec::Core::Runner.run config.rspec_options
22
- end
23
-
24
- end
25
- end
9
+ end # class Checker
10
+ end # module Webspicy
@@ -9,6 +9,7 @@ module Webspicy
9
9
  @children = []
10
10
  @preconditions = []
11
11
  @postconditions = []
12
+ @errconditions = []
12
13
  @listeners = Hash.new{|h,k| h[k] = [] }
13
14
  @rspec_options = default_rspec_options
14
15
  @run_examples = default_run_examples
@@ -126,6 +127,13 @@ module Webspicy
126
127
  attr_accessor :postconditions
127
128
  protected :postconditions=
128
129
 
130
+ # Registers an errcondition matcher
131
+ def errcondition(clazz)
132
+ errconditions << clazz
133
+ end
134
+ attr_accessor :errconditions
135
+ protected :errconditions=
136
+
129
137
  # Returns whether this configuration has children configurations or not
130
138
  def has_children?
131
139
  !children.empty?
@@ -422,6 +430,7 @@ module Webspicy
422
430
  d.children = []
423
431
  d.preconditions = self.preconditions.dup
424
432
  d.postconditions = self.postconditions.dup
433
+ d.errconditions = self.errconditions.dup
425
434
  d.rspec_options = self.rspec_options.dup
426
435
  d.listeners = LISTENER_KINDS.inject({}){|ls,kind|
427
436
  ls.merge(kind => self.listeners(kind).dup)
@@ -7,14 +7,6 @@ module Webspicy
7
7
  end
8
8
  attr_reader :config
9
9
 
10
- def preconditions
11
- config.preconditions
12
- end
13
-
14
- def postconditions
15
- config.postconditions
16
- end
17
-
18
10
  ###
19
11
  ### Eachers -- Allow navigating the web service definitions
20
12
  ###
@@ -25,6 +25,7 @@ Specification = .Webspicy::Specification
25
25
  description : String
26
26
  preconditions :? [String]|String
27
27
  postconditions :? [String]|String
28
+ errconditions :? [String]|String
28
29
  input_schema : Schema
29
30
  output_schema : Schema
30
31
  error_schema : Schema
@@ -45,6 +46,7 @@ Service =
45
46
  description : String
46
47
  preconditions :? [String]|String
47
48
  postconditions :? [String]|String
49
+ errconditions :? [String]|String
48
50
  input_schema : Schema
49
51
  output_schema : Schema
50
52
  error_schema : Schema
@@ -0,0 +1,2 @@
1
+ require_relative 'support/rspec_runnable'
2
+ require_relative 'checker/rspec_checker'
@@ -0,0 +1,24 @@
1
+ module Webspicy
2
+ class Checker
3
+ class RSpecChecker
4
+ include Webspicy::Support::RSpecRunnable
5
+
6
+ protected
7
+
8
+ def load_rspec_examples
9
+ config.each_scope do |scope|
10
+ scope.each_specification_file do |file, folder|
11
+ RSpec.describe file.relative_to(folder).to_s do
12
+
13
+ it 'meets the formal doc data schema' do
14
+ Webspicy.specification(file.load, file, scope)
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ end # class RSpecChecker
23
+ end # class Checker
24
+ end # module Webspicy
@@ -0,0 +1,27 @@
1
+ module Webspicy
2
+ module Support
3
+ module RSpecRunnable
4
+
5
+ def initialize(config)
6
+ @config = Configuration.dress(config)
7
+ end
8
+ attr_reader :config
9
+
10
+ def reset_rspec!
11
+ RSpec.reset
12
+ RSpec.configure do |c|
13
+ c.filter_gems_from_backtrace "rake"
14
+ end
15
+ load_rspec_examples
16
+ end
17
+
18
+ def call(err=$stderr, out=$stdout)
19
+ reset_rspec!
20
+ options = RSpec::Core::ConfigurationOptions.new(config.rspec_options)
21
+ conf = RSpec::Core::Configuration.new
22
+ RSpec::Core::Runner.new(options, conf).run(err, out)
23
+ end
24
+
25
+ end # module RSpecRunnable
26
+ end # module Support
27
+ end # module Webspicy
@@ -0,0 +1,4 @@
1
+ require_relative 'support/rspec_runnable'
2
+ require_relative 'tester/rspec_matchers'
3
+ require_relative 'tester/rspec_asserter'
4
+ require_relative 'tester/rspec_tester'
@@ -8,6 +8,10 @@ module Webspicy
8
8
  end
9
9
  attr_reader :rspec, :invocation
10
10
 
11
+ def self.call(rspec, invocation)
12
+ new(rspec, invocation).send(:assert!)
13
+ end
14
+
11
15
  def response
12
16
  invocation.response
13
17
  end
@@ -20,15 +24,20 @@ module Webspicy
20
24
  test_case.service
21
25
  end
22
26
 
27
+ protected
28
+
23
29
  def assert!
24
- assert_status_met
25
- assert_content_type_met
26
- assert_expected_headers
30
+ rspec.aggregate_failures do
31
+ assert_status_met
32
+ assert_content_type_met
33
+ assert_expected_headers
34
+ end
27
35
  assert_output_schema_met
28
- assert_assertions_met
29
- assert_postconditions_met
30
-
31
- assert_no_other_errors
36
+ rspec.aggregate_failures do
37
+ assert_assertions_met
38
+ assert_postconditions_met
39
+ assert_errconditions_met
40
+ end
32
41
  end
33
42
 
34
43
  def assert_status_met
@@ -98,9 +107,13 @@ module Webspicy
98
107
  end
99
108
  end
100
109
 
101
- def assert_no_other_errors
102
- errors = invocation.errors
103
- rspec.expect(errors).to rspec.be_an_empty_errors_array
110
+ def assert_errconditions_met
111
+ return unless service.has_errconditions?
112
+ return unless test_case.counterexample?
113
+ service.errconditions.each do |post|
114
+ msg = post.check(invocation)
115
+ rspec.expect(msg).to rspec.meet_errcondition(post)
116
+ end
104
117
  end
105
118
 
106
119
  end # class RSpecAsserter
@@ -94,6 +94,16 @@ RSpec::Matchers.define :meet_postcondition do |post|
94
94
  end
95
95
  end
96
96
 
97
+ RSpec::Matchers.define :meet_errcondition do |post|
98
+ match do |actual|
99
+ actual.nil?
100
+ end
101
+ failure_message_for_should do |actual|
102
+ "expected errcondition `#{post.class.name}` to be met, got following error:\n" + \
103
+ " #{actual}"
104
+ end
105
+ end
106
+
97
107
  RSpec::Matchers.define :be_an_empty_errors_array do
98
108
  match do |actual|
99
109
  actual.empty?