Last active
October 21, 2019 05:56
-
-
Save xta0/d6d841b8c27e45ca60a7571fdd654983 to your computer and use it in GitHub Desktop.
Validate Mock Tail Format
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #encoding:utf-8 | |
| require 'json' | |
| require 'ostruct' | |
| =begin | |
| mock_validation.rb | |
| created by Tao Xu on 02/08/2019 | |
| taoxu.dev@gmail.com | |
| This python script is used for validating the mock tail file format. | |
| Documentation for the mock tail format, please see: | |
| [1] "https://github.com/puls/objc-mocktail" | |
| [2] "https://github.com/AliSoftware/OHHTTPStubs/blob/master/OHHTTPStubs/Sources/Mocktail/OHHTTPStubs+Mocktail.m" | |
| The valid mock file should have the format like this: | |
| ---------------------------- | |
| | HTTP method | |
| | regular expression for HTTP URL | |
| | HTTP Status Code | |
| | value of "Content-Type" attribute (bare content without "Content-Type" keyword is also allowed) | |
| | HTTP header attributes(optional) | |
| | (at least one blank line between the header section and the payload section) | |
| | valid payload (could be JSON/image/text) | |
| ---------------------------- | |
| Few RULEs to be noticed: | |
| [1]. The mock file should contain at least 4 lines including HTTP method, HTTP URL, HTTP status code and "Content-Type" for HTTP header. | |
| [2]. Leading blank lines should be deleted | |
| [3]. The regular expression for matching HTTP URL is not very strict, at least one '/' is required. | |
| [4]. For the "Content-Type, only 'image', 'text' and 'application' are covered; bare content without the "Content-Type" keyword is allowed. | |
| [5]. For the HTTP header attributes, the function only validates the format, requires a ":" in the middle, and at least one space after ":". | |
| [6]. At least one blank line between the header section and the payload section is required | |
| =end | |
| doc = { | |
| :invalid_leading_blank_lines => """ | |
| | Leading blank lines should be deleted | |
| """, | |
| :invalid_number_of_lines => """ | |
| | The mock file should contain at least 4 lines | |
| """, | |
| :invalid_http_method => """ | |
| | Invalid HTTP method. Possible reasons: | |
| [1] All letters should be uppercase | |
| [2] Available HTTP methods are ['GET', 'POST', 'HEAD', 'POST', 'PUT', 'DELETE'] | |
| """, | |
| :invalid_http_code => """ | |
| | Invalid HTTP status code. Possible reasons: | |
| [1] The HTTP status code should contain an exact 3 digital numbers | |
| """, | |
| :invalid_http_url => """ | |
| | Invalid HTTP URL, Possible reasons: | |
| [1] The The URL should at least contain one | |
| """, | |
| :invalid_http_content_type => """ | |
| | Invalid value/format for HTTP Content-Type. Possible reasons: | |
| [1] line #4 should either contain the value of the 'Content-Type' or the normal key value pair of the HTTP header's attribute. | |
| [2] Available content types are ['application/', 'image/', 'text/''] | |
| """, | |
| :invalid_http_header => """ | |
| | Invalid format for HTTP header. Possible reasons: | |
| [1] Missing ':' | |
| [2] At least one space is required after ':' | |
| [3] First letter of the attribute's name should be capitalized | |
| """, | |
| :invalid_blank_line_seperator => """ | |
| | The mock file should have at least 1 blank line between the header section and the payload section | |
| """, | |
| :invalid_json_payload => """ | |
| | Invalid JSON payload format. Use \033[94m 'https://jsonlint.com/' \033[0m to see more details | |
| """, | |
| :none => "" | |
| } | |
| def validate(path) | |
| lines = [] | |
| f = File.open(path, "r") | |
| f.each_line { |line| lines.push(line.strip) } | |
| f.close() | |
| filename = path | |
| if ( lines[0].length == 0 ) | |
| return { :code => :invalid_leading_blank_lines, :filename => filename, :lineno => -1 } | |
| end | |
| if lines.count < 4 | |
| return { :code => :invalid_number_of_lines, :filename => filename, :lineno => -1 } | |
| end | |
| ## line[0]: HTTP method | |
| method = lines[0] | |
| regex = /\|?(GET|POST|PUT|DELETE|HEAD)+/ | |
| if not regex.match(method) | |
| return { :code => :invalid_http_method, :filename => filename, :lineno => 0 } | |
| end | |
| ## line[1]: HTTP URL | |
| url = lines[1] | |
| regex = /^(.*)\/(.*)(:\w+)?$/ | |
| if not regex.match(url) | |
| return { :code => :invalid_http_url, :filename => filename,:lineno => 1 } | |
| end | |
| ## line[2]: HTTP status code | |
| code = lines[2] | |
| regex = /^[0-9]{3}$/ | |
| if not regex.match(code) | |
| return { :code => :invalid_http_code, :filename => filename, :lineno => 2 } | |
| end | |
| ## line[3]: check the "Content-Type" | |
| content_type = lines[3] | |
| ## try to match the bare content | |
| regex = /^(application|image|text)(\/[\w\+.\-;=]+)*$/ | |
| if not regex.match(content_type) | |
| # if failed , try matching HTTP header | |
| regex = /^([A-Za-z]+(\-)?)+\s*:\s+.+$/ | |
| if not regex.match(content_type) | |
| return { :code => :invalid_http_content_type, :filename => filename, :lineno => 3 } | |
| end | |
| end | |
| ## From line[4] to the first blank line, expect HTTP header | |
| regex = /^([A-Za-z]*(\-)?)+\s*:\s+.+$/ | |
| i = 4 | |
| while i < lines.count | |
| if (lines[i].length > 0) | |
| if not regex.match(lines[i]) | |
| if lines[i][0] == '{' | |
| return { :code => :invalid_blank_line_seperator, :filename => filename, :lineno => i } | |
| else | |
| return { :code => :invalid_http_header, :filename => filename, :lineno => i } | |
| end | |
| end | |
| else | |
| break | |
| end | |
| i += 1 | |
| end | |
| ## Skip the blank lines | |
| j = i | |
| while j < lines.count and lines[j].length == 0 | |
| j+=1 | |
| end | |
| ## If no appending payload, just return | |
| if j == lines.count | |
| return { :code => :none, :filename => filename, :lineno => -1 } | |
| end | |
| ## At least one blank line is required as a separator | |
| if j-i < 1 | |
| return { :code => :invalid_blank_line_seperator, :filename => filename, :lineno => j } | |
| end | |
| if content_type.include?("json") | |
| payload = "" | |
| while j < lines.count | |
| payload += lines[j] | |
| j+=1 | |
| end | |
| begin | |
| JSON.parse(payload) | |
| rescue | |
| return { :code => :invalid_json_payload, :filename => filename, :lineno => -1 } | |
| end | |
| end | |
| {:code => :none, :filename => path, :lineno => -1} | |
| end | |
| ## Search the mock files recursively in a depth-first order | |
| def dfs(path, results) | |
| if path.end_with?(".tail") | |
| results.push(validate(path)) | |
| return | |
| end | |
| ## skip "." / ".." | |
| if Dir.exist?(path) and not path.end_with?(".") | |
| for file in Dir.entries(path) do | |
| fullpath = path + "/" + file | |
| dfs(fullpath, results) | |
| end | |
| end | |
| end | |
| if ARGV.count == 0 | |
| puts "Please specify the folder path of the mock files" | |
| exit(false) | |
| end | |
| path = ARGV[0] | |
| if not Dir.exist?(path) | |
| puts "Directory for mock files is not exist" | |
| exit(false) | |
| end | |
| # convert the path to absolute path | |
| path = File.expand_path(path) | |
| start_time = Time.now | |
| results = [] | |
| dfs(path, results) | |
| exitcode = true | |
| fc = 0 | |
| for result in results do | |
| if result[:code] == :none | |
| next | |
| end | |
| exitcode = false | |
| fc += 1 | |
| linenostr = "" | |
| if result[:lineno] != -1 | |
| linenostr = "\n | line: #{result[:lineno]+1}" | |
| end | |
| puts "- \033[91m[Failed]\033[0m: #{result[:filename]} #{linenostr} #{doc[result[:code]]}" | |
| end | |
| puts "Done validating in #{Time.now - start_time} seconds. Found #{results.count} mock files, #{fc} failed" | |
| exit(exitcode) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment