webspicy 0.16.0 → 0.18.0

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