Skip to content

Commit

Permalink
Add tests for API change rate limits
Browse files Browse the repository at this point in the history
  • Loading branch information
tomhughes committed Oct 30, 2023
1 parent bf0eba2 commit 8136360
Show file tree
Hide file tree
Showing 4 changed files with 402 additions and 0 deletions.
101 changes: 101 additions & 0 deletions test/controllers/api/changesets_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,107 @@ def test_upload_multiple_delete_block
assert_equal "Precondition failed: Node #{node.id} is still used by ways #{way.id}.", @response.body
end

##
# test initial rate limit
def test_upload_initial_rate_limit
# create a user
user = create(:user)

# create some objects to use
node = create(:node)
way = create(:way_with_nodes, :nodes_count => 2)
relation = create(:relation)

# create a changeset that puts us near the initial rate limit
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 5.minutes,
:num_changes => Settings.initial_changes_per_hour - 2)

# create authentication header
auth_header = basic_authorization_header user.email, "test"

# simple diff to create a node way and relation using placeholders
diff = <<~CHANGESET
<osmChange>
<create>
<node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
<tag k='foo' v='bar'/>
<tag k='baz' v='bat'/>
</node>
<way id='-1' changeset='#{changeset.id}'>
<nd ref='#{node.id}'/>
</way>
</create>
<create>
<relation id='-1' changeset='#{changeset.id}'>
<member type='way' role='some' ref='#{way.id}'/>
<member type='node' role='some' ref='#{node.id}'/>
<member type='relation' role='some' ref='#{relation.id}'/>
</relation>
</create>
</osmChange>
CHANGESET

# upload it
post changeset_upload_path(changeset), :params => diff, :headers => auth_header
assert_response :too_many_requests, "upload did not hit rate limit"
end

##
# test maximum rate limit
def test_upload_maximum_rate_limit
# create a user
user = create(:user)

# create some objects to use
node = create(:node)
way = create(:way_with_nodes, :nodes_count => 2)
relation = create(:relation)

# create a changeset to establish our initial edit time
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 28.days)

# create changeset to put us near the maximum rate limit
total_changes = Settings.max_changes_per_hour - 2
while total_changes.positive?
changes = [total_changes, Changeset::MAX_ELEMENTS].min
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 5.minutes,
:num_changes => changes)
total_changes -= changes
end

# create authentication header
auth_header = basic_authorization_header user.email, "test"

# simple diff to create a node way and relation using placeholders
diff = <<~CHANGESET
<osmChange>
<create>
<node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
<tag k='foo' v='bar'/>
<tag k='baz' v='bat'/>
</node>
<way id='-1' changeset='#{changeset.id}'>
<nd ref='#{node.id}'/>
</way>
</create>
<create>
<relation id='-1' changeset='#{changeset.id}'>
<member type='way' role='some' ref='#{way.id}'/>
<member type='node' role='some' ref='#{node.id}'/>
<member type='relation' role='some' ref='#{relation.id}'/>
</relation>
</create>
</osmChange>
CHANGESET

# upload it
post changeset_upload_path(changeset), :params => diff, :headers => auth_header
assert_response :too_many_requests, "upload did not hit rate limit"
end

##
# when we make some simple changes we get the same changes back from the
# diff download.
Expand Down
85 changes: 85 additions & 0 deletions test/controllers/api/nodes_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,91 @@ def test_string_injection
assert_includes apinode.tags, "\#{@user.inspect}"
end

##
# test initial rate limit
def test_initial_rate_limit
# create a user
user = create(:user)

# create a changeset that puts us near the initial rate limit
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 5.minutes,
:num_changes => Settings.initial_changes_per_hour - 1)

# create authentication header
auth_header = basic_authorization_header user.email, "test"

# try creating a node
xml = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
put node_create_path, :params => xml, :headers => auth_header
assert_response :success, "node create did not return success status"

# get the id of the node we created
nodeid = @response.body

# try updating the node, which should be rate limited
xml = "<osm><node id='#{nodeid}' version='1' lat='1' lon='1' changeset='#{changeset.id}'/></osm>"
put api_node_path(nodeid), :params => xml, :headers => auth_header
assert_response :too_many_requests, "node update did not hit rate limit"

# try deleting the node, which should be rate limited
xml = "<osm><node id='#{nodeid}' version='2' lat='1' lon='1' changeset='#{changeset.id}'/></osm>"
delete api_node_path(nodeid), :params => xml, :headers => auth_header
assert_response :too_many_requests, "node delete did not hit rate limit"

# try creating a node, which should be rate limited
xml = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
put node_create_path, :params => xml, :headers => auth_header
assert_response :too_many_requests, "node create did not hit rate limit"
end

##
# test maximum rate limit
def test_maximum_rate_limit
# create a user
user = create(:user)

# create a changeset to establish our initial edit time
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 28.days)

# create changeset to put us near the maximum rate limit
total_changes = Settings.max_changes_per_hour - 1
while total_changes.positive?
changes = [total_changes, Changeset::MAX_ELEMENTS].min
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 5.minutes,
:num_changes => changes)
total_changes -= changes
end

# create authentication header
auth_header = basic_authorization_header user.email, "test"

# try creating a node
xml = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
put node_create_path, :params => xml, :headers => auth_header
assert_response :success, "node create did not return success status"

# get the id of the node we created
nodeid = @response.body

# try updating the node, which should be rate limited
xml = "<osm><node id='#{nodeid}' version='1' lat='1' lon='1' changeset='#{changeset.id}'/></osm>"
put api_node_path(nodeid), :params => xml, :headers => auth_header
assert_response :too_many_requests, "node update did not hit rate limit"

# try deleting the node, which should be rate limited
xml = "<osm><node id='#{nodeid}' version='2' lat='1' lon='1' changeset='#{changeset.id}'/></osm>"
delete api_node_path(nodeid), :params => xml, :headers => auth_header
assert_response :too_many_requests, "node delete did not hit rate limit"

# try creating a node, which should be rate limited
xml = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
put node_create_path, :params => xml, :headers => auth_header
assert_response :too_many_requests, "node create did not hit rate limit"
end

private

##
Expand Down
111 changes: 111 additions & 0 deletions test/controllers/api/relations_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,117 @@ def test_remove_all_members
end
end

##
# test initial rate limit
def test_initial_rate_limit
# create a user
user = create(:user)

# create some nodes
node1 = create(:node)
node2 = create(:node)

# create a changeset that puts us near the initial rate limit
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 5.minutes,
:num_changes => Settings.initial_changes_per_hour - 1)

# create authentication header
auth_header = basic_authorization_header user.email, "test"

# try creating a relation
xml = "<osm><relation changeset='#{changeset.id}'>" \
"<member ref='#{node1.id}' type='node' role='some'/>" \
"<member ref='#{node2.id}' type='node' role='some'/>" \
"<tag k='test' v='yes' /></relation></osm>"
put relation_create_path, :params => xml, :headers => auth_header
assert_response :success, "relation create did not return success status"

# get the id of the relation we created
relationid = @response.body

# try updating the relation, which should be rate limited
xml = "<osm><relation id='#{relationid}' version='1' changeset='#{changeset.id}'>" \
"<member ref='#{node2.id}' type='node' role='some'/>" \
"<member ref='#{node1.id}' type='node' role='some'/>" \
"<tag k='test' v='yes' /></relation></osm>"
put api_relation_path(relationid), :params => xml, :headers => auth_header
assert_response :too_many_requests, "relation update did not hit rate limit"

# try deleting the relation, which should be rate limited
xml = "<osm><relation id='#{relationid}' version='2' changeset='#{changeset.id}'/></osm>"
delete api_relation_path(relationid), :params => xml, :headers => auth_header
assert_response :too_many_requests, "relation delete did not hit rate limit"

# try creating a relation, which should be rate limited
xml = "<osm><relation changeset='#{changeset.id}'>" \
"<member ref='#{node1.id}' type='node' role='some'/>" \
"<member ref='#{node2.id}' type='node' role='some'/>" \
"<tag k='test' v='yes' /></relation></osm>"
put relation_create_path, :params => xml, :headers => auth_header
assert_response :too_many_requests, "relation create did not hit rate limit"
end

##
# test maximum rate limit
def test_maximum_rate_limit
# create a user
user = create(:user)

# create some nodes
node1 = create(:node)
node2 = create(:node)

# create a changeset to establish our initial edit time
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 28.days)

# create changeset to put us near the maximum rate limit
total_changes = Settings.max_changes_per_hour - 1
while total_changes.positive?
changes = [total_changes, Changeset::MAX_ELEMENTS].min
changeset = create(:changeset, :user => user,
:created_at => Time.now.utc - 5.minutes,
:num_changes => changes)
total_changes -= changes
end

# create authentication header
auth_header = basic_authorization_header user.email, "test"

# try creating a relation
xml = "<osm><relation changeset='#{changeset.id}'>" \
"<member ref='#{node1.id}' type='node' role='some'/>" \
"<member ref='#{node2.id}' type='node' role='some'/>" \
"<tag k='test' v='yes' /></relation></osm>"
put relation_create_path, :params => xml, :headers => auth_header
assert_response :success, "relation create did not return success status"

# get the id of the relation we created
relationid = @response.body

# try updating the relation, which should be rate limited
xml = "<osm><relation id='#{relationid}' version='1' changeset='#{changeset.id}'>" \
"<member ref='#{node2.id}' type='node' role='some'/>" \
"<member ref='#{node1.id}' type='node' role='some'/>" \
"<tag k='test' v='yes' /></relation></osm>"
put api_relation_path(relationid), :params => xml, :headers => auth_header
assert_response :too_many_requests, "relation update did not hit rate limit"

# try deleting the relation, which should be rate limited
xml = "<osm><relation id='#{relationid}' version='2' changeset='#{changeset.id}'/></osm>"
delete api_relation_path(relationid), :params => xml, :headers => auth_header
assert_response :too_many_requests, "relation delete did not hit rate limit"

# try creating a relation, which should be rate limited
xml = "<osm><relation changeset='#{changeset.id}'>" \
"<member ref='#{node1.id}' type='node' role='some'/>" \
"<member ref='#{node2.id}' type='node' role='some'/>" \
"<tag k='test' v='yes' /></relation></osm>"
put relation_create_path, :params => xml, :headers => auth_header
assert_response :too_many_requests, "relation create did not hit rate limit"
end

private

def check_relations_for_element(path, type, id, expected_relations)
Expand Down
Loading

0 comments on commit 8136360

Please sign in to comment.