Add handling of type filters and handling of ETags

Add API documentation and test cases around ETags for the API.
This commit is contained in:
Maciej Szlosarczyk 2020-05-15 14:43:18 +03:00
parent 960e4084e3
commit 6e5a97ad4d
No known key found for this signature in database
GPG key ID: 41D62D42D3B0D765
5 changed files with 182 additions and 19 deletions

View file

@ -2,10 +2,14 @@ module Repp
module V1
class RetainedDomainsController < ActionController::API
def index
domains = RetainedDomains.new
domains = RetainedDomains.new(query_params)
render json: { count: domains.count, domains: domains.to_jsonable }
end
def query_params
params.permit(:type)
end
end
end
end

View file

@ -4,26 +4,55 @@ class RetainedDomains
RESERVED = 'reserved'.freeze
BLOCKED = 'blocked'.freeze
attr_reader :domains
attr_reader :domains,
:type
def initialize
def initialize(params)
@type = establish_type(params)
@domains = gather_domains
end
def gather_domains
blocked_domains = BlockedDomain.order(name: :desc).all
reserved_domains = ReservedDomain.order(name: :desc).all
delegate :count, to: :domains
def to_jsonable
domains.map { |el| domain_to_jsonable(el) }
end
private
def establish_type(params)
type = params[:type]
case type
when 'reserved' then :reserved
when 'blocked' then :blocked
else :all
end
end
def gather_domains
domains = blocked_domains.to_a.union(reserved_domains.to_a)
domains.sort_by(&:name)
end
def to_jsonable
domains.map { |el| domain_to_json(el) }
def blocked_domains
if %i[all blocked].include?(type)
BlockedDomain.order(name: :desc).all
else
[]
end
end
def domain_to_json(domain)
def reserved_domains
if %i[all reserved].include?(type)
ReservedDomain.order(name: :desc).all
else
[]
end
end
def domain_to_jsonable(domain)
status = case domain
when ReservedDomain then RESERVED
when BlockedDomain then BLOCKED
@ -37,6 +66,4 @@ class RetainedDomains
punycode_name: punycode,
}
end
delegate :count, to: :domains
end

View file

@ -0,0 +1,96 @@
## GET /repp/v1/retained_domains
Return a list of reserved and blocked domains, along with total count. You can
filter them by type of the domain, which can be either reserved or blocked.
In contrast with other endpoints in REPP, this one is publicly available for
anyone without authentication.
#### Parameters
| Field name | Required | Type | Allowed values | Description |
| ---------- | -------- | ---- | -------------- | ----------- |
| type | false | string | ["reserved", "blocked"] | Type of domains to show |
#### Request
```
GET /repp/v1/retained_domains?type=reserved HTTP/1.1
Accept: application/json
User-Agent: curl/7.64.1
```
#### Response
```
HTTP/1.1 200 OK
Date: Fri, 15 May 2020 11:30:07 GMT
Content-Type: application/json; charset=utf-8
ETag: W/"a905b531243a6b0be42beb9d6ce60619"
Cache-Control: max-age=0, private, must-revalidate
Transfer-Encoding: chunked
{
"count": 1,
"domains": [
{
"name": "reserved.test",
"status": "reserved",
"punycode_name": "reserved.test"
}
]
}
```
After you have made the first request, you can save the ETag header, and
send it as If-None-Match in the subsequent request for cache validation.
Due to the fact that the lists are not changing frequently and are quite long,
it is recommended that you take advantage of ETag cache.
ETag key values depend on the request parameters. A request for only blocked
domains returns different cache key than request for all domains.
### Cache Request
```
GET /repp/v1/retained_domains?type=reserved HTTP/1.1
Accept: application/json
User-Agent: curl/7.64.1
If-None-Match: W/"a905b531243a6b0be42beb9d6ce60619"
```
#### Cache hit response
Response with no body and status 304 is sent in case the list have not changed.
```
HTTP/1.1 304 Not Modified
Date: Fri, 15 May 2020 11:34:25 GMT
ETag: W/"a905b531243a6b0be42beb9d6ce60619"
Cache-Control: max-age=0, private, must-revalidate
```
#### Cache miss response
Standard 200 response is sent when the list have changed since last requested.
```
HTTP/1.1 200 OK
Date: Fri, 15 May 2020 11:30:07 GMT
Content-Type: application/json; charset=utf-8
ETag: W/"a905b531243a6b0be42beb9d6ce60619"
Transfer-Encoding: chunked
{
"count": 1,
"domains": [
{
"name": "reserved.test",
"status": "reserved",
"punycode_name": "reserved.test"
}
]
}
```

View file

@ -20,3 +20,4 @@ Main communication specification through Restful EPP (REPP):
[Domain transfers](repp/v1/domain_transfers.md)
[Account related functions](repp/v1/account.md)
[Nameservers](repp/v1/nameservers.md)
[Retained domains](repp/v1/retained_domains.md)

View file

@ -24,6 +24,41 @@ class ReppV1RetainedDomainsTest < ActionDispatch::IntegrationTest
assert_equal response_json[:domains], expected_objects
end
def test_get_index_with_type_parameter
get repp_v1_retained_domains_path({ 'type' => 'reserved' })
response_json = JSON.parse(response.body, symbolize_names: true)
assert response_json[:count] == 1
expected_objects = [{ name: 'reserved.test',
status: 'reserved',
punycode_name: 'reserved.test' }]
assert_equal response_json[:domains], expected_objects
end
def test_etags_cache
get repp_v1_retained_domains_path({ 'type' => 'reserved' })
etag = response.headers['ETag']
get repp_v1_retained_domains_path({ 'type' => 'reserved' }),
headers: { 'If-None-Match' => etag }
assert_equal response.status, 304
assert_equal response.body, ''
end
def test_etags_cache_valid_for_type_only
get repp_v1_retained_domains_path({ 'type' => 'blocked' })
etag = response.headers['ETag']
get repp_v1_retained_domains_path, headers: { 'If-None-Match' => etag }
assert_equal response.status, 200
response_json = JSON.parse(response.body, symbolize_names: true)
assert response_json[:count] == 3
end
def test_cors_preflight
process :options, repp_v1_retained_domains_path, headers: { 'Origin' => 'https://example.com' }