ui2api 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0e872bf99eb14e305a4b2e1102aaaa9a0a70a595
4
+ data.tar.gz: 76184318ae145400b35d4c991065077c4028f9d6
5
+ SHA512:
6
+ metadata.gz: 76c017467309f509de73f77048ef2bde2074b5fd1368a642d3d30b6a57624cae3e3d3b27b754e6aaee2c287da9ec7678f8c4711c3a7662b421c107827dbe3fd5
7
+ data.tar.gz: d154b97b7793e9315b17de7fdb2550c8a10389182e246cbd75be1322b96d14c11fbf1ecacb784c7be227bc8573acef2ff1f5fd77fd50cd787285282e166726f0
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /Gemfile.lock
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+ *.idea
14
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ui2api.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Titus Fortner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,179 @@
1
+ # UI2API
2
+
3
+ As more web applications make use of an interface to interact with their service layer, people now have
4
+ more flexibility to set up and verify parts of their UI tests without needing to use a browser.
5
+
6
+ This simple gem makes it easy to subclass `UI2API::Base` and provide all of the information necessary
7
+ to interact with the different REST endpoints available in your application.
8
+
9
+ This code is designed to be used with the [watir_model gem](https://github.com/titusfortner/watir_model).
10
+ The Model stores data in a way that makes it easy to compare the input and output from both the API and the UI.
11
+
12
+ Note that while this gem can be used as the basis of an API Testing suite, its primary focus is on comparing
13
+ input and output from UI to API and back.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'ui2api'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install ui2api
30
+
31
+ ## Usage
32
+
33
+ 1. Set the base url
34
+ ```ruby
35
+ UI2API::Base.base_url = 'https://restful-booker.herokuapp.com'
36
+ ```
37
+ 2. Create a subclass with an endpoint:
38
+ ```ruby
39
+ module API
40
+ class Booking < UI2API::Base
41
+ def self.endpoint
42
+ 'booking'
43
+ end
44
+ end
45
+ end
46
+ ```
47
+
48
+ 3. Make API calls
49
+ ```ruby
50
+ booking = {firstname: 'Trey',
51
+ lastname: 'Ruecker',
52
+ totalprice: 83,
53
+ depositpaid: true,
54
+ bookingdates: {checkin: '3/23/2019',
55
+ checkout: '3/27/2019'}}
56
+
57
+ API::Booking.create(booking)
58
+ ```
59
+
60
+ 4. The Array or Hash of results is accessed with `#data`
61
+ ```ruby
62
+ booking = {firstname: 'David',
63
+ lastname: 'Jones',
64
+ totalprice: 183,
65
+ depositpaid: true,
66
+ bookingdates: {checkin: '3/23/2019',
67
+ checkout: '3/27/2019'}}
68
+
69
+ created_booking = API::Booking.create(booking)
70
+ booking_id = created_booking.data[:bookingid]
71
+
72
+ stored_booking = API::Booking.show(id: booking_id).data
73
+
74
+ expect(stored_booking).to eq booking
75
+ ```
76
+
77
+ 5. Use [Watir Model](https://github.com/titusfortner/watir_model)
78
+
79
+ Note that the code in the previous example will actually fail.
80
+ This is because we are storing dates as `String` values and the input `String`
81
+ does not match the output `String`
82
+
83
+ Hashes are hard to compare, which is why we have `WatirModel`.
84
+ WatirModel is designed to store the canonical representation of related data in the appropriate data type,
85
+ specifically so that data can be correctly compared.
86
+
87
+ ```ruby
88
+ module Model
89
+ class BookingDates < WatirModel
90
+ key(:checkin, data_type: Date) { Faker::Date.forward }
91
+ key(:checkout, data_type: Date) { checkin + 4 }
92
+ end
93
+
94
+ class Booking < WatirModel
95
+ key(:firstname) { Faker::Name.first_name }
96
+ key(:lastname) { Faker::Name.last_name }
97
+ key(:totalprice, data_type: Integer) { Faker::Commerce.price.round }
98
+ key(:depositpaid) { true }
99
+ key(:bookingdates, data_type: BookingDates) { BookingDates.new }
100
+ key(:additionalneeds)
101
+ end
102
+ end
103
+ ```
104
+
105
+ Because we have a model class defined that is named the same as the API class, `UI2API` will
106
+ automatically attempt to create an instance of the model from the return value of the API call.
107
+ It is accessible from a method based on the name of the API/Model classes, so in this case `#booking`:
108
+
109
+ ```ruby
110
+ booking = Model::Booking.new
111
+
112
+ created_booking = API::Booking.create(booking)
113
+ booking_id = created_booking.data[:bookingid]
114
+
115
+ stored_booking = API::Booking.show(id: booking_id).booking
116
+
117
+ expect(stored_booking).to eq booking
118
+ ```
119
+
120
+ 6. Customize
121
+
122
+ You have a subclass, so if you need to add or change things before or after a call, just override the `UI2API` method
123
+ in your subclass:
124
+
125
+ ```ruby
126
+ module API
127
+ class Booking < UI2API::Base
128
+
129
+ attr_reader :id
130
+
131
+ def initialize(*)
132
+ super
133
+ return if @data.is_a?(Array)
134
+ @id = @data[:bookingid]
135
+ end
136
+ end
137
+ end
138
+ ```
139
+ Now we can use this like so:
140
+ ```ruby
141
+ booking = Model::Booking.new
142
+
143
+ created_booking = API::Booking.create(booking)
144
+
145
+ expect(created_booking.id).to eq created_booking.data[:bookingid]
146
+ ```
147
+
148
+ Because this pattern comes in very handy, you can use `#define_attribute` to do the same thing:
149
+
150
+ ```ruby
151
+ module API
152
+ class Booking < UI2API::Base
153
+ def initialize(*)
154
+ super
155
+ return if @data.is_a?(Array)
156
+ define_attribute(:id, @data[:bookingid])
157
+ end
158
+ end
159
+ end
160
+ ```
161
+
162
+ ## Contributing
163
+
164
+ Bug reports and pull requests are welcome on GitHub at https://github.com/titusfortner/ui2api.
165
+
166
+ ## History
167
+
168
+ While I've been leveraging this approach at my past few jobs, my solutions were application specific and `<cough>` not
169
+ satisfyingly elegant. My initial attempts at implementing this gem were much too over-engineered and weren't solving
170
+ the actual general use case, so this project has stayed on the shelf in spite of the industry need for it.
171
+ Then at the 2017 Selenium Conference in Berlin, [Mark Winteringham](https://twitter.com/2bittester) gave
172
+ a talk titled [REST APIs and WebDriver: In Perfect Harmony](https://www.youtube.com/watch?v=ugAlCZBMOvM).
173
+ This gave me the outside vantage point I needed to see how to focus the project. I started by copying the functionality
174
+ of the code in [Mark's repo](https://github.com/mwinteringham/api-framework/tree/master/ruby),
175
+ and then added in some extra goodness that leveraging WatirModel provides.
176
+
177
+ ## License
178
+
179
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,130 @@
1
+ require "rest-client"
2
+ require "json"
3
+ require 'active_support/inflector'
4
+
5
+ module UI2API
6
+ class Base
7
+ class << self
8
+
9
+ def index(opt = {})
10
+ new rest_call({method: :get,
11
+ url: route(opt)}.merge opt)
12
+ end
13
+
14
+ def show(id:, **opt)
15
+ new rest_call({method: :get,
16
+ url: "#{route(opt)}/#{id}".chomp('/')}.merge opt)
17
+ end
18
+
19
+ def create(obj = nil, opt = {})
20
+ new rest_call({method: :post,
21
+ url: route(opt),
22
+ payload: generate_payload(obj)}.merge opt)
23
+ end
24
+
25
+ def destroy(id:, **opt)
26
+ new rest_call({method: :delete,
27
+ url: "#{route(opt)}/#{id}".chomp('/')}.merge opt)
28
+ end
29
+
30
+ def update(id:, with:, **opt)
31
+ new rest_call({method: :put,
32
+ url: "#{route(opt)}/#{id}".chomp('/'),
33
+ payload: generate_payload(with)}.merge opt)
34
+ end
35
+
36
+ def base_url=(base_url)
37
+ @@base_url = base_url
38
+ end
39
+
40
+ def base_url
41
+ @@base_url || ''
42
+ end
43
+
44
+ def route(opt = {})
45
+ "#{opt[:base_url] || base_url}/#{opt.delete(:endpoint) || endpoint}"
46
+ end
47
+
48
+ def endpoint
49
+ ''
50
+ end
51
+
52
+ def model_object
53
+ eval "Model::#{self.to_s[/[^:]*$/]}"
54
+ end
55
+
56
+ private
57
+
58
+ def rest_call(opt)
59
+ opt[:verify_ssl] = opt.delete(:ssl) if opt.key?(:ssl)
60
+ opt[:headers] ||= headers
61
+ RestClient::Request.execute(opt) do |response, request, result|
62
+ [response, request, result]
63
+ end
64
+ end
65
+
66
+ def generate_payload(obj)
67
+ case obj
68
+ when NilClass
69
+ model_object.new.to_api
70
+ when WatirModel
71
+ obj.to_api
72
+ when JSON
73
+ # noop
74
+ else
75
+ obj.to_json
76
+ end
77
+ end
78
+
79
+ def headers
80
+ {content_type: :json}
81
+ end
82
+ end
83
+
84
+ attr_reader :response, :code, :header, :data
85
+
86
+ def initialize(args)
87
+ response, _request, _result = *args
88
+ @response = response
89
+ @code = response.code
90
+ @header = response.instance_variable_get('@headers')
91
+ @data = JSON.parse(response.body, symbolize_names: true) rescue nil
92
+
93
+ set_watir_model_attr
94
+ end
95
+
96
+ def set_watir_model_attr
97
+ return unless defined?(model_object.new)
98
+ model = convert_to_model(@data) unless @data.nil?
99
+ var = model_object.to_s[/[^:]*$/].underscore
100
+ var = var.pluralize if @data.is_a? Array
101
+ define_attribute(var, model)
102
+ define_attribute(:id, @data[:id]) if @data.is_a? Hash
103
+ end
104
+
105
+ def convert_to_model(data)
106
+ if data.is_a? Hash
107
+ return if (model_object.valid_keys & data.keys).empty?
108
+ begin
109
+ model_object.convert(data)
110
+ rescue StandardError => ex
111
+ raise unless ex.message.include?('Can not convert Hash to Model')
112
+ end
113
+ elsif data.is_a? Array
114
+ data.map do |hash|
115
+ model = convert_to_model(hash)
116
+ model.nil? ? return : model
117
+ end
118
+ end
119
+ end
120
+
121
+ def model_object
122
+ self.class.model_object
123
+ end
124
+
125
+ def define_attribute(key, value)
126
+ instance_variable_set("@#{key}", value)
127
+ singleton_class.class_eval { attr_reader key }
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,35 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "ui2api"
6
+ spec.version = "0.3.0"
7
+ spec.authors = ["Titus Fortner"]
8
+ spec.email = ["titusfortner@gmail.com"]
9
+
10
+ spec.summary = %q{A simple class for interacting with a Web App's API using test data}
11
+ spec.description = %q{Send and receive data via a Web App's API, ideally using WatirModel objects. The goal is to
12
+ compare test data with what is input and displayed via UI.}
13
+ spec.homepage = "https://github.com/titusfortner/ui2api"
14
+ spec.license = "MIT"
15
+
16
+ if spec.respond_to?(:metadata)
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+ else
19
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
20
+ end
21
+
22
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features)/})
24
+ end
25
+
26
+ spec.require_paths = ["lib"]
27
+ spec.add_runtime_dependency "rest-client"
28
+ spec.add_runtime_dependency "faker"
29
+ spec.add_runtime_dependency "watir_model", "~> 0.5.0"
30
+
31
+ spec.add_development_dependency "bundler", "~> 1.16"
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.add_development_dependency "rspec", "~> 3.0"
34
+ spec.add_development_dependency "require_all"
35
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ui2api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Titus Fortner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faker
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: watir_model
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.16'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.16'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: require_all
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: |-
112
+ Send and receive data via a Web App's API, ideally using WatirModel objects. The goal is to
113
+ compare test data with what is input and displayed via UI.
114
+ email:
115
+ - titusfortner@gmail.com
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - ".gitignore"
121
+ - ".rspec"
122
+ - ".travis.yml"
123
+ - Gemfile
124
+ - LICENSE.txt
125
+ - README.md
126
+ - Rakefile
127
+ - lib/ui2api.rb
128
+ - ui2api.gemspec
129
+ homepage: https://github.com/titusfortner/ui2api
130
+ licenses:
131
+ - MIT
132
+ metadata:
133
+ allowed_push_host: https://rubygems.org
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 2.6.11
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: A simple class for interacting with a Web App's API using test data
154
+ test_files: []