Skip to content

Instantly share code, notes, and snippets.

@xta0
Last active October 21, 2019 05:56
Show Gist options
  • Select an option

  • Save xta0/d6d841b8c27e45ca60a7571fdd654983 to your computer and use it in GitHub Desktop.

Select an option

Save xta0/d6d841b8c27e45ca60a7571fdd654983 to your computer and use it in GitHub Desktop.
Validate Mock Tail Format
#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