webspicy 0.16.0 → 0.18.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -5
  3. data/examples/restful/webspicy/config.rb +1 -0
  4. data/examples/restful/webspicy/{todo/deleteTodo.yml → formaldef/todo/_one/delete.yml} +3 -0
  5. data/examples/restful/webspicy/{todo/getTodoSingleServiceFormat.yml → formaldef/todo/_one/get.simpler.yml} +0 -0
  6. data/examples/restful/webspicy/{todo/getTodo.yml → formaldef/todo/_one/get.yml} +0 -0
  7. data/examples/restful/webspicy/{todo/patchTodo.yml → formaldef/todo/_one/patch.yml} +0 -0
  8. data/examples/restful/webspicy/{todo/putTodo.yml → formaldef/todo/_one/put.yml} +0 -0
  9. data/examples/restful/webspicy/{todo/getTodos.yml → formaldef/todo/get.yml} +0 -0
  10. data/examples/restful/webspicy/{todo → formaldef/todo}/options.yml +0 -0
  11. data/examples/restful/webspicy/{todo/postCsv.yml → formaldef/todo/post.csv.yml} +0 -0
  12. data/examples/restful/webspicy/{todo/postFile.yml → formaldef/todo/post.file.yml} +0 -0
  13. data/examples/restful/webspicy/{todo/postTodos.yml → formaldef/todo/post.yml} +0 -0
  14. data/examples/restful/webspicy/{todo → formaldef/todo}/todos.csv +0 -0
  15. data/examples/restful/webspicy/support/todo_not_removed.rb +21 -0
  16. data/examples/restful/webspicy/support/todo_removed.rb +5 -3
  17. data/examples/website/specification/get-http.yml +20 -24
  18. data/examples/website/specification/get-https.yml +20 -24
  19. data/lib/webspicy.rb +4 -3
  20. data/lib/webspicy/checker.rb +5 -20
  21. data/lib/webspicy/configuration.rb +9 -0
  22. data/lib/webspicy/configuration/scope.rb +0 -8
  23. data/lib/webspicy/formaldoc.fio +8 -6
  24. data/lib/webspicy/mocker/config.ru +5 -0
  25. data/lib/webspicy/rspec/checker.rb +2 -0
  26. data/lib/webspicy/rspec/checker/rspec_checker.rb +24 -0
  27. data/lib/webspicy/rspec/support/rspec_runnable.rb +27 -0
  28. data/lib/webspicy/rspec/tester.rb +4 -0
  29. data/lib/webspicy/{tester → rspec/tester}/rspec_asserter.rb +24 -11
  30. data/lib/webspicy/{tester → rspec/tester}/rspec_matchers.rb +10 -0
  31. data/lib/webspicy/rspec/tester/rspec_tester.rb +63 -0
  32. data/lib/webspicy/specification.rb +10 -10
  33. data/lib/webspicy/specification/errcondition.rb +16 -0
  34. data/lib/webspicy/specification/precondition/robust_to_invalid_input.rb +1 -1
  35. data/lib/webspicy/specification/service.rb +27 -19
  36. data/lib/webspicy/specification/test_case.rb +3 -9
  37. data/lib/webspicy/support.rb +1 -0
  38. data/lib/webspicy/support/data_object.rb +25 -0
  39. data/lib/webspicy/tester.rb +4 -78
  40. data/lib/webspicy/tester/asserter.rb +9 -4
  41. data/lib/webspicy/tester/assertions.rb +8 -9
  42. data/lib/webspicy/tester/failure.rb +6 -0
  43. data/lib/webspicy/tester/invocation.rb +8 -156
  44. data/lib/webspicy/version.rb +1 -1
  45. data/spec/unit/configuration/scope/test_each_service.rb +2 -2
  46. data/spec/unit/configuration/scope/test_each_specification.rb +7 -7
  47. data/spec/unit/test_configuration.rb +1 -1
  48. data/spec/unit/tester/test_asserter.rb +198 -3
  49. data/spec/unit/tester/test_assertions.rb +8 -6
  50. metadata +33 -25
  51. data/LICENSE.md +0 -22
  52. data/examples/restful/Gemfile.lock +0 -105
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70bfa6517173d1f121b9ebe968ac5970e41b2f6846384b152d288eb2f4913cb7
4
- data.tar.gz: 0e5b728db6ecd9eb3ccb42d166317911a03092d41cf4778f1058960aa0e92bab
3
+ metadata.gz: 2eaf855f36546e0f538b47c50d6cb612a741807057f82172a93bff42542b326f
4
+ data.tar.gz: b2e892ebf4d30e04d843df175d01ac77e8e65e145b7e6a88b4eccc0c24614ea5
5
5
  SHA512:
6
- metadata.gz: 51e6fbe8ad57cb2795435a3c194e142d1f5e822289be923aeebfd9c89ef9440a6fd7dea2fb58bb9e8d8bdd490b1025201dfb12b4d000d7d6604e422de1007dcf
7
- data.tar.gz: 3fe5b84bd684564321712412c2bc0f918944d22dbe665ea119d0a478d181a6ac9483de2c1352e8b0f98b484e1cd361f0193afcf42c6ee6c0095811feec86c555
6
+ metadata.gz: e6c77544f40a49028508709e8be6e7f7b95d979212416bcde782cdb5dd361f0091433d68e38c8908800851c629053c34f2c102ae41be8dea660f5ee51a839177
7
+ data.tar.gz: 8fb1d33bf31723292d39a2451a79fcca4e67c5fc57f37ade00e40290fbe12e3ebbd718fc619e7c2128fdae34e7c8e44a087649fb4199d8d3505d42ae3b0e982f
data/README.md CHANGED
@@ -1,11 +1,13 @@
1
+ [![Build Status](https://travis-ci.com/enspirit/webspicy.svg?branch=master)](https://travis-ci.com/enspirit/webspicy)
1
2
  # Webspicy
2
3
 
3
4
  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.
5
+ operations. Webspicy yields a better test coverage for a smaller testing effort.
6
6
 
7
7
  See webspicy in action and make the tutorial on https://yourbackendisbroken.dev
8
8
 
9
+ Have a look at `doc/*.md` for vocabulary and vision as well as `ROADMAP.md`.
10
+
9
11
  ## Features
10
12
 
11
13
  * Declarative specification of HTTP web services + their tests
@@ -19,7 +21,7 @@ See webspicy in action and make the tutorial on https://yourbackendisbroken.dev
19
21
  your API design better.
20
22
 
21
23
  * Formal and human-friendly data schema with strong data matching semantics,
22
- thanks to finitio.io
24
+ thanks to [http://finitio.io](http://finitio.io)
23
25
 
24
26
  * Test instrumentation and generation, based on PRE & POST contracts.
25
27
 
@@ -30,6 +32,10 @@ See webspicy in action and make the tutorial on https://yourbackendisbroken.dev
30
32
  * Extra goodies: when a specification is written, it can also be used for
31
33
  mocking the API, generating an openapi file, etc.
32
34
 
35
+ ## Is this used on real-world cases?
36
+
37
+ 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.
38
+
33
39
  ## Getting started with the commandline
34
40
 
35
41
  To install webspicy on your developer computer, install ruby then:
@@ -60,7 +66,7 @@ to use our `:tester` docker image. Just mount your test suite as a volume
60
66
  in `/home/app` and you are good to go:
61
67
 
62
68
  ```
63
- docker run -v path/to/tests:/formalspec enspirit/webspicy-tester
69
+ docker run -v path/to/tests:/formalspec enspirit/webspicy:tester
64
70
  ```
65
71
 
66
72
  If your plan is to test a backend that runs on your own machine (vs.
@@ -69,7 +75,7 @@ you will need to add some networking option, as shown below. Please
69
75
  refer to Docker documentation.
70
76
 
71
77
  ```
72
- docker run -v path/to/tests:/formalspec --network=host enspirit/webspicy-tester
78
+ docker run -v path/to/tests:/formalspec --network=host enspirit/webspicy:tester
73
79
  ```
74
80
 
75
81
  ## Contributing
@@ -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
  })
@@ -1,34 +1,30 @@
1
1
  ---
2
- name: |-
3
- Your backend is broken website
4
-
5
2
  url: |-
6
3
  http://yourbackendisbroken.dev
7
4
 
8
- services:
9
- - method: |-
10
- GET
5
+ method: |-
6
+ GET
11
7
 
12
- description: |-
13
- Redirects to the https version
8
+ description: |-
9
+ Redirects to the https version
14
10
 
15
- input_schema: |-
16
- Any
11
+ input_schema: |-
12
+ Any
17
13
 
18
- output_schema: |-
19
- Any
14
+ output_schema: |-
15
+ Any
20
16
 
21
- error_schema: |-
22
- Any
17
+ error_schema: |-
18
+ Any
23
19
 
24
- examples:
20
+ examples:
25
21
 
26
- - description: |-
27
- it works
28
- params:
29
- id: 1
30
- expected:
31
- content_type: text/html
32
- status: 3xx
33
- headers:
34
- Location: https://yourbackendisbroken.dev/?id=1
22
+ - description: |-
23
+ it works
24
+ params:
25
+ id: 1
26
+ expected:
27
+ content_type: text/html
28
+ status: 3xx
29
+ headers:
30
+ Location: https://yourbackendisbroken.dev/?id=1
@@ -1,34 +1,30 @@
1
1
  ---
2
- name: |-
3
- Your backend is broken website
4
-
5
2
  url: |-
6
3
  https://yourbackendisbroken.dev
7
4
 
8
- services:
9
- - method: |-
10
- GET
5
+ method: |-
6
+ GET
11
7
 
12
- description: |-
13
- Returns the web page
8
+ description: |-
9
+ Returns the web page
14
10
 
15
- input_schema: |-
16
- Any
11
+ input_schema: |-
12
+ Any
17
13
 
18
- output_schema: |-
19
- Any
14
+ output_schema: |-
15
+ Any
20
16
 
21
- error_schema: |-
22
- Any
17
+ error_schema: |-
18
+ Any
23
19
 
24
- examples:
20
+ examples:
25
21
 
26
- - description: |-
27
- it works
28
- params:
29
- id: 1
30
- expected:
31
- content_type: text/html
32
- status: 200
33
- assert:
34
- - notEmpty
22
+ - description: |-
23
+ it works
24
+ params:
25
+ id: 1
26
+ expected:
27
+ content_type: text/html
28
+ status: 200
29
+ assert:
30
+ - notEmpty
data/lib/webspicy.rb CHANGED
@@ -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
@@ -89,9 +90,9 @@ module Webspicy
89
90
  module_function :test_case
90
91
 
91
92
  def handle_finitio_error(ex, scope)
92
- # msg = "#{ex.message}:\n #{ex.root_cause.message}"
93
- # msg = Support::Colorize.colorize_error(msg, scope.config)
94
- # fatal(msg)
93
+ msg = "#{ex.message}:\n #{ex.root_cause.message}"
94
+ msg = Support::Colorize.colorize_error(msg, scope.config)
95
+ fatal(msg)
95
96
  raise
96
97
  end
97
98
  module_function :handle_finitio_error
@@ -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
  ###
@@ -18,18 +18,14 @@ FileUpload =
18
18
  }
19
19
 
20
20
  Specification = .Webspicy::Specification
21
- <info> {
22
- name: String
23
- url: String
24
- services: [Service]
25
- }
26
21
  <singleservice> {
27
22
  name :? String
28
- url : String
23
+ url : String
29
24
  method : Method
30
25
  description : String
31
26
  preconditions :? [String]|String
32
27
  postconditions :? [String]|String
28
+ errconditions :? [String]|String
33
29
  input_schema : Schema
34
30
  output_schema : Schema
35
31
  error_schema : Schema
@@ -38,6 +34,11 @@ Specification = .Webspicy::Specification
38
34
  examples :? [TestCase]
39
35
  counterexamples :? [TestCase]
40
36
  }
37
+ <info> {
38
+ name: String
39
+ url: String
40
+ services: [Service]
41
+ }
41
42
 
42
43
  Service =
43
44
  .Webspicy::Specification::Service <info> {
@@ -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,5 @@
1
+ require "webspicy"
2
+ require "webspicy/mocker"
3
+ require 'rack'
4
+ config = Webspicy::Configuration.dress(Path("/formalspec/"))
5
+ run Webspicy::Mocker.new(config)
@@ -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