webspicy 0.15.7 → 0.16.3

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +71 -24
  3. data/bin/webspicy +30 -14
  4. data/examples/restful/Gemfile +2 -2
  5. data/examples/restful/Gemfile.lock +53 -33
  6. data/examples/restful/Rakefile +0 -1
  7. data/examples/restful/app.rb +4 -1
  8. data/examples/restful/webspicy/config.rb +8 -0
  9. data/examples/restful/webspicy/rack.rb +1 -1
  10. data/examples/restful/webspicy/real.rb +1 -1
  11. data/examples/restful/webspicy/schema.fio +2 -2
  12. data/examples/restful/webspicy/support/must_be_authenticated.rb +2 -2
  13. data/examples/restful/webspicy/support/todo_removed.rb +18 -0
  14. data/examples/restful/webspicy/todo/deleteTodo.yml +4 -1
  15. data/examples/restful/webspicy/todo/getTodoSingleServiceFormat.yml +46 -0
  16. data/examples/restful/webspicy/todo/options.yml +1 -1
  17. data/examples/restful/webspicy/todo/patchTodo.yml +3 -0
  18. data/examples/restful/webspicy/todo/postFile.yml +1 -1
  19. data/examples/single_spec/spec.yml +59 -0
  20. data/examples/website/config.rb +2 -0
  21. data/examples/website/schema.fio +1 -0
  22. data/examples/website/specification/get-http.yml +30 -0
  23. data/examples/website/specification/get-https.yml +30 -0
  24. data/lib/finitio/webspicy/scalars.fio +25 -0
  25. data/lib/webspicy.rb +48 -17
  26. data/lib/webspicy/checker.rb +2 -2
  27. data/lib/webspicy/configuration.rb +70 -14
  28. data/lib/webspicy/configuration/scope.rb +162 -0
  29. data/lib/webspicy/configuration/single_url.rb +58 -0
  30. data/lib/webspicy/configuration/single_yml_file.rb +30 -0
  31. data/lib/webspicy/formaldoc.fio +23 -8
  32. data/lib/webspicy/mocker.rb +8 -8
  33. data/lib/webspicy/mocker/config.ru +5 -0
  34. data/lib/webspicy/openapi.rb +1 -0
  35. data/lib/webspicy/openapi/generator.rb +127 -0
  36. data/lib/webspicy/{resource.rb → specification.rb} +28 -5
  37. data/lib/webspicy/specification/file_upload.rb +37 -0
  38. data/lib/webspicy/specification/postcondition.rb +16 -0
  39. data/lib/webspicy/specification/precondition.rb +19 -0
  40. data/lib/webspicy/specification/precondition/global_request_headers.rb +35 -0
  41. data/lib/webspicy/specification/precondition/robust_to_invalid_input.rb +68 -0
  42. data/lib/webspicy/{resource → specification}/service.rb +11 -6
  43. data/lib/webspicy/specification/test_case.rb +139 -0
  44. data/lib/webspicy/support.rb +1 -0
  45. data/lib/webspicy/support/colorize.rb +28 -0
  46. data/lib/webspicy/support/status_range.rb +6 -1
  47. data/lib/webspicy/tester.rb +16 -11
  48. data/lib/webspicy/tester/asserter.rb +3 -2
  49. data/lib/webspicy/tester/assertions.rb +5 -1
  50. data/lib/webspicy/tester/client.rb +63 -0
  51. data/lib/webspicy/tester/client/http_client.rb +154 -0
  52. data/lib/webspicy/tester/client/rack_test_client.rb +188 -0
  53. data/lib/webspicy/tester/client/support.rb +65 -0
  54. data/lib/webspicy/tester/invocation.rb +218 -0
  55. data/lib/webspicy/tester/rspec_asserter.rb +108 -0
  56. data/lib/webspicy/tester/rspec_matchers.rb +104 -0
  57. data/lib/webspicy/version.rb +2 -2
  58. data/spec/{unit/spec_helper.rb → spec_helper.rb} +0 -0
  59. data/spec/unit/configuration/scope/test_each_service.rb +49 -0
  60. data/spec/unit/configuration/scope/test_each_specification.rb +68 -0
  61. data/spec/unit/configuration/scope/test_expand_example.rb +65 -0
  62. data/spec/unit/configuration/scope/test_to_real_url.rb +82 -0
  63. data/spec/unit/openapi/test_generator.rb +28 -0
  64. data/spec/unit/specification/precondition/test_global_request_headers.rb +42 -0
  65. data/spec/unit/{resource → specification}/service/test_dress_params.rb +2 -2
  66. data/spec/unit/specification/test_case/test_mutate.rb +24 -0
  67. data/spec/unit/{resource → specification}/test_instantiate_url.rb +5 -5
  68. data/spec/unit/{resource → specification}/test_url_placeholders.rb +4 -4
  69. data/spec/unit/test_configuration.rb +24 -7
  70. data/spec/unit/tester/client/test_around.rb +61 -0
  71. data/spec/unit/tester/test_asserter.rb +51 -0
  72. data/spec/unit/tester/test_assertions.rb +4 -4
  73. data/tasks/test.rake +3 -1
  74. metadata +88 -45
  75. data/LICENSE.md +0 -22
  76. data/lib/webspicy/client.rb +0 -61
  77. data/lib/webspicy/client/http_client.rb +0 -145
  78. data/lib/webspicy/client/rack_test_client.rb +0 -181
  79. data/lib/webspicy/client/support.rb +0 -48
  80. data/lib/webspicy/file_upload.rb +0 -35
  81. data/lib/webspicy/postcondition.rb +0 -14
  82. data/lib/webspicy/precondition.rb +0 -15
  83. data/lib/webspicy/resource/service/invocation.rb +0 -212
  84. data/lib/webspicy/resource/service/test_case.rb +0 -132
  85. data/lib/webspicy/scope.rb +0 -160
  86. data/spec/unit/client/test_around.rb +0 -59
  87. data/spec/unit/scope/test_each_resource.rb +0 -66
  88. data/spec/unit/scope/test_each_service.rb +0 -47
  89. data/spec/unit/scope/test_expand_example.rb +0 -63
  90. data/spec/unit/scope/test_to_real_url.rb +0 -80
@@ -15,13 +15,16 @@ services:
15
15
  preconditions:
16
16
  - Must be an admin
17
17
 
18
+ postconditions:
19
+ - The todo has been removed
20
+
18
21
  input_schema: |-
19
22
  {
20
23
  id: Integer
21
24
  }
22
25
 
23
26
  output_schema: |-
24
- .
27
+ Any
25
28
 
26
29
  error_schema: |-
27
30
  ErrorSchema
@@ -0,0 +1,46 @@
1
+ ---
2
+ url: |-
3
+ /todo/{id}
4
+
5
+ method: |-
6
+ GET
7
+
8
+ description: |-
9
+ Returns a single todo item
10
+
11
+ input_schema: |-
12
+ {
13
+ id: Integer
14
+ }
15
+
16
+ output_schema: |-
17
+ Todo
18
+
19
+ error_schema: |-
20
+ ErrorSchema
21
+
22
+ examples:
23
+
24
+ - description: |-
25
+ when requested on an existing TODO
26
+ params:
27
+ id: 1
28
+ expected:
29
+ content_type: application/json
30
+ status: 200
31
+ assert:
32
+ - "pathFD('', id: 1)"
33
+ - "match('description', /Refactor/)"
34
+ - "notMatch('description', /Foo/)"
35
+
36
+ counterexamples:
37
+
38
+ - description: |-
39
+ when requested on an unexisting TODO
40
+ params:
41
+ id: 999254654
42
+ expected:
43
+ content_type: application/json
44
+ status: 404
45
+ assert:
46
+ - "pathFD('', error: 'No such todo')"
@@ -17,7 +17,7 @@ services:
17
17
  }
18
18
 
19
19
  output_schema: |-
20
- .
20
+ Any
21
21
 
22
22
  error_schema: |-
23
23
  ErrorSchema
@@ -36,6 +36,9 @@ services:
36
36
  params:
37
37
  id: 1
38
38
  description: 'hello world'
39
+ expected:
40
+ content_type: application/json
41
+ status: 200
39
42
  assert:
40
43
  - "pathFD('', description: 'hello world')"
41
44
 
@@ -16,7 +16,7 @@ services:
16
16
  - Must be authenticated
17
17
 
18
18
  input_schema: |-
19
- .
19
+ Any
20
20
 
21
21
  output_schema: |-
22
22
  { count: Integer }
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: |-
3
+ Todo
4
+
5
+ url: |-
6
+ http://127.0.0.1:4567/todo/{id}
7
+
8
+ services:
9
+ - method: |-
10
+ GET
11
+
12
+ description: |-
13
+ Returns a single todo item
14
+
15
+ input_schema: |-
16
+ {
17
+ id: Integer
18
+ }
19
+
20
+ output_schema: |-
21
+ {
22
+ id: Integer
23
+ description: String
24
+ }
25
+
26
+ error_schema: |-
27
+ {
28
+ error: String
29
+ }
30
+
31
+ examples:
32
+
33
+ - description: |-
34
+ when requested on an existing TODO
35
+ headers:
36
+ Accept: application/json
37
+ params:
38
+ id: 1
39
+ expected:
40
+ content_type: application/json
41
+ status: 200
42
+ assert:
43
+ - "pathFD('', id: 1)"
44
+ - "match('description', /Refactor/)"
45
+ - "notMatch('description', /Foo/)"
46
+
47
+ counterexamples:
48
+
49
+ - description: |-
50
+ when requested on an unexisting TODO
51
+ headers:
52
+ Accept: application/json
53
+ params:
54
+ id: 999254654
55
+ expected:
56
+ content_type: application/json
57
+ status: 404
58
+ assert:
59
+ - "pathFD('', error: 'No such todo')"
@@ -0,0 +1,2 @@
1
+ Webspicy::Configuration.new{|c|
2
+ }
@@ -0,0 +1 @@
1
+ @import webspicy/scalars
@@ -0,0 +1,30 @@
1
+ ---
2
+ url: |-
3
+ http://yourbackendisbroken.dev
4
+
5
+ method: |-
6
+ GET
7
+
8
+ description: |-
9
+ Redirects to the https version
10
+
11
+ input_schema: |-
12
+ Any
13
+
14
+ output_schema: |-
15
+ Any
16
+
17
+ error_schema: |-
18
+ Any
19
+
20
+ examples:
21
+
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
@@ -0,0 +1,30 @@
1
+ ---
2
+ url: |-
3
+ https://yourbackendisbroken.dev
4
+
5
+ method: |-
6
+ GET
7
+
8
+ description: |-
9
+ Returns the web page
10
+
11
+ input_schema: |-
12
+ Any
13
+
14
+ output_schema: |-
15
+ Any
16
+
17
+ error_schema: |-
18
+ Any
19
+
20
+ examples:
21
+
22
+ - description: |-
23
+ it works
24
+ params:
25
+ id: 1
26
+ expected:
27
+ content_type: text/html
28
+ status: 200
29
+ assert:
30
+ - notEmpty
@@ -0,0 +1,25 @@
1
+ # Any
2
+ Any = .
3
+
4
+ # Booleans
5
+ Nil = .NilClass
6
+ True = .TrueClass
7
+ False = .FalseClass
8
+ Boolean = .TrueClass|.FalseClass
9
+
10
+ # Numbers
11
+ Numeric = .Numeric
12
+ Integer = .Integer
13
+ Float = .Float
14
+ Real = .Float
15
+
16
+ # String
17
+ String = .String
18
+
19
+ # Date, Time, DateTime
20
+ Date = .Date <iso8601> .String \( s | Date.iso8601(s) )
21
+ \( d | d.iso8601 )
22
+ Time = .Time <iso8601> .String \( s | Time.iso8601(s) )
23
+ \( t | t.iso8601 )
24
+ DateTime = .DateTime <iso8601> .String \( s | DateTime.iso8601(s) )
25
+ \( t | t.iso8601 )
@@ -7,25 +7,33 @@ require 'ostruct'
7
7
  require 'yaml'
8
8
  require 'rspec'
9
9
  require 'rack/test'
10
- require "mustermann"
10
+ require 'mustermann'
11
+ require 'colorized_string'
12
+ require 'securerandom'
11
13
  module Webspicy
12
14
 
13
15
  ###
14
16
  ### Load library
15
17
  ###
16
18
 
19
+ require 'webspicy/version'
17
20
  require 'webspicy/support'
21
+ require 'webspicy/specification'
18
22
  require 'webspicy/configuration'
19
- require 'webspicy/file_upload'
20
- require 'webspicy/scope'
21
- require 'webspicy/client'
22
- require 'webspicy/resource'
23
- require 'webspicy/precondition'
24
- require 'webspicy/postcondition'
25
23
  require 'webspicy/checker'
26
24
  require 'webspicy/tester'
27
- require 'webspicy/tester/assertions'
28
- require 'webspicy/tester/asserter'
25
+
26
+ ###
27
+ ### Backward compatibility
28
+ ###
29
+ Client = Tester::Client
30
+ HttpClient = Tester::HttpClient
31
+ RackTestClient = Tester::RackTestClient
32
+ Resource = Specification
33
+ Precondition = Specification::Precondition
34
+ Postcondition = Specification::Postcondition
35
+ FileUpload = Specification::FileUpload
36
+ Scope = Configuration::Scope
29
37
 
30
38
  ###
31
39
  ### About folders
@@ -36,30 +44,38 @@ module Webspicy
36
44
  EXAMPLES_FOLDER = ROOT_FOLDER/('examples')
37
45
 
38
46
  ###
39
- ### About formal doc and resources defined there
47
+ ### About formal doc and specifications defined there
40
48
  ###
41
-
49
+ Finitio.stdlib_path(Path.dir/"finitio")
50
+ DEFAULT_SYSTEM = Finitio.system(<<~FIO)
51
+ @import webspicy/scalars
52
+ FIO
42
53
  FORMALDOC = Finitio.system(Path.dir/("webspicy/formaldoc.fio"))
43
54
 
44
55
  # Returns a default scope instance.
45
56
  def default_scope
46
- Scope.new(Configuration.new)
57
+ Configuration::Scope.new(Configuration.new)
47
58
  end
48
59
  module_function :default_scope
49
60
 
50
- def resource(raw, file = nil, scope = default_scope)
61
+ def specification(raw, file = nil, scope = default_scope)
62
+ raw = YAML.load(raw) if raw.is_a?(String)
51
63
  with_scope(scope) do
52
- r = FORMALDOC["Resource"].dress(raw)
64
+ r = FORMALDOC["Specification"].dress(raw)
53
65
  r.located_at!(file) if file
54
66
  r
55
67
  end
68
+ rescue Finitio::Error => ex
69
+ handle_finitio_error(ex, scope)
56
70
  end
57
- module_function :resource
71
+ module_function :specification
58
72
 
59
73
  def service(raw, scope = default_scope)
60
74
  with_scope(scope) do
61
75
  FORMALDOC["Service"].dress(raw)
62
76
  end
77
+ rescue Finitio::Error => ex
78
+ handle_finitio_error(ex)
63
79
  end
64
80
  module_function :service
65
81
 
@@ -67,9 +83,19 @@ module Webspicy
67
83
  with_scope(scope) do
68
84
  FORMALDOC["TestCase"].dress(raw)
69
85
  end
86
+ rescue Finitio::Error => ex
87
+ handle_finitio_error(ex)
70
88
  end
71
89
  module_function :test_case
72
90
 
91
+ 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)
95
+ raise
96
+ end
97
+ module_function :handle_finitio_error
98
+
73
99
  #
74
100
  # Yields the block after having installed `scope` globally.
75
101
  #
@@ -120,7 +146,7 @@ module Webspicy
120
146
  if scope = Thread.current[:webspicy_scope]
121
147
  scope.parse_schema(fio)
122
148
  else
123
- Finitio.system(fio)
149
+ DEFAULT_SYSTEM.system(fio)
124
150
  end
125
151
  end
126
152
  module_function :schema
@@ -132,7 +158,7 @@ module Webspicy
132
158
  LOGGER = ::Logger.new(STDOUT)
133
159
  LOGGER.level = Logger.const_get(ENV['LOG_LEVEL'] || 'WARN')
134
160
  LOGGER.formatter = proc { |severity, datetime, progname, msg|
135
- " " + msg + "\n"
161
+ " " + msg + "\n"
136
162
  }
137
163
 
138
164
  def info(*args, &bl)
@@ -145,4 +171,9 @@ module Webspicy
145
171
  end
146
172
  module_function :debug
147
173
 
174
+ def fatal(*args, &bl)
175
+ LOGGER && LOGGER.fatal(*args, &bl)
176
+ end
177
+ module_function :fatal
178
+
148
179
  end
@@ -8,11 +8,11 @@ module Webspicy
8
8
 
9
9
  def call
10
10
  config.each_scope do |scope|
11
- scope.each_resource_file do |file, folder|
11
+ scope.each_specification_file do |file, folder|
12
12
  RSpec.describe file.relative_to(folder).to_s do
13
13
 
14
14
  it 'meets the formal doc data schema' do
15
- Webspicy.resource(file.load, file, scope)
15
+ Webspicy.specification(file.load, file, scope)
16
16
  end
17
17
 
18
18
  end
@@ -13,27 +13,57 @@ module Webspicy
13
13
  @rspec_options = default_rspec_options
14
14
  @run_examples = default_run_examples
15
15
  @run_counterexamples = default_run_counterexamples
16
+ @run_generated_counterexamples = default_run_generated_counterexamples
16
17
  @file_filter = default_file_filter
17
18
  @service_filter = default_service_filter
18
19
  @test_case_filter = default_test_case_filter
19
- @client = HttpClient
20
+ @colors = {
21
+ :highlight => :cyan,
22
+ :error => :red,
23
+ :success => :green
24
+ }
25
+ @scope_factory = ->(config){ Scope.new(config) }
26
+ @client = Tester::HttpClient
20
27
  Path.require_tree(folder/'support') if (folder/'support').exists?
21
28
  yield(self) if block_given?
22
29
  end
23
30
  attr_accessor :folder
24
31
  protected :folder=
25
32
 
33
+ attr_accessor :colors
34
+
26
35
  def self.dress(arg, &bl)
27
- return arg if arg.is_a?(Configuration)
28
- arg = Path(arg)
29
- if arg.file?
30
- c = Kernel.instance_eval arg.read, arg.to_s
31
- yield(c) if block_given?
32
- c
33
- elsif (arg/'config.rb').file?
34
- dress(arg/'config.rb', &bl)
36
+ case arg
37
+ when Configuration
38
+ arg
39
+ when /^https?:\/\//
40
+ Configuration.new{|c|
41
+ c.scope_factory = SingleUrl.new(arg)
42
+ }
43
+ when ->(f){ Path(f).exists? }
44
+ arg = Path(arg)
45
+ if arg.file? && arg.ext == ".rb"
46
+ c = Kernel.instance_eval arg.read, arg.to_s
47
+ yield(c) if block_given?
48
+ c
49
+ elsif arg.file? && arg.ext == '.yml'
50
+ folder = arg.backfind("[config.rb]")
51
+ if folder && folder.exists?
52
+ dress(folder/"config.rb"){|c|
53
+ c.scope_factory = SingleYmlFile.new(arg)
54
+ }
55
+ else
56
+ Configuration.new{|c|
57
+ c.scope_factory = SingleYmlFile.new(arg)
58
+ }
59
+ end
60
+ elsif arg.directory? and (arg/'config.rb').file?
61
+ dress(arg/'config.rb', &bl)
62
+ else
63
+ raise ArgumentError, "Missing config.rb file"
64
+ end
35
65
  else
36
- raise ArgumentError, "Missing config.rb file"
66
+ raise ArgumentError, "Unable to turn `#{arg}` to a configuration"
37
67
  end
38
68
  end
39
69
 
@@ -51,10 +81,16 @@ module Webspicy
51
81
  config.each_scope(&bl)
52
82
  end
53
83
  else
54
- yield Scope.new(self)
84
+ yield factor_scope
55
85
  end
56
86
  end
57
87
 
88
+ attr_accessor :scope_factory
89
+
90
+ def factor_scope
91
+ @scope_factory.call(self)
92
+ end
93
+
58
94
  # Adds a folder to the list of folders where test case definitions are
59
95
  # to be found.
60
96
  def folder(folder = nil, &bl)
@@ -108,7 +144,7 @@ module Webspicy
108
144
 
109
145
  # Returns the defaut value for run_examples
110
146
  def default_run_examples
111
- ENV['ROBUST'].nil? || (ENV['ROBUST'] != 'only')
147
+ ENV['ROBUST'].nil? || (ENV['ROBUST'] != 'only' && ENV['ROBUST'] != 'generated')
112
148
  end
113
149
  private :default_run_examples
114
150
 
@@ -125,10 +161,27 @@ module Webspicy
125
161
 
126
162
  # Returns the defaut value for run_counterexamples
127
163
  def default_run_counterexamples
128
- ENV['ROBUST'].nil? || (ENV['ROBUST'] != 'no')
164
+ ENV['ROBUST'].nil? || (ENV['ROBUST'] != 'no' && ENV['ROBUST'] != 'generated')
129
165
  end
130
166
  private :default_run_counterexamples
131
167
 
168
+ # Sets whether generated counter examples have to be ran or not.
169
+ def run_generated_counterexamples=(run_generated_counterexamples)
170
+ @run_generated_counterexamples = run_generated_counterexamples
171
+ end
172
+ attr_reader :run_generated_counterexamples
173
+
174
+ # Whether generated counter examples must be ran or not.
175
+ def run_generated_counterexamples?
176
+ @run_generated_counterexamples
177
+ end
178
+
179
+ # Returns the defaut value for run_generated_counterexamples
180
+ def default_run_generated_counterexamples
181
+ ENV['ROBUST'].nil? || (ENV['ROBUST'] != 'no')
182
+ end
183
+ private :default_run_generated_counterexamples
184
+
132
185
  # Installs a host (resolver).
133
186
  #
134
187
  # The host resolver is responsible from transforming URLs found in
@@ -347,7 +400,7 @@ module Webspicy
347
400
  elsif not(self.parent.nil?)
348
401
  self.parent.data_system
349
402
  else
350
- Finitio::DEFAULT_SYSTEM
403
+ Webspicy::DEFAULT_SYSTEM
351
404
  end
352
405
  end
353
406
 
@@ -379,3 +432,6 @@ module Webspicy
379
432
 
380
433
  end
381
434
  end
435
+ require_relative 'configuration/scope'
436
+ require_relative 'configuration/single_url'
437
+ require_relative 'configuration/single_yml_file'