Compare commits
3 Commits
public_mod
...
c3f3b3eba1
| Author | SHA1 | Date | |
|---|---|---|---|
|
c3f3b3eba1
|
|||
|
297016e799
|
|||
|
c0a05f653c
|
62
README.md
62
README.md
@@ -4,7 +4,7 @@
|
|||||||
A simplistic Dynamic DNS-ish server.
|
A simplistic Dynamic DNS-ish server.
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
`rynDNS` is a simple REST-API for `name` to `ip` resolving in the spirit of Dynamic DNS. Clients can `PUT` their `ipv4` address up to be read by other clients. Access is controlled via `api_key` and permissions (`read`, `write`, `admin`).
|
`rynDNS` is a simple REST-API for `name` to `ip` resolving in the spirit of Dynamic DNS. Clients can `PUT` their `ipv4` address up to be read by other clients. Access is controlled via `api_key` and permissions (`read`, `write`, `admin`). No special client side application needed, just something to do a `POST` request like `wget`, `curl` or `httpie`.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>
|
<strong>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
• <a href="#getting-started">Getting started</a>
|
• <a href="#getting-started">Getting started</a>
|
||||||
• <a href="#rest-api">Rest API</a>
|
• <a href="#rest-api">Rest API</a>
|
||||||
• <a href="#configuration">Configuration</a>
|
• <a href="#configuration">Configuration</a>
|
||||||
|
• <a href="#roadmap">Roadmap</a>
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -43,38 +44,75 @@ JSON_REQUEST_BODY='{
|
|||||||
"public_ip": false
|
"public_ip": false
|
||||||
}'
|
}'
|
||||||
http post $API_SERVER/admin/client $API_KEY <<< $JSON_REQUEST_BODY
|
http post $API_SERVER/admin/client $API_KEY <<< $JSON_REQUEST_BODY
|
||||||
|
|
||||||
|
# Alternatively using query strings (be sure to encode special characters in the param values)
|
||||||
|
API_KEY='X-API-Key=MdSUxrQQ.d5d40cd9.e2s1BsQEqmSE5MqyHYbDBVxGr/KY4BYIV5QkdjiCOF0='
|
||||||
|
CLIENT_PARAMS='name=clientA&description=Client+across+the+river&permission=rw&public_ip=false'
|
||||||
|
http post "$API_SERVER/admin/client?$API_KEY&$CLIENT_PARAMS"
|
||||||
|
```
|
||||||
|
Updating a write client's `ipv4`:
|
||||||
|
```bash
|
||||||
|
# using httpie
|
||||||
|
API_SERVER=http://localhost:3000/rynDNS
|
||||||
|
API_KEY=X-API-Key:zW3If4s5.3d6129cc.48d41d2a28b16a9a274d3f407026aee2b82cddbb3c11a5496ffe329c78e5d54b
|
||||||
|
|
||||||
|
# automatically set ip based on request itself
|
||||||
|
http put $API_SERVER/client $API_KEY
|
||||||
|
|
||||||
|
# set a custom ip for the client
|
||||||
|
JSON_REQUEST_BODY='{
|
||||||
|
"ipv4": "115.16.0.99"
|
||||||
|
}'
|
||||||
|
http put $API_SERVER/client $API_KEY <<< $JSON_REQUEST_BODY
|
||||||
|
```
|
||||||
|
Reading a write client's `ipv4` with a read client:
|
||||||
|
```bash
|
||||||
|
# using httpie
|
||||||
|
API_SERVER=http://localhost:3000/rynDNS
|
||||||
|
API_KEY=X-API-Key:zW3If4s5.28aef5cb.d502cb94ba0802c6363f95ecb3a70de7e2e4b889779e435b7c033f2c72ed4f20
|
||||||
|
CLIENT_NAME=write_client
|
||||||
|
|
||||||
|
http get $API_SERVER/clients/$CLIENT_NAME $ API_KEY
|
||||||
|
|
||||||
|
# returns clients name and ipv4 as json
|
||||||
|
#{
|
||||||
|
# "name": "write_client",
|
||||||
|
# "ipv4": "115.16.0.99"
|
||||||
|
#}
|
||||||
```
|
```
|
||||||
## REST API
|
## REST API
|
||||||
A detailed and interactive specification can be found in the [demo](https://demo.api.pdev.dev) which is a render of the OpenAPI specification [api.yml](api.yml) with `swager-ui`.
|
A detailed and interactive specification can be found in the [demo](https://demo.api.pdev.dev) which is a render of the OpenAPI specification [api.yml](api.yml) with `swager-ui`.
|
||||||
|
|
||||||
All requests except for `/public/*` require a valid `X-API-Key` header with appropriate permission.
|
All requests except for `/public/*` require a valid `X-API-Key` header with appropriate permission.
|
||||||
|
|
||||||
|
**Experimental:** All parameters (including `X-API-Key`) can be passed as query string.
|
||||||
|
|
||||||
The following is a quick overview of the available commands.
|
The following is a quick overview of the available commands.
|
||||||
|
|
||||||
### Admin
|
### Admin
|
||||||
Permission `x`
|
Permission `x`
|
||||||
|
|
||||||
* [`POST /admin/client`](https://demo.api.pdev.dev/#/admin/create_client) - Create or update a client and retrieve a new API key
|
* [`POST /admin/client`](https://demo.api.pdev.dev/#/admin/create_client) - Create or update a client and retrieve a new API key
|
||||||
* [`PUT /admin/client`](https://demo.api.pdev.dev/#/admin/update_client) - Update an existing client
|
* [`PUT /admin/client`](https://demo.api.pdev.dev/#/admin/update_client) - Update an existing client
|
||||||
* [`GET /admin/clients`](https://demo.api.pdev.dev/#/admin/get_all_client) - Retrieve all clients information
|
* [`GET /admin/clients`](https://demo.api.pdev.dev/#/admin/get_all_client) - Retrieve all clients information
|
||||||
* [`GET /admin/clients/{client_name}`](https://demo.api.pdev.dev/#/admin/get_client) - Retrieve client information
|
* [`GET /admin/clients/{client_name}`](https://demo.api.pdev.dev/#/admin/get_client) - Retrieve client information
|
||||||
* [`DELETE /admin/clients/{client_name}`](https://demo.api.pdev.dev/#/admin/delete_client) - Delete an existing client
|
* [`DELETE /admin/clients/{client_name}`](https://demo.api.pdev.dev/#/admin/delete_client) - Delete an existing client
|
||||||
|
|
||||||
### Client (write)
|
### Client (write)
|
||||||
Permission `w`
|
Permission `w`
|
||||||
|
|
||||||
* [`PUT /client`](https://demo.api.pdev.dev/#/client%20(write)/update_client_ip) - Update ipv4 client information based on request IP or body if present
|
* [`PUT /client`](https://demo.api.pdev.dev/#/client%20(write)/update_client_ip) - Update ipv4 client information based on request IP or body if present
|
||||||
* [`DELETE /client`](https://demo.api.pdev.dev/#/client%20(write)/delete_client_ip) - Delete the requesting client's ipv4 information
|
* [`DELETE /client`](https://demo.api.pdev.dev/#/client%20(write)/delete_client_ip) - Delete the requesting client's ipv4 information
|
||||||
|
|
||||||
### Client (read)
|
### Client (read)
|
||||||
Permission `r`
|
Permission `r`
|
||||||
|
|
||||||
* [`GET /clients/{client_name}`](https://demo.api.pdev.dev/#/client%20(read)/get_client_ip) - Get ipv4 client information
|
* [`GET /clients/{client_name}`](https://demo.api.pdev.dev/#/client%20(read)/get_client_ip) - Get ipv4 client information
|
||||||
|
|
||||||
### Public
|
### Public
|
||||||
Client attribute `public_ip: true`
|
Client attribute `public_ip: true`
|
||||||
|
|
||||||
* [`GET /public/{client_name}`](https://demo.api.pdev.dev/#/public/get_public_client_ip) - Get ipv4 client information
|
* [`GET /public/{client_name}`](https://demo.api.pdev.dev/#/public/get_public_client_ip) - Get ipv4 client information
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -96,3 +134,11 @@ RAILS_API_DEMO: 1
|
|||||||
# [DOCKER only] set cycle to reset demo data [default: 60m]
|
# [DOCKER only] set cycle to reset demo data [default: 60m]
|
||||||
RAILS_DEMO_RESET: 60m
|
RAILS_DEMO_RESET: 60m
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
* `Public Mode` - to allow for quick deployment without having to create clients/api-keys in a trusted environment (see #1)
|
||||||
|
* ~~Support Query Strings - as alternative to api key in header and json body~~ (merged 297016e799)
|
||||||
|
* add full test coverage for query strings (considered experimental until then)
|
||||||
|
* add query strings to `api.yml` and additional examples
|
||||||
|
* DNS Server in Docker container - resolve `client_name`s via DNS server
|
||||||
|
|||||||
33
api.yml
33
api.yml
@@ -253,39 +253,6 @@ paths:
|
|||||||
$ref: "#/components/schemas/client_info"
|
$ref: "#/components/schemas/client_info"
|
||||||
"401":
|
"401":
|
||||||
$ref: "#/components/responses/unauthorized_public"
|
$ref: "#/components/responses/unauthorized_public"
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "public"
|
|
||||||
summary: "Set ipv4 client information. This is ONLY available if ENV['RAILS_API_PUBLIC'] is set"
|
|
||||||
description: "If body is present, it will take precedence over the request IP. Response contains the newly set `ipv4` value."
|
|
||||||
requestBody:
|
|
||||||
description: "An existing clients properties wil only be updated if the parameter is present."
|
|
||||||
required: false
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/client_update"
|
|
||||||
operationId: "set_public_client_ip"
|
|
||||||
security: []
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: "Client created"
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/client_info"
|
|
||||||
"201":
|
|
||||||
description: "Client updated"
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/client_info"
|
|
||||||
"403":
|
|
||||||
description: "Forbidden - API not public"
|
|
||||||
"406":
|
|
||||||
description: "Not Acceptable - IPv4 Address is invalid"
|
|
||||||
"429":
|
|
||||||
description: "Too many clients - Maximum client count reached"
|
|
||||||
|
|
||||||
components:
|
components:
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,11 @@
|
|||||||
require 'resolv'
|
require 'resolv'
|
||||||
|
|
||||||
class ClientsController < ApplicationController
|
class ClientsController < ApplicationController
|
||||||
before_action :parse_api_key_header, except: %i[show_public_ipv4 create_public_ipv4]
|
before_action :parse_api_key_header, except: [:show_public_ipv4]
|
||||||
before_action :authenticate_client, except: %i[show_public_ipv4 create_public_ipv4]
|
before_action :authenticate_client, except: [:show_public_ipv4]
|
||||||
before_action :authorize_perm_r, only: [:show_ipv4]
|
before_action :authorize_perm_r, only: [:show_ipv4]
|
||||||
before_action :authorize_perm_w, only: %i[update_ipv4 destroy_ipv4]
|
before_action :authorize_perm_w, only: %i[update_ipv4 destroy_ipv4]
|
||||||
before_action :authorize_perm_x, only: %i[create update index show destroy]
|
before_action :authorize_perm_x, only: %i[create update index show destroy]
|
||||||
before_action :authorize_public, only: [:create_public_ipv4] if ENV['RAILS_API_PUBLIC']
|
|
||||||
before_action :client_authorized, except: [:show_public_ipv4]
|
before_action :client_authorized, except: [:show_public_ipv4]
|
||||||
before_action :demo, only: %i[create update destroy] if ENV['RAILS_API_DEMO']
|
before_action :demo, only: %i[create update destroy] if ENV['RAILS_API_DEMO']
|
||||||
|
|
||||||
@@ -130,29 +129,6 @@ class ClientsController < ApplicationController
|
|||||||
json_response(client_read_object(client))
|
json_response(client_read_object(client))
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /public/:id
|
|
||||||
def create_public_ipv4
|
|
||||||
ipv4 = request_ip
|
|
||||||
|
|
||||||
# return 406 if body with invalid ipv4 is present
|
|
||||||
# TODO: move this into the model (update)
|
|
||||||
return head(406) unless ipv4
|
|
||||||
|
|
||||||
client_params = params.permit(:name)
|
|
||||||
client = Client.find_by name: params[:name]
|
|
||||||
status_code = client ? 200 : 201
|
|
||||||
|
|
||||||
# create new client or update existing
|
|
||||||
if client.nil?
|
|
||||||
client ||= Client.new_client(client_params)
|
|
||||||
return head(429) if client.nil? # reached max client count
|
|
||||||
end
|
|
||||||
client.public_ip = true
|
|
||||||
client.ipv4 = ipv4
|
|
||||||
client.save
|
|
||||||
json_response(client_read_object(client), status_code)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# helper methods
|
# helper methods
|
||||||
@@ -202,10 +178,12 @@ class ClientsController < ApplicationController
|
|||||||
# check if the api is valid and has the appropriate prefix
|
# check if the api is valid and has the appropriate prefix
|
||||||
def parse_api_key_header
|
def parse_api_key_header
|
||||||
# TODO: move api_key checks and formatting to helper class
|
# TODO: move api_key checks and formatting to helper class
|
||||||
# check if api key is present and can be split into 3 parts: <prefix>.<name_id>.<api_key>
|
# check if api key is present in header or param and can be split into 3 parts: <prefix>.<name_id>.<api_key>
|
||||||
# NOTE: .compact is more for got measure than really necessary, but this ensures no nil values in the array
|
# NOTE: .compact is more for got measure than really necessary, but this ensures no nil values in the array
|
||||||
api_key_header = request.headers[RynDNS::Application::API_KEY_HEADER].split('.').compact unless
|
api_key_header = request.headers[RynDNS::Application::API_KEY_HEADER].split('.').compact unless
|
||||||
request.headers[RynDNS::Application::API_KEY_HEADER].nil?
|
request.headers[RynDNS::Application::API_KEY_HEADER].nil?
|
||||||
|
api_key_header ||= params[RynDNS::Application::API_KEY_HEADER].split('.').compact if
|
||||||
|
params.has_key?(RynDNS::Application::API_KEY_HEADER)
|
||||||
return head(401) if api_key_header.nil? || api_key_header.length < 3
|
return head(401) if api_key_header.nil? || api_key_header.length < 3
|
||||||
|
|
||||||
prefix, @name_id, @api_key = api_key_header
|
prefix, @name_id, @api_key = api_key_header
|
||||||
@@ -239,10 +217,6 @@ class ClientsController < ApplicationController
|
|||||||
@authorized = @requesting_client.admin?
|
@authorized = @requesting_client.admin?
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize_public
|
|
||||||
@authorized = true
|
|
||||||
end
|
|
||||||
|
|
||||||
# check if request is authorized
|
# check if request is authorized
|
||||||
def client_authorized
|
def client_authorized
|
||||||
return head(403) unless @authorized
|
return head(403) unless @authorized
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ Rails.application.routes.draw do
|
|||||||
# separate public path to allow for easier segregation and controls via reverse proxy
|
# separate public path to allow for easier segregation and controls via reverse proxy
|
||||||
scope '/public' do
|
scope '/public' do
|
||||||
get ':name', to: 'clients#show_public_ipv4', format: false
|
get ':name', to: 'clients#show_public_ipv4', format: false
|
||||||
post ':name', to: 'clients#create_public_ipv4', format: false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ RSpec.describe 'Clients', type: :request do
|
|||||||
let(:valid_client) do
|
let(:valid_client) do
|
||||||
{
|
{
|
||||||
name: 'clientA',
|
name: 'clientA',
|
||||||
description: 'Client across the river',
|
description: 'Client&across the=river', # added &= for query string test
|
||||||
permission: 'rw',
|
permission: 'rw',
|
||||||
public_ip: false
|
public_ip: false
|
||||||
}
|
}
|
||||||
@@ -78,31 +78,18 @@ RSpec.describe 'Clients', type: :request do
|
|||||||
context 'when sending a valid request' do
|
context 'when sending a valid request' do
|
||||||
before { post '/admin/client', params: valid_client, headers: api_key_header(admin_client) }
|
before { post '/admin/client', params: valid_client, headers: api_key_header(admin_client) }
|
||||||
|
|
||||||
it 'creates client that can read/write and returns 201' do
|
it_behaves_like 'POST /admin/client 201'
|
||||||
expect(json_response).not_to be_empty
|
|
||||||
expect(json_response['name']).to eq valid_client[:name]
|
|
||||||
expect(json_response['api_key'].length).to eq 82
|
|
||||||
expect(response).to have_http_status(201)
|
|
||||||
api_key = json_response['api_key']
|
|
||||||
# check if request was parsed properly
|
|
||||||
get "/admin/clients/#{valid_client[:name]}", headers: api_key_header(admin_client)
|
|
||||||
expect(json_response).not_to be_empty
|
|
||||||
expect(json_response['name']).to eq valid_client[:name]
|
|
||||||
expect(json_response['description']).to eq valid_client[:description]
|
|
||||||
expect(json_response['permission']).to eq valid_client[:permission]
|
|
||||||
expect(json_response['public_ip']).to eq valid_client[:public_ip]
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
# test the new client write
|
|
||||||
put '/client', headers: api_key_header(nil, api_key)
|
|
||||||
expect(json_response).not_to be_empty
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
ipv4 = json_response['ipv4']
|
|
||||||
# test the new client read
|
|
||||||
get "/clients/#{valid_client[:name]}", headers: api_key_header(nil, api_key)
|
|
||||||
expect(json_response).not_to be_empty
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
expect(json_response['ipv4']).to eq ipv4
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: add query string tests for all requests and use shared examples
|
||||||
|
context 'when sending a valid request as query string' do
|
||||||
|
before do
|
||||||
|
api_key = "#{RynDNS::Application::API_KEY_HEADER}=#{api_key_header(admin_client)[RynDNS::Application::API_KEY_HEADER]}"
|
||||||
|
body = hash_to_query_string(valid_client)
|
||||||
|
post "/admin/client?#{api_key}&#{body}"
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'POST /admin/client 201'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when sending a valid request without api_key' do
|
context 'when sending a valid request without api_key' do
|
||||||
@@ -587,49 +574,4 @@ RSpec.describe 'Clients', type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST /public/:name' do
|
|
||||||
# FIXME: rather have this automated than adding ENV by hand
|
|
||||||
if ENV['RAILS_API_PUBLIC']
|
|
||||||
|
|
||||||
context 'when request to write public client' do
|
|
||||||
before { post "/public/#{public_client.name}" }
|
|
||||||
it 'returns status code 200' do
|
|
||||||
expect(json_response['name']).to eq public_client.name
|
|
||||||
expect(json_response['ipv4']).to eq '127.0.0.1'
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when requesting to write public client with body' do
|
|
||||||
before { post "/public/#{public_client.name}", params: { ipv4: '99.55.66.2' } }
|
|
||||||
it 'returns status code 200' do
|
|
||||||
expect(json_response['name']).to eq public_client.name
|
|
||||||
expect(json_response['ipv4']).to eq '99.55.66.2'
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when requesting to write non existing client' do
|
|
||||||
before { post "/public/#{non_existing_client}" }
|
|
||||||
it 'returns status code 201' do
|
|
||||||
expect(json_response['name']).to eq non_existing_client
|
|
||||||
expect(json_response['ipv4']).to eq '127.0.0.1'
|
|
||||||
expect(response).to have_http_status(201)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# TODO: 406 and 429, check client count on 200
|
|
||||||
else
|
|
||||||
|
|
||||||
context 'when request to write public client' do
|
|
||||||
before { post "/public/#{public_client.name}" }
|
|
||||||
it 'returns status code 403' do
|
|
||||||
expect(response.body).to be_empty
|
|
||||||
expect(response).to have_http_status(403)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# TODO: check client count/modification
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ module RequestSpecHelper
|
|||||||
"#{'r' if perm_r}#{'w' if perm_w}#{'x' if perm_x}"
|
"#{'r' if perm_r}#{'w' if perm_w}#{'x' if perm_x}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hash_to_query_string(hash)
|
||||||
|
result = ''
|
||||||
|
hash.each do |k, v|
|
||||||
|
# escape value to be uri save
|
||||||
|
result += "#{k}=#{CGI.escape(v.to_s)}&"
|
||||||
|
end
|
||||||
|
result.delete_suffix('&')
|
||||||
|
end
|
||||||
|
|
||||||
def list_equals_db?(client_list)
|
def list_equals_db?(client_list)
|
||||||
return false unless Client.count == client_list.count
|
return false unless Client.count == client_list.count
|
||||||
|
|
||||||
|
|||||||
29
spec/support/request_spec_shared.rb
Normal file
29
spec/support/request_spec_shared.rb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.shared_examples 'POST /admin/client 201' do
|
||||||
|
it 'creates client that can read/write and returns 201' do
|
||||||
|
expect(json_response).not_to be_empty
|
||||||
|
expect(json_response['name']).to eq valid_client[:name]
|
||||||
|
expect(json_response['api_key'].length).to eq 82
|
||||||
|
expect(response).to have_http_status(201)
|
||||||
|
api_key = json_response['api_key']
|
||||||
|
# check if request was parsed properly
|
||||||
|
get "/admin/clients/#{valid_client[:name]}", headers: api_key_header(admin_client)
|
||||||
|
expect(json_response).not_to be_empty
|
||||||
|
expect(json_response['name']).to eq valid_client[:name]
|
||||||
|
expect(json_response['description']).to eq valid_client[:description]
|
||||||
|
expect(json_response['permission']).to eq valid_client[:permission]
|
||||||
|
expect(json_response['public_ip']).to eq valid_client[:public_ip]
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
# test the new client write
|
||||||
|
put '/client', headers: api_key_header(nil, api_key)
|
||||||
|
expect(json_response).not_to be_empty
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
ipv4 = json_response['ipv4']
|
||||||
|
# test the new client read
|
||||||
|
get "/clients/#{valid_client[:name]}", headers: api_key_header(nil, api_key)
|
||||||
|
expect(json_response).not_to be_empty
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(json_response['ipv4']).to eq ipv4
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user