Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compare request by score #9

Merged
merged 7 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .ameba.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This configuration file was generated by `ameba --gen-config`
# on 2022-12-03 11:45:30 UTC using Ameba version 1.3.1.
# The point is for the user to remove these configuration records
# one by one as the reported problems are removed from the code base.

# Problems found: 1
# Run `ameba --only Metrics/CyclomaticComplexity` for details
Metrics/CyclomaticComplexity:
Description: Disallows methods with a cyclomatic complexity higher than `MaxComplexity`
MaxComplexity: 15
Excluded:
- src/replay/http/request.cr
Enabled: true
Severity: Convention
91 changes: 86 additions & 5 deletions spec/replay/http/request_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,56 @@ describe HTTPRequest do
actual_metadatas["indexed"]["body"].should eq("")
actual_metadatas["not_indexed"]["body"].should eq("HELLO")
end

it "can compare json proeprties" do
headers = HTTP::Headers{
"Content-Type" => "application/json",
}
json = %q{
{
"foo":"bar",
"baz":"qux",
"hoge": {
"fuga": 1,
"moge":"bar",
"baz" : [1,3,2]
}
}
}

another_json = %q{
{
"hoge": {
"fuga": 1
}
}
}
request = HTTP::Request.new("POST", "/hello", headers, json)
request1 = HTTPRequest.new(request, URI.parse "http://base.uri")
request2 = HTTPRequest.new(id: "foo", base_uri: URI.parse("http://base.uri"), path: "/hello", method: "POST", headers: headers.to_h, body: another_json, params: {} of String => String)
(request2.score(request1)).should eq 1
end

it "can get multiple properties via server request" do
headers = HTTP::Headers{"Content-Type" => "text/plain"}
request = HTTP::Request.new("POST", "/hello", headers, "HELLO")
actual = HTTPRequest.new(request, URI.parse "http://base.uri")
# Write to response
actual.host_name.should eq("base.uri")
actual.path.should eq("/hello")
actual.method.should eq("POST")
actual.body.should eq("HELLO")
actual.body.should eq("HELLO")
actual.headers["Content-Type"][0].should eq("text/plain")
actual.base_index.should eq("ce3e48c460a779f1554cd6e845a5fadf8e2c9f3b5126c65207294508e2592f6e")
actual_metadatas = actual.metadatas
actual_metadatas["host"].should eq("base.uri")
actual_metadatas["method"].should eq("POST")
actual_metadatas["path"].should eq("/hello")
actual_metadatas["indexed"]["body"].should eq("")
actual_metadatas["not_indexed"]["body"].should eq("HELLO")
end

it "can compare json proeprties" do
headers = HTTP::Headers{
"Content-Type" => "application/json",
Expand Down Expand Up @@ -47,8 +97,38 @@ describe HTTPRequest do
request = HTTP::Request.new("POST", "/hello", headers, json)
request1 = HTTPRequest.new(request, URI.parse "http://base.uri")
request2 = HTTPRequest.new(id: "foo", base_uri: URI.parse("http://base.uri"), path: "/hello", method: "POST", headers: headers.to_h, body: another_json, params: {} of String => String)
(request2 == request1).should be_true
(request2.score(request1)).should eq 2
end

it "can compare json proeprties" do
headers = HTTP::Headers{
"Content-Type" => "application/json",
}
json = %q{
{
"foo":"bar",
"baz":"qux",
"hoge": {
"fuga": 1,
"moge":"bar",
"baz" : [1,3,2]
}
}
}

another_json = %q{
{
"hoge": {
"fuga": 1
}
}
}
request = HTTP::Request.new("POST", "/hello", headers, json)
request1 = HTTPRequest.new(request, URI.parse "http://base.uri")
request2 = HTTPRequest.new(id: "foo", base_uri: URI.parse("http://base.uri"), path: "/bye", method: "POST", headers: headers.to_h, body: another_json, params: {} of String => String)
(request2.score(request1)).should eq -1
end

it "can not compare json proeprties" do
headers = HTTP::Headers{
"Content-Type" => "application/json",
Expand Down Expand Up @@ -76,8 +156,9 @@ describe HTTPRequest do
request = HTTP::Request.new("POST", "/hello", headers, json)
request1 = HTTPRequest.new(request, URI.parse "http://base.uri")
request2 = HTTPRequest.new(id: "foo", base_uri: URI.parse("http://base.uri"), path: "/hello", method: "POST", headers: headers.to_h, body: another_json, params: {} of String => String)
(request2 == request1).should be_false
(request2.score(request1)).should eq -1
end

it "can compare form proeprties" do
headers = HTTP::Headers{
"Content-Type" => "application/x-www-form-urlencoded",
Expand All @@ -88,7 +169,7 @@ describe HTTPRequest do
request = HTTP::Request.new("POST", "/hello", headers, form)
request1 = HTTPRequest.new(request, URI.parse "http://base.uri")
request2 = HTTPRequest.new(id: "foo", base_uri: URI.parse("http://base.uri"), path: "/hello", method: "POST", headers: headers.to_h, body: another_form, params: {} of String => String)
(request2 == request1).should be_true
(request2.score(request1)).should eq 2
end
it "can not compare form proeprties" do
headers = HTTP::Headers{
Expand All @@ -98,7 +179,7 @@ describe HTTPRequest do
another_form = "foo=baz&fuga=1"
request = HTTP::Request.new("POST", "/hello", headers, form)
request1 = HTTPRequest.new(request, URI.parse "http://base.uri")
request2 = HTTPRequest.new(id: "foo", base_uri: URI.parse("http://base.uri"), path: "/hello", method: "POST", headers: headers.to_h, body: another_form, params: {} of String => String)
(request2 == request1).should be_false
request2 = HTTPRequest.new(id: "foo", base_uri: URI.parse("http://base.uri"), path: "/hello_foo", method: "POST", headers: headers.to_h, body: another_form, params: {} of String => String)
(request2.score(request1)).should eq -1
end
end
8 changes: 6 additions & 2 deletions spec/support/mocks.cr
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,12 @@ class MockRequest
@base_index
end

def ==(other : Request)
self.hash == other.hash
def score(other : Request) : Int32
if self.hash == other.hash
1
else
-1
end
end

def proxy
Expand Down
2 changes: 1 addition & 1 deletion src/replay.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ require "./cli"

# TODO: Write documentation for `Replay`
module Replay
VERSION = "0.2.0"
VERSION = "0.2.4"
end
16 changes: 11 additions & 5 deletions src/replay/datasource/filesystem.cr
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ class FileSystemDatasource
Replay::Log.debug { "No index_file avairable." }
NoIndexFound.new(meta_index)
else
found_index_file = index_files.find do |index_file|
Replay::Log.debug { "#{index_files.size} index_file are avairable." }
most_matched_index_file_path : String? = index_files.map do |index_file|
Replay::Log.debug { "Parsing: #{index_file}" }
candidate = @requests.from(JSON.parse(File.read(index_file)))
candidate == request
end
found_index_file.try do |found|
Replay::Log.debug { "Found index_file path: #{found}" }
scored = candidate.score(request)
Replay::Log.debug { "Score: #{scored} for #{index_file}" }
{scored, index_file}
end.max_by? do |t|
t[0]
end.try &.[1]
most_matched_index_file_path.try do |found|
Replay::Log.debug { "Most matched index_file path: #{found}" }
found_index = @requests.from(JSON.parse(File.read(found)))
body_file = Dir["#{@reply_file_dir}/#{found_index.id_index}"].first?
header_file = Dir["#{@reply_file_dir}/#{found_index.id_index}_headers"].first?
Expand Down
4 changes: 2 additions & 2 deletions src/replay/errors.cr
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ struct NoResourceFound
end

struct CorruptedReplayResource
getter index
getter message, index

def initialize(@index : String)
def initialize(@message : String, @index : String? = nil)
end
end
2 changes: 1 addition & 1 deletion src/replay/http/errors.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class HTTPErrorHandler
message = "Not recorded yet : No resource found : #{error.index}"
when CorruptedReplayResource
response.status_code = 500
message = "Broken resource : #{error.index}"
message = "Broken resource : #{error.message} -> #{error.index}"
else
response.status_code = 500
end
Expand Down
66 changes: 40 additions & 26 deletions src/replay/http/request.cr
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,24 @@ class HTTPRequest
"#{base_index}_#{@id}"
}

def ==(other : Request) : Bool
def score(other : Request) : Int32
Replay::Log.debug { "Comparing : #{self.base_index} and #{other.base_index}." }
case other
when HTTPRequest
# TODO: Plaggable comparators
other.base_index == self.base_index &&
match_headers(self, other) &&
(self.body.empty? || self.body == other.body || match_json(self, other) || match_form(self, other))
if (other.base_index != self.base_index || !match_headers(self, other))
-1
else
# TODO: Plaggable comparators
if self.body.empty?
0
elsif self.body == other.body
1024
else
match_json(self, other) + match_form(self, other)
end
end
else
false
-1
end
end

Expand All @@ -90,46 +98,52 @@ class HTTPRequest
(i.params.empty? || i.params.find { |k, v| !other.params[k] || other.params[k] != v } == nil)
end

private def match_json(i : Request, other : Request) : Bool
private def match_json(i : Request, other : Request) : Int32
me = JSON.parse i.body
another = JSON.parse other.body
match_json_internal me, another
rescue e : JSON::ParseException
false
0
end

private def match_json_internal(me : JSON::Any, other : JSON::Any) : Bool
me.as_h.keys.find do |key|
private def match_json_internal(me : JSON::Any, other : JSON::Any) : Int32
me.as_h.keys.reduce(0) do |acc, key|
case value = other[key]
when .as_s?
value != me[key].as_s?
value == me[key].as_s? ? acc + 1 : return -1
when .as_i?
value != me[key].as_i?
value == me[key].as_i? ? acc + 1 : return -1
when .as_bool?
value != me[key].as_bool?
value == me[key].as_bool? ? acc + 1 : return -1
when .as_a?
value != me[key].as_a?
value == me[key].as_a? ? acc + 1 : return -1
when .as_f?
value != me[key].as_f?
value == me[key].as_f? ? acc + 1 : return -1
when .as_h?
me[key].as_h?.try do |_|
match_json_internal me[key], value
end ? nil : value
child = match_json_internal me[key], value
child == -1 ? return -1 : child + acc
end || acc
else
acc
end
end == nil
end || 0
end

private def match_form(i : Request, other : Request) : Bool
private def match_form(i : Request, other : Request) : Int32
me = split_form(i.body)
another = split_form(other.body)
me.keys.find do |k|
!another.keys.includes?(k)
end == nil &&
me.find do |k, v|
another[k]?.try do |a|
v == nil || a != v
begin
me.keys.reduce(0) do |acc, key|
if me[key] == another[key]
acc + 1
else
return -1
end
end == nil
end
rescue e : KeyError
0
end
end

private def split_form(body)
Expand Down
2 changes: 1 addition & 1 deletion src/replay/request.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Request
def base_index : String
end

def ==(other : Request) : Bool
def score(other : Request) : Int32
end

def proxy
Expand Down