Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
test_web.py 28.67 KiB
# -*- coding: utf-8 -*-
"""
tests.functional.test_web
~~~~~~~~~~~~~~~~~~~~~~~~~

This module defines basic web tests.

:copyright: (c) 2017 European Spallation Source ERIC
:license: BSD 2-Clause, see LICENSE for more details.

"""
import pytest
import re
from app import models


def login(client, username, password):
    data = {"username": username, "password": password}
    return client.post("/user/login", data=data, follow_redirects=True)


def logout(client):
    return client.get("/user/logout", follow_redirects=True)


@pytest.fixture
def logged_client(client):
    login(client, "user_ro", "userro")
    yield client
    logout(client)


@pytest.fixture
def logged_rw_client(client):
    login(client, "user_rw", "userrw")
    yield client
    logout(client)


@pytest.fixture
def logged_admin_client(client):
    login(client, "admin", "adminpasswd")
    yield client
    logout(client)


@pytest.fixture
def no_login_check_client(request, app):
    app.config["LOGIN_DISABLED"] = True
    client = app.test_client()
    # We still need to login, otherwise an AnonymousUserMixin is returned
    # An AnonymousUser doesn't have all the User methods
    login(client, "user_ro", "userro")
    yield client
    app.config["LOGIN_DISABLED"] = False
    logout(client)


def test_login_logout(client):
    response = login(client, "unknown", "invalid")
    assert b"<title>Login - CSEntry</title>" in response.data
    response = login(client, "user_rw", "invalid")
    assert b"<title>Login - CSEntry</title>" in response.data
    response = login(client, "user_rw", "userrw")
    assert b"Control System Entry" in response.data
    assert b"User RW" in response.data
    response = logout(client)
    assert b"<title>Login - CSEntry</title>" in response.data


def test_index(logged_client):
    response = logged_client.get("/")
    assert b"Control System Entry" in response.data
    assert b"User RO" in response.data


@pytest.mark.parametrize("url", ["/", "/inventory/items", "/network/hosts"])
def test_protected_url_get(url, client):
    response = client.get(url)
    assert response.status_code == 302
    assert "/user/login" in response.headers["Location"]
    login(client, "user_ro", "userro")
    response = client.get(url)
    assert response.status_code == 200


@pytest.mark.parametrize("url", ["/network/networks", "/network/scopes"])
def test_admin_protected_url_get(url, client):
    login(client, "user_rw", "userrw")
    response = client.get(url)
    assert response.status_code == 403
    logout(client)
    login(client, "admin", "adminpasswd")
    response = client.get(url)
    assert response.status_code == 200


@pytest.mark.parametrize(
    "url", ["/inventory/_retrieve_items", "/network/_retrieve_hosts"]
)
def test_protected_url_post(url, client):
    response = client.post(url)
    assert response.status_code == 302
    assert "/user/login" in response.headers["Location"]
    login(client, "user_ro", "userro")
    response = client.post(url)
    assert response.status_code == 200


def test_retrieve_items(logged_client, item_factory):
    response = logged_client.post("/inventory/_retrieve_items")
    assert response.get_json()["data"] == []
    serial_numbers = ("12345", "45678")
    for sn in serial_numbers:
        item_factory(serial_number=sn)
    response = logged_client.post("/inventory/_retrieve_items")
    items = response.get_json()["data"]
    assert set(serial_numbers) == set(item["serial_number"] for item in items)
    assert len(items[0]) == 18


def test_retrieve_items_pagination(logged_client, item_factory):
    for sn in range(1000, 1030):
        item_factory(serial_number=sn)
    response = logged_client.post(
        "/inventory/_retrieve_items", data={"draw": "50", "length": 10, "start": 0}
    )
    r = response.get_json()
    assert r["draw"] == 50
    assert r["recordsTotal"] == 30
    assert r["recordsFiltered"] == 30
    assert len(r["data"]) == 10
    serial_numbers = [item["serial_number"] for item in r["data"]]
    response = logged_client.post(
        "/inventory/_retrieve_items", data={"draw": "51", "length": 10, "start": 10}
    )
    serial_numbers.extend(
        [item["serial_number"] for item in response.get_json()["data"]]
    )
    response = logged_client.post(
        "/inventory/_retrieve_items", data={"draw": "52", "length": 10, "start": 20}
    )
    serial_numbers.extend(
        [item["serial_number"] for item in response.get_json()["data"]]
    )
    assert sorted(serial_numbers) == list(str(i) for i in range(1000, 1030))


def test_retrieve_items_filter(logged_client, item_factory):
    for sn in range(1000, 1010):
        item_factory(serial_number=sn)
    response = logged_client.post(
        "/inventory/_retrieve_items",
        data={
            "draw": "50",
            "length": 20,
            "start": 0,
            "search[value]": "serial_number:1005",
        },
    )
    r = response.get_json()
    assert r["recordsTotal"] == 10
    assert r["recordsFiltered"] == 1
    assert len(r["data"]) == 1
    assert r["data"][0]["serial_number"] == "1005"


def test_retrieve_items_sort(logged_client, item_factory):
    serial_numbers = ["AAA001", "AAB034", "AAA100", "AAB001"]
    for sn in serial_numbers:
        item_factory(serial_number=sn)
    response = logged_client.post(
        "/inventory/_retrieve_items",
        data={
            "draw": "50",
            "length": 20,
            "start": 0,
            "order[0][column]": "3",
            "columns[3][data]": "serial_number",
        },
    )
    items = response.get_json()["data"]
    assert sorted(serial_numbers) == [item["serial_number"] for item in items]
    response = logged_client.post(
        "/inventory/_retrieve_items",
        data={
            "draw": "50",
            "length": 20,
            "start": 0,
            "order[0][column]": "3",
            "order[0][dir]": "desc",
            "columns[3][data]": "serial_number",
        },
    )
    items = response.get_json()["data"]
    assert sorted(serial_numbers, reverse=True) == [
        item["serial_number"] for item in items
    ]


def test_retrieve_items_case_insensitive(logged_client, model_factory, item_factory):
    juniper_model = model_factory(name="Juniper")
    item_factory(serial_number="BBB001", model=juniper_model)
    item_factory(serial_number="ABB042")
    response = logged_client.post(
        "/inventory/_retrieve_items",
        data={"draw": "50", "length": 20, "start": 0, "search[value]": "juniper"},
    )
    r = response.get_json()
    assert r["recordsTotal"] == 2
    assert r["recordsFiltered"] == 1
    assert len(r["data"]) == 1
    assert r["data"][0]["model"] == "Juniper"


def test_retrieve_items_one_word(logged_client, manufacturer_factory, item_factory):
    manufacturer = manufacturer_factory(name="Concurrent Technologies")
    item_factory(serial_number="AAA001", manufacturer=manufacturer)
    item_factory(serial_number="ABB042")
    response = logged_client.post(
        "/inventory/_retrieve_items",
        data={"draw": "50", "length": 20, "start": 0, "search[value]": "concurrent"},
    )
    r = response.get_json()
    assert r["recordsTotal"] == 2
    assert r["recordsFiltered"] == 1
    assert len(r["data"]) == 1
    assert r["data"][0]["manufacturer"] == "Concurrent Technologies"


def test_generate_random_mac(logged_client):
    response = logged_client.get("/network/_generate_random_mac")
    mac = response.get_json()["data"]["mac"]
    assert re.match("^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$", mac) is not None
    assert mac.startswith("02:42:42")


def test_retrieve_hosts(logged_client, interface_factory, host_factory):
    response = logged_client.post("/network/_retrieve_hosts")
    assert response.get_json()["data"] == []
    host1 = host_factory(name="host1")
    host2 = host_factory(name="host2")
    interface_factory(name="host1", host=host1)
    interface_factory(name="host2", host=host2)
    response = logged_client.post("/network/_retrieve_hosts")
    hosts = response.get_json()["data"]
    assert {host1.name, host2.name} == set(host["name"] for host in hosts)
    assert len(hosts[0]) == 15
    assert len(hosts[0]["interfaces"][0]) == 15


def test_retrieve_hosts_by_ip(logged_client, interface_factory):
    interface1 = interface_factory()
    interface_factory()
    response = logged_client.post(
        "/network/_retrieve_hosts",
        data={"draw": "50", "length": 20, "start": 0, "search[value]": interface1.ip},
    )
    r = response.get_json()
    assert r["recordsTotal"] == 2
    assert r["recordsFiltered"] == 1
    assert len(r["data"]) == 1
    assert r["data"][0]["name"] == interface1.host.name


def test_delete_interface_from_index(
    no_login_check_client, interface_factory, host_factory
):
    host1 = host_factory(name="host1")
    interface_factory(name="host1", host=host1)
    interface2 = interface_factory(name="host1b", host=host1)
    # The interface is in the index
    instances, nb = models.Host.search("host1b")
    assert list(instances) == [host1]
    assert nb == 1
    # Delete the interface
    response = no_login_check_client.post(
        "/network/interfaces/delete", data={"interface_id": interface2.id}
    )
    assert response.status_code == 302
    # It's not in the database anymore
    assert models.Interface.query.get(interface2.id) is None
    # Neither in the index
    instances, nb = models.Host.search("host1b")
    assert list(instances) == []
    assert nb == 0
    # But host1 is still in the index
    instances, nb = models.Host.search("host1")
    assert list(instances) == [host1]
    assert nb == 1


def test_edit_item_comment_in_index(
    logged_rw_client, item_factory, item_comment_factory
):
    item1 = item_factory(ics_id="AAA001")
    comment = item_comment_factory(body="Hello", item_id=item1.id)
    assert item1.comments == [comment]
    # Edit the comment
    body = "Hello world!"
    response = logged_rw_client.post(
        f"/inventory/items/comment/edit/{comment.id}", data={"body": body}
    )
    assert response.status_code == 302
    # The comment was updated in the database
    updated_comment = models.ItemComment.query.get(comment.id)
    assert updated_comment.body == body
    # And in the index
    instances, nb = models.Item.search("world")
    assert list(instances) == [item1]


def test_create_host(client, network_scope_factory, network_factory, device_type):
    scope = network_scope_factory(name="ProdNetworks", supernet="192.168.0.0/16")
    network = network_factory(
        address="192.168.1.0/24",
        first_ip="192.168.1.10",
        last_ip="192.168.1.250",
        scope=scope,
    )
    name = "myhost"
    ip = "192.168.1.11"
    mac = "02:42:42:45:3c:89"
    form = {
        "network_id": network.id,
        "name": name,
        "device_type_id": device_type.id,
        "is_ioc": False,
        "ip": ip,
        "mac": mac,
        "description": "test",
        "ansible_vars": "foo: hello",
        "ansible_groups": [],
        "random_mac": False,
        "cnames_string": "",
    }
    # Invalid network_id with user_lab user
    # (form validation error because the network is not part of the choices
    # for this user)
    login(client, "user_lab", "userlab")
    response = client.post(f"/network/hosts/create", data=form)
    assert response.status_code == 200
    # The host wasn't created
    assert models.Host.query.filter_by(name=name).first() is None
    logout(client)
    # Success with user_prod user
    login(client, "user_prod", "userprod")
    response = client.post(f"/network/hosts/create", data=form, follow_redirects=True)
    assert response.status_code == 200
    # The host was created
    assert b"created!" in response.data
    assert b"View host" in response.data
    host = models.Host.query.filter_by(name=name).first()
    assert host is not None
    assert host.interfaces[0].ip == ip
    assert host.interfaces[0].mac == mac
    assert host.interfaces[0].name == name


def test_create_host_invalid_fields(
    session, client, network_scope_factory, network_factory, device_type
):
    scope = network_scope_factory(name="ProdNetworks", supernet="192.168.0.0/16")
    network = network_factory(
        address="192.168.1.0/24",
        first_ip="192.168.1.10",
        last_ip="192.168.1.250",
        scope=scope,
    )
    name = "myhost"
    ip = "192.168.1.11"
    mac = "02:42:42:45:3c:89"
    form = {
        "network_id": network.id,
        "name": name,
        "device_type_id": device_type.id,
        "is_ioc": False,
        "ip": ip,
        "mac": mac,
        "description": "test",
        "ansible_vars": "",
        "ansible_groups": [],
        "random_mac": False,
        "cnames_string": "",
    }
    login(client, "user_prod", "userprod")
    # Invalid mac
    data = form.copy()
    data["mac"] = "ea:ea:60:45:a8:96:se"
    response = client.post(f"/network/hosts/create", data=data, follow_redirects=True)
    assert response.status_code == 200
    assert b"Register new host" in response.data
    assert b"Invalid MAC address" in response.data
    # An exception was raised during validation (on Select in the Unique Validator),
    # so we need to rollback.
    session.rollback()
    # Invalid hostname
    data = form.copy()
    data["name"] = "invalid_host"
    response = client.post(f"/network/hosts/create", data=data, follow_redirects=True)
    assert response.status_code == 200
    assert b"Register new host" in response.data
    assert b"Invalid input" in response.data


def test_create_interface(
    client, host_factory, network_scope_factory, network_factory, interface_factory
):
    host = host_factory(name="myhost")
    scope = network_scope_factory(name="ProdNetworks", supernet="192.168.0.0/16")
    network1 = network_factory(scope=scope)
    interface_factory(network=network1, host=host)
    network2 = network_factory(
        address="192.168.2.0/24",
        first_ip="192.168.2.10",
        last_ip="192.168.2.250",
        scope=scope,
    )
    name = host.name + "-2"
    ip = "192.168.2.11"
    mac = "02:42:42:46:3c:75"
    form = {
        "host_id": host.id,
        "interface_name": name,
        "network_id": network2.id,
        "random_mac": False,
        "ip": ip,
        "mac": mac,
        "cnames_string": "",
    }
    # Permission denied
    # user_lab doesn't have permissions for the host domain: prod.example.org
    login(client, "user_lab", "userlab")
    response = client.post(f"/network/interfaces/create/{host.name}", data=form)
    assert response.status_code == 403
    # The host wasn't created
    assert models.Interface.query.filter_by(name=name).first() is None
    logout(client)
    # Success with user_prod user
    login(client, "user_prod", "userprod")
    response = client.post(f"/network/interfaces/create/{host.name}", data=form)
    assert response.status_code == 302
    # The interface was created
    interface = models.Interface.query.filter_by(name=name).first()
    assert interface is not None
    assert interface.ip == ip
    assert interface.mac == mac
    assert interface.name == name
    assert interface.host == host


def test_add_interface_to_empty_host(
    client, host_factory, network_scope_factory, network_factory
):
    host = host_factory(name="myhost")
    scope = network_scope_factory(name="ProdNetworks", supernet="192.168.0.0/16")
    network = network_factory(
        address="192.168.2.0/24",
        first_ip="192.168.2.10",
        last_ip="192.168.2.250",
        scope=scope,
    )
    name = host.name
    ip = "192.168.2.11"
    mac = "02:42:42:46:3c:75"
    form = {
        "host_id": host.id,
        "interface_name": name,
        "network_id": network.id,
        "random_mac": False,
        "ip": ip,
        "mac": mac,
        "cnames_string": "",
    }
    # user_lab doesn't have permissions for the network domain: prod.example.org
    # form validation will fail because the network_id won't be in the choices
    login(client, "user_lab", "userlab")
    response = client.post(f"/network/interfaces/create/{host.name}", data=form)
    assert response.status_code == 200
    # The host wasn't created
    assert models.Interface.query.filter_by(name=name).first() is None
    logout(client)
    # Success with user_prod user
    login(client, "user_prod", "userprod")
    response = client.post(f"/network/interfaces/create/{host.name}", data=form)
    assert response.status_code == 302
    # The interface was created
    interface = models.Interface.query.filter_by(name=name).first()
    assert interface.ip == ip
    assert interface.mac == mac
    assert interface.name == name
    assert interface.host == host


def check_vm_creation_response(response, success=True):
    assert response.status_code == 200
    assert (b"View task" in response.data) is success
    assert (b"View host" in response.data) is not success
    assert (b"Please contact an admin user" in response.data) is not success


def test_create_vm(
    client,
    network_scope_factory,
    network_factory,
    device_type_factory,
    host_factory,
    interface_factory,
):
    virtualmachine = device_type_factory(name="VirtualMachine")
    scope_prod = network_scope_factory(name="ProdNetworks")
    scope_lab = network_scope_factory(name="LabNetworks")
    network_prod = network_factory(scope=scope_prod)
    network_lab = network_factory(scope=scope_lab)
    vm_prod = host_factory(device_type=virtualmachine)
    interface_factory(name=vm_prod.name, host=vm_prod, network=network_prod)
    vioc_prod = host_factory(device_type=virtualmachine, is_ioc=True)
    interface_factory(name=vioc_prod.name, host=vioc_prod, network=network_prod)
    vm_lab = host_factory(device_type=virtualmachine)
    interface_factory(name=vm_lab.name, host=vm_lab, network=network_lab)
    vioc_lab = host_factory(device_type=virtualmachine, is_ioc=True)
    interface_factory(name=vioc_lab.name, host=vioc_lab, network=network_lab)
    form = {"cores": 1, "memory": 4, "disk": 15, "osversion": "centos7"}
    # User has access to the lab networks and can create VM and VIOC there
    login(client, "user_lab", "userlab")
    response = client.post(
        f"/network/hosts/view/{vm_prod.name}", data=form, follow_redirects=True
    )
    check_vm_creation_response(response, success=False)
    response = client.post(
        f"/network/hosts/view/{vioc_prod.name}", data=form, follow_redirects=True
    )
    check_vm_creation_response(response, success=False)
    response = client.post(
        f"/network/hosts/view/{vm_lab.name}", data=form, follow_redirects=True
    )
    check_vm_creation_response(response, success=True)
    response = client.post(
        f"/network/hosts/view/{vioc_lab.name}", data=form, follow_redirects=True
    )
    check_vm_creation_response(response, success=True)
    logout(client)
    # User has access to the prod networks but can only create VIOC due to ALLOWED_VM_CREATION_DOMAINS
    login(client, "user_prod", "userprod")
    response = client.post(
        f"/network/hosts/view/{vm_prod.name}", data=form, follow_redirects=True
    )
    check_vm_creation_response(response, success=False)
    response = client.post(
        f"/network/hosts/view/{vioc_prod.name}", data=form, follow_redirects=True
    )
    check_vm_creation_response(response, success=True)
    response = client.post(
        f"/network/hosts/view/{vm_lab.name}", data=form, follow_redirects=True
    )
    check_vm_creation_response(response, success=False)
    response = client.post(
        f"/network/hosts/view/{vioc_lab.name}", data=form, follow_redirects=True
    )
    check_vm_creation_response(response, success=False)


def test_delete_host_as_admin(logged_admin_client, host_factory, user_factory):
    # admin can delete any host
    admin = models.User.query.filter_by(username="admin").first()
    user1 = user_factory(username="user1")
    host1 = host_factory(name="host1", user=admin)
    host2 = host_factory(name="host2", user=user1)
    assert len(models.Host.query.all()) == 2
    response = logged_admin_client.post(
        "/network/hosts/delete", data={"host_id": host1.id}
    )
    assert response.status_code == 302
    response = logged_admin_client.post(
        "/network/hosts/delete", data={"host_id": host2.id}
    )
    assert response.status_code == 302
    assert len(models.Host.query.all()) == 0


def test_delete_host_as_normal_user(logged_rw_client, host_factory, user_factory):
    # a normal user can only delete its own hosts
    user_rw = models.User.query.filter_by(username="user_rw").first()
    user1 = user_factory(username="user1")
    host1 = host_factory(name="host1", user=user_rw)
    host2 = host_factory(name="host2", user=user1)
    assert len(models.Host.query.all()) == 2
    # user_rw can delete its host
    response = logged_rw_client.post(
        "/network/hosts/delete", data={"host_id": host1.id}
    )
    assert response.status_code == 302
    # user_rw can't delete host owned by user1
    response = logged_rw_client.post(
        "/network/hosts/delete", data={"host_id": host2.id}
    )
    assert response.status_code == 403
    assert len(models.Host.query.all()) == 1


def test_create_network_scope(logged_admin_client, domain_factory):
    domain = domain_factory(name="prod.example.org")
    name = "MyNetworks"
    first_vlan = 200
    last_vlan = 300
    supernet = "192.168.0.0/16"
    form = {
        "name": name,
        "first_vlan": first_vlan,
        "last_vlan": last_vlan,
        "supernet": supernet,
        "domain_id": domain.id,
    }
    response = logged_admin_client.post("/network/scopes/create", data=form)
    assert response.status_code == 302
    # The network scope was created
    scope = models.NetworkScope.query.filter_by(name=name).first()
    assert scope is not None
    assert scope.name == name
    assert scope.first_vlan == first_vlan
    assert scope.last_vlan == last_vlan
    assert scope.supernet == supernet


def test_create_network_scope_no_vlan(logged_admin_client, domain_factory):
    domain = domain_factory(name="lab.example.org")
    name = "NoVlan"
    supernet = "192.168.0.0/16"
    form = {
        "name": name,
        "first_vlan": "",
        "last_vlan": None,
        "supernet": supernet,
        "domain_id": domain.id,
    }
    response = logged_admin_client.post("/network/scopes/create", data=form)
    assert response.status_code == 302
    # The network scope was created
    scope = models.NetworkScope.query.filter_by(name=name).first()
    assert scope is not None
    assert scope.name == name
    assert scope.first_vlan is None
    assert scope.last_vlan is None
    assert scope.supernet == supernet


def test_create_network(logged_admin_client, domain_factory, network_scope_factory):
    domain = domain_factory(name="lab.example.org")
    scope = network_scope_factory(
        name="MyNetworks",
        first_vlan=100,
        last_vlan=200,
        supernet="192.168.0.0/16",
        domain_id=domain.id,
    )
    vlan_name = "my-network"
    form = {
        "scope_id": scope.id,
        "vlan_name": vlan_name,
        "vlan_id": 101,
        "address": "192.168.0.0/24",
        "first_ip": "192.168.0.11",
        "last_ip": "192.168.0.249",
        "gateway": "192.168.0.254",
        "domain_id": domain.id,
        "admin_only": False,
    }
    response = logged_admin_client.post("/network/networks/create", data=form)
    assert response.status_code == 302
    # The network was created
    network = models.Network.query.filter_by(vlan_name=vlan_name).first()
    assert network is not None
    assert network.vlan_name == vlan_name
    assert network.address == form["address"]
    assert network.vlan_id == form["vlan_id"]


def test_create_network_no_vlan(
    logged_admin_client, domain_factory, network_scope_factory
):
    domain = domain_factory(name="lab.example.org")
    scope = network_scope_factory(
        name="NoVlanNetworks",
        first_vlan=None,
        last_vlan=None,
        supernet="192.168.0.0/16",
        domain_id=domain.id,
    )
    vlan_name = "my-network"
    form = {
        "scope_id": scope.id,
        "vlan_name": vlan_name,
        "vlan_id": "",
        "address": "192.168.0.0/24",
        "first_ip": "192.168.0.11",
        "last_ip": "192.168.0.249",
        "gateway": "192.168.0.254",
        "domain_id": domain.id,
        "admin_only": False,
    }
    response = logged_admin_client.post("/network/networks/create", data=form)
    assert response.status_code == 302
    # The network was created
    network = models.Network.query.filter_by(vlan_name=vlan_name).first()
    assert network is not None
    assert network.vlan_name == vlan_name
    assert network.address == form["address"]
    assert network.vlan_id is None


def test_create_item_invalid_ics_id(logged_rw_client):
    ics_id = "AAA1100"
    form = {"ics_id": ics_id, "serial_number": "12345"}
    response = logged_rw_client.post(
        f"/inventory/items/create", data=form, follow_redirects=True
    )
    assert response.status_code == 200
    assert b"Register new item" in response.data
    assert b"The ICS id shall be composed of 3 letters and 3 digits" in response.data


def test_create_item_with_stack_member(
    host_factory, device_type_factory, item_factory, logged_rw_client
):
    # Test for JIRA INFRA-1648
    network_type = device_type_factory(name="NETWORK")
    host = host_factory(device_type=network_type)
    item1 = item_factory(ics_id="AAA001", host=host, stack_member=0)
    ics_id = "AAA042"
    form = {
        "ics_id": ics_id,
        "serial_number": "12345",
        "host_id": host.id,
        "stack_member": 1,
    }
    response = logged_rw_client.post(f"/inventory/items/create", data=form)
    assert response.status_code == 302
    item2 = models.Item.query.filter_by(ics_id=ics_id).first()
    assert host.stack_members() == [item1, item2]


def test_create_item_with_host_and_no_stack_member(
    host_factory, device_type_factory, item_factory, logged_rw_client
):
    network_type = device_type_factory(name="NETWORK")
    host = host_factory(device_type=network_type)
    ics_id = "AAA042"
    form = {
        "ics_id": ics_id,
        "serial_number": "12345",
        "host_id": host.id,
        "stack_member": "",
    }
    response = logged_rw_client.post(f"/inventory/items/create", data=form)
    assert response.status_code == 302
    item = models.Item.query.filter_by(ics_id=ics_id).first()
    assert item.host == host
    assert item.stack_member is None


def test_ansible_groups_no_recursive_dependency(
    ansible_group_factory, logged_admin_client
):
    group3 = ansible_group_factory()
    group2 = ansible_group_factory(children=[group3])
    group1 = ansible_group_factory(children=[group2])
    form = {"name": group3.name, "type": group3.type, "children": [group1.id]}
    response = logged_admin_client.post(
        f"/network/groups/edit/{group3.name}", data=form
    )
    assert response.status_code == 200
    assert b"creates a recursive dependency loop" in response.data


def test_create_network_overlapping(
    network_scope_factory, network_factory, logged_admin_client
):
    scope = network_scope_factory(
        first_vlan=3800, last_vlan=4000, supernet="172.30.0.0/16"
    )
    network_factory(
        vlan_name="network1",
        vlan_id=3800,
        address="172.30.0.0/23",
        first_ip="172.30.0.3",
        last_ip="172.30.1.240",
        scope=scope,
    )
    form = {
        "vlan_name": "network2",
        "vlan_id": 3842,
        "scope_id": scope.id,
        "address": "172.30.1.0/24",
        "first_ip": "172.30.1.5",
        "last_ip": "172.30.1.245",
        "gateway": "172.30.1.248",
        "domain_id": scope.domain_id,
    }
    response = logged_admin_client.post("/network/networks/create", data=form)
    assert response.status_code == 200
    assert b"172.30.1.0/24 overlaps network1 (172.30.0.0/23)" in response.data


def test_create_network_scope_overlapping(network_scope_factory, logged_admin_client):
    scope1 = network_scope_factory(
        name="scope1", first_vlan=3800, last_vlan=4000, supernet="172.30.0.0/16"
    )
    form = {
        "name": "scope2",
        "first_vlan": 200,
        "last_vlan": 500,
        "supernet": "172.30.200.0/22",
        "domain_id": scope1.domain_id,
    }
    response = logged_admin_client.post("/network/scopes/create", data=form)
    assert response.status_code == 200
    assert b"172.30.200.0/22 overlaps scope1 (172.30.0.0/16)" in response.data


def test_view_network_restriction(client, network_scope_factory, network_factory):
    scope = network_scope_factory(name="ProdNetworks", supernet="192.168.0.0/16")
    network = network_factory(
        address="192.168.1.0/24",
        first_ip="192.168.1.10",
        last_ip="192.168.1.250",
        scope=scope,
    )
    # user_lab doesn't have the permissions to see that network
    login(client, "user_lab", "userlab")
    response = client.get(f"/network/networks/view/{network}")
    assert response.status_code == 403
    logout(client)
    # Success with user_prod user
    login(client, "user_prod", "userprod")
    response = client.get(f"/network/networks/view/{network}")
    assert response.status_code == 200