├── .gitignore ├── Contents.md ├── Future.md ├── Gemfile ├── Gemfile.lock ├── Gemfile.md ├── LICENSE ├── README.md ├── Rakefile ├── Rakefile.md ├── Watchwords.md ├── examples ├── Examples.md ├── changes_report │ ├── ChangesReport.html │ ├── ChangesReport.md │ ├── Rakefile │ └── example_test.rb ├── github │ ├── Rakefile │ ├── TesterTour.md │ ├── api │ │ ├── api_client.rb │ │ ├── base_classes │ │ │ └── base_class_for_endpoint.rb │ │ └── endpoints │ │ │ ├── get_rate_limit.rb │ │ │ └── labels │ │ │ ├── delete_labels_name.rb │ │ │ ├── get_labels.rb │ │ │ ├── get_labels_name.rb │ │ │ ├── patch_labels_name.rb │ │ │ └── post_labels.rb │ ├── base_classes │ │ ├── base_class_for_resource.rb │ │ └── base_class_for_test.rb │ ├── data │ │ ├── label.rb │ │ └── rate_limit.rb │ ├── tester_tour │ │ ├── md_files │ │ │ ├── Crud.md │ │ │ ├── DataObjects.md │ │ │ ├── DeleteLabelsName.md │ │ │ ├── EndpointObjects.md │ │ │ ├── EndpointTests.md │ │ │ ├── Existence.md │ │ │ ├── FlatDataEqual.md │ │ │ ├── FlatDataLog.md │ │ │ ├── FlatDataNew.md │ │ │ ├── FlatDataValid.md │ │ │ ├── GetLabels.md │ │ │ ├── GetLabelsName.md │ │ │ ├── Getters.md │ │ │ ├── Meet.md │ │ │ ├── MoreToCome.md │ │ │ ├── NestedDataEqual.md │ │ │ ├── NestedDataLog.md │ │ │ ├── NestedDataNew.md │ │ │ ├── NestedDataNew2.md │ │ │ ├── NestedDataValid.md │ │ │ ├── Overview.md │ │ │ ├── PatchLabelsName.md │ │ │ ├── PostLabels.md │ │ │ ├── RescuedException.md │ │ │ ├── ResourceMethods.md │ │ │ ├── RestApi.md │ │ │ ├── Sections.md │ │ │ ├── UnrescuedException.md │ │ │ ├── Verdicts.md │ │ │ └── WebUi.md │ │ ├── md_templates │ │ │ ├── Crud.txt │ │ │ ├── DataObjects.txt │ │ │ ├── DeleteLabelsName.txt │ │ │ ├── EndpointObjects.txt │ │ │ ├── EndpointTests.txt │ │ │ ├── Existence.txt │ │ │ ├── FlatDataEqual.txt │ │ │ ├── FlatDataLog.txt │ │ │ ├── FlatDataNew.txt │ │ │ ├── FlatDataValid.txt │ │ │ ├── GetLabels.txt │ │ │ ├── GetLabelsName.txt │ │ │ ├── Getters.txt │ │ │ ├── Meet.txt │ │ │ ├── MoreToCome.txt │ │ │ ├── NestedDataEqual.txt │ │ │ ├── NestedDataLog.txt │ │ │ ├── NestedDataNew.txt │ │ │ ├── NestedDataNew2.txt │ │ │ ├── NestedDataValid.txt │ │ │ ├── Overview.txt │ │ │ ├── PatchLabelsName.txt │ │ │ ├── PostLabels.txt │ │ │ ├── RescuedException.txt │ │ │ ├── ResourceMethods.txt │ │ │ ├── RestApi.txt │ │ │ ├── Sections.txt │ │ │ ├── UnrescuedException.txt │ │ │ ├── Verdicts.txt │ │ │ └── WebUi.txt │ │ └── tests │ │ │ ├── crud_test.rb │ │ │ ├── delete_labels_name_test.rb │ │ │ ├── existence_test.rb │ │ │ ├── flat_data_equal_test.rb │ │ │ ├── flat_data_log_test.rb │ │ │ ├── flat_data_new_test.rb │ │ │ ├── flat_data_valid_test.rb │ │ │ ├── get_labels_name_test.rb │ │ │ ├── get_labels_test.rb │ │ │ ├── getters_test.rb │ │ │ ├── meet_test.rb │ │ │ ├── nested_data_equal_test.rb │ │ │ ├── nested_data_log_test.rb │ │ │ ├── nested_data_new_2_test.rb │ │ │ ├── nested_data_new_test.rb │ │ │ ├── nested_data_valid_test.rb │ │ │ ├── patch_labels_name_test.rb │ │ │ ├── post_labels_test.rb │ │ │ ├── rescued_exception_test.rb │ │ │ ├── rest_api_test.rb │ │ │ ├── sections_test.rb │ │ │ ├── unrescued_exception_test.rb │ │ │ ├── verdicts_test.rb │ │ │ └── web_ui_test.rb │ └── ui │ │ ├── base_classes │ │ └── base_class_for_page.rb │ │ ├── pages │ │ ├── home_page.rb │ │ ├── labels_page.rb │ │ ├── login_page.rb │ │ └── repo_page.rb │ │ └── ui_client.rb ├── github_api │ ├── Moved.md │ └── TesterTour.md ├── log │ ├── Log.md │ ├── Rakefile │ └── log_examples.rb └── rest_api │ ├── Rakefile │ ├── RestAPI.md │ ├── base_classes │ ├── base_class_for_endpoint.rb │ ├── base_class_for_resource.rb │ ├── base_class_for_test.rb │ └── endpoints │ │ ├── base_class_for_delete_id.rb │ │ ├── base_class_for_get.rb │ │ ├── base_class_for_get_id.rb │ │ ├── base_class_for_post.rb │ │ └── base_class_for_put_id.rb │ ├── data │ ├── album.rb │ ├── comment.rb │ ├── photo.rb │ ├── post.rb │ ├── todo.rb │ └── user.rb │ ├── endpoints │ ├── albums │ │ ├── delete_albums_id.rb │ │ ├── get_albums.rb │ │ ├── get_albums_id.rb │ │ ├── post_albums.rb │ │ └── put_albums_id.rb │ ├── comments │ │ ├── delete_comments_id.rb │ │ ├── get_comments.rb │ │ ├── get_comments_id.rb │ │ ├── post_comments.rb │ │ └── put_comments_id.rb │ ├── photos │ │ ├── delete_photos_id.rb │ │ ├── get_photos.rb │ │ ├── get_photos_id.rb │ │ ├── post_photos.rb │ │ └── put_photos_id.rb │ ├── posts │ │ ├── delete_posts_id.rb │ │ ├── get_posts.rb │ │ ├── get_posts_id.rb │ │ ├── post_posts.rb │ │ └── put_posts_id.rb │ ├── todos │ │ ├── delete_todos_id.rb │ │ ├── get_todos.rb │ │ ├── get_todos_id.rb │ │ ├── post_todos.rb │ │ └── put_todos_id.rb │ └── users │ │ ├── delete_users_id.rb │ │ ├── get_users.rb │ │ ├── get_users_id.rb │ │ ├── post_users.rb │ │ └── put_users_id.rb │ ├── example_rest_client.rb │ └── tests │ ├── albums_test.rb │ ├── comments_test.rb │ ├── photos_test.rb │ ├── posts_test.rb │ ├── todos_test.rb │ └── users_test.rb ├── lib ├── Lib.md ├── base_classes │ ├── BaseClasses.md │ ├── base_class.rb │ └── base_class_for_data.rb ├── changes_report.rb ├── configuration.rb ├── helpers │ ├── Helpers.md │ ├── debug_helper.rb │ ├── hash_helper.rb │ ├── lorem_helper.rb │ ├── markdown_helper.rb │ ├── object_helper.rb │ ├── set_helper.rb │ ├── string_helper.rb │ ├── test_helper.rb │ ├── time_helper.rb │ └── values_helper.rb └── log │ ├── Log.md │ ├── constants.rb │ ├── log.rb │ ├── verdict_assertion.rb │ ├── verdict_boolean.rb │ ├── verdict_integer.rb │ ├── verdict_range.rb │ └── verdict_string.rb ├── rakefiles ├── build.rake ├── examples.rake └── test.rake └── test ├── Test.md ├── assertion_helper.rb ├── common_requires.rb ├── configuration_test.rb ├── helpers ├── debug_helper_test.rb ├── hash_helper_test.rb ├── set_helper_test.rb ├── string_helper_test.rb └── values_helper_test.rb └── log_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | html/ 3 | output/ 4 | 5 | -------------------------------------------------------------------------------- /Contents.md: -------------------------------------------------------------------------------- 1 | # Contents (Markdown Pages) 2 | 3 | - [RubyTest](./README.md#rubytest) 4 | - [Watchwords](./Watchwords.md#watchwords) 5 | - [Future](./Future.md#future) 6 | - [Rakefile](./Rakefile.md#rakefile) 7 | - [Gemfile](./Gemfile.md#gemfile) 8 | - examples/ 9 | - [Examples](./examples/Examples.md#examples) 10 | - changes_report/ 11 | - [Changes Report](./examples/changes_report/ChangesReport.md#changes-report) 12 | - github/ 13 | - [Tester Tour](./examples/github/TesterTour.md#tester-tour) 14 | - github_api/ 15 | - [Moved](./examples/github_api/Moved.md#moved) 16 | - [Tester Tour](./examples/github_api/TesterTour.md#tester-tour) 17 | - log/ 18 | - [Log Examples](./examples/log/Log.md#log-examples) 19 | - rest_api/ 20 | - [REST API Test Example](./examples/rest_api/RestAPI.md#rest-api-test-example) 21 | - lib/ 22 | - [Lib](./lib/Lib.md#lib) 23 | - base_classes/ 24 | - [Base Classes](./lib/base_classes/BaseClasses.md#base-classes) 25 | - helpers/ 26 | - [Helpers](./lib/helpers/Helpers.md#helpers) 27 | - log/ 28 | - [Log](./lib/log/Log.md#log) 29 | - test/ 30 | - [Unit Tests](./test/Test.md#unit-tests) 31 | -------------------------------------------------------------------------------- /Future.md: -------------------------------------------------------------------------------- 1 | # Future 2 | 3 | Here are possible future enhancements of RubyTest (in no particular order). 4 | 5 | To comment on these, or possibilities, please open an Issue. 6 | 7 | ## Support for Testing a Web Application (via gem watir-webdriver) 8 | 9 | ### Example 10 | 11 | A large-scale example that tests a web application using Ruby gem watir-webdriver. 12 | 13 | ## Support for Testing a SOAP API (via gem Savon) 14 | 15 | ### Example 16 | 17 | A large-scale example that tests a SOAP API using Ruby gem Savon. 18 | 19 | ## Reporting 20 | 21 | ### History Report (Spreadsheet) 22 | 23 | History of each verdict's passed/failed/blocked status. 24 | 25 | ### Notifications 26 | 27 | Code to perform notifications: email, RSS feeds, Skype, etc. 28 | 29 | ## New/Enhanced Helpers 30 | 31 | Methods to make code clearer, cleaner, and more reliable. 32 | 33 | ### Array Helper 34 | 35 | ### Date Helper 36 | 37 | ### Debug Helper 38 | 39 | ### Hash Helper 40 | 41 | ### Lorem Helper 42 | 43 | ### Object helper 44 | 45 | ### Path Helper 46 | 47 | ### Set Helper 48 | 49 | ### String Helper 50 | 51 | ### Test Helper 52 | 53 | ### Tk Helper 54 | 55 | ### Values Helper 56 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'bundler', '1.14.6' 4 | 5 | gem 'contracts', '0.16.0' 6 | 7 | gem 'json', '1.8.1' 8 | 9 | gem 'lorem-ipsum', '0.1.1' 10 | 11 | gem 'minitest', '5.10.1' 12 | 13 | gem 'nokogiri', '1.6.7.2' 14 | 15 | gem 'rest-client', '2.0.1' 16 | 17 | gem 'retriable', '3.0.1' 18 | 19 | gem 'watir', '6.10.0' 20 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | childprocess (0.8.0) 5 | ffi (~> 1.0, >= 1.0.11) 6 | contracts (0.16.0) 7 | domain_name (0.5.20170404) 8 | unf (>= 0.0.5, < 1.0.0) 9 | ffi (1.9.18-x64-mingw32) 10 | ffi (1.9.18-x86-mingw32) 11 | http-cookie (1.0.3) 12 | domain_name (~> 0.5) 13 | json (1.8.1) 14 | lorem-ipsum (0.1.1) 15 | mime-types (3.1) 16 | mime-types-data (~> 3.2015) 17 | mime-types-data (3.2016.0521) 18 | mini_portile2 (2.0.0) 19 | minitest (5.10.1) 20 | netrc (0.11.0) 21 | nokogiri (1.6.7.2-x64-mingw32) 22 | mini_portile2 (~> 2.0.0.rc2) 23 | nokogiri (1.6.7.2-x86-mingw32) 24 | mini_portile2 (~> 2.0.0.rc2) 25 | rest-client (2.0.1-x64-mingw32) 26 | ffi (~> 1.9) 27 | http-cookie (>= 1.0.2, < 2.0) 28 | mime-types (>= 1.16, < 4.0) 29 | netrc (~> 0.8) 30 | rest-client (2.0.1-x86-mingw32) 31 | ffi (~> 1.9) 32 | http-cookie (>= 1.0.2, < 2.0) 33 | mime-types (>= 1.16, < 4.0) 34 | netrc (~> 0.8) 35 | retriable (3.0.1) 36 | rubyzip (1.2.1) 37 | selenium-webdriver (3.8.0) 38 | childprocess (~> 0.5) 39 | rubyzip (~> 1.0) 40 | unf (0.1.4) 41 | unf_ext 42 | unf_ext (0.0.7.4-x64-mingw32) 43 | unf_ext (0.0.7.4-x86-mingw32) 44 | watir (6.10.0) 45 | selenium-webdriver (~> 3.4, >= 3.4.1) 46 | 47 | PLATFORMS 48 | x64-mingw32 49 | x86-mingw32 50 | 51 | DEPENDENCIES 52 | bundler (= 1.14.6) 53 | contracts (= 0.16.0) 54 | json (= 1.8.1) 55 | lorem-ipsum (= 0.1.1) 56 | minitest (= 5.10.1) 57 | nokogiri (= 1.6.7.2) 58 | rest-client (= 2.0.1) 59 | retriable (= 3.0.1) 60 | watir (= 6.10.0) 61 | 62 | BUNDLED WITH 63 | 1.14.6 64 | -------------------------------------------------------------------------------- /Gemfile.md: -------------------------------------------------------------------------------- 1 | # Gemfile 2 | 3 | ## Gems 4 | 5 | - [bundler](https://rubygems.org/gems/bundler) 6 | - [contracts](https://rubygems.org/gems/contracts) 7 | - [json](https://rubygems.org/gems/json) 8 | - [lorem-ipsum](https://rubygems.org/gems/lorem-ipsum) 9 | - [minitest](https://rubygems.org/gems/minitest) 10 | - [nokogiri](https://rubygems.org/gems/nokogiri) 11 | - [rest-client](https://rubygems.org/gems/rest-client) 12 | - [retriable](https://rubygems.org/gems/retriable) 13 | ## Source 14 | 15 | - [Gemfile](Gemfile) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RubyTest 2 | 3 | It's all well and good to _write_ about testing software. (Actually I _do_ write about it, over at [my blog](https://burdettelamar.wordpress.com/).) 4 | 5 | But it's so much better to _show_ actual working code. And that's what this project does. 6 | 7 | It provides _working software_ to help with automated software testing. 8 | 9 | The project consists of examples, the framework that supports them, and of course testing for the framework. 10 | 11 | Links to the markdown documentation are at: 12 | 13 | - [Contents](./Contents.md#contents-markdown-pages) 14 | 15 | ## Examples 16 | 17 | An important example is about testing for the GitHub REST API. Take the [Tester Tour](./examples/github/TesterTour.md#tester-tour). 18 | 19 | ## Framework 20 | 21 | The testing is supported by 22 | 23 | - [Robust logging](./lib/log/Log.md#log) 24 | - [Helpers](./lib/helpers/Helpers.md#helpers) 25 | - [Base classes](./lib/base_classes/BaseClasses.md#base-classes) 26 | 27 | The framework itself is tested by 28 | 29 | - [Unit testing](./test/Test.md#unit-tests) 30 | 31 | There are also 32 | 33 | - [Logging examples](./examples/log/Log.md#log-examples) 34 | 35 | ## Rake Tasks 36 | 37 | You can also review all rake tasks by running command 38 | 39 | - rake -D 40 | 41 | or by visiting the documentation for the [Rakefile](./Rakefile.md#rakefile). 42 | 43 | ## RDoc 44 | 45 | You can build the RDoc by cloning and running command 46 | 47 | - rake build:rerdoc 48 | 49 | The RDoc output will be in directory html/, with the index file at html/index.html. 50 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | RAKEFILES_DIR_PATH = File.join( 2 | File.dirname(__FILE__), 3 | 'rakefiles' 4 | ) 5 | 6 | load "#{RAKEFILES_DIR_PATH}/examples.rake" 7 | load "#{RAKEFILES_DIR_PATH}/build.rake" 8 | load "#{RAKEFILES_DIR_PATH}/test.rake" 9 | 10 | task :default do 11 | message = 'No default for this rakefile; Type "rake -D" for task descriptions.' 12 | raise ArgumentError.new(message) 13 | end 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Rakefile.md: -------------------------------------------------------------------------------- 1 | # Rakefile 2 | 3 | ## Descriptions 4 | 5 | rake build:all 6 | 7 | Build everything 8 | 9 | rake build:clobber_rdoc 10 | 11 | Remove RDoc HTML files 12 | 13 | rake build:markdown_for_gemfile 14 | 15 | Build markdown for Gemfile 16 | 17 | rake build:markdown_for_rakefile 18 | 19 | Build markdown for Rakefile 20 | 21 | rake build:markdown_toc 22 | 23 | Build markdown table of contents 24 | 25 | rake build:rdoc 26 | 27 | Build RDoc HTML files 28 | 29 | rake build:rerdoc 30 | 31 | Rebuild RDoc HTML files 32 | 33 | rake examples:all 34 | 35 | Run all examples 36 | 37 | rake test:all 38 | 39 | Test all classes 40 | 41 | rake test:configuration 42 | 43 | Test class Configuration 44 | 45 | rake test:hash_helper 46 | 47 | Test class HashHelper 48 | 49 | rake test:log 50 | 51 | Test class Log 52 | 53 | rake test:set_helper 54 | 55 | Test class SetHelper 56 | 57 | rake test:string_helper 58 | 59 | Test class StringHelper 60 | 61 | rake test:values_helper 62 | 63 | Test class ValuesHelper 64 | 65 | ## Source 66 | 67 | - [Rakefile](Rakefile) 68 | -------------------------------------------------------------------------------- /Watchwords.md: -------------------------------------------------------------------------------- 1 | # Watchwords 2 | 3 | | Watchword | Remarks | 4 | | :-------: | ------- | 5 | | Readability | Make code easy to read: code is read more often than written. | 6 | | DRYness (Don't Repeat Yourself) | Avoid redundant code and data. | 7 | | YAGNIty (You Ain't Gonna Need It) | Don't write code before it's needed. | 8 | | Sloth | Maximize use of Ruby gems. *The line of code you don't write is the line of code you never have to debug.* -- Steve Jobs | 9 | | Explicitness | Explicate everything, even when "unnecessary." *If it goes without saying, it would go better by saying it.* -- Tallyrand | 10 | | Cleanliness | Keep everything clean and consistent. Resolve all code inspection issues before committing code. | 11 | | Failed Verdict Diagnostics | Log data sufficient to diagnose a failed verdict. | 12 | | Test Error Diagnostics | Detect test errors early, fail early, provide useful information. | 13 | | Monitoring | Trust, but verify. Monitor documentation for changes. | 14 | -------------------------------------------------------------------------------- /examples/Examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | There is example code for: 4 | 5 | - [Logging](./log/Log.md) 6 | - [Testing a REST API](./rest_api/RestAPI.md) 7 | - [Creating the Changes Report](./changes_report/ChangesReport.md) 8 | -------------------------------------------------------------------------------- /examples/changes_report/ChangesReport.md: -------------------------------------------------------------------------------- 1 | # Changes Report 2 | 3 | ## Why a Changes Report? 4 | 5 | New testing for old (legacy) software commonly will reveal numerous failures, most of which will be low-priority. 6 | 7 | These are usually not going to be fixed anytime soon, so the test team must manage these numerous persistent failures. 8 | 9 | Here are some ways to handle that problem: 10 | 11 | - :heavy_multiplication_x: **Difficult (and maybe Dumb):** Manually verify that each failed verdict is the same as in the previous test run. 12 | 13 | This is *very* tedious (and error-prone). Testers will skip doing this if they can get away with it, in which case it won't be known whether failures have changed. 14 | 15 | - :heavy_multiplication_x::heavy_multiplication_x: **Dumb:** Disable offending verdicts. 16 | 17 | This guarantees that it won't be known whether failures have changed. 18 | 19 | - :heavy_multiplication_x::heavy_multiplication_x::heavy_multiplication_x: **Dumber:** Disable offending tests entirely. 20 | 21 | (Believe it or not, I've worked in a group that did this.) 22 | 23 | A test that has a persistent but trivial failure certainly could nevertheless reveal a new and important (crash?) bug. 24 | 25 | Every test should execute in every test run. 26 | 27 | - :heavy_check_mark::heavy_check_mark::heavy_check_mark: **Smart:** Generate a Changes Report. 28 | 29 | Read on. 30 | 31 | ## What Is the Changes Report 32 | 33 | The Changes Report details verdict outcomes in the current and previous test runs -- *what has changed* vs. *what has not*, via eight categories: 34 | 35 | | Category | Previously | Now | 36 | | :-------: | ------- | --- | 37 | | New Blocked | Passed or Failed | Blocked | 38 | | New Failed | Passed or Blocked | Passed | 39 | | Changed Failed | Failed | Failed differently | 40 | | New Passed | Failed or Blocked | Passed | 41 | | Changed Passed | Failed or blocked | Passed differently | 42 | | Old Blocked | Blocked | Blocked | 43 | | Old Failed | Failed | Failed identically | 44 | | Old Passed | Passed | Passed identically | 45 | 46 | ## Ignore the Ignorable 47 | 48 | The key feature of the Changes Report is that it specifies *what can safely be ignored*: 49 | 50 | In most test environments, the vast majority of verdicts will be **Old Blocked**, **Old Failed**, or **Old Passed**. 51 | 52 | If testers have been doing their jobs, these have been dealt with previously, when they were **New Blocked**, **New Failed**, or **New Passed**. So these unchanged verdicts can safely be ignored. 53 | 54 | Of particular value is that **Old Failed** verdicts are now *known* to behave *exactly* as before, and so do not need attention. 55 | 56 | (An exception to this is a verdict whose bug status is **Awaiting Verification**: the verdict should have changed from **Old Blocked** or **Old Failed** to **New Passed**; if it did not, it requires attention.) 57 | 58 | ## Focus on the Significant 59 | 60 | A verdict that is **New Blocked** or **New Failed** is obviously significant. It represent a new software failure that must be investigated. 61 | 62 | A verdict that is **Changed Failed** or even **Changed Passed** also must be investigated -- something's different. 63 | 64 | Finally, a verdict that is **New Passed** should represent a bug whose status is **Awaiting Verification**. If all is as expected, the status can be changed to **Verified**. 65 | 66 | ## Conclusion 67 | 68 | The Changes Report is a huge time-saver. 69 | 70 | In one test environment I worked on, there were thousands of verdicts, including hundreds that were **Old Blocked**, **Old Failed**. 71 | 72 | The ability to safely ignore these saved us *hours* each day. 73 | 74 | ## Example 75 | 76 | - [Changes Report](https://htmlpreview.github.io/?https://github.com/BurdetteLamar/RubyTest/master/examples/changes_report/ChangesReport.html) 77 | -------------------------------------------------------------------------------- /examples/changes_report/Rakefile: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/changes_report' 2 | 3 | rakefile_dir_path = File.dirname(__FILE__) 4 | 5 | namespace :create do 6 | 7 | desc 'Create changes report' 8 | task :changes_report do 9 | test_file_path = File.join( 10 | rakefile_dir_path, 11 | 'example_test.rb', 12 | ) 13 | command = format('ruby %s', test_file_path) 14 | system(command) 15 | exe_file_path = File.absolute_path(File.join( 16 | rakefile_dir_path, 17 | '..', 18 | '..', 19 | 'lib', 20 | 'changes_report.rb' 21 | )) 22 | command = format('ruby -r %s -e "ChangesReport.create_report \'changes_report\'', exe_file_path) 23 | system(command) 24 | # And copy into our source tree. 25 | log_dir_path = TestHelper.get_app_log_dir_path('changes_report', back = 0) 26 | report_file_name = 'ChangesReport.html' 27 | report_file_path = File.join( 28 | log_dir_path, 29 | report_file_name 30 | ) 31 | FileUtils.copy(report_file_path, rakefile_dir_path) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /examples/github/TesterTour.md: -------------------------------------------------------------------------------- 1 | # Tester Tour 2 | 3 | This Tester Tour covers topics that are of interest to a tester who wants to develop and run tests for: 4 | 5 | - A REST API. The target REST API is the [GitHub REST API](https://developer.github.com/v3/). 6 | - A web UI. The target web UI is the [GitHub web UI](https://github.com/BurdetteLamar/CrashDummy). 7 | 8 | Each tour stop assumes you've seen all its predecessors, so repetition is minimal. 9 | 10 | ## Tour Stops 11 | 12 | ### Getting Started 13 | - [Overview](./tester_tour/md_files/Overview.md/#overview) 14 | - [Meet the Log and Clients](./tester_tour/md_files/Meet.md/#meet-the-log-and-clients) 15 | - [Web UI Test](./tester_tour/md_files/WebUi.md/#web-ui-test) 16 | - [Rest API Test](./tester_tour/md_files/RestApi.md/#rest-api-test) 17 | ### Logging 18 | - [Test Sections and Nesting](./tester_tour/md_files/Sections.md/#test-sections-and-nesting) 19 | - [Verdicts](./tester_tour/md_files/Verdicts.md/#verdicts) 20 | - [Rescued Exception](./tester_tour/md_files/RescuedException.md/#rescued-exception) 21 | - [Unrescued Exception](./tester_tour/md_files/UnrescuedException.md/#unrescued-exception) 22 | ### Data Objects 23 | - [The Data Object](./tester_tour/md_files/DataObjects.md/#the-data-object) 24 | - [Logging a Data Object](./tester_tour/md_files/FlatDataLog.md/#logging-a-data-object) 25 | - [Creating a Data Object](./tester_tour/md_files/FlatDataNew.md/#creating-a-data-object) 26 | - [Verifying a Data Object](./tester_tour/md_files/FlatDataEqual.md/#verifying-a-data-object) 27 | - [Validating a Data Object](./tester_tour/md_files/FlatDataValid.md/#validating-a-data-object) 28 | ### Nested Data Objects 29 | - [Logging Nested Data Objects](./tester_tour/md_files/NestedDataLog.md/#logging-nested-data-objects) 30 | - [Creating Nested Data Objects](./tester_tour/md_files/NestedDataNew.md/#creating-nested-data-objects) 31 | - [Creating Nested Data Objects, Part 2](./tester_tour/md_files/NestedDataNew2.md/#creating-nested-data-objects-part-2) 32 | - [Verifying Nested Data Objects](./tester_tour/md_files/NestedDataEqual.md/#verifying-nested-data-objects) 33 | - [Validating Nested Data Objects](./tester_tour/md_files/NestedDataValid.md/#validating-nested-data-objects) 34 | ### Resources 35 | - [Resource Methods](./tester_tour/md_files/ResourceMethods.md/#resource-methods) 36 | - [CRUD](./tester_tour/md_files/Crud.md/#crud) 37 | - [Getters](./tester_tour/md_files/Getters.md/#getters) 38 | - [Existence Methods](./tester_tour/md_files/Existence.md/#existence-methods) 39 | ### Endpoints 40 | - [Endpoint Objects](./tester_tour/md_files/EndpointObjects.md/#endpoint-objects) 41 | - [Endpoint Tests](./tester_tour/md_files/EndpointTests.md/#endpoint-tests) 42 | - [GetLabels Test](./tester_tour/md_files/GetLabels.md/#getlabels-test) 43 | - [PostLabels Test](./tester_tour/md_files/PostLabels.md/#postlabels-test) 44 | - [GetLabelsName Test](./tester_tour/md_files/GetLabelsName.md/#getlabelsname-test) 45 | - [PatchLabelsName Test](./tester_tour/md_files/PatchLabelsName.md/#patchlabelsname-test) 46 | - [DeleteLabelsName Test](./tester_tour/md_files/DeleteLabelsName.md/#deletelabelsname-test) 47 | ### Next 48 | - [More to Come ...](./tester_tour/md_files/MoreToCome.md/#more-to-come-) 49 | -------------------------------------------------------------------------------- /examples/github/api/base_classes/base_class_for_endpoint.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../../lib/base_classes/base_class' 2 | require_relative '../../../../lib/helpers/object_helper' 3 | 4 | require_relative '../api_client' 5 | 6 | class BaseClassForEndpoint < BaseClass 7 | 8 | Contract ApiClient, Args[Any] => Any 9 | def self.call(client, *args) 10 | # Derived class must define self.call_and_return_payload. 11 | return_val, _ = self.call_and_return_payload(client, *args) 12 | return_val 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /examples/github/api/endpoints/get_rate_limit.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_endpoint' 2 | 3 | require_relative '../../data/rate_limit' 4 | 5 | class GetRateLimit < BaseClassForEndpoint 6 | 7 | Contract ApiClient, Maybe[Hash] => [RateLimit, Any] 8 | def self.call_and_return_payload(client, query_elements = {}) 9 | url_elements = [ 10 | 'rate_limit', 11 | ] 12 | payload = client.get(url_elements, query_elements) 13 | rehash = HashHelper.rehash_to_symbol_keys(payload) 14 | rate_limit = RateLimit.new(rehash) 15 | [rate_limit, payload] 16 | end 17 | 18 | Contract ApiClient, VERDICT_ID, Maybe[Hash] => RateLimit 19 | def self.verdict_call_and_verify_success(client, verdict_id, query_elements = {}) 20 | log = client.log 21 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 22 | rate_limit = self.call(client, query_elements) 23 | log.section('Evaluation') do 24 | rate_limit.log(log) 25 | rate_limit.verdict_valid?(log, verdict_id) 26 | end 27 | return rate_limit 28 | end 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /examples/github/api/endpoints/labels/delete_labels_name.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_endpoint' 2 | 3 | require_relative '../../../data/label' 4 | 5 | class DeleteLabelsName < BaseClassForEndpoint 6 | 7 | Contract ApiClient, Label, Maybe[Hash] => [nil, nil] 8 | def self.call_and_return_payload(client, label_to_delete, query_elements = {}) 9 | url_elements = [ 10 | client.repo_url_elements, 11 | 'labels', 12 | label_to_delete.name, 13 | ] 14 | payload = client.delete(url_elements, query_elements) 15 | [payload, payload] 16 | end 17 | 18 | Contract ApiClient, VERDICT_ID, Label, Maybe[Hash] => nil 19 | def self.verdict_call_and_verify_success(client, verdict_id, label_to_delete, query_elements = {}) 20 | log = client.log 21 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 22 | payload = self.call(client, label_to_delete, query_elements) 23 | log.section('Evaluation') do 24 | log.section('Response empty') do 25 | v_id = [verdict_id, :payload_nil] 26 | log.verdict_assert_nil?(v_id, payload) 27 | end 28 | log.section('Label deleted') do 29 | v_id = [verdict_id, :label_deleted] 30 | label_to_delete.verdict_refute_exist?(client, log, v_id) 31 | end 32 | end 33 | return payload 34 | end 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /examples/github/api/endpoints/labels/get_labels.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_endpoint' 2 | 3 | require_relative '../../../data/label' 4 | 5 | class GetLabels < BaseClassForEndpoint 6 | 7 | Contract ApiClient, Maybe[Hash] => [ArrayOf[Label], Any] 8 | def self.call_and_return_payload(client, query_elements = {}) 9 | url_elements = [ 10 | client.repo_url_elements, 11 | 'labels', 12 | ] 13 | payload = client.get(url_elements, query_elements) 14 | labels = [] 15 | payload.each do |label_data| 16 | rehash = HashHelper.rehash_to_symbol_keys(label_data) 17 | label = Label.new(rehash) 18 | labels.push(label) 19 | end 20 | [labels, payload] 21 | end 22 | 23 | Contract ApiClient, VERDICT_ID, Maybe[Hash] => ArrayOf[Label] 24 | def self.verdict_call_and_verify_success(client, verdict_id, query_elements = {}) 25 | log = client.log 26 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 27 | labels = self.call(client, query_elements) 28 | label = labels.first 29 | log.section('Info') do 30 | log.put_element('data', {:fetched_labels_count => labels.size}) 31 | label.log(log, 'First label fetched') 32 | end 33 | log.section('Evaluation') do 34 | log.section('Returned label valid') do 35 | v_id = [verdict_id, :valid] 36 | label.verdict_valid?(log, v_id) 37 | end 38 | end 39 | return labels 40 | end 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /examples/github/api/endpoints/labels/get_labels_name.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_endpoint' 2 | 3 | require_relative '../../../data/label' 4 | 5 | class GetLabelsName < BaseClassForEndpoint 6 | 7 | Contract ApiClient, Label, Maybe[Hash] => [Label, Any] 8 | def self.call_and_return_payload(client, label_to_fetch, query_elements = {}) 9 | url_elements = [ 10 | client.repo_url_elements, 11 | 'labels', 12 | label_to_fetch.name, 13 | ] 14 | payload = client.get(url_elements, query_elements) 15 | rehash = HashHelper.rehash_to_symbol_keys(payload) 16 | label = Label.new(rehash) 17 | [label, payload] 18 | end 19 | 20 | Contract ApiClient, VERDICT_ID, Label, Maybe[Hash] => Label 21 | def self.verdict_call_and_verify_success(client, verdict_id, label_to_fetch, query_elements = {}) 22 | log = client.log 23 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 24 | label_fetched = self.call(client, label_to_fetch, query_elements) 25 | log.section('Evaluation') do 26 | v_id = [verdict_id, :name] 27 | log.verdict_assert_equal?(v_id, label_to_fetch.name, label_fetched.name) 28 | v_id = [verdict_id, :valid] 29 | label_fetched.verdict_valid?(log, v_id) 30 | end 31 | return label_fetched 32 | end 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /examples/github/api/endpoints/labels/patch_labels_name.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_endpoint' 2 | 3 | require_relative '../../../data/label' 4 | 5 | class PatchLabelsName < BaseClassForEndpoint 6 | 7 | Contract ApiClient, Label, Label, Maybe[Hash] => [Label, Any] 8 | def self.call_and_return_payload(client, label_target, label_source, query_elements = {}) 9 | url_elements = [ 10 | client.repo_url_elements, 11 | 'labels', 12 | label_target.name, 13 | ] 14 | parameters = { 15 | :color => label_source.color, 16 | :name => label_source.name, 17 | } 18 | payload = client.patch(url_elements, query_elements, parameters) 19 | rehash = HashHelper.rehash_to_symbol_keys(payload) 20 | label_updated = Label.new(rehash) 21 | [label_updated, payload] 22 | end 23 | 24 | Contract ApiClient, VERDICT_ID, Label, Label, Maybe[Hash] => Label 25 | def self.verdict_call_and_verify_success(client, verdict_id, label_target, label_source, query_elements = {}) 26 | log = client.log 27 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 28 | label_updated = self.call(client, label_target, label_source, query_elements) 29 | log.section('Evaluation') do 30 | log.section('Returned label correct') do 31 | v_id = [verdict_id, :updated_label] 32 | Label.verdict_equal?(log, v_id, label_target, label_source, 'Updated label correct') 33 | end 34 | log.section('Label updated') do 35 | fetched_label = label_updated.read(client,) 36 | v_id = [verdict_id, :fetched_label] 37 | Label.verdict_equal?(log, v_id, label_source, fetched_label, 'Fetched label correct') 38 | end 39 | end 40 | return label_updated 41 | end 42 | end 43 | 44 | end 45 | -------------------------------------------------------------------------------- /examples/github/api/endpoints/labels/post_labels.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_endpoint' 2 | 3 | require_relative '../../../data/label' 4 | 5 | class PostLabels < BaseClassForEndpoint 6 | 7 | Contract ApiClient, Label, Maybe[Hash] => [Label, Any] 8 | def self.call_and_return_payload(client, label_to_create, query_elements = {}) 9 | url_elements = [ 10 | client.repo_url_elements, 11 | 'labels', 12 | ] 13 | parameters = { 14 | :name => label_to_create.name, 15 | :color => label_to_create.color, 16 | } 17 | payload = client.post(url_elements, query_elements, parameters) 18 | rehash = HashHelper.rehash_to_symbol_keys(payload) 19 | label_created = Label.new(rehash) 20 | [label_created, payload] 21 | end 22 | 23 | Contract ApiClient, VERDICT_ID, Label, Maybe[Hash] => Label 24 | def self.verdict_call_and_verify_success(client, verdict_id, label_to_create, query_elements = {}) 25 | log = client.log 26 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 27 | label_created = self.call(client, label_to_create, query_elements) 28 | log.section('Evaluation') do 29 | log.section('Returned label correct') do 30 | v_id = [verdict_id, :name] 31 | log.verdict_assert_equal?(v_id, label_to_create.name, label_created.name) 32 | v_id = [verdict_id, :color] 33 | log.verdict_assert_equal?(v_id, label_to_create.color, label_created.color) 34 | end 35 | log.section('Returned label valid') do 36 | v_id = [verdict_id, :valid] 37 | label_created.verdict_valid?(log, v_id) 38 | end 39 | log.section('Label created') do 40 | v_id = [verdict_id, :exists] 41 | label_created.verdict_assert_exist?(client, log, v_id) 42 | end 43 | end 44 | return label_created 45 | end 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /examples/github/base_classes/base_class_for_resource.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/base_classes/base_class_for_data' 2 | require_relative '../../../lib/helpers/object_helper' 3 | 4 | class BaseClassForResource < BaseClassForData 5 | 6 | Contract ApiClient => Bool 7 | # RubyMine cannot find RestClient::NotFound 8 | # noinspection RubyResolve 9 | def exist?(client) 10 | begin 11 | read(client) 12 | return true 13 | rescue 14 | return false 15 | end 16 | end 17 | 18 | Contract ApiClient, Log, VERDICT_ID => Bool 19 | def verdict_assert_exist?(client, log, verdict_id) 20 | log.va?(verdict_id, exist?(client)) 21 | end 22 | alias va_exist? verdict_assert_exist? 23 | 24 | Contract ApiClient, Log, VERDICT_ID => Bool 25 | def verdict_refute_exist?(client, log, verdict_id) 26 | log.vr?(verdict_id, exist?(client)) 27 | end 28 | alias vr_exist? verdict_refute_exist? 29 | 30 | Contract ApiClient => Bool 31 | def delete_if_exist?(client) 32 | if exist?(client) 33 | delete(client) 34 | return true 35 | end 36 | false 37 | end 38 | 39 | Contract ApiClient => self 40 | def self.get_first(client) 41 | all = self.get_all(client) 42 | raise RuntimeError.new('No %s available' % self.name) unless all.size > 0 43 | all.first 44 | end 45 | 46 | end -------------------------------------------------------------------------------- /examples/github/base_classes/base_class_for_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require_relative '../../../lib/helpers/test_helper' 4 | require_relative '../api/api_client' 5 | require_relative '../ui/ui_client' 6 | 7 | class BaseClassForTest < Minitest::Test 8 | 9 | def prelude 10 | raise 'No block given' unless (block_given?) 11 | log_dir_path = TestHelper.get_app_log_dir_path('github_api') 12 | TestHelper.test(self, log_dir_path) do |log| 13 | @repo_username = ENV['REPO_USERNAME'] 14 | @repo_password = ENV['REPO_PASSWORD'] 15 | @repo_name = ENV['REPO_NAME'] 16 | unless @repo_username && @repo_password && @repo_name 17 | message = 'ENV must define REPO_USERNAME, REPO_PASSWORD, REPO_NAME' 18 | raise RuntimeError.new(message) 19 | end 20 | log.section('Test') do 21 | yield log 22 | end 23 | end 24 | end 25 | 26 | def with_api_client(log) 27 | ApiClient.with(log, @repo_username, @repo_password, @repo_name) do |api_client| 28 | yield api_client 29 | end 30 | end 31 | 32 | def with_ui_client(log) 33 | UiClient.with(log, @repo_username, @repo_password, @repo_name) do |ui_client, home_page| 34 | yield ui_client, home_page 35 | end 36 | end 37 | 38 | end 39 | 40 | -------------------------------------------------------------------------------- /examples/github/data/rate_limit.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_resource' 2 | 3 | class RateLimit < BaseClassForResource 4 | 5 | FIELDS = Set.new([ 6 | :resources, 7 | :rate, 8 | ]) 9 | 10 | attr_accessor *FIELDS 11 | 12 | # Constructor. 13 | Contract Hash => nil 14 | def initialize(values = {}) 15 | super(FIELDS, values) 16 | self.resources = new_unless_already(Resources, values.fetch(:resources)) 17 | self.rate = new_unless_already(Rate, values.fetch(:rate)) 18 | nil 19 | end 20 | 21 | Contract Log, VERDICT_ID, Symbol => Bool 22 | def verdict_field_valid?(log, verdict_id, field) 23 | case field 24 | when :rate 25 | rate.verdict_valid?(log, [verdict_id, :rate]) 26 | when :resources 27 | resources.verdict_valid?(log, [verdict_id, :resources]) 28 | else 29 | ArgumentError.new(field.inspect) 30 | end 31 | end 32 | 33 | Contract Symbol => Any 34 | def self.invalid_value_for(field) 35 | case 36 | when :rate 37 | 'not Rate object' 38 | when :resources 39 | 'not Resources object' 40 | else 41 | ArgumentError.new(field.inspect) 42 | end 43 | end 44 | 45 | Contract ApiClient => self 46 | def self.get(client) 47 | require_relative '../api/endpoints/get_rate_limit' 48 | GetRateLimit.call(client) 49 | end 50 | 51 | class Info < BaseClassForData 52 | 53 | FIELDS = Set.new([ 54 | :limit, 55 | :remaining, 56 | :reset, 57 | ]) 58 | 59 | attr_accessor *FIELDS 60 | 61 | # Constructor. 62 | Contract Any => nil 63 | def initialize(values = {}) 64 | super(FIELDS, values) 65 | end 66 | 67 | Contract Log, VERDICT_ID, Symbol => Bool 68 | def verdict_field_valid?(log, verdict_id, field) 69 | value = self.send(field) 70 | log.verdict_assert_integer_positive?(verdict_id, value) 71 | end 72 | 73 | Contract Symbol => Any 74 | def self.invalid_value_for(field) 75 | # Have to cite the parameter to avoid RubyMine code inspection complaint. 76 | field 77 | # For all fields. 78 | 0 79 | end 80 | 81 | # This is harmlessly redundant, but helps RubyMine code inspection. 82 | attr_accessor \ 83 | :limit, 84 | :remaining, 85 | :reset 86 | end 87 | 88 | class Resources < BaseClassForData 89 | 90 | FIELDS = Set.new([ 91 | :core, 92 | :search, 93 | :graphql 94 | ]) 95 | 96 | attr_accessor *FIELDS 97 | 98 | # Constructor. 99 | Contract Any => nil 100 | def initialize(values = {}) 101 | super(FIELDS, values) 102 | self.core = new_unless_already(Core_, values.fetch(:core)) 103 | self.search = new_unless_already(Search, values.fetch(:search)) 104 | self.graphql = new_unless_already(Graphql, values.fetch(:graphql)) 105 | nil 106 | end 107 | 108 | Contract Log, VERDICT_ID, Symbol => Bool 109 | def verdict_field_valid?(log, verdict_id, field) 110 | case field 111 | when :core 112 | core.verdict_valid?(log, [verdict_id, :core]) 113 | when :search 114 | search.verdict_valid?(log, [verdict_id, :search]) 115 | when :graphql 116 | graphql.verdict_valid?(log, [verdict_id, :graphql]) 117 | else 118 | ArgumentError.new(field.inspect) 119 | end 120 | end 121 | 122 | Contract Symbol => Any 123 | def self.invalid_value_for(field) 124 | case 125 | when :core 126 | 'not Core object' 127 | when :search 128 | 'not Search object' 129 | when :graphql 130 | 'not Graphql object' 131 | else 132 | ArgumentError.new(field.inspect) 133 | end 134 | end 135 | 136 | # This is harmlessly redundant, but helps RubyMine code inspection. 137 | attr_accessor \ 138 | :core, 139 | :search, 140 | :graphql 141 | 142 | end 143 | 144 | # This is harmlessly redundant, but helps RubyMine code inspection. 145 | attr_accessor \ 146 | :resources, 147 | :rate 148 | 149 | class Rate < Info 150 | end 151 | 152 | # Class name Core would conflict with Contracts::Core. 153 | class Core_ < Info 154 | end 155 | 156 | class Search < Info 157 | end 158 | 159 | class Graphql < Info 160 | end 161 | 162 | end 163 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/DataObjects.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Unrescued Exception](./UnrescuedException.md#unrescued-exception) 3 | 4 | **Next Stop:** [Logging a Data Object](./FlatDataLog.md#logging-a-data-object) 5 | 6 | 7 | # The Data Object 8 | 9 | A key concept in this framework is the _data object_. 10 | 11 | In these examples, a data object corresponds to a GitHub API _resource_. 12 | 13 | The main data objects seen here will be: 14 | 15 | - `Label` 16 | - `RateLimit` 17 | 18 | A data object has _fields_, such as: 19 | 20 | - `:name` 21 | - `:id` 22 | 23 | Each data class derives from base classes that provide for: 24 | 25 | - Initialization. 26 | - Self-logging. 27 | - Verdicts: automatically-logged verifications. 28 | - Automatically-logged self-validation. 29 | - Determining and verifying resource existence. 30 | 31 | **Prev Stop:** [Unrescued Exception](./UnrescuedException.md#unrescued-exception) 32 | 33 | **Next Stop:** [Logging a Data Object](./FlatDataLog.md#logging-a-data-object) 34 | 35 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/EndpointObjects.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Existence Methods](./Existence.md#existence-methods) 3 | 4 | **Next Stop:** [Endpoint Tests](./EndpointTests.md#endpoint-tests) 5 | 6 | 7 | # Endpoint Objects 8 | 9 | In testing web applications, the _page object design pattern_ has become justifiably famous. Its job is classic data hiding: each such object encapsulates an HMTL page. 10 | 11 | The corresponding object for testing REST APIs is the _endpoint object_, which fully encapsulates a REST API endpoint. 12 | 13 | And that is how this framework implements access to API endpoints: via endpoint objects. Each endpoint in encapsulated by its own endpoint class. 14 | 15 | Each endpoint class has method `self.verdict_call_and_verify_success`, which may be used in a test. Typically, that method accepts the client and zero or more data objects. 16 | 17 | Examples: 18 | 19 | - `GetLabels.verdict_call_and_verify_success(client)` 20 | - `GetLabelsName.verdict_call_and_verify_success(client, label)` 21 | - `PostLabelsName.verdict_call_and_verify_success(client, label)` 22 | - `PatchLabelsName.verdict_call_and_verify_success(client, label)` 23 | - `DeleteLabelsName.verdict_call_and_verify_success(client, label)` 24 | 25 | Each of these methods defines what _success_ means (encapsulation!), and each performs and logs the actual verifications. 26 | 27 | **Prev Stop:** [Existence Methods](./Existence.md#existence-methods) 28 | 29 | **Next Stop:** [Endpoint Tests](./EndpointTests.md#endpoint-tests) 30 | 31 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/EndpointTests.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Endpoint Objects](./EndpointObjects.md#endpoint-objects) 3 | 4 | **Next Stop:** [GetLabels Test](./GetLabels.md#getlabels-test) 5 | 6 | 7 | # Endpoint Tests 8 | 9 | The next few stops will show how to test some GitHub REST API endpoints. 10 | 11 | These endpoints will be: 12 | 13 | - `GET /labels` - Get all labels. 14 | - `POST /labels` - Create a label. 15 | - `GET /labels/:name` - Get a label by name. 16 | - `PATCH /labels/:name` - Update a label by name. 17 | - `DELETE /labels/:name` - Delete a label by name. 18 | 19 | **Prev Stop:** [Endpoint Objects](./EndpointObjects.md#endpoint-objects) 20 | 21 | **Next Stop:** [GetLabels Test](./GetLabels.md#getlabels-test) 22 | 23 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/FlatDataLog.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [The Data Object](./DataObjects.md#the-data-object) 3 | 4 | **Next Stop:** [Creating a Data Object](./FlatDataNew.md#creating-a-data-object) 5 | 6 | 7 | # Logging a Data Object 8 | 9 | A data object can log itself. 10 | 11 | ## Example Test 12 | 13 | flat_data_log_test.rb 14 | ```ruby 15 | require_relative '../../base_classes/base_class_for_test' 16 | 17 | require_relative '../../data/label' 18 | 19 | class FlatDataLogTest < BaseClassForTest 20 | 21 | def test_flat_data_log 22 | prelude do |log| 23 | with_api_client(log) do |api_client| 24 | log.section('Fetch and log an instance of Label') do 25 | label = nil 26 | log.section('Fetch a label') do 27 | label = Label.get_first(api_client) 28 | end 29 | label.log(log, 'Fetched label') 30 | end 31 | end 32 | end 33 | end 34 | 35 | end 36 | ``` 37 | 38 | Notes: 39 | 40 | - Log a data object by calling its `log` method. 41 | 42 | ## Log 43 | 44 | test_flat_data_log.xml 45 | ```xml 46 | 47 | 48 | 49 |
50 |
51 |
52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 |
63 |
64 |
65 |
66 |
67 | 68 | 0 69 | 0 70 | 71 |
72 | 73 | ``` 74 | 75 | Notes: 76 | 77 | - Element `ApiClient` records an interaction with the GitHub API, showing the HTTP method and url. 78 | - Its subelement `execution` shows the timestamp and duration for the interaction. 79 | - The section named `Fetched Label` shows the values in the fetched rate limit. 80 | 81 | **Prev Stop:** [The Data Object](./DataObjects.md#the-data-object) 82 | 83 | **Next Stop:** [Creating a Data Object](./FlatDataNew.md#creating-a-data-object) 84 | 85 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/FlatDataNew.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Logging a Data Object](./FlatDataLog.md#logging-a-data-object) 3 | 4 | **Next Stop:** [Verifying a Data Object](./FlatDataEqual.md#verifying-a-data-object) 5 | 6 | 7 | # Creating a Data Object 8 | 9 | A data object's `new` method takes a hash of name/value pairs that initialize its fields. 10 | 11 | ## Example Test 12 | 13 | flat_data_new_test.rb 14 | ```ruby 15 | require_relative '../../base_classes/base_class_for_test' 16 | 17 | require_relative '../../data/label' 18 | 19 | class FlatDataNewTest < BaseClassForTest 20 | 21 | def test_flat_data_new 22 | prelude do |log| 23 | log.section('Instantiate and log an instance of Label') do 24 | label = Label.new( 25 | :id => 710733210, 26 | :url => 'https://api.github.com/repos/BurdetteLamar/CrashDummy/labels/enhancement', 27 | :name => 'enhancement', 28 | :color => '84b6eb', 29 | :default => true, 30 | ) 31 | log.section('Instantiated label') do 32 | label.log(log) 33 | end 34 | end 35 | end 36 | end 37 | 38 | end 39 | ``` 40 | 41 | Notes: 42 | 43 | - Create a new data object by calling method `new` on the data class. 44 | - Pass the initial values in a hash of name/value pairs. 45 | - (Note that the instantiated `Label` exists only here in the test, and not in the Github API itself.) 46 | 47 | ## Log 48 | 49 | test_flat_data_new.xml 50 | ```xml 51 | 52 | 53 | 54 |
55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 |
64 |
65 |
66 |
67 |
68 |
69 | 70 | 0 71 | 0 72 | 73 |
74 | 75 | ``` 76 | 77 | **Prev Stop:** [Logging a Data Object](./FlatDataLog.md#logging-a-data-object) 78 | 79 | **Next Stop:** [Verifying a Data Object](./FlatDataEqual.md#verifying-a-data-object) 80 | 81 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/Meet.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Overview](./Overview.md#overview) 3 | 4 | **Next Stop:** [Web UI Test](./WebUi.md#web-ui-test) 5 | 6 | 7 | # Meet the Log and Clients 8 | 9 | The test framework sets up for the test by offers objects the test will need: 10 | 11 | - An open test log. 12 | - A domain-specific GitHub API client. 13 | - A domain-specific GitHub web UI client. 14 | 15 | ## Example Test 16 | 17 | meet_test.rb 18 | ```ruby 19 | require_relative '../../base_classes/base_class_for_test' 20 | 21 | class MeetTest < BaseClassForTest 22 | 23 | def test_meet 24 | prelude do |log| 25 | log.comment('Method prelude yields an instance of %s, for logging the test.' % log.class.name) 26 | with_api_client(log) do |api_client| 27 | log.comment('Method with_api_client yields an instance of %s, for accessing the GitHub API' % api_client.class.name) 28 | end 29 | with_ui_client(log) do |ui_client| 30 | log.comment('Method with_ui_client yields an instance of %s, for accessing the GitHub UI.' % ui_client.class.name) 31 | end 32 | end 33 | end 34 | 35 | end 36 | ``` 37 | 38 | Notes: 39 | 40 | - Create a new test class by deriving from `BaseClassForTest`. 41 | - Choose a test method-name that begins with `test`, which tells the test framework that it can be executed at test-time. 42 | - Call methods inherited from the base class: 43 | - `prelude`: yields a `Log` object. 44 | - `with_api_client`: yields an `ApiClient` object. 45 | - `with_ui_client`: yields a `UiClient` object. 46 | 47 | ## Log 48 | 49 | test_meet.xml 50 | ```xml 51 | 52 | 53 | 54 |
55 | Method prelude yields an instance of Log, for logging the test. 56 | 57 | Method with_api_client yields an instance of ApiClient, for accessing 58 | the GitHub API 59 | 60 | 61 | Method with_ui_client yields an instance of UiClient, for accessing the 62 | GitHub UI. 63 | 64 |
65 |
66 |
67 | 68 | 0 69 | 0 70 | 71 |
72 | 73 | ``` 74 | 75 | Notes: 76 | 77 | - At the top of the log is a summary of the counts of verdicts, failures (failed verdicts), and errors (unexpected exceptions). 78 | - Element `test_method` gives the test name, timestamp, and duration. 79 | - The section named `Test` contains all logging from the test itself. 80 | - The last section gives the count of errors (unexpected exceptions). Its verdict expects that count to be 0. 81 | - (Attribute `volatile`, seen in element `verdict`, has to do with the Changes Report, and is of no present interest.) 82 | 83 | **Prev Stop:** [Overview](./Overview.md#overview) 84 | 85 | **Next Stop:** [Web UI Test](./WebUi.md#web-ui-test) 86 | 87 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/MoreToCome.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [DeleteLabelsName Test](./DeleteLabelsName.md#deletelabelsname-test) 3 | 4 | 5 | # More to Come ... 6 | 7 | This tour documentation is under active development. 8 | 9 | Stay tuned.... 10 | 11 | **Prev Stop:** [DeleteLabelsName Test](./DeleteLabelsName.md#deletelabelsname-test) 12 | 13 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/NestedDataLog.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Validating a Data Object](./FlatDataValid.md#validating-a-data-object) 3 | 4 | **Next Stop:** [Creating Nested Data Objects](./NestedDataNew.md#creating-nested-data-objects) 5 | 6 | 7 | # Logging Nested Data Objects 8 | 9 | Nested data objects can log themselves recursively. 10 | 11 | Up to now, the data objects seen have been instances of class `Label`. 12 | 13 | These objects are 'flat' in the sense that the values they store are all scalars: 14 | 15 | - Integers. 16 | - Strings. 17 | - Booleans. 18 | 19 | Now we begin to look at the handling of objects in class `RateLimit`, some of whose values are _not_ scalars, but are actually _other data objects_. In other words, they're nested data objects. 20 | 21 | ## Example Test 22 | 23 | nested_data_log_test.rb 24 | ```ruby 25 | require_relative '../../base_classes/base_class_for_test' 26 | 27 | require_relative '../../data/rate_limit' 28 | 29 | class NestedDataLogTest < BaseClassForTest 30 | 31 | def test_nested_data_log 32 | prelude do |log| 33 | with_api_client(log) do |api_client| 34 | log.section('Fetch and log a rate limit') do 35 | rate_limit = nil 36 | log.section('Fetch rate limit') do 37 | rate_limit = RateLimit.get(api_client) 38 | end 39 | rate_limit.log(log, 'Fetched rate limit') 40 | end 41 | end 42 | end 43 | end 44 | 45 | end 46 | 47 | ``` 48 | 49 | Notes: 50 | 51 | - Log nested data objects by calling the `log` method. 52 | 53 | ## Log 54 | 55 | test_nested_data_log.xml 56 | ```xml 57 | 58 | 59 | 60 |
61 |
62 |
63 | 64 | 65 | 66 |
67 |
68 |
69 |
70 | 71 | 72 | 73 |
74 |
75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 |
84 |
85 |
86 | 87 | 88 | 89 |
90 |
91 |
92 |
93 |
94 |
95 | 96 | 0 97 | 0 98 | 99 |
100 | 101 | ``` 102 | 103 | Notes: 104 | 105 | - The section named `Fetched rate limit` logs the values in the fetched rate limit. 106 | - The nested objects recursively log themselves into nested log sections. 107 | - The structure of the logged nested objects: 108 | - `RateLimit` 109 | - `RateLimit::Resources` 110 | - `RateLimit::Core` 111 | - `RateLimit::Search` 112 | - `RateLimit::Graphql` 113 | - `RateLimit::Rate` 114 | 115 | **Prev Stop:** [Validating a Data Object](./FlatDataValid.md#validating-a-data-object) 116 | 117 | **Next Stop:** [Creating Nested Data Objects](./NestedDataNew.md#creating-nested-data-objects) 118 | 119 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/NestedDataNew.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Logging Nested Data Objects](./NestedDataLog.md#logging-nested-data-objects) 3 | 4 | **Next Stop:** [Creating Nested Data Objects, Part 2](./NestedDataNew2.md#creating-nested-data-objects-part-2) 5 | 6 | 7 | # Creating Nested Data Objects 8 | 9 | Nested data objects may be created 'from scratch'. That is, all of the nested objects are initialized at once, from simple nested hashes. 10 | 11 | ## Example Test 12 | 13 | nested_data_new_test.rb 14 | ```ruby 15 | require_relative '../../base_classes/base_class_for_test' 16 | 17 | require_relative '../../data/rate_limit' 18 | 19 | class NestedDataNewTest < BaseClassForTest 20 | 21 | def test_nested_data_new 22 | prelude do |log| 23 | log.section('Create and log nested data objects') do 24 | rate_limit = RateLimit.new( 25 | { 26 | :resources => { 27 | :core => { 28 | :limit => 5000, 29 | :remaining => 4984, 30 | :reset => 1507676679, 31 | }, 32 | :search => { 33 | :limit => 30, 34 | :remaining => 30, 35 | :reset => 1507673695, 36 | }, 37 | :graphql => { 38 | :limit => 5000, 39 | :remaining => 5000, 40 | :reset => 1507677235, 41 | } 42 | }, 43 | :rate => { 44 | :limit => 5000, 45 | :remaining => 4984, 46 | :reset => 1507676679, 47 | } 48 | } 49 | ) 50 | rate_limit.log(log) 51 | end 52 | end 53 | end 54 | 55 | end 56 | ``` 57 | 58 | Notes: 59 | 60 | - Create a new nested data object by calling method `new` on the data class. 61 | - The nested hashes are formed into nested data objects. 62 | - Note that the instantiated objects exists only here in the test, and not in the Github API itself. 63 | 64 | ## Log 65 | 66 | test_nested_data_new.xml 67 | ```xml 68 | 69 | 70 | 71 |
72 |
73 |
74 |
75 |
76 | 77 | 78 | 79 |
80 |
81 | 82 | 83 | 84 |
85 |
86 | 87 | 88 | 89 |
90 |
91 |
92 | 93 | 94 | 95 |
96 |
97 |
98 |
99 |
100 |
101 | 102 | 0 103 | 0 104 | 105 |
106 | 107 | ``` 108 | 109 | **Prev Stop:** [Logging Nested Data Objects](./NestedDataLog.md#logging-nested-data-objects) 110 | 111 | **Next Stop:** [Creating Nested Data Objects, Part 2](./NestedDataNew2.md#creating-nested-data-objects-part-2) 112 | 113 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/Overview.md: -------------------------------------------------------------------------------- 1 | 2 | **Next Stop:** [Meet the Log and Clients](./Meet.md#meet-the-log-and-clients) 3 | 4 | 5 | # Overview 6 | 7 | This tour shows how to use the `RubyTest` framework to test: 8 | 9 | - The GitHub REST API. 10 | - The GitHub web UI. 11 | 12 | The domain-specific GitHub framework is layered onto the underlying RubyTest core framework, and implements: 13 | 14 | - A domain-specific web UI client. 15 | - Page objects, corresponding to web UI pages. 16 | - A domain-specific REST API client. 17 | - Endpoint objects, corresponding to REST API endpoints. 18 | - Data objects, corresponding to REST API resources. 19 | 20 | **Next Stop:** [Meet the Log and Clients](./Meet.md#meet-the-log-and-clients) 21 | 22 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/ResourceMethods.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Validating Nested Data Objects](./NestedDataValid.md#validating-nested-data-objects) 3 | 4 | **Next Stop:** [CRUD](./Crud.md#crud) 5 | 6 | 7 | # Resource Methods 8 | 9 | The data objects in this framework offer methods for accessing the API resources: 10 | 11 | - CRUD methods: 12 | - `create` 13 | - `read` 14 | - `update` 15 | - `delete` 16 | - Getter methods: 17 | - `self.get_all` 18 | - `self.get_first` 19 | - Existence methods: 20 | - `exist?` 21 | - `verdict_assert_exist?` 22 | - `verdict_refute_exist?` 23 | - `delete_if_exist?` 24 | 25 | **Prev Stop:** [Validating Nested Data Objects](./NestedDataValid.md#validating-nested-data-objects) 26 | 27 | **Next Stop:** [CRUD](./Crud.md#crud) 28 | 29 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/RestApi.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Web UI Test](./WebUi.md#web-ui-test) 3 | 4 | **Next Stop:** [Test Sections and Nesting](./Sections.md#test-sections-and-nesting) 5 | 6 | 7 | # Rest API Test 8 | 9 | Here's a simple REST API test, one that tests CRUD methods (create, read, update, delete) to operate on `Label` objects. 10 | 11 | ## Example Test 12 | 13 | rest_api_test.rb 14 | ```ruby 15 | require_relative '../../base_classes/base_class_for_test' 16 | 17 | require_relative '../../data/label' 18 | 19 | class RestApiTest < BaseClassForTest 20 | 21 | def test_rest_api 22 | 23 | prelude do |log| 24 | 25 | with_api_client(log) do |api_client| 26 | 27 | label_to_create = Label.provisioned 28 | label_created = nil 29 | label_updated = nil 30 | 31 | log.section('Create') do 32 | label_created = label_to_create.create!(api_client) 33 | Label.verdict_equal?(log, :create_return_correct, label_to_create, label_created) 34 | label_created.verdict_read_and_verify?(api_client, log, :created_correctly) 35 | end 36 | 37 | log.section('Read') do 38 | label_read = label_created.read(api_client) 39 | Label.verdict_equal?(log, :read_correctly, label_created, label_read) 40 | end 41 | 42 | log.section('Update') do 43 | label_source = label_created.perturb 44 | label_source.url = nil 45 | label_source.default = nil 46 | label_updated = label_created.update!(api_client, label_source) 47 | Label.verdict_equal?(log, :update_return_correct, label_source, label_updated) 48 | label_updated.verdict_read_and_verify?(api_client, log, :updated_correctly) 49 | end 50 | 51 | log.section('Delete') do 52 | return_value = label_updated.delete(api_client) 53 | log.verdict_assert_nil?(:delete_return_correct, return_value) 54 | label_created.verdict_refute_exist?(api_client, log, :deleted_correctly) 55 | end 56 | 57 | end 58 | 59 | end 60 | 61 | end 62 | 63 | end 64 | ``` 65 | 66 | Notes: 67 | 68 | - The test first defines: 69 | - `label_to_create`, a variable to house data for creating a label. 70 | - `label_created`, a variable that will house the data for a created label, including new values for `:id` and `:url`. 71 | - `label_updated`, a variable that will house the data for an updated label, including new values for `:id` and `:url`. 72 | - In section `Create`: 73 | - `create!`, a method that first deletes the label if it exists, then creates the label. (Method `:create`, without the exclamation point, would fail if the label exists.) 74 | - `:create_return_correct` and `:created_correctly`, symbols that are _verdict identifiers_. A verdict identifier appears in each verdict method call. 75 | - `Label.verdict_equal?`, a method that verifies that the returned label data is correct. 76 | - `label_returned.verdict_read_and_verify?`, a method that verifies that the label was correctly created in GitHub. 77 | - In section `Read`: 78 | - `label_created.read` a method that reads a label, which is stored into variable `label_read`. 79 | - `Label.verdict_equal?`, a method that verifies that the returned label data is correct. 80 | - In section `Update`: 81 | - `label_created.perturb`, a method that perturbs data from `label_created`, which is stored into `label_source`. 82 | - The web UI does not expose the label's url or default, so these are set to nil, and will be ignored. 83 | - `Label.verdict_equal?`, a method that verifies that the returned label data is correct. 84 | - `label_returned.verdict_read_and_verify?`, a method that verifies that the label was correctly updated in GitHub. 85 | - In section `Delete`: 86 | - `label_updated.delete`, a method that deletes a label. 87 | - `log.verdict_assert_nil?`, a method that verifies that the return value is `nil`. 88 | - `label_created.verdict_refute_exist?`, a method that verifies that the label was deleted in GitHub. 89 | 90 | **Prev Stop:** [Web UI Test](./WebUi.md#web-ui-test) 91 | 92 | **Next Stop:** [Test Sections and Nesting](./Sections.md#test-sections-and-nesting) 93 | 94 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/Sections.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Rest API Test](./RestApi.md#rest-api-test) 3 | 4 | **Next Stop:** [Verdicts](./Verdicts.md#verdicts) 5 | 6 | 7 | # Test Sections and Nesting 8 | 9 | A test and its log have many duties, but one of the first is their duty to tell a story. 10 | 11 | Reading the test code or the log, you should be able to understand how the test is organized, and what it's trying to do. Nested sections help with that. 12 | 13 | ## Example Test 14 | 15 | sections_test.rb 16 | ```ruby 17 | require_relative '../../base_classes/base_class_for_test' 18 | 19 | class SectionsTest < BaseClassForTest 20 | 21 | def test_sections 22 | prelude do |log, _| 23 | log.section('First outer section') do 24 | log.section('First inner section') do 25 | log.comment('Some test code can go here') 26 | end 27 | log.section('Second inner section') do 28 | log.comment('Some test code can go here') 29 | end 30 | end 31 | log.section('Second outer section') do 32 | log.comment('Some test code can go here') 33 | end 34 | log.section('Section with timestamp', :timestamp) do 35 | log.comment('Some test code can go here') 36 | end 37 | log.section('Section with timestamp', :duration) do 38 | log.comment('Some test code can go here') 39 | sleep 1 40 | end 41 | log.section('Section with timestamp and duration', :timestamp, :duration) do 42 | log.comment('Some test code can go here') 43 | sleep 2 44 | end 45 | log.section('Order does not matter', :duration, :timestamp) do 46 | log.comment('Some test code can go here') 47 | sleep 3 48 | end 49 | end 50 | end 51 | 52 | end 53 | ``` 54 | 55 | Notes: 56 | 57 | - Use nested sections to organize test code. 58 | - Use optional argument `:timestamp` to add a timestamp to the logged section. 59 | - Use optional argument `:duration` to add an execution duration to the logged section. 60 | - (This test does not use the client, and so by Ruby convention uses the variable name `_` instead of variable name `client`.) 61 | 62 | ## Log 63 | 64 | test_sections.xml 65 | ```xml 66 | 67 | 68 | 69 |
70 |
71 |
72 | Some test code can go here 73 |
74 |
75 | Some test code can go here 76 |
77 |
78 |
79 | Some test code can go here 80 |
81 |
82 | Some test code can go here 83 |
84 |
85 | Some test code can go here 86 |
87 |
88 | Some test code can go here 89 |
90 |
91 | Some test code can go here 92 |
93 |
94 |
95 |
96 | 97 | 0 98 | 0 99 | 100 |
101 | 102 | ``` 103 | 104 | Notes: 105 | 106 | - The sections in the test are propagated to the log, so that the log is organized the same way as the test. 107 | 108 | **Prev Stop:** [Rest API Test](./RestApi.md#rest-api-test) 109 | 110 | **Next Stop:** [Verdicts](./Verdicts.md#verdicts) 111 | 112 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_files/WebUi.md: -------------------------------------------------------------------------------- 1 | 2 | **Prev Stop:** [Meet the Log and Clients](./Meet.md#meet-the-log-and-clients) 3 | 4 | **Next Stop:** [Rest API Test](./RestApi.md#rest-api-test) 5 | 6 | 7 | # Web UI Test 8 | 9 | Here's a simple web UI test, one that tests CRUD methods (create, read, update, delete) to operate on `Label` objects. 10 | 11 | ## Example Test 12 | 13 | web_ui_test.rb 14 | ```ruby 15 | require_relative '../../base_classes/base_class_for_test' 16 | 17 | require_relative '../../data/label' 18 | 19 | require_relative '../../ui/pages/labels_page' 20 | 21 | class WebUiTest < BaseClassForTest 22 | 23 | def test_web_ui 24 | 25 | prelude do |log| 26 | 27 | with_ui_client(log) do |ui_client| 28 | 29 | label_to_create = nil 30 | 31 | ui_client.login 32 | labels_page = LabelsPage.new(ui_client).visit 33 | 34 | log.section('Create label') do 35 | label_to_create = Label.provisioned 36 | # The value for field :default is not available in the web UI. 37 | label_to_create.default = nil 38 | labels_page.create_label!(label_to_create) 39 | labels_page.wait_for_label(label_to_create) 40 | labels_page.verdict_assert_exist?(log, :label_created, label_to_create) 41 | end 42 | 43 | log.section('Read label') do 44 | label_to_read = label_to_create 45 | label_read = labels_page.read_label(label_to_read) 46 | Label.verdict_equal?(log, :label_read, label_to_read, label_read) 47 | end 48 | 49 | label_source = nil 50 | log.section('Update label') do 51 | label_source = label_to_create.perturb 52 | # The value for field :default is not available in the web UI. 53 | label_source.default = nil 54 | labels_page.update_label(label_to_create, label_source) 55 | labels_page.wait_for_label(label_source) 56 | labels_page.verdict_assert_exist?(log, :label_updated, label_source) 57 | end 58 | 59 | log.section('Delete label') do 60 | label_to_delete = label_source 61 | labels_page.delete_label(label_to_delete) 62 | ui_client.browser.refresh 63 | labels_page.verdict_refute_exist?(log, :label_deleted, label_to_delete) 64 | end 65 | 66 | end 67 | end 68 | end 69 | 70 | end 71 | ``` 72 | 73 | Notes: 74 | 75 | - The test first: 76 | - Declares variable `label_to_create` to house data for creating a label. 77 | - It then calls methods: 78 | - `ui_client.login` to log into the web UI. 79 | - `LabelsPage.new(ui_client).visit` to visit the labels page. 80 | - In section `Create` the test calls methods: 81 | - `Label.provisioned` to acquire a provisioned label object, ready for creation. 82 | - `labels_page.create_label!` to first delete the label if it exists, then creates the label. (Method `:create_label`, without the exclamation point, would fail if the label exists.) 83 | - `labels_page.wait_for_label` to wait for the created label to appear. 84 | - `labels_page.verdict_assert_exist?` to verify that the new label exists. Symbol `:label_created` is a verdict identifier. 85 | - In section `Read` the test call methods: 86 | - `labels_page.read_label` to read the new label in the UI. 87 | - `Label.verdict_equal?` to verify that the label read is correct. Symbol `:label_read` is a verdict identifier. 88 | - In section `Update` the test calls methods: 89 | - `label_to_create.perturb` to modify the label to be updated. 90 | - `labels_page.update_label` to update the label. 91 | - `labels_page.wait_for_label` to wait for the updated label to appear. 92 | - `labels_page.verdict_assert_exist?` to verify that the updated label appears on the page. Symbol `:label_updated` is a verdict identifer. 93 | - In section `Delete` the test calls methods: 94 | - `labels_page.delete_label` to delete the label. 95 | - `ui_client.browser.refresh` to refresh the page. 96 | - `labels_page.verdict_refute_exist?` to verify that the label no longer appears on the page. Symbol `:label_deleted` is a verdict identifier. 97 | 98 | **Prev Stop:** [Meet the Log and Clients](./Meet.md#meet-the-log-and-clients) 99 | 100 | **Next Stop:** [Rest API Test](./RestApi.md#rest-api-test) 101 | 102 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/Crud.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # CRUD 4 | 5 | Methods: 6 | 7 | - `obj#create` 8 | - `obj#read` 9 | - `obj#update` 10 | - `obj#delete` 11 | s 12 | ## Example Test 13 | 14 | [file_source](../tests/crud_test.rb) 15 | 16 | Notes: 17 | 18 | - Here we create, read, update, and delete a Label object, using the CRUD methods (instead of accessing endpoints directly). 19 | - The conditional passages show how we would do these both without and with the CRUD methods. 20 | - Before creating, we delete the label, if it exists. 21 | 22 | ## Log 23 | 24 | [file_source](../logs/test_crud.xml) 25 | 26 | [navigation_links] 27 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/DataObjects.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # The Data Object 4 | 5 | A key concept in this framework is the _data object_. 6 | 7 | In these examples, a data object corresponds to a GitHub API _resource_. 8 | 9 | The main data objects seen here will be: 10 | 11 | - `Label` 12 | - `RateLimit` 13 | 14 | A data object has _fields_, such as: 15 | 16 | - `:name` 17 | - `:id` 18 | 19 | Each data class derives from base classes that provide for: 20 | 21 | - Initialization. 22 | - Self-logging. 23 | - Verdicts: automatically-logged verifications. 24 | - Automatically-logged self-validation. 25 | - Determining and verifying resource existence. 26 | 27 | [navigation_links] 28 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/DeleteLabelsName.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # DeleteLabelsName Test 4 | 5 | This is a test for endpoint `DELETE /labels/:name`, which deletes a label. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/delete_labels_name_test.rb) 10 | 11 | Notes: 12 | 13 | - The test creates the label that it will delete. 14 | - It uses method `Label#delete_if_exist?` before and after, to avoid collisions and to clean up. 15 | - Test uses the data-object method `Label#create` to create the label. 16 | - Class `DeleteLabelsName` encapsulates the endpoint. 17 | - Its method `verdict_call_and_verify_success`: 18 | - Accepts the client, a verdict id, and the label to be deleted. 19 | - Accesses the endpoint. 20 | - Verifies that the response is empty. 21 | - Verifies that the label no longer exists. 22 | 23 | ## Log 24 | 25 | [file_source](../logs/test_delete_labels_name.xml) 26 | 27 | Notes: 28 | 29 | - Section `ApiClient` shows the endpoint access. 30 | - In section `Evaluation`: 31 | - Section `Response empty` verifies that the response was empty. 32 | - Section `Label deleted` verifies that the label no longer exists. 33 | [navigation_links] 34 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/EndpointObjects.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Endpoint Objects 4 | 5 | In testing web applications, the _page object design pattern_ has become justifiably famous. Its job is classic data hiding: each such object encapsulates an HMTL page. 6 | 7 | The corresponding object for testing REST APIs is the _endpoint object_, which fully encapsulates a REST API endpoint. 8 | 9 | And that is how this framework implements access to API endpoints: via endpoint objects. Each endpoint in encapsulated by its own endpoint class. 10 | 11 | Each endpoint class has method `self.verdict_call_and_verify_success`, which may be used in a test. Typically, that method accepts the client and zero or more data objects. 12 | 13 | Examples: 14 | 15 | - `GetLabels.verdict_call_and_verify_success(client)` 16 | - `GetLabelsName.verdict_call_and_verify_success(client, label)` 17 | - `PostLabelsName.verdict_call_and_verify_success(client, label)` 18 | - `PatchLabelsName.verdict_call_and_verify_success(client, label)` 19 | - `DeleteLabelsName.verdict_call_and_verify_success(client, label)` 20 | 21 | Each of these methods defines what _success_ means (encapsulation!), and each performs and logs the actual verifications. 22 | 23 | [navigation_links] 24 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/EndpointTests.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Endpoint Tests 4 | 5 | The next few stops will show how to test some GitHub REST API endpoints. 6 | 7 | These endpoints will be: 8 | 9 | - `GET /labels` - Get all labels. 10 | - `POST /labels` - Create a label. 11 | - `GET /labels/:name` - Get a label by name. 12 | - `PATCH /labels/:name` - Update a label by name. 13 | - `DELETE /labels/:name` - Delete a label by name. 14 | 15 | [navigation_links] 16 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/Existence.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Existence Methods 4 | 5 | ## Example Test 6 | 7 | [file_source](../tests/existence_test.rb) 8 | 9 | Notes: 10 | 11 | - `Label#exist?(client)` determines whether the label exists in the resource. 12 | - `Label#verdict_assert_exist?(client, log, verdict_id)` logs a verdict asserting the label's existence. 13 | - `Label#verdict_refute_exist?(client, log, verdict_id)` logs a verdict refuting the label's existence. 14 | - `Label#delete_if_exist?(client)` deletes the label if it exists. 15 | 16 | [file_source](../logs/test_existence.xml) 17 | 18 | [navigation_links] 19 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/FlatDataEqual.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Verifying a Data Object 4 | 5 | A data class has methods for: 6 | 7 | - Testing for object equality (without logging). 8 | - Verifying object equality (with logging). 9 | 10 | ## Example Test 11 | 12 | [file_source](../tests/flat_data_equal_test.rb) 13 | 14 | Notes: 15 | 16 | - Use method `equal?` to test data object equality, without logging. 17 | - Use method `verdict_equal?` to test object equality, including logging. 18 | - The test gets a known `Label`, then clones it. 19 | - We know that `Label` is flat, but it's good practice to use `deep_clone`, not `clone` just to be sure. 20 | - In section `These are equal`: 21 | - Method `Label.equal?` tests equality, but does no logging. 22 | - The `fail unless` proves that it worked. 23 | - Method `Label.verdict_equal?` verifies and logs each value in the rate limit. 24 | - In section `These are not equal`: 25 | - One value in the rate limit is modified. 26 | - Method `Label.equal?` tests equality, but does no logging. 27 | - The `fail if ` proves that it worked. 28 | - Method `Label.verdict_equal?` verifies and logs each value in the rate limit. 29 | 30 | ## Log 31 | 32 | [file_source](../logs/test_flat_data_equal.xml) 33 | 34 | Notes: 35 | 36 | - Each actual value is verified in a separate verdict. 37 | - In section `These are equal`, all verdicts pass. 38 | - In section `These are not equal`, one verdict fails. 39 | 40 | [navigation_links] 41 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/FlatDataLog.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Logging a Data Object 4 | 5 | A data object can log itself. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/flat_data_log_test.rb) 10 | 11 | Notes: 12 | 13 | - Log a data object by calling its `log` method. 14 | 15 | ## Log 16 | 17 | [file_source](../logs/test_flat_data_log.xml) 18 | 19 | Notes: 20 | 21 | - Element `ApiClient` records an interaction with the GitHub API, showing the HTTP method and url. 22 | - Its subelement `execution` shows the timestamp and duration for the interaction. 23 | - The section named `Fetched Label` shows the values in the fetched rate limit. 24 | 25 | [navigation_links] 26 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/FlatDataNew.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Creating a Data Object 4 | 5 | A data object's `new` method takes a hash of name/value pairs that initialize its fields. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/flat_data_new_test.rb) 10 | 11 | Notes: 12 | 13 | - Create a new data object by calling method `new` on the data class. 14 | - Pass the initial values in a hash of name/value pairs. 15 | - (Note that the instantiated `Label` exists only here in the test, and not in the Github API itself.) 16 | 17 | ## Log 18 | 19 | [file_source](../logs/test_flat_data_new.xml) 20 | 21 | [navigation_links] 22 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/FlatDataValid.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Validating a Data Object 4 | 5 | A data object can validate itself. 6 | 7 | The term _valid_, as used here, is about whether a value is of the correct _form_. This is as distinguished from the earlier term _equal_, which is about whether the value is _correct_. 8 | 9 | Example of usefulness: creating a new label returns an `Label` object with a new `:id` value. The _value_ of the `id` is unpredictable, but it is still useful to verify that it is a positive integer, as required. 10 | 11 | ## Example Test 12 | 13 | [file_source](../tests/flat_data_valid_test.rb) 14 | 15 | Notes: 16 | 17 | - Use method `verdict_valid?` to verify that a data object's data are valid. 18 | - In section `This is valid`: 19 | - A new `Label` with valid values is instantiated. 20 | - Method `Label.verdict_valid? validates and logs each value in the rate limit. 21 | - In section `This is not valid`: 22 | - A new `Label` with invalid values is instantiated. 23 | - Method `Label.verdict_valid? validates and logs each value in the rate limit. 24 | - The invalid value is not in the test itself, but instead comes from the data class. 25 | 26 | ## Log 27 | 28 | [file_source](../logs/test_flat_data_valid.xml) 29 | s 30 | Notes: 31 | 32 | - In section `This is valid`, all verdicts pass. 33 | - When a value has multiple validation verdicts (as all of these do), the verdicts are logged into a separate subsection. 34 | - In section `This is not valid`, three verdicts fail. 35 | 36 | [navigation_links] 37 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/GetLabels.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # GetLabels Test 4 | 5 | This is a test for endpoint `GET /labels`, which returns all labels. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/get_labels_test.rb) 10 | 11 | Notes: 12 | 13 | - Class `GetLabels` encapsulates endpoint `GET /labels`. 14 | - Method `GetLabels.verdict_call_and_verify_success`: 15 | - Accepts the client and a verdict id. 16 | - Accesses the endpoint. 17 | - Forms the returned data into an array of `Label` objects. 18 | - Performs verifications on those objects. 19 | - Returns the array. 20 | 21 | ## Log 22 | 23 | [file_source](../logs/test_get_labels.xml) 24 | 25 | Notes: 26 | 27 | - Section `ApiClient` shows the endpoint access. 28 | - We don't know what labels to expect, so section `Info` just shows: 29 | - The count of labels. 30 | - The first label. 31 | - Section 'Evaluation' shows validations for the first label. 32 | 33 | [navigation_links] 34 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/GetLabelsName.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # GetLabelsName Test 4 | 5 | This is a test for endpoint `GET /labels/:name`, which fetches a label. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/get_labels_name_test.rb) 10 | 11 | Notes: 12 | 13 | - The test creates the label that it will fetch. 14 | - It uses method `Label#delete_if_exist?` before and after, to avoid collisions and to clean up. 15 | - Test uses the data-object method `Label#create` to create the label. 16 | - Class `GetLabelsName` encapsulates the endpoint. 17 | - Its method `verdict_call_and_verify_success`: 18 | - Accepts the client, a verdict id, and the label to be fetched. 19 | - Accesses the endpoint. 20 | - Forms the returned data into a `Label` object. 21 | - Performs verifications on the label object. 22 | - Returns the label object. 23 | 24 | ## Log 25 | 26 | [file_source](../logs/test_get_labels_name.xml) 27 | 28 | Notes: 29 | 30 | - Section `ApiClient` shows the endpoint access. 31 | - Section `Evaluation` verifies the returned label. 32 | 33 | [navigation_links] 34 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/Getters.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Getters 4 | 5 | A data class may offer convenience methods for getting resources from the API. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/getters_test.rb) 10 | 11 | Notes: 12 | 13 | - Method Label.get_first returns the first Label object. 14 | - Method Label.get_all returns all Label objects. 15 | 16 | ## Log 17 | 18 | [file_source](../logs/test_getters.xml) 19 | 20 | [navigation_links] 21 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/Meet.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Meet the Log and Clients 4 | 5 | The test framework sets up for the test by offers objects the test will need: 6 | 7 | - An open test log. 8 | - A domain-specific GitHub API client. 9 | - A domain-specific GitHub web UI client. 10 | 11 | ## Example Test 12 | 13 | [file_source](../tests/meet_test.rb) 14 | 15 | Notes: 16 | 17 | - Create a new test class by deriving from `BaseClassForTest`. 18 | - Choose a test method-name that begins with `test`, which tells the test framework that it can be executed at test-time. 19 | - Call methods inherited from the base class: 20 | - `prelude`: yields a `Log` object. 21 | - `with_api_client`: yields an `ApiClient` object. 22 | - `with_ui_client`: yields a `UiClient` object. 23 | 24 | ## Log 25 | 26 | [file_source](../logs/test_meet.xml) 27 | 28 | Notes: 29 | 30 | - At the top of the log is a summary of the counts of verdicts, failures (failed verdicts), and errors (unexpected exceptions). 31 | - Element `test_method` gives the test name, timestamp, and duration. 32 | - The section named `Test` contains all logging from the test itself. 33 | - The last section gives the count of errors (unexpected exceptions). Its verdict expects that count to be 0. 34 | - (Attribute `volatile`, seen in element `verdict`, has to do with the Changes Report, and is of no present interest.) 35 | 36 | [navigation_links] 37 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/MoreToCome.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # More to Come ... 4 | 5 | This tour documentation is under active development. 6 | 7 | Stay tuned.... 8 | 9 | [navigation_links] 10 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/NestedDataEqual.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Verifying Nested Data Objects 4 | 5 | Nested data objects can be verified recursively. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/nested_data_equal_test.rb) 10 | 11 | Notes: 12 | 13 | - Use method `equal?` to test nested data object equality, without logging. 14 | - Use method `verdict_equal?` to test nested object equality, including logging. 15 | - The test gets a known `RateLimit`, then clones it. 16 | - We know that `RateLimit` has nested data objects, so it's _necessary_ to use `deep_clone`, not `clone`. 17 | - In section `These are equal`: 18 | - Method `RateLimit.equal?` tests equality, but does no logging. 19 | - The `fail unless` proves that it worked. 20 | - Method `RateLimit.verdict_equal?` verifies and logs each value in the rate limit. 21 | - In section `These are not equal`: 22 | - One value in the rate limit is modified. The changed value is in a nested object. 23 | - Method `RateLimit.equal?` test equality, but does no logging. 24 | - The `fail if ` proves that it worked. 25 | - Method `RateLimit.verdict_equal?` verifies and logs each value in the rate limit. 26 | 27 | ## Log 28 | 29 | [file_source](../logs/test_nested_data_equal.xml) 30 | 31 | - Each actual value, even one that's in a nested object, is verified in a separate verdict. 32 | - In section `These are equal`, all verdicts pass. 33 | - In section `These are not equal`, one (deeply nested) verdict fails. 34 | 35 | [navigation_links] 36 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/NestedDataLog.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Logging Nested Data Objects 4 | 5 | Nested data objects can log themselves recursively. 6 | 7 | Up to now, the data objects seen have been instances of class `Label`. 8 | 9 | These objects are 'flat' in the sense that the values they store are all scalars: 10 | 11 | - Integers. 12 | - Strings. 13 | - Booleans. 14 | 15 | Now we begin to look at the handling of objects in class `RateLimit`, some of whose values are _not_ scalars, but are actually _other data objects_. In other words, they're nested data objects. 16 | 17 | ## Example Test 18 | 19 | [file_source](../tests/nested_data_log_test.rb) 20 | 21 | Notes: 22 | 23 | - Log nested data objects by calling the `log` method. 24 | 25 | ## Log 26 | 27 | [file_source](../logs/test_nested_data_log.xml) 28 | 29 | Notes: 30 | 31 | - The section named `Fetched rate limit` logs the values in the fetched rate limit. 32 | - The nested objects recursively log themselves into nested log sections. 33 | - The structure of the logged nested objects: 34 | - `RateLimit` 35 | - `RateLimit::Resources` 36 | - `RateLimit::Core` 37 | - `RateLimit::Search` 38 | - `RateLimit::Graphql` 39 | - `RateLimit::Rate` 40 | 41 | [navigation_links] 42 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/NestedDataNew.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Creating Nested Data Objects 4 | 5 | Nested data objects may be created 'from scratch'. That is, all of the nested objects are initialized at once, from simple nested hashes. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/nested_data_new_test.rb) 10 | 11 | Notes: 12 | 13 | - Create a new nested data object by calling method `new` on the data class. 14 | - The nested hashes are formed into nested data objects. 15 | - Note that the instantiated objects exists only here in the test, and not in the Github API itself. 16 | 17 | ## Log 18 | 19 | [file_source](../logs/test_nested_data_new.xml) 20 | 21 | [navigation_links] 22 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/NestedDataNew2.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Creating Nested Data Objects, Part 2 4 | 5 | Nested data objects may be created using already-existing data objects. 6 | 7 | In the previous stop, we saw how to create nested data objects using nested hashes. That is, none of the nested objects existed prior to the single call to `new`. 8 | 9 | Here we see how to create nested data objects using already-existing data objects. 10 | 11 | ## Example Test 12 | 13 | [file_source](../tests/nested_data_new_2_test.rb) 14 | 15 | Notes: 16 | 17 | - Create data objects individually by calling their `new` method. 18 | - Instead of passing hashes as arguments, pass already-existing data objects. 19 | - Note that the instantiated objects exists only here in the test, and not in the Github API itself. 20 | 21 | ## Log 22 | 23 | [file_source](../logs/test_nested_data_new_2.xml) 24 | 25 | [navigation_links] 26 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/NestedDataValid.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Validating Nested Data Objects 4 | 5 | Nested data objects can be validated recursively. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/nested_data_valid_test.rb) 10 | 11 | Notes: 12 | 13 | - Use method `verdict_valid?` to verify that a nested data object's values are valid. 14 | - In section `This is valid`: 15 | - A new `RateLimit` with valid values is instantiated. 16 | - Method `RateLimit.verdict_valid? validates and logs each value in the rate limit. 17 | - In section `This is not valid`: 18 | - A new `RateLimit` with invalid values is instantiated. 19 | - Method `RateLimit.verdict_valid? validates and logs each value in the rate limit. 20 | - The invalid value is not in the test itself, but instead comes from the data class. 21 | 22 | ## Log 23 | 24 | [file_source](../logs/test_nested_data_valid.xml) 25 | 26 | Notes: 27 | 28 | - In section `This is valid`, all verdicts pass. 29 | - When a value has multiple validation verdicts (as all of these do), the verdicts are logged into a separate subsection. 30 | - In section `This is not valid`, three verdicts fail. 31 | 32 | [navigation_links] 33 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/Overview.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Overview 4 | 5 | This tour shows how to use the `RubyTest` framework to test: 6 | 7 | - The GitHub REST API. 8 | - The GitHub web UI. 9 | 10 | The domain-specific GitHub framework is layered onto the underlying RubyTest core framework, and implements: 11 | 12 | - A domain-specific web UI client. 13 | - Page objects, corresponding to web UI pages. 14 | - A domain-specific REST API client. 15 | - Endpoint objects, corresponding to REST API endpoints. 16 | - Data objects, corresponding to REST API resources. 17 | 18 | [navigation_links] 19 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/PatchLabelsName.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # PatchLabelsName Test 4 | 5 | ## Example Test 6 | 7 | [file_source](../tests/patch_labels_name_test.rb) 8 | 9 | This test a test for endpoint `PATCH /labels/:name`, which updates a label. 10 | 11 | Notes: 12 | 13 | - Class `PatchLabelsName` encapsulates the endpoint. 14 | - Its method `verdict_call_and_verify_success`: 15 | - Accepts the client, a verdict id, and the label to be patched. 16 | - Accesses the endpoint. 17 | - Forms the returned data into a `Label` object. 18 | - Performs verifications on the label object. 19 | - Returns the label object. 20 | 21 | ## Log 22 | 23 | [file_source](../logs/test_patch_labels_name.xml) 24 | 25 | Notes: 26 | 27 | - In section `Evaluation`: 28 | - Section `Returned label correct` verifies the returned label. 29 | - Section `Label updated` verifies that the label was updated. 30 | 31 | [navigation_links] 32 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/PostLabels.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # PostLabels Test 4 | 5 | This is a test for endpoint `POST /labels`, which creates a label. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/post_labels_test.rb) 10 | 11 | Notes: 12 | 13 | - The test calls method `Label#delete_if_exist?` before the creation, to avoid collision with an existing label. 14 | - Class `PostLabels` encapsulates endpoint `POST /labels`. 15 | - Its method `PostLabels.verdict_call_and_verify_success`: 16 | - Accepts the client, a verdict id, and the label to be created. 17 | - Accesses the endpoint. 18 | - Forms the returned data into a `Label` object. 19 | - Performs verifications on the label object. 20 | - Returns the label object. 21 | - The test calls method `Label#delete_if_exist?` after the creation, to clean up. 22 | 23 | ## Log 24 | 25 | [file_source](../logs/test_post_labels.xml) 26 | 27 | Notes: 28 | 29 | - Section `ApiClient` shows the endpoint access, including the parameters. 30 | - In section `Evaluation`: 31 | - Section `Returned label correct` verifies the created label: `:name` and `:color`. 32 | - Section `Returned label valid` validates the created label: all fields. 33 | - Section `Label exists` fetches the new label and verifies all its fields. 34 | 35 | [navigation_links] 36 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/RescuedException.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Rescued Exception 4 | 5 | A test (actually, each test section) determines what happens when an unexpected exception is raised. 6 | 7 | It does so by making selected sections 'rescuable'. 8 | 9 | ## Example Test 10 | 11 | [file_source](../tests/rescued_exception_test.rb) 12 | 13 | Notes: 14 | 15 | - Use optional argument `:rescue` in a `log.section` that should rescue an exception. 16 | - Section `Exception rescued` rescues its exception. The test exits the _section_, and any following code _in that section_ is not reached. 17 | - Execution continues at the code following the rescued section. 18 | - Any section, including a nested section, may rescue an exception. (Or not -- it's an independent choice for each section.) 19 | 20 | ## Log 21 | 22 | [file_source](../logs/test_rescued_exception.xml) 23 | 24 | Notes: 25 | 26 | - The logged exception includes its message and backtrace. 27 | - The last section in the log, named `Count of errors (unexpected exceptions)` has a verdict that expects that count to be 0. 28 | - Because of the exception, that count is 1, and the verdict fails. 29 | 30 | [navigation_links] 31 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/ResourceMethods.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Resource Methods 4 | 5 | The data objects in this framework offer methods for accessing the API resources: 6 | 7 | - CRUD methods: 8 | - `create` 9 | - `read` 10 | - `update` 11 | - `delete` 12 | - Getter methods: 13 | - `self.get_all` 14 | - `self.get_first` 15 | - Existence methods: 16 | - `exist?` 17 | - `verdict_assert_exist?` 18 | - `verdict_refute_exist?` 19 | - `delete_if_exist?` 20 | 21 | [navigation_links] 22 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/RestApi.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Rest API Test 4 | 5 | Here's a simple REST API test, one that tests CRUD methods (create, read, update, delete) to operate on `Label` objects. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/rest_api_test.rb) 10 | 11 | Notes: 12 | 13 | - The test first defines: 14 | - `label_to_create`, a variable to house data for creating a label. 15 | - `label_created`, a variable that will house the data for a created label, including new values for `:id` and `:url`. 16 | - `label_updated`, a variable that will house the data for an updated label, including new values for `:id` and `:url`. 17 | - In section `Create`: 18 | - `create!`, a method that first deletes the label if it exists, then creates the label. (Method `:create`, without the exclamation point, would fail if the label exists.) 19 | - `:create_return_correct` and `:created_correctly`, symbols that are _verdict identifiers_. A verdict identifier appears in each verdict method call. 20 | - `Label.verdict_equal?`, a method that verifies that the returned label data is correct. 21 | - `label_returned.verdict_read_and_verify?`, a method that verifies that the label was correctly created in GitHub. 22 | - In section `Read`: 23 | - `label_created.read` a method that reads a label, which is stored into variable `label_read`. 24 | - `Label.verdict_equal?`, a method that verifies that the returned label data is correct. 25 | - In section `Update`: 26 | - `label_created.perturb`, a method that perturbs data from `label_created`, which is stored into `label_source`. 27 | - The web UI does not expose the label's url or default, so these are set to nil, and will be ignored. 28 | - `Label.verdict_equal?`, a method that verifies that the returned label data is correct. 29 | - `label_returned.verdict_read_and_verify?`, a method that verifies that the label was correctly updated in GitHub. 30 | - In section `Delete`: 31 | - `label_updated.delete`, a method that deletes a label. 32 | - `log.verdict_assert_nil?`, a method that verifies that the return value is `nil`. 33 | - `label_created.verdict_refute_exist?`, a method that verifies that the label was deleted in GitHub. 34 | 35 | [navigation_links] 36 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/Sections.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Test Sections and Nesting 4 | 5 | A test and its log have many duties, but one of the first is their duty to tell a story. 6 | 7 | Reading the test code or the log, you should be able to understand how the test is organized, and what it's trying to do. Nested sections help with that. 8 | 9 | ## Example Test 10 | 11 | [file_source](../tests/sections_test.rb) 12 | 13 | Notes: 14 | 15 | - Use nested sections to organize test code. 16 | - Use optional argument `:timestamp` to add a timestamp to the logged section. 17 | - Use optional argument `:duration` to add an execution duration to the logged section. 18 | - (This test does not use the client, and so by Ruby convention uses the variable name `_` instead of variable name `client`.) 19 | 20 | ## Log 21 | 22 | [file_source](../logs/test_sections.xml) 23 | 24 | Notes: 25 | 26 | - The sections in the test are propagated to the log, so that the log is organized the same way as the test. 27 | 28 | [navigation_links] 29 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/UnrescuedException.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Unrescued Exception 4 | 5 | For an exception that is not rescued via `:rescue`, the log itself acts, by rescuing and logging the exception and its details. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/unrescued_exception_test.rb) 10 | 11 | Notes: 12 | 13 | - Section `Exception not rescued` does not rescue its exception. The test exits entirely, and any following code in the test is not reached. 14 | 15 | ## Log 16 | 17 | [file_source](../logs/test_unrescued_exception.xml) 18 | 19 | Notes: 20 | 21 | - The unrescued exception actually is rescued after all, by the log itself. And of course it's logged. 22 | 23 | [navigation_links] 24 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/Verdicts.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Verdicts 4 | 5 | Verifications are the heart of a test -- indeed, its purpose. 6 | 7 | The framework offers robust support for logging verifications, via its `verdict` methods. 8 | 9 | ## Example Test 10 | 11 | [file_source](../tests/verdicts_test.rb) 12 | 13 | Notes: 14 | 15 | - Use `verdict` methods for verifications. 16 | - A call to a verdict method will have: 17 | - A verdict identifier, which must be unique within the test. 18 | - Other parameters, as appropriate to the particular method. 19 | - A message string. 20 | - An `assert` verdict expects something to be truthy (not `false` or `nil`). 21 | - A `refute` verdict expects something to be `false` or `nil`. 22 | - There are many other verdict methods, some of which will be described later in this tour. 23 | - A verdict method returns a boolean value, and therefore follows the Ruby convention of ending the method name with `?`. 24 | 25 | 26 | ## Log 27 | 28 | [file_source](../logs/test_verdicts.xml) 29 | 30 | Notes: 31 | 32 | - Every verdict is fully logged. 33 | - A failed verdict logs its backtrace. 34 | 35 | [navigation_links] 36 | -------------------------------------------------------------------------------- /examples/github/tester_tour/md_templates/WebUi.txt: -------------------------------------------------------------------------------- 1 | [navigation_links] 2 | 3 | # Web UI Test 4 | 5 | Here's a simple web UI test, one that tests CRUD methods (create, read, update, delete) to operate on `Label` objects. 6 | 7 | ## Example Test 8 | 9 | [file_source](../tests/web_ui_test.rb) 10 | 11 | Notes: 12 | 13 | - The test first: 14 | - Declares variable `label_to_create` to house data for creating a label. 15 | - It then calls methods: 16 | - `ui_client.login` to log into the web UI. 17 | - `LabelsPage.new(ui_client).visit` to visit the labels page. 18 | - In section `Create` the test calls methods: 19 | - `Label.provisioned` to acquire a provisioned label object, ready for creation. 20 | - `labels_page.create_label!` to first delete the label if it exists, then creates the label. (Method `:create_label`, without the exclamation point, would fail if the label exists.) 21 | - `labels_page.wait_for_label` to wait for the created label to appear. 22 | - `labels_page.verdict_assert_exist?` to verify that the new label exists. Symbol `:label_created` is a verdict identifier. 23 | - In section `Read` the test call methods: 24 | - `labels_page.read_label` to read the new label in the UI. 25 | - `Label.verdict_equal?` to verify that the label read is correct. Symbol `:label_read` is a verdict identifier. 26 | - In section `Update` the test calls methods: 27 | - `label_to_create.perturb` to modify the label to be updated. 28 | - `labels_page.update_label` to update the label. 29 | - `labels_page.wait_for_label` to wait for the updated label to appear. 30 | - `labels_page.verdict_assert_exist?` to verify that the updated label appears on the page. Symbol `:label_updated` is a verdict identifer. 31 | - In section `Delete` the test calls methods: 32 | - `labels_page.delete_label` to delete the label. 33 | - `ui_client.browser.refresh` to refresh the page. 34 | - `labels_page.verdict_refute_exist?` to verify that the label no longer appears on the page. Symbol `:label_deleted` is a verdict identifier. 35 | 36 | [navigation_links] 37 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/crud_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | class CrudTest < BaseClassForTest 6 | 7 | def test_crud 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | label_created = nil 14 | label_read = nil 15 | label_updated = nil 16 | 17 | log.section('Create') do 18 | label_to_create = Label.provisioned 19 | label_to_create.log(log, 'Label to create') 20 | log.section('Delete if exists, to avoid collision') do 21 | deleted = label_to_create.delete_if_exist?(api_client) 22 | log.comment(format('Deleted? %s.', deleted ? 'Yes' : 'No')) 23 | end 24 | if ENV['NO_CRUD'] 25 | # Here's how we create a label via the endpoint. 26 | require_relative '../../api/endpoints/labels/post_labels' 27 | label_created = PostLabels.call(api_client, label_to_create) 28 | else 29 | # And here's how via the CRUD method. 30 | label_created = label_to_create.create(api_client) 31 | end 32 | label_created.log(log, 'Label created') 33 | end 34 | 35 | log.section('Read') do 36 | label_to_read = label_created 37 | label_to_read.log(log, 'Label to read') 38 | if ENV['NO_CRUD'] 39 | # Here's how we read a label via the endpoint. 40 | require_relative '../../api/endpoints/labels/get_labels_name' 41 | label_read = GetLabelsName.call(api_client, label_to_read) 42 | else 43 | # And here's how via the CRUD method. 44 | label_read = label_to_read.read(api_client) 45 | end 46 | label_read.log(log, 'Label read') 47 | end 48 | 49 | log.section('Update') do 50 | label_target = label_read 51 | label_source = label_target.perturb 52 | label_target.log(log, 'Label to update') 53 | if ENV['NO_CRUD'] 54 | # Here's how we update a label via the endpoint. 55 | require_relative '../../api/endpoints/labels/patch_labels_name' 56 | label_updated = PatchLabelsName.call(api_client, label_target, label_source) 57 | else 58 | # And here's how via the CRUD method. 59 | label_updated = label_target.update(api_client, label_source) 60 | end 61 | label_updated.log(log, 'Label updated') 62 | end 63 | 64 | log.section('Delete') do 65 | label_to_delete = label_updated 66 | if ENV['NO_CRUD'] 67 | # Here's how we delete a label via the endpoint. 68 | require_relative '../../api/endpoints/labels/delete_labels_name' 69 | DeleteLabelsName.call(api_client, label_to_delete) 70 | else 71 | # And here's how via the CRUD method. 72 | label_to_delete.delete(api_client) 73 | end 74 | end 75 | end 76 | 77 | end 78 | 79 | end 80 | 81 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/delete_labels_name_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../api/endpoints/labels/delete_labels_name' 4 | 5 | class DeleteLabelsNameTest < BaseClassForTest 6 | 7 | def test_delete_labels_name 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | log.section('Test DeleteLabelsName') do 14 | label_to_create = nil 15 | label_to_delete = nil 16 | log.section('Create the label to be deleted') do 17 | label_to_create = Label.provisioned 18 | label_to_create.delete_if_exist?(api_client) 19 | label_to_delete = label_to_create.create(api_client) 20 | end 21 | log.section('Test deleting the created label') do 22 | DeleteLabelsName.verdict_call_and_verify_success(api_client, :delete_label, label_to_delete) 23 | end 24 | log.section('Clean up') do 25 | label_to_create.delete_if_exist?(api_client) 26 | end 27 | end 28 | 29 | end 30 | 31 | end 32 | 33 | end 34 | 35 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/existence_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | class ExistenceTest < BaseClassForTest 6 | 7 | def test_existence 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | label_to_create = Label.new( 14 | :name => 'test_label', 15 | :color => '000000', 16 | :default => false, 17 | ) 18 | 19 | existing_label = nil 20 | log.section('Create a label') do 21 | label_to_create.delete_if_exist?(api_client) 22 | existing_label = label_to_create.create(api_client) 23 | end 24 | 25 | log.section('Determine existence') do 26 | exist = existing_label.exist?(api_client) 27 | comment = format('Label exists? %s', exist) 28 | log.comment(comment) 29 | end 30 | 31 | log.section('Assert existence in verdict') do 32 | existing_label.verdict_assert_exist?(api_client, log, :assert_exist) 33 | end 34 | 35 | log.section('Delete if exist') do 36 | deleted = existing_label.delete_if_exist?(api_client) 37 | comment = format('Label deleted? %s', deleted) 38 | log.comment(comment) 39 | end 40 | 41 | log.section('Refute existence in verdict') do 42 | existing_label.verdict_refute_exist?(api_client, log, :refute_exist) 43 | end 44 | 45 | end 46 | 47 | end 48 | 49 | end 50 | 51 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/flat_data_equal_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | class FlatDataEqualTest < BaseClassForTest 6 | 7 | def test_flat_data_equal 8 | prelude do |log| 9 | with_api_client(log) do |api_client| 10 | label_0 = nil 11 | log.section('Fetch an instance of Label') do 12 | log.section('Fetch a label') do 13 | label_0 = Label.get_first(api_client) 14 | end 15 | end 16 | label_1 = Label.deep_clone(label_0) 17 | log.section('These are equal') do 18 | fail unless Label.equal?(label_0, label_1) 19 | Label.verdict_equal?(log, :label_equal, label_0, label_1) 20 | end 21 | log.section('These are not equal') do 22 | label_1.perturb! 23 | fail if Label.equal?(label_0, label_1) 24 | Label.verdict_equal?(log, :label_not_equal, label_0, label_1) 25 | end 26 | end 27 | end 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/flat_data_log_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | class FlatDataLogTest < BaseClassForTest 6 | 7 | def test_flat_data_log 8 | prelude do |log| 9 | with_api_client(log) do |api_client| 10 | log.section('Fetch and log an instance of Label') do 11 | label = nil 12 | log.section('Fetch a label') do 13 | label = Label.get_first(api_client) 14 | end 15 | label.log(log, 'Fetched label') 16 | end 17 | end 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/flat_data_new_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | class FlatDataNewTest < BaseClassForTest 6 | 7 | def test_flat_data_new 8 | prelude do |log| 9 | log.section('Instantiate and log an instance of Label') do 10 | label = Label.new( 11 | :id => 710733210, 12 | :url => 'https://api.github.com/repos/BurdetteLamar/CrashDummy/labels/enhancement', 13 | :name => 'enhancement', 14 | :color => '84b6eb', 15 | :default => true, 16 | ) 17 | log.section('Instantiated label') do 18 | label.log(log) 19 | end 20 | end 21 | end 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/flat_data_valid_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | class FlatDataValidTest < BaseClassForTest 6 | 7 | def test_flat_data_valid 8 | prelude do |log| 9 | with_api_client(log) do |api_client| 10 | label = Label.get_first(api_client) 11 | log.section('This is valid') do 12 | label.verdict_valid?(log, :label_valid) 13 | end 14 | log.section('This is not valid') do 15 | label.color = Label.invalid_value_for(:color) 16 | label.verdict_valid?(log, :label_not_valid) 17 | end 18 | end 19 | end 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/get_labels_name_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../api/endpoints/labels/get_labels_name' 4 | 5 | class GetLabelsNameTest < BaseClassForTest 6 | 7 | def test_get_labels_name 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | log.section('Test GetLabelsName') do 14 | label_to_fetch = nil 15 | log.section('Create the label to be fetched') do 16 | label_to_create = Label.new( 17 | :id => nil, 18 | :url => nil, 19 | :name => 'test_label', 20 | :color => '000000', 21 | :default => false, 22 | ) 23 | label_to_create.delete_if_exist?(api_client) 24 | label_to_fetch = label_to_create.create(api_client) 25 | end 26 | log.section('Test fetching the created label') do 27 | GetLabelsName.verdict_call_and_verify_success(api_client, :get_label, label_to_fetch) 28 | end 29 | log.section('Clean up') do 30 | label_to_fetch.delete_if_exist?(api_client) 31 | end 32 | end 33 | 34 | end 35 | 36 | end 37 | 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/get_labels_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../api/endpoints/labels/get_labels' 4 | 5 | class LabelBasicsTest < BaseClassForTest 6 | 7 | def test_get_labels 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | log.section('Test GetLabels') do 14 | GetLabels.verdict_call_and_verify_success(api_client, :get_labels) 15 | end 16 | 17 | end 18 | 19 | end 20 | 21 | end 22 | 23 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/getters_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | class GettersTest < BaseClassForTest 6 | 7 | def test_getters 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | log.section('Get the first label') do 14 | label = Label.get_first(api_client) 15 | label.log(log) 16 | end 17 | 18 | log.section('Get all labels') do 19 | labels = Label.get_all(api_client) 20 | labels.each do |label| 21 | label.log(log) 22 | end 23 | end 24 | 25 | end 26 | 27 | end 28 | 29 | end 30 | 31 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/meet_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | class MeetTest < BaseClassForTest 4 | 5 | def test_meet 6 | prelude do |log| 7 | log.comment('Method prelude yields an instance of %s, for logging the test.' % log.class.name) 8 | with_api_client(log) do |api_client| 9 | log.comment('Method with_api_client yields an instance of %s, for accessing the GitHub API' % api_client.class.name) 10 | end 11 | with_ui_client(log) do |ui_client| 12 | log.comment('Method with_ui_client yields an instance of %s, for accessing the GitHub UI.' % ui_client.class.name) 13 | end 14 | end 15 | end 16 | 17 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/nested_data_equal_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/rate_limit' 4 | 5 | class NestedDataEqualTest < BaseClassForTest 6 | 7 | def test_nested_data_equal 8 | prelude do |log| 9 | with_api_client(log) do |api_client| 10 | rate_limit_0 = RateLimit.get(api_client) 11 | rate_limit_1 = RateLimit.deep_clone(rate_limit_0) 12 | log.section('These are equal') do 13 | fail unless RateLimit.equal?(rate_limit_0, rate_limit_1) 14 | RateLimit.verdict_equal?(log, :rate_limits_equal, rate_limit_0, rate_limit_1, 'Using RateLimit.verdict_equal?') 15 | end 16 | log.section('These are not equal') do 17 | rate_limit_1.resources.core.limit = RateLimit::Core_.invalid_value_for(:limit) 18 | fail if RateLimit.equal?(rate_limit_0, rate_limit_1) 19 | RateLimit.verdict_equal?(log, :rate_limits_not_equal, rate_limit_0, rate_limit_1, 'Using RateLimit.verdict_equal?') 20 | end 21 | end 22 | end 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/nested_data_log_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/rate_limit' 4 | 5 | class NestedDataLogTest < BaseClassForTest 6 | 7 | def test_nested_data_log 8 | prelude do |log| 9 | with_api_client(log) do |api_client| 10 | log.section('Fetch and log a rate limit') do 11 | rate_limit = nil 12 | log.section('Fetch rate limit') do 13 | rate_limit = RateLimit.get(api_client) 14 | end 15 | rate_limit.log(log, 'Fetched rate limit') 16 | end 17 | end 18 | end 19 | end 20 | 21 | end 22 | 23 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/nested_data_new_2_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/rate_limit' 4 | 5 | class NestedDataNewTest < BaseClassForTest 6 | 7 | def test_nested_data_new_2 8 | prelude do |log| 9 | log.section('Create and log nested data objects') do 10 | core = RateLimit::Core_.new( 11 | :limit => 5000, 12 | :remaining => 4984, 13 | :reset => 1507676679 14 | ) 15 | search = RateLimit::Search.new( 16 | :limit => 30, 17 | :remaining => 30, 18 | :reset => 1507673695, 19 | ) 20 | graphql = RateLimit::Graphql.new( 21 | :limit => 5000, 22 | :remaining => 5000, 23 | :reset => 1507677235, 24 | ) 25 | resources = RateLimit::Resources.new( 26 | :core => core, 27 | :search => search, 28 | :graphql => graphql, 29 | ) 30 | rate = RateLimit::Rate.new( 31 | :limit => 5000, 32 | :remaining => 4984, 33 | :reset => 1507676679, 34 | ) 35 | rate_limit = RateLimit.new( 36 | { 37 | :resources => resources, 38 | :rate => rate, 39 | } 40 | ) 41 | rate_limit.log(log) 42 | end 43 | end 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/nested_data_new_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/rate_limit' 4 | 5 | class NestedDataNewTest < BaseClassForTest 6 | 7 | def test_nested_data_new 8 | prelude do |log| 9 | log.section('Create and log nested data objects') do 10 | rate_limit = RateLimit.new( 11 | { 12 | :resources => { 13 | :core => { 14 | :limit => 5000, 15 | :remaining => 4984, 16 | :reset => 1507676679, 17 | }, 18 | :search => { 19 | :limit => 30, 20 | :remaining => 30, 21 | :reset => 1507673695, 22 | }, 23 | :graphql => { 24 | :limit => 5000, 25 | :remaining => 5000, 26 | :reset => 1507677235, 27 | } 28 | }, 29 | :rate => { 30 | :limit => 5000, 31 | :remaining => 4984, 32 | :reset => 1507676679, 33 | } 34 | } 35 | ) 36 | rate_limit.log(log) 37 | end 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/nested_data_valid_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/rate_limit' 4 | 5 | class NestedDataValidTest < BaseClassForTest 6 | 7 | def test_nested_data_valid 8 | prelude do |log| 9 | with_api_client(log) do |api_client| 10 | rate_limit = RateLimit.get(api_client) 11 | log.section('This is valid') do 12 | rate_limit.verdict_valid?(log, :rate_limit_valid) 13 | end 14 | log.section('This is not valid') do 15 | rate_limit.resources.core.reset = RateLimit::Core_.invalid_value_for(:reset) 16 | rate_limit.verdict_valid?(log, :rate_limit_not_valid) 17 | end 18 | end 19 | end 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/patch_labels_name_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../api/endpoints/labels/patch_labels_name' 4 | 5 | class PatchLabelsNameTest < BaseClassForTest 6 | 7 | def test_patch_labels_name 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | log.section('Test PatchLabelsName') do 14 | label_to_create = Label.new( 15 | :id => nil, 16 | :url => nil, 17 | :name => 'test_label', 18 | :color => '000000', 19 | :default => false, 20 | ) 21 | label_target = label_to_create.create!(api_client) 22 | label_source = label_target.perturb 23 | label_source.url = nil 24 | label_source.default = nil 25 | PatchLabelsName.verdict_call_and_verify_success(api_client, :patch_label, label_target, label_source) 26 | label_source.delete_if_exist?(api_client) 27 | end 28 | 29 | end 30 | 31 | end 32 | 33 | end 34 | 35 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/post_labels_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../api/endpoints/labels/post_labels' 4 | 5 | class PostLabelsTest < BaseClassForTest 6 | 7 | def test_post_labels 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | label_to_create = Label.new( 14 | :id => nil, 15 | :url => nil, 16 | :name => 'test_label', 17 | :color => '000000', 18 | :default => false, 19 | ) 20 | log.section('Test PostLabels') do 21 | label_to_create.delete_if_exist?(api_client) 22 | PostLabels.verdict_call_and_verify_success(api_client, :post_label, label_to_create) 23 | end 24 | log.section('Clean up') do 25 | label_to_create.delete_if_exist?(api_client) 26 | end 27 | 28 | end 29 | 30 | end 31 | 32 | end 33 | 34 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/rescued_exception_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | class RescuedExceptionTest < BaseClassForTest 4 | 5 | def test_rescued_exception 6 | prelude do |log, _| 7 | log.section('Rescued exception', :rescue) do 8 | numerator = 1 9 | denominator = 0 10 | quotient = numerator / denominator 11 | log.section('This section is not reached because of the exception') do 12 | log.verdict_assert?(:not_reached, quotient) 13 | end 14 | end 15 | log.section('This section is reached because the above exception was rescued') do 16 | log.verdict_assert?(:reached, true) 17 | end 18 | end 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/rest_api_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | class RestApiTest < BaseClassForTest 6 | 7 | def test_rest_api 8 | 9 | prelude do |log| 10 | 11 | with_api_client(log) do |api_client| 12 | 13 | label_to_create = Label.provisioned 14 | label_created = nil 15 | label_updated = nil 16 | 17 | log.section('Create') do 18 | label_created = label_to_create.create!(api_client) 19 | Label.verdict_equal?(log, :create_return_correct, label_to_create, label_created) 20 | label_created.verdict_read_and_verify?(api_client, log, :created_correctly) 21 | end 22 | 23 | log.section('Read') do 24 | label_read = label_created.read(api_client) 25 | Label.verdict_equal?(log, :read_correctly, label_created, label_read) 26 | end 27 | 28 | log.section('Update') do 29 | label_source = label_created.perturb 30 | label_source.url = nil 31 | label_source.default = nil 32 | label_updated = label_created.update!(api_client, label_source) 33 | Label.verdict_equal?(log, :update_return_correct, label_source, label_updated) 34 | label_updated.verdict_read_and_verify?(api_client, log, :updated_correctly) 35 | end 36 | 37 | log.section('Delete') do 38 | return_value = label_updated.delete(api_client) 39 | log.verdict_assert_nil?(:delete_return_correct, return_value) 40 | label_created.verdict_refute_exist?(api_client, log, :deleted_correctly) 41 | end 42 | 43 | end 44 | 45 | end 46 | 47 | end 48 | 49 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/sections_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | class SectionsTest < BaseClassForTest 4 | 5 | def test_sections 6 | prelude do |log, _| 7 | log.section('First outer section') do 8 | log.section('First inner section') do 9 | log.comment('Some test code can go here') 10 | end 11 | log.section('Second inner section') do 12 | log.comment('Some test code can go here') 13 | end 14 | end 15 | log.section('Second outer section') do 16 | log.comment('Some test code can go here') 17 | end 18 | log.section('Section with timestamp', :timestamp) do 19 | log.comment('Some test code can go here') 20 | end 21 | log.section('Section with timestamp', :duration) do 22 | log.comment('Some test code can go here') 23 | sleep 1 24 | end 25 | log.section('Section with timestamp and duration', :timestamp, :duration) do 26 | log.comment('Some test code can go here') 27 | sleep 2 28 | end 29 | log.section('Order does not matter', :duration, :timestamp) do 30 | log.comment('Some test code can go here') 31 | sleep 3 32 | end 33 | end 34 | end 35 | 36 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/unrescued_exception_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | class UnrescuedExceptionTest < BaseClassForTest 4 | 5 | def test_unrescued_exception 6 | prelude do |log, _| 7 | log.section('Unrescued exception') do 8 | numerator = 1 9 | denominator = 0 10 | quotient = numerator / denominator 11 | log.section('This section is not reached because of the exception') do 12 | log.verdict_assert?(:not_reached_in_section, quotient) 13 | end 14 | end 15 | log.section('This section is not reached because of the unrescued exception') do 16 | log.verdict_assert?(:not_reached_in_test, true) 17 | end 18 | end 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/verdicts_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | class VerdictsTest < BaseClassForTest 4 | 5 | def test_verdicts 6 | prelude do |log, _| 7 | # Using extra variables in these verdicts, to make usage clear. 8 | log.section('These verdicts should pass') do 9 | log.section('An assert verdict that should pass') do 10 | log.verdict_assert?( 11 | verdict_id = :assert_should_pass, 12 | actual = true, 13 | ) 14 | end 15 | log.section('A refute verdict that should pass') do 16 | log.verdict_refute?( 17 | verdict_id = :refute_should_pass, 18 | actual = false, 19 | ) 20 | end 21 | end 22 | log.section('These verdicts should fail') do 23 | log.section('An assert verdict that should fail') do 24 | log.verdict_assert?( 25 | verdict_id = :assert_should_fail, 26 | actual = false, 27 | ) 28 | end 29 | log.section('A refute verdict that should fail') do 30 | log.verdict_refute?( 31 | verdict_id = :refute_should_fail, 32 | actual = true, 33 | ) 34 | end 35 | end 36 | end 37 | end 38 | 39 | end -------------------------------------------------------------------------------- /examples/github/tester_tour/tests/web_ui_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/base_class_for_test' 2 | 3 | require_relative '../../data/label' 4 | 5 | require_relative '../../ui/pages/labels_page' 6 | 7 | class WebUiTest < BaseClassForTest 8 | 9 | def test_web_ui 10 | 11 | prelude do |log| 12 | 13 | with_ui_client(log) do |ui_client| 14 | 15 | label_to_create = nil 16 | 17 | ui_client.login 18 | labels_page = LabelsPage.new(ui_client).visit 19 | 20 | log.section('Create label') do 21 | label_to_create = Label.provisioned 22 | # The value for field :default is not available in the web UI. 23 | label_to_create.default = nil 24 | labels_page.create_label!(label_to_create) 25 | labels_page.wait_for_label(label_to_create) 26 | labels_page.verdict_assert_exist?(log, :label_created, label_to_create) 27 | end 28 | 29 | log.section('Read label') do 30 | label_to_read = label_to_create 31 | label_read = labels_page.read_label(label_to_read) 32 | Label.verdict_equal?(log, :label_read, label_to_read, label_read) 33 | end 34 | 35 | label_source = nil 36 | log.section('Update label') do 37 | label_source = label_to_create.perturb 38 | # The value for field :default is not available in the web UI. 39 | label_source.default = nil 40 | labels_page.update_label(label_to_create, label_source) 41 | labels_page.wait_for_label(label_source) 42 | labels_page.verdict_assert_exist?(log, :label_updated, label_source) 43 | end 44 | 45 | log.section('Delete label') do 46 | label_to_delete = label_source 47 | labels_page.delete_label(label_to_delete) 48 | ui_client.browser.refresh 49 | labels_page.verdict_refute_exist?(log, :label_deleted, label_to_delete) 50 | end 51 | 52 | end 53 | end 54 | end 55 | 56 | end -------------------------------------------------------------------------------- /examples/github/ui/base_classes/base_class_for_page.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../../lib/base_classes/base_class' 2 | 3 | require_relative '../../../../lib/log/log' 4 | 5 | require_relative '../ui_client' 6 | 7 | class BaseClassForPage < BaseClass 8 | 9 | attr_accessor :ui_client, :url, :locators 10 | 11 | Contract UiClient, String, HashOf[Symbol, Array[Symbol, HashOf[Symbol, Or[Regexp, String, ArrayOf[String]]]]] => nil 12 | def initialize(ui_client, relative_url, locators) 13 | self.ui_client = ui_client 14 | self.url = File.join('https://github.com', relative_url) 15 | self.locators = locators 16 | nil 17 | end 18 | 19 | Contract nil => self 20 | def visit 21 | ui_client.browser.goto(url) 22 | self 23 | end 24 | 25 | Contract Symbol => Or[Watir::Element, Watir::ElementCollection] 26 | def locate(locator_name) 27 | ui_client.browser.send(*locators[locator_name]) 28 | end 29 | 30 | end -------------------------------------------------------------------------------- /examples/github/ui/pages/home_page.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_page' 2 | 3 | class HomePage < BaseClassForPage 4 | 5 | LOCATORS = {} 6 | 7 | Contract UiClient => nil 8 | def initialize(ui_client) 9 | relative_url = '' 10 | super(ui_client, relative_url, LOCATORS) 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /examples/github/ui/pages/login_page.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_page' 2 | 3 | require_relative 'home_page' 4 | 5 | class LoginPage < BaseClassForPage 6 | 7 | LOCATORS = { 8 | :username => [:text_field, {:id => 'login_field'}], 9 | :password => [:text_field, {:id => 'password'}], 10 | :submit => [:button, {:name => 'commit'}], 11 | } 12 | 13 | Contract UiClient => nil 14 | def initialize(ui_client) 15 | relative_url = 'login' 16 | super(ui_client, relative_url, LOCATORS) 17 | end 18 | 19 | Contract String, String => HomePage 20 | def login(username, password) 21 | locate(:username).set(username) 22 | locate(:password).set(password) 23 | locate(:submit).click 24 | HomePage.new(ui_client) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /examples/github/ui/pages/repo_page.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_page' 2 | 3 | class RepoPage < BaseClassForPage 4 | 5 | LOCATORS = {} 6 | 7 | Contract UiClient => nil 8 | def initialize(ui_client) 9 | relative_url = File.join(ui_client.username, ui_client.repo_name) 10 | super(ui_client, relative_url, LOCATORS) 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /examples/github/ui/ui_client.rb: -------------------------------------------------------------------------------- 1 | require 'watir' 2 | 3 | require_relative '../../../lib/log/log' 4 | 5 | require_relative '../../../lib/base_classes/base_class' 6 | 7 | class UiClient < BaseClass 8 | 9 | attr_accessor :log, :browser, :username, :password, :repo_name 10 | 11 | # Instantiate a client for the caller's block. 12 | Contract Log, String, String, String, Proc => nil 13 | def self.with(log, username, password, repo_name) 14 | raise 'No block given' unless (block_given?) 15 | ui_client = self.new(log, username, password, repo_name, im_ok_youre_not_ok = true) 16 | yield ui_client 17 | nil 18 | end 19 | 20 | Contract Log, String, String, String, Maybe[Bool] => nil 21 | def initialize(log, username, password, repo_name, im_ok_youre_not_ok = false) 22 | raise RuntimeError('Call method with, not new') unless im_ok_youre_not_ok 23 | self.log = log 24 | self.username = username 25 | self.password = password 26 | self.repo_name = repo_name 27 | nil 28 | end 29 | 30 | require_relative 'pages/home_page' 31 | Contract nil => HomePage 32 | def login 33 | self.browser = Watir::Browser.new 34 | browser.window.maximize 35 | require_relative 'pages/login_page' 36 | page = LoginPage.new(self).visit 37 | page.login(username, password) 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /examples/github_api/Moved.md: -------------------------------------------------------------------------------- 1 | # Moved 2 | 3 | The GitHub example code has moved: 4 | 5 | - [github](../github) 6 | 7 | The move is in preparation for adding GitHub UI example testing. 8 | 9 | -------------------------------------------------------------------------------- /examples/github_api/TesterTour.md: -------------------------------------------------------------------------------- 1 | # Tester Tour 2 | 3 | The tour has moved: 4 | 5 | - [Tester Tour](../github/TesterTour.md#tester-tour) 6 | 7 | The move is in preparation for adding GitHub UI example testing. 8 | 9 | -------------------------------------------------------------------------------- /examples/log/Log.md: -------------------------------------------------------------------------------- 1 | # Log Examples 2 | 3 | ## Code Examples 4 | 5 | Source code examples of logging may be found here: 6 | 7 | - [log_examples.rb](./log_examples.rb) 8 | 9 | ## Log File Examples 10 | 11 | Examples of log files may be generated by executing command: 12 | 13 | - rake examples:log 14 | 15 | After that execution, examples of the generated log files may be found in diretory output. 16 | -------------------------------------------------------------------------------- /examples/log/Rakefile: -------------------------------------------------------------------------------- 1 | rakefile_dir_path = File.dirname(__FILE__) 2 | 3 | desc 'Run examples' 4 | task :examples do 5 | test_file_path = File.join( 6 | rakefile_dir_path, 7 | 'log_examples.rb', 8 | ) 9 | command = format('ruby %s', test_file_path) 10 | system(command) 11 | end 12 | 13 | # namespace :report do 14 | # 15 | # desc 'Create changes report' 16 | # task :changes do 17 | # report_file_path = File.join( 18 | # rakefile_dir_path, 19 | # 'changes_report.html', 20 | # ) 21 | # require_relative '../../lib/changes_report' 22 | # ChangesReport.create_report('log', report_file_path) 23 | # end 24 | # 25 | # end 26 | -------------------------------------------------------------------------------- /examples/rest_api/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | include REXML 3 | 4 | require_relative '../../lib/helpers/markdown_helper' 5 | require_relative '../../lib/helpers/string_helper' 6 | require_relative '../../lib/helpers/test_helper' 7 | 8 | desc 'Run examples' 9 | task :examples do 10 | rakefile_dir_path = File.dirname(__FILE__) 11 | test_dir_path = File.join( 12 | rakefile_dir_path, 13 | 'tests', 14 | ) 15 | TestHelper.create_app_log_dir('rest_api') 16 | %w/ 17 | albums 18 | comments 19 | photos 20 | posts 21 | todos 22 | users 23 | /.each do |name| 24 | test_file_name = format('%s_test.rb', name) 25 | test_file_path = File.join( 26 | test_dir_path, 27 | test_file_name, 28 | ) 29 | command = format('ruby %s', test_file_path) 30 | system(command) 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /examples/rest_api/base_classes/base_class_for_endpoint.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/base_classes/base_class' 2 | require_relative '../../../lib/helpers/object_helper' 3 | 4 | require_relative '../example_rest_client' 5 | 6 | class BaseClassForEndpoint < BaseClass 7 | 8 | Contract ExampleRestClient, Args[Any] => Any 9 | def self.call(client, *args) 10 | # Derived class must define self.call_and_return_payload. 11 | return_val, _ = self.call_and_return_payload(client, *args) 12 | return_val 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /examples/rest_api/base_classes/base_class_for_resource.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/base_classes/base_class_for_data' 2 | require_relative '../../../lib/helpers/object_helper' 3 | 4 | class BaseClassForResource < BaseClassForData 5 | 6 | Contract ExampleRestClient, self => Bool 7 | # RubyMine cannot find RestClient::NotFound 8 | # noinspection RubyResolve 9 | def self.exist?(client, object) 10 | begin 11 | self.read(client, object) 12 | return true 13 | rescue RestClient::NotFound 14 | return false 15 | end 16 | end 17 | 18 | Contract ExampleRestClient, Log, VERDICT_ID, self => Bool 19 | def self.verdict_exist?(client, log, verdict_id, object) 20 | log.va?(verdict_id, self.exist?(client, object), message: self.name + ' exists') 21 | end 22 | 23 | Contract ExampleRestClient, Log, VERDICT_ID, self => Bool 24 | def self.verdict_not_exist?(client, log, verdict_id, object) 25 | log.vr?(verdict_id, self.exist?(client, object), message: self.name + ' not exist') 26 | end 27 | 28 | # Convenience CRUD (create, read, update, delete), etc. 29 | # Caller should not have to know how to do the CRUD. 30 | 31 | Contract ExampleRestClient, self => self 32 | def self.create(client, object) 33 | token = self.name.downcase 34 | require_relative '../endpoints/%ss/post_%ss' % [token, token] 35 | endpoint_class_name = 'Post%ss' % self.name 36 | klass = ObjectHelper.get_class_for_class_name(endpoint_class_name) 37 | klass.call(client, object) 38 | end 39 | 40 | Contract ExampleRestClient, self => self 41 | def self.read(client, object) 42 | token = self.name.downcase 43 | require_relative '../endpoints/%ss/get_%ss_id' % [token, token] 44 | endpoint_class_name = 'Get%ssId' % self.name 45 | klass = ObjectHelper.get_class_for_class_name(endpoint_class_name) 46 | klass.call(client, object) 47 | end 48 | 49 | Contract ExampleRestClient, self => self 50 | def self.update(client, object) 51 | token = self.name.downcase 52 | require_relative '../endpoints/%ss/put_%ss_id' % [token, token] 53 | endpoint_class_name = 'Put%ssId' % self.name 54 | klass = ObjectHelper.get_class_for_class_name(endpoint_class_name) 55 | klass.call(client, object) 56 | end 57 | 58 | Contract ExampleRestClient, self => nil 59 | def self.delete(client, object) 60 | token = self.name.downcase 61 | require_relative '../endpoints/%ss/delete_%ss_id' % [token, token] 62 | endpoint_class_name = 'Delete%ssId' % self.name 63 | klass = ObjectHelper.get_class_for_class_name(endpoint_class_name) 64 | klass.call(client, object) 65 | end 66 | 67 | Contract ExampleRestClient => Maybe[ArrayOf[self]] 68 | def self.get_all(client) 69 | token = self.name.downcase 70 | require_relative '../endpoints/%ss/get_%ss' % [token, token] 71 | endpoint_class_name = 'Get%ss' % self.name 72 | klass = ObjectHelper.get_class_for_class_name(endpoint_class_name) 73 | klass.call(client) 74 | end 75 | 76 | Contract ExampleRestClient => self 77 | def self.get_first(client) 78 | all = self.get_all(client) 79 | raise RuntimeError.new('No %s available' % self.name) unless all.size > 0 80 | all.first 81 | end 82 | 83 | end -------------------------------------------------------------------------------- /examples/rest_api/base_classes/base_class_for_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require_relative '../../../lib/helpers/test_helper' 4 | require_relative '../example_rest_client' 5 | 6 | class BaseClassForTest < Minitest::Test 7 | 8 | def prelude 9 | raise 'No block given' unless (block_given?) 10 | log_dir_path = TestHelper.get_app_log_dir_path('rest_api') 11 | TestHelper.test(self, log_dir_path) do |log| 12 | ExampleRestClient.with(log) do |client| 13 | yield client, log 14 | end 15 | end 16 | end 17 | 18 | end 19 | 20 | -------------------------------------------------------------------------------- /examples/rest_api/base_classes/endpoints/base_class_for_delete_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_class_for_endpoint' 2 | 3 | class BaseClassForDeleteId < BaseClassForEndpoint 4 | 5 | def self.call_and_return_payload(client, object_to_delete) 6 | url_elements = [ 7 | url_element.downcase, 8 | object_to_delete.id.to_s, 9 | ] 10 | client.delete(url_elements) 11 | nil 12 | end 13 | 14 | def self.verdict_call_and_verify_success(client, log, verdict_id, object_to_delete) 15 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 16 | payload = self.call(client, object_to_delete) 17 | log.section('Evaluation') do 18 | v_id = [verdict_id, :payload_nil] 19 | log.verdict_assert_nil?(v_id, payload) 20 | # The JSONPlaceholder does not actually modify itself, 21 | # and so the following verdict would fail. 22 | # klass = ObjectHelper.get_class_for_class_name(data_class_name) 23 | # v_id = [verdict_id, :not_exist] 24 | # klass.verdict_not_exist?(client, log, v_id, object_to_delete) 25 | end 26 | return payload 27 | end 28 | end 29 | 30 | private 31 | 32 | def self.data_class_name 33 | self.url_element.sub(/s$/, '') 34 | end 35 | 36 | def self.url_element 37 | self.to_s.sub('Delete', '').sub('Id', '') 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /examples/rest_api/base_classes/endpoints/base_class_for_get.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_class_for_endpoint' 2 | 3 | class BaseClassForGet < BaseClassForEndpoint 4 | 5 | def self.call_and_return_payload(client, query_elements) 6 | url_elements = [ 7 | url_element.downcase, 8 | ] 9 | payload = client.get(url_elements, query_elements) 10 | objects = [] 11 | payload.each do |hash| 12 | rehash = HashHelper.rehash_to_symbol_keys(hash) 13 | obj = ObjectHelper.instantiate_class_for_class_name(data_class_name, rehash) 14 | objects.push(obj) 15 | end 16 | [objects, payload] 17 | end 18 | 19 | def self.verdict_call_and_verify_success(client, log, verdict_id, query_elements) 20 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 21 | objects = self.call(client, query_elements) 22 | log.section('Evaluation') do 23 | object = objects.first 24 | log.put_element('data', {:fetched_object_count => objects.size}) 25 | log.section('First fetched') do 26 | object.log(log) 27 | end 28 | v_id = [verdict_id, :valid] 29 | object.verdict_valid?(log, v_id) 30 | end 31 | return objects 32 | end 33 | end 34 | 35 | private 36 | 37 | def self.data_class_name 38 | self.url_element.sub(/s$/, '') 39 | end 40 | 41 | def self.url_element 42 | self.to_s.sub('Get', '') 43 | end 44 | 45 | end -------------------------------------------------------------------------------- /examples/rest_api/base_classes/endpoints/base_class_for_get_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_class_for_endpoint' 2 | 3 | class BaseClassForGetId < BaseClassForEndpoint 4 | 5 | def self.call_and_return_payload(client, object_to_fetch) 6 | url_elements = [ 7 | url_element.downcase, 8 | object_to_fetch.id.to_s, 9 | ] 10 | payload = client.get(url_elements) 11 | rehash = HashHelper.rehash_to_symbol_keys(payload) 12 | object_got = ObjectHelper.instantiate_class_for_class_name(data_class_name, rehash) 13 | [object_got, payload] 14 | end 15 | 16 | def self.verdict_call_and_verify_success(client, log, verdict_id, object_to_get) 17 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 18 | object_got = self.call(client, object_to_get) 19 | log.section('Evaluation') do 20 | klass = ObjectHelper.get_class_for_class_name(data_class_name) 21 | v_id = [verdict_id, data_class_name.to_sym] 22 | klass.verdict_equal?(log, v_id, object_to_get, object_got) 23 | end 24 | return object_got 25 | end 26 | end 27 | 28 | private 29 | 30 | def self.data_class_name 31 | self.url_element.sub(/s$/, '') 32 | end 33 | 34 | def self.url_element 35 | self.to_s.sub('Get', '').sub('Id', '') 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /examples/rest_api/base_classes/endpoints/base_class_for_post.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_class_for_endpoint' 2 | 3 | class BaseClassForPost < BaseClassForEndpoint 4 | 5 | def self.call_and_return_payload(client, object_to_create) 6 | url_elements = [ 7 | url_element.downcase, 8 | ] 9 | parameters = object_to_create.to_hash 10 | parameters.delete(:id) 11 | payload = client.post(url_elements, parameters) 12 | rehash = HashHelper.rehash_to_symbol_keys(payload) 13 | object_posted = ObjectHelper.instantiate_class_for_class_name(data_class_name, rehash) 14 | [object_posted, payload] 15 | end 16 | 17 | def self.verdict_call_and_verify_success(client, log, verdict_id, object_to_post) 18 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 19 | object_posted = nil 20 | log.section('Post') do 21 | object_to_post.log(log) 22 | object_posted = self.call(client, object_to_post) 23 | end 24 | log.section('Evaluation') do 25 | klass = ObjectHelper.get_class_for_class_name(data_class_name) 26 | v_id = [verdict_id, data_class_name.to_sym, :posted] 27 | klass.verdict_equal?(log, v_id, object_to_post, object_posted) 28 | # The JSONPlaceholder does not actually modify itself, 29 | # and so the following verdict would fail. 30 | # object_fetched = klass.read(client, object_posted) 31 | # v_id = [verdict_id, data_class_name.to_sym, :fetched] 32 | # klass.verdict_equal?(log, v_id, object_posted, object_fetched) 33 | end 34 | return object_posted 35 | end 36 | end 37 | 38 | private 39 | 40 | def self.data_class_name 41 | self.url_element.sub(/s$/, '') 42 | end 43 | 44 | def self.url_element 45 | self.to_s.sub('Post', '') 46 | end 47 | 48 | end -------------------------------------------------------------------------------- /examples/rest_api/base_classes/endpoints/base_class_for_put_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_class_for_endpoint' 2 | 3 | class BaseClassForPutId < BaseClassForEndpoint 4 | 5 | def self.call_and_return_payload(client, object_to_put) 6 | url_elements = [ 7 | url_element.downcase, 8 | object_to_put.id.to_s, 9 | ] 10 | parameters = object_to_put.to_hash 11 | payload = client.put(url_elements, parameters) 12 | rehash = HashHelper.rehash_to_symbol_keys(payload) 13 | object_posted = ObjectHelper.instantiate_class_for_class_name(data_class_name, rehash) 14 | [object_posted, payload] 15 | end 16 | 17 | def self.verdict_call_and_verify_success(client, log, verdict_id, object_to_put) 18 | # Some verdicts should fail, because JSONplaceholder will not actually update the instance. 19 | log.section(verdict_id.to_s, :rescue, :timestamp, :duration) do 20 | object_put = self.call(client, object_to_put) 21 | log.section('Evaluation') do 22 | klass = ObjectHelper.get_class_for_class_name(data_class_name) 23 | v_id = [verdict_id, data_class_name.to_sym, :put] 24 | klass.verdict_equal?(log, v_id, object_to_put, object_put) 25 | # The JSONPlaceholder does not actually modify itself, 26 | # and so the following verdict would fail. 27 | # object_fetched = klass.read(client, object_to_put) 28 | # v_id = [verdict_id, data_class_name.to_sym, :fetched] 29 | # klass.verdict_equal?(log, v_id, object_to_put, object_fetched) 30 | end 31 | return object_put 32 | end 33 | end 34 | 35 | private 36 | 37 | def self.data_class_name 38 | self.url_element.sub(/s$/, '') 39 | end 40 | 41 | def self.url_element 42 | self.to_s.sub('Put', '').sub('Id', '') 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /examples/rest_api/data/album.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_resource' 2 | 3 | require_relative '../../../lib/helpers/string_helper' 4 | 5 | class Album < BaseClassForResource 6 | 7 | FIELDS = Set.new([ 8 | :id, 9 | :userId, 10 | :title, 11 | ]) 12 | 13 | attr_accessor *FIELDS 14 | 15 | ID_MIN_VALUE = 1 16 | USER_ID_MIN_VALUE = 1 17 | TITLE_LENGTH_RANGE = (1..50) 18 | 19 | # Constructor. 20 | Contract Hash => nil 21 | def initialize(values = {}) 22 | super(FIELDS, values) 23 | end 24 | 25 | Contract Log, VERDICT_ID, Symbol => Bool 26 | def verdict_field_valid?(log, verdict_id, field) 27 | value = self.send(field) 28 | case 29 | when [ 30 | :id, 31 | :userId, 32 | ].include?(field) 33 | log.verdict_assert_integer_positive?(verdict_id, value) 34 | when [ 35 | :title, 36 | ].include?(field) 37 | log.verdict_assert_string_length_in_range?(verdict_id, TITLE_LENGTH_RANGE, value) 38 | else 39 | ArgumentError.new(field.inspect) 40 | end 41 | end 42 | 43 | def self.id_valid 44 | ID_MIN_VALUE 45 | end 46 | 47 | def self.id_invalid 48 | ID_MIN_VALUE - 1 49 | end 50 | 51 | def self.user_id_valid 52 | USER_ID_MIN_VALUE 53 | end 54 | 55 | def self.user_id_invalid 56 | USER_ID_MIN_VALUE - 1 57 | end 58 | 59 | def self.title_valid 60 | StringHelper.string_of_min_length(TITLE_LENGTH_RANGE) 61 | end 62 | 63 | def self.title_invalid 64 | StringHelper.string_too_short(TITLE_LENGTH_RANGE) 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /examples/rest_api/data/comment.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_resource' 2 | 3 | class Comment < BaseClassForResource 4 | 5 | FIELDS = Set.new([ 6 | :postId, 7 | :id, 8 | :name, 9 | :email, 10 | :body, 11 | ]) 12 | 13 | attr_accessor *FIELDS 14 | 15 | # Constructor. 16 | Contract Hash => nil 17 | def initialize(values = {}) 18 | super(FIELDS, values) 19 | nil 20 | end 21 | 22 | Contract Log, VERDICT_ID => Bool 23 | def verdict_valid?(log, verdict_id) 24 | log.verdict_assert_integer_positive?([verdict_id, :post_id], self.postId) 25 | log.verdict_assert_integer_positive?([verdict_id, :id], self.id) 26 | log.verdict_assert_string_not_empty?([verdict_id, :name], self.name) 27 | log.verdict_assert_string_not_empty?([verdict_id, :email], self.email) 28 | log.verdict_assert_string_not_empty?([verdict_id, :body], self.body) 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /examples/rest_api/data/photo.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_resource' 2 | 3 | class Photo < BaseClassForResource 4 | 5 | FIELDS = Set.new([ 6 | :albumId, 7 | :id, 8 | :title, 9 | :url, 10 | :thumbnailUrl, 11 | ]) 12 | 13 | attr_accessor *FIELDS 14 | 15 | # Constructor. 16 | Contract Hash => nil 17 | def initialize(values = {}) 18 | super(FIELDS, values) 19 | nil 20 | end 21 | 22 | Contract Log, VERDICT_ID => Bool 23 | def verdict_valid?(log, verdict_id) 24 | log.verdict_assert_integer_positive?([verdict_id, :album_id], self.albumId) 25 | log.verdict_assert_integer_positive?([verdict_id, :id], self.id) 26 | log.verdict_assert_string_not_empty?([verdict_id, :title], self.title) 27 | log.verdict_assert_string_not_empty?([verdict_id, :url], self.url) 28 | log.verdict_assert_string_not_empty?([verdict_id, :thumbnailUrl], self.thumbnailUrl) 29 | end 30 | 31 | # This is redundant, but it helps RubyMine code inspection. 32 | attr_accessor \ 33 | :albumId, 34 | :thumbnailUrl, 35 | :url 36 | 37 | end 38 | -------------------------------------------------------------------------------- /examples/rest_api/data/post.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_resource' 2 | 3 | class Post < BaseClassForResource 4 | 5 | FIELDS = Set.new([ 6 | :id, 7 | :userId, 8 | :title, 9 | :body, 10 | ]) 11 | 12 | attr_accessor *FIELDS 13 | 14 | # Constructor. 15 | Contract Hash => nil 16 | def initialize(values = {}) 17 | super(FIELDS, values) 18 | end 19 | 20 | Contract Log, VERDICT_ID => Bool 21 | def verdict_valid?(log, verdict_id) 22 | log.verdict_assert_integer_positive?([verdict_id, :id], self.id) 23 | log.verdict_assert_integer_positive?([verdict_id, :user_id], self.userId) 24 | log.verdict_assert_string_not_empty?([verdict_id, :title], self.title) 25 | log.verdict_assert_string_not_empty?([verdict_id, :body], self.body) 26 | end 27 | 28 | # This is redundant, but it helps RubyMine code inspection. 29 | attr_accessor \ 30 | :postId 31 | 32 | end 33 | -------------------------------------------------------------------------------- /examples/rest_api/data/todo.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class_for_resource' 2 | 3 | class Todo < BaseClassForResource 4 | 5 | FIELDS = Set.new([ 6 | :userId, 7 | :id, 8 | :title, 9 | :completed, 10 | ]) 11 | 12 | attr_accessor *FIELDS 13 | 14 | # Constructor. 15 | Contract Hash => nil 16 | def initialize(values = {}) 17 | super(FIELDS, values) 18 | nil 19 | end 20 | 21 | Contract Log, VERDICT_ID => Bool 22 | def verdict_valid?(log, verdict_id) 23 | log.verdict_assert_integer_positive?([verdict_id, :user_id], self.userId) 24 | log.verdict_assert_integer_positive?([verdict_id, :id], self.id) 25 | log.verdict_assert_string_not_empty?([verdict_id, :title], self.title) 26 | log.verdict_assert_boolean?([verdict_id, :completed], self.completed) 27 | end 28 | 29 | # This is redundant, but it helps RubyMine code inspection. 30 | attr_accessor \ 31 | :userId, 32 | :completed 33 | 34 | end 35 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/albums/delete_albums_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_delete_id' 2 | 3 | require_relative '../../../rest_api/data/album' 4 | 5 | class DeleteAlbumsId < BaseClassForDeleteId 6 | 7 | Contract ExampleRestClient, Album => nil 8 | def self.call_and_return_payload(client, album) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Album => nil 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, album) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/albums/get_albums.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get' 2 | 3 | require_relative '../../data/album' 4 | 5 | class GetAlbums < BaseClassForGet 6 | 7 | Contract ExampleRestClient, Maybe[Hash] => [ArrayOf[Album], ArrayOf[Hash]] 8 | def self.call_and_return_payload(client, query_elements = {}) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Maybe[Hash] => ArrayOf[Album] 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, query_elements = {}) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/albums/get_albums_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get_id' 2 | 3 | require_relative '../../../rest_api/data/album' 4 | 5 | class GetAlbumsId < BaseClassForGetId 6 | 7 | Contract ExampleRestClient, Album => [Album, Hash] 8 | def self.call_and_return_payload(client, album) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Album => Album 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, album) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/albums/post_albums.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_post' 2 | 3 | require_relative '../../../rest_api/data/album' 4 | 5 | class PostAlbums < BaseClassForPost 6 | 7 | Contract ExampleRestClient, Album => [Album, Any] 8 | def self.call_and_return_payload(client, album) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Album => Album 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, album) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/albums/put_albums_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_put_id' 2 | 3 | require_relative '../../../rest_api/data/album' 4 | 5 | class PutAlbumsId < BaseClassForPutId 6 | 7 | Contract ExampleRestClient, Album => [Album, Any] 8 | def self.call_and_return_payload(client, album) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Album => Album 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, album_to_put) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/comments/delete_comments_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_delete_id' 2 | 3 | require_relative '../../../rest_api/data/comment' 4 | 5 | class DeleteCommentsId < BaseClassForDeleteId 6 | 7 | Contract ExampleRestClient, Comment => nil 8 | def self.call_and_return_payload(client, comment) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Comment => nil 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, comment) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/comments/get_comments.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get' 2 | 3 | require_relative '../../data/comment' 4 | 5 | class GetComments < BaseClassForGet 6 | 7 | Contract ExampleRestClient, Maybe[Hash] => [ArrayOf[Comment], ArrayOf[Hash]] 8 | def self.call_and_return_payload(client, query_elements = {}) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Maybe[Hash] => ArrayOf[Comment] 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, query_elements = {}) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/comments/get_comments_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get_id' 2 | 3 | require_relative '../../../rest_api/data/comment' 4 | 5 | class GetCommentsId < BaseClassForGetId 6 | 7 | Contract ExampleRestClient, Comment => [Comment, Hash] 8 | def self.call_and_return_payload(client, comment) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Comment => Comment 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, comment) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/comments/post_comments.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_post' 2 | 3 | require_relative '../../../rest_api/data/comment' 4 | 5 | class PostComments < BaseClassForPost 6 | 7 | Contract ExampleRestClient, Comment => [Comment, Any] 8 | def self.call_and_return_payload(client, comment) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Comment => Comment 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, comment) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/comments/put_comments_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_put_id' 2 | 3 | require_relative '../../../rest_api/data/comment' 4 | 5 | class PutCommentsId < BaseClassForPutId 6 | 7 | Contract ExampleRestClient, Comment => [Comment, Any] 8 | def self.call_and_return_payload(client, comment) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Comment => Comment 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, comment_to_put) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/photos/delete_photos_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_delete_id' 2 | 3 | require_relative '../../../rest_api/data/photo' 4 | 5 | class DeletePhotosId < BaseClassForDeleteId 6 | 7 | Contract ExampleRestClient, Photo => nil 8 | def self.call_and_return_payload(client, photo) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Photo => nil 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, photo) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/photos/get_photos.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get' 2 | 3 | require_relative '../../data/photo' 4 | 5 | class GetPhotos < BaseClassForGet 6 | 7 | Contract ExampleRestClient, Maybe[Hash] => [ArrayOf[Photo], ArrayOf[Hash]] 8 | def self.call_and_return_payload(client, query_elements = {}) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Maybe[Hash] => ArrayOf[Photo] 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, query_elements = {}) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/photos/get_photos_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get_id' 2 | 3 | require_relative '../../../rest_api/data/photo' 4 | 5 | class GetPhotosId < BaseClassForGetId 6 | 7 | Contract ExampleRestClient, Photo => [Photo, Hash] 8 | def self.call_and_return_payload(client, photo) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Photo => Photo 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, photo) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/photos/post_photos.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_post' 2 | 3 | require_relative '../../../rest_api/data/photo' 4 | 5 | class PostPhotos < BaseClassForPost 6 | 7 | Contract ExampleRestClient, Photo => [Photo, Any] 8 | def self.call_and_return_payload(client, photo) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Photo => Photo 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, photo) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/photos/put_photos_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_put_id' 2 | 3 | require_relative '../../../rest_api/data/photo' 4 | 5 | class PutPhotosId < BaseClassForPutId 6 | 7 | Contract ExampleRestClient, Photo => [Photo, Any] 8 | def self.call_and_return_payload(client, photo) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Photo => Photo 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, photo_to_put) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/posts/delete_posts_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_delete_id' 2 | 3 | require_relative '../../../rest_api/data/post' 4 | 5 | class DeletePostsId < BaseClassForDeleteId 6 | 7 | Contract ExampleRestClient, Post => nil 8 | def self.call_and_return_payload(client, post) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Post => nil 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, post) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/posts/get_posts.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get' 2 | 3 | require_relative '../../data/post' 4 | 5 | class GetPosts < BaseClassForGet 6 | 7 | Contract ExampleRestClient, Maybe[Hash] => [ArrayOf[Post], ArrayOf[Hash]] 8 | def self.call_and_return_payload(client, query_elements = {}) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Maybe[Hash] => ArrayOf[Post] 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, query_elements = {}) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/posts/get_posts_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get_id' 2 | 3 | require_relative '../../../rest_api/data/post' 4 | 5 | class GetPostsId < BaseClassForGetId 6 | 7 | Contract ExampleRestClient, Post => [Post, Hash] 8 | def self.call_and_return_payload(client, post) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Post => Post 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, post) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/posts/post_posts.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_post' 2 | 3 | require_relative '../../../rest_api/data/post' 4 | 5 | class PostPosts < BaseClassForPost 6 | 7 | Contract ExampleRestClient, Post => [Post, Any] 8 | def self.call_and_return_payload(client, post) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Post => Post 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, post) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/posts/put_posts_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_put_id' 2 | 3 | require_relative '../../../rest_api/data/post' 4 | 5 | class PutPostsId < BaseClassForPutId 6 | 7 | Contract ExampleRestClient, Post => [Post, Any] 8 | def self.call_and_return_payload(client, post) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Post => Post 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, post_to_put) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/todos/delete_todos_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_delete_id' 2 | 3 | require_relative '../../../rest_api/data/todo' 4 | 5 | class DeleteTodosId < BaseClassForDeleteId 6 | 7 | Contract ExampleRestClient, Todo => nil 8 | def self.call_and_return_payload(client, todo) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Todo => nil 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, todo) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/todos/get_todos.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get' 2 | 3 | require_relative '../../data/todo' 4 | 5 | class GetTodos < BaseClassForGet 6 | 7 | Contract ExampleRestClient, Maybe[Hash] => [ArrayOf[Todo], ArrayOf[Hash]] 8 | def self.call_and_return_payload(client, query_elements = {}) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Maybe[Hash] => ArrayOf[Todo] 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, query_elements = {}) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/todos/get_todos_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get_id' 2 | 3 | require_relative '../../../rest_api/data/todo' 4 | 5 | class GetTodosId < BaseClassForGetId 6 | 7 | Contract ExampleRestClient, Todo => [Todo, Hash] 8 | def self.call_and_return_payload(client, todo) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Todo => Todo 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, todo) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/todos/post_todos.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_post' 2 | 3 | require_relative '../../../rest_api/data/todo' 4 | 5 | class PostTodos < BaseClassForPost 6 | 7 | Contract ExampleRestClient, Todo => [Todo, Any] 8 | def self.call_and_return_payload(client, todo) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Todo => Todo 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, todo) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/todos/put_todos_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_put_id' 2 | 3 | require_relative '../../../rest_api/data/todo' 4 | 5 | class PutTodosId < BaseClassForPutId 6 | 7 | Contract ExampleRestClient, Todo => [Todo, Any] 8 | def self.call_and_return_payload(client, todo) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Todo => Todo 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, todo_to_put) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/users/delete_users_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_delete_id' 2 | 3 | require_relative '../../../rest_api/data/user' 4 | 5 | class DeleteUsersId < BaseClassForDeleteId 6 | 7 | Contract ExampleRestClient, User => nil 8 | def self.call_and_return_payload(client, user) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, User => nil 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, user) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/users/get_users.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get' 2 | 3 | require_relative '../../data/user' 4 | 5 | class GetUsers < BaseClassForGet 6 | 7 | Contract ExampleRestClient, Maybe[Hash] => [ArrayOf[User], ArrayOf[Hash]] 8 | def self.call_and_return_payload(client, query_elements = {}) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, Maybe[Hash] => ArrayOf[User] 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, query_elements = {}) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/users/get_users_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_get_id' 2 | 3 | require_relative '../../../rest_api/data/user' 4 | 5 | class GetUsersId < BaseClassForGetId 6 | 7 | Contract ExampleRestClient, User => [User, Hash] 8 | def self.call_and_return_payload(client, user) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, User => User 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, user) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/users/post_users.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_post' 2 | 3 | require_relative '../../../rest_api/data/user' 4 | 5 | class PostUsers < BaseClassForPost 6 | 7 | Contract ExampleRestClient, User => [User, Any] 8 | def self.call_and_return_payload(client, user) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, User => User 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, user) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/endpoints/users/put_users_id.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../base_classes/endpoints/base_class_for_put_id' 2 | 3 | require_relative '../../../rest_api/data/user' 4 | 5 | class PutUsersId < BaseClassForPutId 6 | 7 | Contract ExampleRestClient, User => [User, Any] 8 | def self.call_and_return_payload(client, user) 9 | super 10 | end 11 | 12 | Contract ExampleRestClient, Log, VERDICT_ID, User => User 13 | def self.verdict_call_and_verify_success(client, log, verdict_id, user_to_put) 14 | super 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /examples/rest_api/example_rest_client.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'rest-client' 3 | require 'retriable' 4 | require 'uri' 5 | 6 | require_relative '../../lib/base_classes/base_class' 7 | 8 | class ExampleRestClient < BaseClass 9 | 10 | # Instantiate a client for the caller's block. 11 | Contract Log, Proc => nil 12 | def self.with(log) 13 | raise 'No block given' unless (block_given?) 14 | log.section('With %s' % self, :rescue) do 15 | client = self.new(log, im_ok_youre_not_ok = true) 16 | # Client should retrieve and log the API version if it's available. 17 | # (Here, it's not.) 18 | yield client 19 | # Client should calculate and log summary information here. 20 | # TBS. 21 | end 22 | nil 23 | end 24 | 25 | # Tests should use method with, above, not method new. 26 | Contract Log, Bool => nil 27 | def initialize(log, im_ok_youre_not_ok = false) 28 | raise RuntimeError('Call method with, not new') unless im_ok_youre_not_ok 29 | @base_url = 'https://jsonplaceholder.typicode.com' 30 | @log = log 31 | @uri = URI.parse(@base_url) 32 | nil 33 | end 34 | 35 | # Get. 36 | Contract Array, Maybe[Hash] => Or[Array, Hash] 37 | def get(url_elements, query_elements = {}) 38 | client_method(__method__, url_elements, query_elements, parameters = {}) 39 | end 40 | 41 | # Post. 42 | Contract Array, Hash => Hash 43 | def post(url_elements, parameters) 44 | client_method(__method__, url_elements, query_elements = {}, parameters) 45 | end 46 | 47 | # Put. 48 | Contract Array, Hash => Hash 49 | def put(url_elements, parameters) 50 | client_method(__method__, url_elements, query_elements = {}, parameters) 51 | end 52 | 53 | # Delete. 54 | Contract Array => Hash 55 | def delete(url_elements) 56 | client_method(__method__, url_elements, query_elements = {}, parameters = {}) 57 | end 58 | 59 | private 60 | 61 | # Do one of the above. 62 | Contract Symbol, Array, Hash, Hash => Or[String, Array, Hash] 63 | def client_method(rest_method, url_elements, query_elements, parameters) 64 | url = File.join(@base_url, *url_elements) 65 | query_elements.to_a.each_with_index do |pair, i| 66 | char = (i == 0) ? '?' : '&' 67 | url += '%s%s=%s' % [char, *pair] 68 | end 69 | url = URI.escape(url) 70 | args = Hash.new 71 | args.store(:method, rest_method) 72 | args.store(:url, url) 73 | case 74 | when [ 75 | :delete, 76 | :get 77 | ].include?(rest_method) 78 | when [ 79 | :post, 80 | :put 81 | ].include?(rest_method) 82 | headers = { 83 | :content_type => 'application/json', 84 | } 85 | 86 | parameters_json = parameters.to_json 87 | args.store(:payload, parameters_json) 88 | args.store(:headers, headers) 89 | else 90 | raise ArgumentError(rest_method) 91 | end 92 | 93 | args.store(:timeout, 60) 94 | 95 | log_retry = Proc.new do |exception, try, elapsed_time, next_interval| 96 | puts "#{exception.class}: '#{exception.message}'" 97 | puts "#{try} tries in #{elapsed_time} seconds and #{next_interval} seconds until the next try." 98 | end 99 | 100 | response = nil 101 | @log.put_element('REST_API', :method => rest_method.to_s.upcase, :url => url) do 102 | @log.put_element('parameters', parameters) unless parameters.empty? 103 | @log.put_element('execution', :timestamp, :duration) do 104 | # noinspection RubyResolve 105 | Retriable.retriable on: RestClient::RequestTimeout, tries: 10, base_interval: 1, on_retry: log_retry do 106 | response = RestClient::Request.execute(args) 107 | end 108 | end 109 | end 110 | # RubyMine inspection thinks this should have no argument. 111 | # noinspection RubyArgCount 112 | parser = JSON::Ext::Parser.new(response) 113 | parser.parse 114 | end 115 | 116 | end -------------------------------------------------------------------------------- /lib/Lib.md: -------------------------------------------------------------------------------- 1 | # Lib 2 | 3 | Contains: 4 | 5 | - [BaseClass](base_class.rb) 6 | - [Configuration](configuration.rb) 7 | - [Helpers](helpers/helpers.md) 8 | - [Log](log.rb) 9 | -------------------------------------------------------------------------------- /lib/base_classes/BaseClasses.md: -------------------------------------------------------------------------------- 1 | # Base Classes 2 | 3 | ## Source 4 | 5 | - [BaseClass](./base_class.rb) 6 | - [BaseClassForData](./base_class_for_data.rb) 7 | -------------------------------------------------------------------------------- /lib/base_classes/base_class.rb: -------------------------------------------------------------------------------- 1 | require 'contracts' 2 | 3 | require_relative '../helpers/debug_helper' 4 | 5 | class BaseClass 6 | 7 | include Contracts 8 | 9 | end 10 | 11 | # Fix bugs in Contracts::MethodHandler 12 | 13 | module Contracts 14 | 15 | class MethodHandler 16 | # Override this with two small (but important) changes. 17 | def validate_decorators! 18 | return if decorators.size == 1 19 | # 1. Weird bug; thinks this method has 55 contracts! 20 | return if method_name == :psych_yaml_as 21 | # 2. Original has #{name}, which is not defined; #{method_name} works. 22 | # This is fixed in the GitHub project, but not yet in the Ruby gem. 23 | fail %{ 24 | Oops, it looks like method '#{method_name}' has multiple contracts: 25 | #{decorators.map { |x| x[1][0].inspect }.join("\n")} 26 | 27 | Did you accidentally put more than one contract on a single function, like so? 28 | 29 | Contract String => String 30 | Contract Num => String 31 | def foo x 32 | end 33 | 34 | If you did NOT, then you have probably discovered a bug in this library. 35 | Please file it along with the relevant code at: 36 | https://github.com/egonSchiele/contracts.ruby/issues 37 | } 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /lib/helpers/Helpers.md: -------------------------------------------------------------------------------- 1 | # Helpers 2 | 3 | Various helpful(!) convenience methods. 4 | 5 | ## Source 6 | 7 | - [DebugHelper](debug_helper.rb) 8 | - [HashHelper](hash_helper.rb) 9 | - [LoremHelper](lorem_helper.rb) 10 | - [MarkdownHelper](markdown_helper.rb) 11 | - [ObjectHelper](object_helper.rb) 12 | - [SetHelper](set_helper.rb) 13 | - [StringHelper](string_helper.rb) 14 | - [TestHelper](test_helper.rb) 15 | - [TimeHelper](time_helper.rb) 16 | - [ValuesHelper](values_helper.rb) 17 | -------------------------------------------------------------------------------- /lib/helpers/debug_helper.rb: -------------------------------------------------------------------------------- 1 | # This class does not derive from BaseClass, 2 | # because that would cause a circular dependency. 3 | # But it still needs Contracts. 4 | 5 | require 'contracts' 6 | 7 | # Class to help in 'printf' debugging. 8 | class DebugHelper 9 | 10 | include Contracts 11 | 12 | # Contract Any, Maybe[String] => nil 13 | def self.printf(data, name = data.class.to_s, description = '') 14 | size = data.respond_to?(:size) ? data.size : 1 15 | puts format('%s (size=%d) %s', name, size, description) 16 | case 17 | when data.respond_to?(:each_pair) 18 | # Hash-like. 19 | data.each_pair do |k, v| 20 | puts format(' %s => %s', k, v) 21 | end 22 | when data.respond_to?(:each_with_index) 23 | # Array-like or Set-like. 24 | data.each_with_index do |v, i| 25 | puts format(' %6d: %s', i, v) 26 | end 27 | else 28 | puts format(' %s', data.inspect) 29 | end 30 | nil 31 | end 32 | 33 | end 34 | 35 | 36 | -------------------------------------------------------------------------------- /lib/helpers/hash_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class' 2 | 3 | class HashHelper < BaseClass 4 | 5 | Contract Hash, Hash => HashOf[Symbol => Hash] 6 | # Compare two hashes. 7 | # Returns a hash with keys +:ok+, +:missing+, +:unexpected+, +:changed+. 8 | def self.compare(expected, actual) 9 | result = { 10 | :missing => {}, 11 | :unexpected => {}, 12 | :changed => {}, 13 | :ok => {}, 14 | } 15 | expected.each_pair do |key_expected, value_expected| 16 | if actual.include?(key_expected) 17 | value_actual = actual[key_expected] 18 | if value_actual == value_expected 19 | result[:ok][key_expected] = value_expected 20 | else 21 | result[:changed][key_expected] = {:expected => value_expected, :actual => value_actual} 22 | end 23 | else 24 | result[:missing][key_expected] = value_expected 25 | end 26 | end 27 | actual.each_pair do |key_actual, value_actual| 28 | next if expected.include?(key_actual) 29 | result[:unexpected][key_actual] = value_actual 30 | end 31 | result 32 | end 33 | 34 | Contract Hash => Hash 35 | def self.rehash_to_symbol_keys(hash) 36 | rehash = {} 37 | hash.each_pair do |k, v| 38 | if v.respond_to?(:each_pair) 39 | v = self.rehash_to_symbol_keys(v) 40 | end 41 | rehash.store(k.to_sym, v) 42 | end 43 | rehash 44 | end 45 | 46 | end 47 | 48 | -------------------------------------------------------------------------------- /lib/helpers/lorem_helper.rb: -------------------------------------------------------------------------------- 1 | require 'lorem-ipsum' 2 | 3 | require_relative '../base_classes/base_class' 4 | 5 | class LoremHelper < BaseClass 6 | 7 | include Contracts 8 | 9 | class Lorem 10 | 11 | include Contracts 12 | include LoremIpsum 13 | 14 | def initialize 15 | # Locate the file (in the gem's directory). 16 | spec = Gem::Specification.find_by_name('lorem-ipsum') 17 | gem_root = spec.gem_dir 18 | lorem_file_path = File.join(gem_root, 'data', 'lorem.txt') 19 | @lorem = Generator.new 20 | @lorem.analyze(lorem_file_path) 21 | end 22 | 23 | Contract Num => String 24 | # Return lorem-ipsum string with given word count. 25 | def sentence(word_count) 26 | @lorem.next_sentence(word_count).strip 27 | end 28 | 29 | def words(word_count) 30 | sentence(word_count).gsub(/\./, '').downcase 31 | end 32 | 33 | end 34 | 35 | Contract None => self::Lorem 36 | # Return Lorem generator. 37 | def self.generator 38 | self::Lorem.new 39 | end 40 | 41 | end 42 | 43 | 44 | -------------------------------------------------------------------------------- /lib/helpers/object_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class' 2 | 3 | class ObjectHelper < BaseClass 4 | 5 | Contract String => Class 6 | # Get the class (not an instance) for the given class name. 7 | def self.get_class_for_class_name(class_name) 8 | Object::const_get(class_name) 9 | end 10 | 11 | Contract String, Args[Any] => Any 12 | # Instantiate (i.e., create an instance of) a class for the given class name. 13 | def self.instantiate_class_for_class_name(class_name, *args) 14 | self.get_class_for_class_name(class_name).new(*args) 15 | end 16 | 17 | end 18 | 19 | -------------------------------------------------------------------------------- /lib/helpers/set_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class' 2 | 3 | class SetHelper < BaseClass 4 | 5 | Contract Set, Set => HashOf[Symbol => Set] 6 | # Compare two sets. 7 | # Returns a hash with keys +:ok+, +:missing+, +:unexpected+. 8 | def self.compare(expected, actual) 9 | { 10 | :missing => expected - actual, 11 | :unexpected => actual - expected, 12 | :ok => expected & actual, 13 | } 14 | end 15 | 16 | end 17 | 18 | -------------------------------------------------------------------------------- /lib/helpers/string_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class' 2 | 3 | class StringHelper < BaseClass 4 | 5 | include Contracts 6 | 7 | DEFAULT_BASE_STRING = 'x' 8 | 9 | # Contract value for non-negative integer. 10 | # Using casing that agrees with other contract values, 11 | # which RubyMine did not like, so here's the suppressing pragma. 12 | # noinspection RubyConstantNamingConvention 13 | PosInteger = And[Not[Neg], Integer] 14 | 15 | Contract PosInteger, Maybe[String] => String 16 | # Return a string of the given length. 17 | # Use trimmed or extended base_string. 18 | def self.string_of_length(length, base_string = DEFAULT_BASE_STRING) 19 | raise ArgumentError.new('length < 0: %d' % length) if length < 0 20 | case 21 | when length == 0 22 | return '' 23 | when base_string.nil? 24 | return DEFAULT_BASE_STRING * length 25 | when base_string.length > length 26 | # Trim. 27 | return base_string[0..length-1] 28 | else 29 | # Extend. 30 | s = base_string 31 | while s.length < length 32 | s = s * 2 33 | end 34 | return s[0..length-1] 35 | end 36 | end 37 | 38 | Contract Range, Maybe[String] => Maybe[String] 39 | # Return a string of minimum length. 40 | # Use trimmed or extended base_string. 41 | def self.string_of_min_length(range, base_string = nil) 42 | self.string_of_length(range.first, base_string) 43 | end 44 | 45 | Contract Range, Maybe[String] => String 46 | # Return a string of maximum length. 47 | # Use trimmed or extended base_string. 48 | def self.string_of_max_length(range, base_string = nil) 49 | self.string_of_length(range.last, base_string) 50 | end 51 | 52 | Contract Range, Maybe[String] => Maybe[String] 53 | # Return a string that's just out of lower range. 54 | # Use trimmed or extended base_string. 55 | def self.string_too_short(range, base_string=nil) 56 | self.string_of_length(range.first - 1, base_string) 57 | end 58 | 59 | Contract Range, Maybe[String] => Maybe[String] 60 | # Return a string that's just out of upper range. 61 | # Use trimmed or extended base_string. 62 | def self.string_too_long(range, base_string=nil) 63 | self.string_of_length(range.last + 1, base_string) 64 | end 65 | 66 | Contract String => String 67 | # Return the upper camel case version of a snake case string. 68 | def self.to_upper_camel_case(snake_case) 69 | upper_camel_words = [] 70 | snake_case.split('_').each do |word| 71 | upper_camel_words.push(word.capitalize) 72 | end 73 | upper_camel_words.join('') 74 | end 75 | 76 | Contract String => String 77 | def self.to_title(s) 78 | small_words = %w{a an and the or for of nor} 79 | title_words = [] 80 | s.split('_').each_with_index do |word, i| 81 | word.capitalize! unless small_words.include?(word) 82 | word.capitalize! if i == 0 83 | title_words.push(word) 84 | end 85 | title_words.join(' ') 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /lib/helpers/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/test' 2 | require 'rbconfig' 3 | 4 | require_relative '../../lib/base_classes/base_class' 5 | 6 | require_relative '../../lib/log/log' 7 | require_relative 'time_helper' 8 | 9 | class TestHelper < BaseClass 10 | 11 | Contract Minitest::Test, Or[String, Proc], Maybe[Proc] => NilClass 12 | # Set up for a test. 13 | def self.test(test, log_dir_path = '.') 14 | raise 'No block given' unless (block_given?) 15 | FileUtils.mkdir_p(log_dir_path) 16 | test_name, test_method_name = self.get_test_name_and_test_method_name 17 | # TODO: Get log directory path from configuration file. 18 | # TODO: Construct file path from directory path and test name. 19 | log_file_name = test_name + '.xml' 20 | log_file_path = File.join(log_dir_path, log_file_name) 21 | Log.open(test, :root_name => test_method_name, :file_path => log_file_path) do |log| 22 | log.test_method(:rescue, :timestamp, :duration, :name => test_method_name) do 23 | yield log 24 | end 25 | end 26 | end 27 | 28 | Contract None => ArrayOf[String] 29 | # Get the test file name (without the extension), and the test name 30 | def self.get_test_name_and_test_method_name 31 | # We are getting the test name from the call stack. 32 | # It begins with 'test_' 33 | all_names = [] 34 | test_files_by_name = {} 35 | caller.each do |_caller| 36 | words = _caller.split(/\W/) 37 | test_name = words[-6] 38 | name = words.last 39 | all_names.push(name) 40 | if name.start_with?('test_') 41 | test_files_by_name.store(name, test_name) 42 | end 43 | end 44 | case test_files_by_name.size 45 | when 0 46 | message = 'Found no test name among %s' % all_names.inspect 47 | raise Exception.new(message) 48 | when 1 49 | return test_files_by_name.to_a.first 50 | else 51 | message = 'Found %d test names and files: %s' % test_files_by_name.inspect 52 | raise Exception.new(message) 53 | end 54 | end 55 | 56 | Contract None => String 57 | def self.get_test_name 58 | _, test_name = self.get_test_name_and_test_method_name 59 | test_name 60 | end 61 | 62 | Contract None => String 63 | def self.get_method_name 64 | method_name, _ = self.get_test_name_and_test_method_name 65 | method_name 66 | end 67 | 68 | def self.get_log_root_dir_path 69 | host_os = RbConfig::CONFIG['host_os'] 70 | dir_path = case 71 | when host_os =~ /mswin|windows|cygwin|mingw32/i 72 | # Some Windows OS. 73 | File.join( 74 | ENV['appdata'], 75 | 'RubyTest', 76 | 'logs', 77 | ) 78 | when host_os =~ /linux/i 79 | # Some linux OS. 80 | '/var/log/RubyTest' 81 | else 82 | raise NotImplementedError.new(host_os) 83 | end 84 | File.absolute_path(dir_path) 85 | end 86 | 87 | def self.create_log_root_dir 88 | log_root_dir_path = self.get_log_root_dir_path 89 | FileUtils.mkdir_p(log_root_dir_path) 90 | log_root_dir_path 91 | end 92 | 93 | def self.create_app_log_dir(app_name) 94 | dir_path = File.join( 95 | self.get_log_root_dir_path, 96 | app_name, 97 | TimeHelper.timestamp(milliseconds = false), 98 | ) 99 | FileUtils.mkdir_p(dir_path) 100 | dir_path 101 | end 102 | 103 | # Get app test logs directory path. 104 | # This will have a timestamp directory name. 105 | # Accepts a non-negative offset into the list of directory names: 106 | # 0 for most recent result; 1 for the next earlier; etc. 107 | Contract String, And[Num, Not[Neg]] => String 108 | def self.get_app_log_dir_path(app_name, back = 0) 109 | timestamp_dir_names = Dir.glob(File.join( 110 | self.get_log_root_dir_path, 111 | app_name, 112 | '*', 113 | '' 114 | )) 115 | timestamp_dir_names.sort.reverse[back] 116 | end 117 | 118 | end 119 | -------------------------------------------------------------------------------- /lib/helpers/time_helper.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | 3 | require_relative '../../lib/base_classes/base_class' 4 | 5 | class TimeHelper < BaseClass 6 | 7 | Contract Bool => String 8 | # Timestamp with no path-forbidden characters. 9 | def self.timestamp(milliseconds = false) 10 | now = DateTime.now 11 | if milliseconds 12 | now.strftime('%Y.%m.%d-%a-%H.%M.%S.%3N') 13 | else 14 | now.strftime('%Y.%m.%d-%a-%H.%M.%S') 15 | end 16 | 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /lib/helpers/values_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../base_classes/base_class' 2 | 3 | require_relative 'string_helper' 4 | 5 | # Class to provide values for testing. 6 | # 7 | # Each method returns a hash of symbol/value pairs. 8 | # 9 | # Use hash.each_pair to loop through the hash. 10 | # 11 | # For each, construct a verdict using the value in the expected value, 12 | # and the symbol in the verdict's message. 13 | class ValuesHelper < BaseClass 14 | 15 | include Contracts 16 | 17 | Contract RangeOf[Integer] => HashOf[Symbol, Integer] 18 | # Return hash of integers in range. 19 | def self.integers_in_range(range) 20 | { 21 | :min => range.first, 22 | :max => range.last, 23 | } 24 | end 25 | 26 | Contract RangeOf[Integer] => HashOf[Symbol, Integer] 27 | # Return hash of integers not in range. 28 | def self.integers_not_in_range(range) 29 | { 30 | :too_small => range.first.pred, 31 | :too_large => range.last.succ, 32 | } 33 | end 34 | 35 | Contract Range, Maybe[String] => HashOf[Symbol, Maybe[String]] 36 | # Return hash of strings in range. 37 | def self.strings_in_length_range(range, base_string=nil) 38 | { 39 | :max_length => StringHelper.string_of_max_length(range, base_string), 40 | :min_length => StringHelper.string_of_min_length(range, base_string), 41 | } 42 | end 43 | 44 | Contract Range, Maybe[String] => HashOf[Symbol, Maybe[String]] 45 | # Return hash of strings out of range. 46 | def self.strings_not_in_length_range(range, base_string=nil) 47 | values = { 48 | :too_short => StringHelper.string_too_short(range, base_string), 49 | :too_long => StringHelper.string_too_long(range, base_string), 50 | } 51 | values.delete(:too_short) unless values.fetch(:too_short) 52 | values 53 | end 54 | 55 | end -------------------------------------------------------------------------------- /lib/log/Log.md: -------------------------------------------------------------------------------- 1 | # Log 2 | 3 | TBS 4 | 5 | ## Source 6 | 7 | - [Constants](./constants.rb) 8 | - [Log](./log.rb) 9 | - [VerdictAssertions](./verdict_assertion.rb) 10 | - [VerdictBoolean](./verdict_boolean.rb) 11 | - [VerdictInteger](./verdict_integer.rb) 12 | - [VerdictString](./verdict_string.rb) 13 | -------------------------------------------------------------------------------- /lib/log/constants.rb: -------------------------------------------------------------------------------- 1 | require 'contracts' 2 | 3 | include Contracts 4 | 5 | # Custom contract type for verdict id. 6 | VERDICT_ID = Or[Symbol, Array] 7 | # Custom contract type for verdict message. 8 | VERDICT_MESSAGE = Maybe[Or[Symbol, String]] 9 | # Custom contract type for verdict volatility. 10 | VERDICT_VOLATILE = Maybe[Bool] 11 | # Custom contract type for put_element args. 12 | ARGS = Args[Any] 13 | -------------------------------------------------------------------------------- /lib/log/verdict_boolean.rb: -------------------------------------------------------------------------------- 1 | require_relative 'constants' 2 | 3 | module VerdictBoolean 4 | 5 | # TODO: Create test for this module. 6 | # TODO: Create examples for this module. 7 | 8 | Contract VERDICT_ID, Any, VERDICT_MESSAGE, VERDICT_VOLATILE => Bool 9 | # \Log a verdict asserting a boolean. 10 | def verdict_assert_boolean?(verdict_id, actual, message: nil, volatile: false) 11 | boolean_classes = [ 12 | TrueClass, 13 | FalseClass, 14 | ] 15 | va_includes?(verdict_id, boolean_classes, actual.class, message: message, volatile: volatile) 16 | end 17 | alias va_boolean? verdict_assert_boolean? 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/log/verdict_integer.rb: -------------------------------------------------------------------------------- 1 | require_relative 'constants' 2 | 3 | module VerdictInteger 4 | 5 | # TODO: Create test for this module. 6 | # TODO: Create examples for this module. 7 | 8 | Contract VERDICT_ID, Any, VERDICT_MESSAGE, VERDICT_VOLATILE => Bool 9 | # \Log a verdict asserting a positive integer. 10 | def verdict_assert_integer_positive?(verdict_id, actual, message: nil, volatile: false) 11 | passed = true 12 | section(__method__.to_s) do 13 | passed = va_kind_of?([verdict_id, :integer], Integer, actual, message: message, volatile: volatile) && passed 14 | passed = va_operator?([verdict_id, :positive], actual, :>, 0, message: message, volatile: volatile) && passed 15 | end 16 | passed 17 | end 18 | alias va_integer_positive? verdict_assert_integer_positive? 19 | 20 | end -------------------------------------------------------------------------------- /lib/log/verdict_range.rb: -------------------------------------------------------------------------------- 1 | require_relative 'constants' 2 | 3 | module VerdictRange 4 | 5 | # TODO: Create test for this module. 6 | # TODO: Create examples for this module. 7 | 8 | Contract VERDICT_ID, Range, Any, VERDICT_MESSAGE, VERDICT_VOLATILE => Bool 9 | # \Log a verdict asserting range membership. 10 | def verdict_assert_in_range?(verdict_id, range, actual, message: nil, volatile: false) 11 | passed = nil 12 | section('Value in range') do 13 | put_element('exp_range', range) 14 | put_element('act_value', actual) 15 | passed = va?(verdict_id, range.include?(actual), message: message, volatile: volatile) 16 | end 17 | passed 18 | end 19 | alias va_in_range? verdict_assert_in_range? 20 | 21 | end -------------------------------------------------------------------------------- /lib/log/verdict_string.rb: -------------------------------------------------------------------------------- 1 | require_relative 'constants' 2 | 3 | module VerdictString 4 | 5 | # TODO: Create test for this module. 6 | # TODO: Create examples for this module. 7 | 8 | Contract VERDICT_ID, Any, VERDICT_MESSAGE, VERDICT_VOLATILE => Bool 9 | # \Log a verdict refuting an empty string. 10 | def verdict_assert_string_not_empty?(verdict_id, actual, message: nil, volatile: false) 11 | passed = true 12 | section(__method__.to_s) do 13 | passed = va_kind_of?([verdict_id, :string], String, actual, message: message, volatile: volatile) && passed 14 | passed = vr_empty?([verdict_id, :not_empty], actual, message: message, volatile: volatile) && passed 15 | end 16 | passed 17 | end 18 | alias va_string_not_empty? verdict_assert_string_not_empty? 19 | 20 | Contract VERDICT_ID, Range, Any, VERDICT_MESSAGE, VERDICT_VOLATILE => Bool 21 | def verdict_assert_string_length_in_range?(verdict_id, range, actual, message: nil, volatile: false) 22 | passed = true 23 | section(__method__.to_s) do 24 | passed = va_kind_of?([verdict_id, :string], String, actual, message: message, volatile: volatile) && passed 25 | passed = verdict_assert_in_range?([verdict_id, :in_range], range, actual.size, message: message, volatile: volatile) && passed 26 | end 27 | passed 28 | end 29 | alias va_string_length_in_range? verdict_assert_string_length_in_range? 30 | 31 | end 32 | -------------------------------------------------------------------------------- /rakefiles/build.rake: -------------------------------------------------------------------------------- 1 | namespace :build do 2 | 3 | require 'rdoc' 4 | 5 | desc 'Build RDoc' 6 | RDoc::Task.new :rdoc do |rdoc| 7 | # For some reason, RubyMine thinks it cannot 'resolve' the quoted string, so this pragma. 8 | # noinspection RubyResolve 9 | rdoc.rdoc_files.include('*.rb') 10 | rdoc.title = 'RubyTest' 11 | end 12 | 13 | desc 'Build markdown table of contents' 14 | task :markdown_toc do 15 | path = File.join( 16 | File.dirname(__FILE__), 17 | '..', 18 | 'lib', 19 | 'helpers', 20 | 'markdown_helper.rb', 21 | ) 22 | command = "ruby -r #{path} -e MarkdownHelper.build_toc" 23 | system(command) 24 | end 25 | 26 | desc 'Build markdown for Rakefile' 27 | task :markdown_for_rakefile do 28 | Dir.chdir(File.join(File.dirname(__FILE__), '..')) do |_| 29 | output = `rake -D` 30 | output.gsub!("\n ", "\n\n ") 31 | File.open('Rakefile.md', 'w') do |file| 32 | file.puts < %w/ 84 | build:markdown_for_gemfile 85 | build:markdown_for_rakefile 86 | build:markdown_toc 87 | build:rerdoc 88 | / 89 | 90 | end 91 | -------------------------------------------------------------------------------- /rakefiles/examples.rake: -------------------------------------------------------------------------------- 1 | require_relative '../lib/helpers/test_helper' 2 | require_relative '../lib/helpers/time_helper' 3 | 4 | namespace :examples do 5 | 6 | desc 'Run all examples' 7 | task :all do 8 | rakefile_dir_path = File.dirname(__FILE__) 9 | %w/ 10 | changes_report 11 | log 12 | rest_api 13 | /.each do |examples_dir_name| 14 | TestHelper.create_app_log_dir(examples_dir_name) 15 | rake_file_path = File.absolute_path(File.join( 16 | rakefile_dir_path, 17 | '..', 18 | 'examples', 19 | examples_dir_name, 20 | 'Rakefile', 21 | )) 22 | command = format('rake -f %s examples', rake_file_path) 23 | system(command) 24 | end 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /rakefiles/test.rake: -------------------------------------------------------------------------------- 1 | namespace :test do 2 | 3 | TEST_DIR = File.join( 4 | File.dirname(__FILE__), 5 | 'test', 6 | ) 7 | 8 | def autorun(test_file_path) 9 | path = File.absolute_path(File.join( 10 | TEST_DIR, 11 | test_file_path, 12 | )) 13 | command = "ruby #{path}" 14 | exit unless system(command) 15 | end 16 | 17 | desc 'Test class Configuration' 18 | task :configuration do 19 | autorun(File.join( 20 | '..', 21 | '..', 22 | 'test', 23 | 'configuration_test.rb', 24 | ) 25 | ) 26 | end 27 | 28 | desc 'Test class DebugHelper' 29 | task :debug_helper do 30 | autorun(File.join( 31 | '..', 32 | '..', 33 | 'test', 34 | 'helpers', 35 | 'debug_helper_test.rb', 36 | ) 37 | ) 38 | end 39 | 40 | desc 'Test class HashHelper' 41 | task :hash_helper do 42 | autorun(File.join( 43 | '..', 44 | '..', 45 | 'test', 46 | 'helpers', 47 | 'hash_helper_test.rb', 48 | ) 49 | ) 50 | end 51 | 52 | desc 'Test class Log' 53 | task :log do 54 | autorun(File.join( 55 | '..', 56 | '..', 57 | 'test', 58 | 'log_test.rb', 59 | ) 60 | ) 61 | end 62 | 63 | desc 'Test class SetHelper' 64 | task :set_helper do 65 | autorun(File.join( 66 | '..', 67 | '..', 68 | 'test', 69 | 'helpers', 70 | 'set_helper_test.rb', 71 | ) 72 | ) 73 | end 74 | 75 | desc 'Test class StringHelper' 76 | task :string_helper do 77 | autorun(File.join( 78 | '..', 79 | '..', 80 | 'test', 81 | 'helpers', 82 | 'string_helper_test.rb', 83 | ) 84 | ) 85 | end 86 | 87 | desc 'Test class ValuesHelper' 88 | task :values_helper do 89 | autorun(File.join( 90 | '..', 91 | '..', 92 | 'test', 93 | 'helpers', 94 | 'values_helper_test.rb', 95 | ) 96 | ) 97 | end 98 | 99 | desc 'Test all classes' 100 | task :all => %w/ 101 | test:configuration 102 | test:hash_helper 103 | test:log 104 | test:set_helper 105 | test:string_helper 106 | test:values_helper 107 | / 108 | 109 | end 110 | -------------------------------------------------------------------------------- /test/Test.md: -------------------------------------------------------------------------------- 1 | # Unit Tests 2 | 3 | (This page is about the testing for RubyTest itself -- unit testing -- not about using RubyTest to test other software.) 4 | 5 | ## Assertions, Not Verdicts 6 | 7 | The unit tests use assertions (which halt the testing), not verdicts (which do not). 8 | 9 | That's because the unit tests function as build-verification tests, which must pass before every merge. 10 | 11 | ## Log 12 | 13 | The largest part of the testing here is for one class: Log. 14 | 15 | - [log_test.rb](./log_test.rb) 16 | 17 | ## Helpers 18 | 19 | Tests for helper classes include: 20 | 21 | - [hash_helper_test.rb](./helpers/hash_helper_test.rb) 22 | - [set_helper_test.rb](./helpers/set_helper_test.rb) 23 | - [string_helper_test.rb](./helpers/string_helper_test.rb) 24 | - [values_helper_test.rb](./helpers/values_helper_test.rb) 25 | -------------------------------------------------------------------------------- /test/assertion_helper.rb: -------------------------------------------------------------------------------- 1 | require 'contracts' 2 | 3 | # Class of convenience methods for assertions. 4 | class AssertionHelper 5 | 6 | include Contracts 7 | 8 | Contract Minitest::Test, Or[String, Proc], Maybe[Proc] => Bool 9 | # The tools we're using lack assert_nothing_raised. 10 | # - +test+: +Minitest::Test+ object, to make assertions available. 11 | # - +message+: Message for assertion. 12 | def self.assert_nothing_raised(test, message = 'Nothing raised') 13 | x = nil 14 | begin 15 | yield 16 | rescue => y 17 | x = y 18 | end 19 | test.assert_nil(x, message: message) 20 | end 21 | 22 | Contract Minitest::Test, Any, String, Proc => Bool 23 | # +assert_raises+, but with a message. 24 | # - +test+: +Minitest::Test+ object, to make assertions available. 25 | # - +expected_class+: exception class expected. 26 | # - +expected_message+: message expected. 27 | def self.assert_raises_with_message(test, expected_class, expected_message) 28 | x = test.assert_raises(expected_class) do 29 | yield 30 | end 31 | test.assert_equal(expected_message, x.message, 'Exception message') 32 | end 33 | 34 | # Format for contract violation message; used here and in self tests. 35 | CONTRACT_VIOLATION_MSG_FMT = 'Contract violation for argument %d of %d' 36 | 37 | Contract Num, Num => String 38 | # Return message for contract violation. 39 | # - +argument_index+: 0-based index of violated argument. 40 | # - +argument_count+: count of arguments in contract. 41 | def self.contract_violation_message(argument_index, argument_count) 42 | format(CONTRACT_VIOLATION_MSG_FMT, 1 + argument_index, argument_count) 43 | end 44 | 45 | Contract Minitest::Test, Num, Num, Proc => nil 46 | # Assert a contract violation. 47 | # - +test+: +Minitest::Test+ object, to make assertions available. 48 | # - +argument_index+: 0-based index of violated argument. 49 | # - +argument_count+: count of arguments in contract. 50 | def self.assert_contract_violation(test, argument_index, argument_count) 51 | x = test.assert_raises ParamContractError do 52 | yield 53 | end 54 | expected_message = format(CONTRACT_VIOLATION_MSG_FMT, 1 + argument_index, argument_count) 55 | test.assert_match(expected_message, x.message, 'Error message') 56 | nil 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /test/common_requires.rb: -------------------------------------------------------------------------------- 1 | require 'contracts' 2 | require 'minitest/autorun' 3 | 4 | require_relative 'assertion_helper' 5 | require_relative '../lib/log/log' 6 | -------------------------------------------------------------------------------- /test/configuration_test.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | require_relative 'common_requires' 4 | 5 | require_relative '../lib/configuration' 6 | 7 | # Class to test class +Configuration+. 8 | class ConfigurationTest < MiniTest::Test 9 | 10 | def test_all 11 | 12 | dir_path = Dir.mktmpdir 13 | 14 | main_file_name = 'main.yaml' 15 | main_file_path = File.join( 16 | dir_path, 17 | main_file_name, 18 | ) 19 | include_file_name = 'include.yaml' 20 | include_file_path = File.join( 21 | dir_path, 22 | include_file_name, 23 | ) 24 | more_file_name = 'more.yaml' 25 | more_file_path = File.join( 26 | dir_path, 27 | more_file_name, 28 | ) 29 | 30 | # Yaml with included files. 31 | main_yaml = < 'whatever', 79 | :bar => [ 80 | { 81 | :fruit => 'apple', 82 | :name => 'steve', 83 | :sport => 'baseball', 84 | }, 85 | 'more', 86 | { 87 | :python => 'rocks', 88 | :perl => 'papers', 89 | :ruby => 'scissors' 90 | } 91 | ], 92 | :languages => %w/ 93 | Ruby 94 | Perl 95 | Python 96 | /, 97 | :websites => { 98 | :YAML => 'yaml.org', 99 | :Ruby => 'ruby-lang.org', 100 | :Python => 'python.org', 101 | :Perl => 'use.perl.org', 102 | }, 103 | :zero => 0, 104 | :simple => 12, 105 | :'one-thousand' => 1000, 106 | :'negative one-thousand' => -1000 107 | } 108 | 109 | config = Configuration.new(main_file_path) 110 | actual_data = config.get 111 | 112 | # Verify the full configuration. 113 | self.assert_equal(expected_data, actual_data, 'Full configuration') 114 | 115 | # Verify levelled gets. 116 | expected_websites = expected_data[:websites] 117 | actual_websites = config.get('websites') 118 | self.assert_equal(expected_websites, actual_websites, 'Node websites') 119 | expected_ruby = expected_websites[:Ruby] 120 | actual_ruby = config.get('Ruby', actual_websites) 121 | self.assert_equal(expected_ruby, actual_ruby, 'Node Ruby') 122 | 123 | # Verify get with multi-node path. 124 | expected_ruby = expected_data[:websites][:Ruby] 125 | actual_ruby = config.get('websites/Ruby') 126 | self.assert_equal(expected_ruby, actual_ruby, 'Node Ruby') 127 | 128 | FileUtils.rm_r(dir_path) 129 | 130 | end 131 | 132 | end 133 | 134 | 135 | -------------------------------------------------------------------------------- /test/helpers/debug_helper_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../common_requires' 2 | 3 | # Class to test class +DebugHelper+. 4 | class DebugHelperTest < MiniTest::Test 5 | 6 | def test_printf 7 | 8 | # Does not verify output; just ensures successful execution. 9 | 10 | hash = {} 11 | DebugHelper.printf(hash) 12 | hash = {:a => 0, :b => 1, :c => 2} 13 | DebugHelper.printf(hash) 14 | 15 | DebugHelper.printf(hash, 'My name', 'My description') 16 | 17 | array = [] 18 | DebugHelper.printf(array) 19 | array = [:a, :b, :c] 20 | DebugHelper.printf(array) 21 | 22 | set = Set.new([]) 23 | DebugHelper.printf(set) 24 | set = Set.new([:a, :b, :c]) 25 | DebugHelper.printf(set) 26 | 27 | DebugHelper.printf(nil) 28 | 29 | DebugHelper.printf('') 30 | DebugHelper.printf('foo') 31 | 32 | DebugHelper.printf(0) 33 | 34 | DebugHelper.printf(:a) 35 | 36 | DebugHelper.printf(true) 37 | 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /test/helpers/hash_helper_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../common_requires' 2 | 3 | require_relative '../../lib/helpers/hash_helper' 4 | 5 | # Class to test class +HashHelper+. 6 | class HashHelperTest < MiniTest::Test 7 | 8 | def test_compare 9 | 10 | # Contract violation for expected hash arg. 11 | assert_raises ParamContractError do 12 | HashHelper.compare(nil, {}) 13 | end 14 | 15 | # Contract violation for actual hash arg. 16 | assert_raises ParamContractError do 17 | HashHelper.compare({}, nil) 18 | end 19 | 20 | # General case. 21 | expected_missing = { 22 | :a => 0, 23 | :b => 1, 24 | } 25 | expected_unexpected = { 26 | :c => 2, 27 | :d => 3, 28 | } 29 | expected_changed = { 30 | :e => {:expected => 5, :actual => 6}, 31 | :f => {:expected => 6, :actual => 5}, 32 | } 33 | expected_ok = { 34 | :g => 7, 35 | :h => 8, 36 | } 37 | # Build the arguments.\\ hashes, expected and actual. 38 | expected = {} 39 | actual = {} 40 | expected_missing.each_pair do |key, value| 41 | expected[key] = value 42 | end 43 | expected_unexpected.each_pair do |key, value| 44 | actual[key] = value 45 | end 46 | expected_ok.each_pair do |key, value| 47 | expected[key] = value 48 | actual[key] = value 49 | end 50 | expected_changed.each_pair do |key, value| 51 | expected[key] = value[:expected] 52 | actual[key] = value[:actual] 53 | end 54 | # Call and assert. 55 | actual = HashHelper.compare(expected, actual) 56 | # Use fetch, not [], so that key errors would be raised. 57 | actual_missing = actual.fetch(:missing) 58 | actual_unexpected = actual.fetch(:unexpected) 59 | actual_changed = actual.fetch(:changed) 60 | actual_ok = actual.fetch(:ok) 61 | assert_equal(expected_missing, actual_missing, 'expected only') 62 | assert_equal(expected_unexpected, actual_unexpected, 'actual only') 63 | assert_equal(expected_changed, actual_changed, 'changed') 64 | assert_equal(expected_ok, actual_ok, 'same') 65 | end 66 | 67 | end -------------------------------------------------------------------------------- /test/helpers/set_helper_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../common_requires' 2 | 3 | require_relative '../../lib/helpers/set_helper' 4 | 5 | # Class to test class +SetHelper+. 6 | class SetHelperTest < MiniTest::Test 7 | 8 | def test_compare 9 | 10 | # Contract violation for expected set arg. 11 | assert_raises ParamContractError do 12 | SetHelper.compare(nil, {}) 13 | end 14 | 15 | # Contract violation for actual set arg. 16 | assert_raises ParamContractError do 17 | SetHelper.compare({}, nil) 18 | end 19 | 20 | # General case. 21 | expected_missing = Set.new([:a, :b]) 22 | expected_unexpected = Set.new([:c, :d]) 23 | expected_ok = Set.new([:e, :f]) 24 | # Build the argument sets, expected and actual. 25 | expected = Set.new 26 | actual = Set.new 27 | expected_missing.each do |value| 28 | expected.add(value) 29 | end 30 | expected_unexpected.each do |value| 31 | actual.add(value) 32 | end 33 | expected_ok.each do |value| 34 | expected.add(value) 35 | actual.add(value) 36 | end 37 | # Call and assert. 38 | actual = SetHelper.compare(expected, actual) 39 | # Use fetch, not [], so that key errors would be raised. 40 | actual_missing = actual.fetch(:missing) 41 | actual_unexpected = actual.fetch(:unexpected) 42 | actual_ok = actual.fetch(:ok) 43 | assert_equal(expected_missing, actual_missing, 'expected only') 44 | assert_equal(expected_unexpected, actual_unexpected, 'actual only') 45 | assert_equal(expected_ok, actual_ok, 'same') 46 | end 47 | 48 | end --------------------------------------------------------------------------------