webspicy 0.16.3 → 0.17.0

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