Skip to content
Snippets Groups Projects
Commit 573d1c87 authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Add network to API

- Allow to pass several mandatory fields in create_generic_model
- Add tests
parent 704f4f6b
No related branches found
No related tags found
No related merge requests found
......@@ -14,7 +14,7 @@ from flask import (current_app, Blueprint, jsonify, request)
from flask_jwt_extended import create_access_token, jwt_required
from flask_ldap3_login import AuthenticationResponseStatus
from ..extensions import ldap_manager, db
from ..models import Item, Manufacturer, Model, Location, Status, Action
from ..models import Item, Manufacturer, Model, Location, Status, Action, Network
from .. import utils
from ..decorators import jwt_groups_accepted
......@@ -48,13 +48,14 @@ def get_generic_model(model, args):
return jsonify(data)
def create_generic_model(model, mandatory_field='name'):
def create_generic_model(model, mandatory_fields=('name',)):
data = request.get_json()
if data is None:
raise utils.CSEntryError('Body should be a JSON object')
current_app.logger.debug(f'Received: {data}')
if mandatory_field not in data:
raise utils.CSEntryError(f"Missing mandatory field '{mandatory_field}'", status_code=422)
for mandatory_field in mandatory_fields:
if mandatory_field not in data:
raise utils.CSEntryError(f"Missing mandatory field '{mandatory_field}'", status_code=422)
try:
instance = model(**data)
except TypeError as e:
......@@ -119,7 +120,7 @@ def create_item():
# an item so ics_id should also be a mandatory field.
# But there are existing items (in confluence and JIRA) that we want to
# import and associate after they have been created.
return create_generic_model(Item, mandatory_field='serial_number')
return create_generic_model(Item, mandatory_fields=('serial_number',))
@bp.route('/items/<id_>', methods=['PATCH'])
......@@ -234,3 +235,21 @@ def get_status():
@jwt_groups_accepted('admin', 'create')
def create_status():
return create_generic_model(Status)
@bp.route('/networks')
@jwt_required
def get_networks():
# TODO: add pagination
query = utils.get_query(Network.query, request.args)
networks = query.order_by(Network.id)
data = [network.to_dict() for network in networks]
return jsonify(data)
@bp.route('/networks', methods=['POST'])
@jwt_required
@jwt_groups_accepted('admin')
def create_network():
"""Create a new network"""
return create_generic_model(Network, mandatory_fields=('prefix', 'first', 'last'))
......@@ -21,9 +21,10 @@ ENDPOINT_MODEL = {
'locations': models.Location,
'status': models.Status,
'items': models.Item,
'networks': models.Network,
}
GENERIC_GET_ENDPOINTS = [key for key in ENDPOINT_MODEL.keys() if key != 'items']
GENERIC_CREATE_ENDPOINTS = [key for key in ENDPOINT_MODEL.keys() if key not in ('items', 'actions')]
GENERIC_GET_ENDPOINTS = [key for key in ENDPOINT_MODEL.keys() if key not in ('items', 'networks')]
GENERIC_CREATE_ENDPOINTS = [key for key in ENDPOINT_MODEL.keys() if key not in ('items', 'actions', 'networks')]
CREATE_AUTH_ENDPOINTS = [key for key in ENDPOINT_MODEL.keys() if key != 'actions']
......@@ -106,10 +107,10 @@ def check_names(response, names):
assert set(names) == response_names
def check_items(response, inputs):
def check_input_is_subset_of_response(response, inputs):
# Sort the response by id to match the inputs order
response_items = sorted(response.json, key=lambda d: d['id'])
for d1, d2 in zip(inputs, response_items):
response_elts = sorted(response.json, key=lambda d: d['id'])
for d1, d2 in zip(inputs, response_elts):
assert set(d1.items()).issubset(set(d2.items()))
......@@ -211,7 +212,7 @@ def test_create_item(client, user_token):
# check all items that were created
assert models.Item.query.count() == 3
response = get(client, '/api/items', user_token)
check_items(response, (data, data, data2))
check_input_is_subset_of_response(response, (data, data, data2))
def test_create_item_invalid_ics_id(client, user_token):
......@@ -410,20 +411,121 @@ def test_get_items(client, session, readonly_token):
response = get(client, '/api/items', token=readonly_token)
assert response.status_code == 200
assert len(response.json) == 3
check_items(response, (item1.to_dict(), item2.to_dict(), item3.to_dict()))
check_input_is_subset_of_response(response, (item1.to_dict(), item2.to_dict(), item3.to_dict()))
# test filtering
response = get(client, '/api/items?serial_number=234567', token=readonly_token)
assert response.status_code == 200
assert len(response.json) == 1
check_items(response, (item2.to_dict(),))
check_input_is_subset_of_response(response, (item2.to_dict(),))
# filtering on location_id works but not location (might want to change that)
response = get(client, f'/api/items?location_id={item1.location_id}', token=readonly_token)
assert response.status_code == 200
assert len(response.json) == 1
check_items(response, (item1.to_dict(),))
check_input_is_subset_of_response(response, (item1.to_dict(),))
response = get(client, '/api/items?location=ESS', token=readonly_token)
check_response_message(response, 'Invalid query arguments', 422)
# using an unknown key raises a 422
response = get(client, '/api/items?foo=bar', token=readonly_token)
check_response_message(response, 'Invalid query arguments', 422)
def test_get_networks(client, session, readonly_token):
# Create some networks
location = models.Location(name='G02')
session.add(location)
session.flush()
network1 = models.Network(prefix='172.16.1.0/24', first='172.16.1.1', last='172.16.1.254', label='network1')
network2 = models.Network(prefix='172.16.20.0/22', first='172.16.20.11', last='172.16.20.250')
network3 = models.Network(prefix='172.16.5.0/24', first='172.16.5.10', last='172.16.5.254', location_id=location.id)
for network in (network1, network2, network3):
session.add(network)
session.commit()
response = get(client, '/api/networks', token=readonly_token)
assert response.status_code == 200
assert len(response.json) == 3
check_input_is_subset_of_response(response, (network1.to_dict(), network2.to_dict(), network3.to_dict()))
# test filtering by location_id
response = get(client, f'/api/networks?location_id={location.id}', token=readonly_token)
assert response.status_code == 200
assert len(response.json) == 1
check_input_is_subset_of_response(response, (network3.to_dict(),))
# test filtering by prefix
response = get(client, '/api/networks?prefix=172.16.20.0/22', token=readonly_token)
assert response.status_code == 200
assert len(response.json) == 1
check_input_is_subset_of_response(response, (network2.to_dict(),))
def test_create_network_auth_fail(client, session, user_token):
# admin is required to create networks
response = post(client, '/api/networks', data={}, token=user_token)
check_response_message(response, "User doesn't have the required group", 403)
def test_create_network(client, session, admin_token):
location = models.Location(name='G02')
session.add(location)
session.commit()
# check that prefix, first and last are mandatory
response = post(client, '/api/networks', data={}, token=admin_token)
check_response_message(response, "Missing mandatory field 'prefix'", 422)
response = post(client, '/api/networks', data={'first': '172.16.1.10', 'last': '172.16.1.250'}, token=admin_token)
check_response_message(response, "Missing mandatory field 'prefix'", 422)
response = post(client, '/api/networks', data={'prefix': '172.16.1.0/24'}, token=admin_token)
check_response_message(response, "Missing mandatory field 'first'", 422)
response = post(client, '/api/networks', data={'prefix': '172.16.1.0/24', 'first': '172.16.1.10'}, token=admin_token)
check_response_message(response, "Missing mandatory field 'last'", 422)
data = {'prefix': '172.16.1.0/24',
'first': '172.16.1.10',
'last': '172.16.1.250'}
response = post(client, '/api/networks', data=data, token=admin_token)
assert response.status_code == 201
assert {'id', 'prefix', 'first', 'last', 'label', 'vlanid', 'gateway', 'location'} == set(response.json.keys())
assert response.json['prefix'] == '172.16.1.0/24'
assert response.json['first'] == '172.16.1.10'
assert response.json['last'] == '172.16.1.250'
# Check that prefix shall be unique
response = post(client, '/api/networks', data=data, token=admin_token)
check_response_message(response, 'IntegrityError', 409)
# Check that all parameters can be passed
data2 = {'prefix': '172.16.5.0/24',
'first': '172.16.5.11',
'last': '172.16.5.250',
'label': 'G02',
'gateway': '172.16.5.10',
'vlanid': 1601,
'location_id': location.id}
response = post(client, '/api/networks', data=data2, token=admin_token)
assert response.status_code == 201
assert response.json['location'] == location.name
# check all items that were created
assert models.Network.query.count() == 2
def test_create_network_constraint_fail(client, session, admin_token):
# first not in prefix
data = {'prefix': '172.16.1.0/24',
'first': '172.16.2.10',
'last': '172.16.1.250'}
response = post(client, '/api/networks', data=data, token=admin_token)
check_response_message(response, 'IntegrityError', 409)
# last not in prefix
data = {'prefix': '172.16.1.0/24',
'first': '172.16.1.10',
'last': '172.16.5.250'}
response = post(client, '/api/networks', data=data, token=admin_token)
check_response_message(response, 'IntegrityError', 409)
# first > last
data = {'prefix': '172.16.1.0/24',
'first': '172.16.1.10',
'last': '172.16.1.9'}
response = post(client, '/api/networks', data=data, token=admin_token)
check_response_message(response, 'IntegrityError', 409)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment