Standardize IDs, fix group APIs, fix tests
This commit is contained in:
@ -1,12 +1,16 @@
|
||||
"""Tests of the admin interface."""
|
||||
|
||||
import os
|
||||
import allure
|
||||
from dataclasses import dataclass, field
|
||||
from httpx import Response
|
||||
import pytest
|
||||
|
||||
from allure_commons.types import Severity
|
||||
|
||||
from ..types import AdminServer
|
||||
from .base import BaseAdminTests
|
||||
from sshecret_admin.services.models import ClientSecretGroup, ClientSecretGroupList
|
||||
|
||||
|
||||
@allure.title("Admin API")
|
||||
@ -129,7 +133,8 @@ class TestAdminApiSecrets(BaseAdminTests):
|
||||
assert isinstance(data, dict)
|
||||
assert data["name"] == "testsecret"
|
||||
assert data["secret"] == "secretstring"
|
||||
assert "testclient" in data["clients"]
|
||||
client_names = [cl["name"] for cl in data["clients"]]
|
||||
assert "testclient" in client_names
|
||||
|
||||
@allure.title("Test adding a secret with automatic value")
|
||||
@allure.description(
|
||||
@ -153,7 +158,7 @@ class TestAdminApiSecrets(BaseAdminTests):
|
||||
assert isinstance(data, dict)
|
||||
assert data["name"] == "testsecret"
|
||||
assert len(data["secret"]) == 17
|
||||
assert "testclient" in data["clients"]
|
||||
assert "testclient" in [cl["name"] for cl in data["clients"]]
|
||||
|
||||
@allure.title("Test updating a secret")
|
||||
@allure.description("Test that we can update the value of a stored secret.")
|
||||
@ -182,3 +187,392 @@ class TestAdminApiSecrets(BaseAdminTests):
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert len(data["secret"]) == 16
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class GroupHier:
|
||||
"""Group hierarchy for testing."""
|
||||
|
||||
name: str
|
||||
secrets: list[str]
|
||||
path: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class TestSecretGroupApi(BaseAdminTests):
|
||||
"""Test secret group api."""
|
||||
|
||||
async def add_group(
|
||||
self,
|
||||
admin_server: AdminServer,
|
||||
group_name: str,
|
||||
parent: str | None = None,
|
||||
description: str | None = None,
|
||||
) -> None:
|
||||
"""Add a group."""
|
||||
path = "api/v1/secrets/groups/"
|
||||
async with self.http_client(admin_server) as http_client:
|
||||
data = {"name": group_name, "parent_group": parent}
|
||||
if description:
|
||||
data["description"] = description
|
||||
resp = await http_client.post(path, json=data)
|
||||
assert resp.status_code == 200
|
||||
|
||||
async def get_group(self, admin_server: AdminServer, groups: list[str]) -> Response:
|
||||
"""Get group."""
|
||||
group_name = "/".join(groups)
|
||||
path = f"api/v1/secrets/groups/{group_name}/"
|
||||
async with self.http_client(admin_server) as http_client:
|
||||
resp = await http_client.get(path)
|
||||
return resp
|
||||
|
||||
async def get_groups(self, admin_server: AdminServer) -> Response:
|
||||
"""Get groups."""
|
||||
path = "api/v1/secrets/groups/"
|
||||
async with self.http_client(admin_server) as http_client:
|
||||
resp = await http_client.get(path)
|
||||
return resp
|
||||
|
||||
async def add_secret(
|
||||
self, admin_server: AdminServer, secret_name: str, group: str | None = None
|
||||
) -> Response:
|
||||
"""Add a secret."""
|
||||
async with self.http_client(admin_server) as http_client:
|
||||
data = {
|
||||
"name": secret_name,
|
||||
"value": "secretstring",
|
||||
}
|
||||
if group:
|
||||
data["group"] = group
|
||||
resp = await http_client.post("api/v1/secrets/", json=data)
|
||||
return resp
|
||||
|
||||
async def add_secret_to_group(
|
||||
self, admin_server: AdminServer, secret_name: str, groups: list[str] | None
|
||||
) -> Response:
|
||||
"""Add a secret to a group.
|
||||
|
||||
Secret should be created in advance.
|
||||
"""
|
||||
path = f"api/v1/secrets/set-group"
|
||||
|
||||
if not groups:
|
||||
groups = []
|
||||
groups.insert(0, "")
|
||||
group_path = "/".join(groups)
|
||||
|
||||
async with self.http_client(admin_server) as http_client:
|
||||
resp = await http_client.post(
|
||||
path, json={"secret_name": secret_name, "group_path": group_path}
|
||||
)
|
||||
|
||||
return resp
|
||||
|
||||
async def delete_secret_group(
|
||||
self, admin_server: AdminServer, group_path: str
|
||||
) -> Response:
|
||||
"""Delete secret group."""
|
||||
if group_path.startswith("/"):
|
||||
group_path = group_path[1:]
|
||||
path = os.path.join(f"/api/v1/secrets/groups", group_path) + "/"
|
||||
async with self.http_client(admin_server) as http_client:
|
||||
resp = await http_client.delete(path)
|
||||
return resp
|
||||
|
||||
async def move_secret_group(
|
||||
self, admin_server: AdminServer, group_path: str, new_path: str
|
||||
) -> Response:
|
||||
"""Move a secret group."""
|
||||
if group_path.startswith("/"):
|
||||
group_path = group_path[1:]
|
||||
path = f"/api/v1/secrets/move-group/{group_path}"
|
||||
async with self.http_client(admin_server) as http_client:
|
||||
resp = await http_client.post(path, json={"path": new_path})
|
||||
|
||||
return resp
|
||||
|
||||
async def update_secret_group(
|
||||
self,
|
||||
admin_server: AdminServer,
|
||||
name: str,
|
||||
group_path: str,
|
||||
*,
|
||||
description: str | None = None,
|
||||
parent: str | None = None,
|
||||
) -> Response:
|
||||
"""Update secret group."""
|
||||
if group_path.startswith("/"):
|
||||
group_path = group_path[1:]
|
||||
|
||||
data = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"parent_group": parent,
|
||||
}
|
||||
|
||||
path = f"/api/v1/secrets/groups/{group_path}/"
|
||||
async with self.http_client(admin_server) as http_client:
|
||||
resp = await http_client.put(path, json=data)
|
||||
|
||||
return resp
|
||||
|
||||
@pytest.mark.parametrize("group_name", ["test", "test with spaces", "blåbærgrød"])
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_group(self, admin_server: AdminServer, group_name: str) -> None:
|
||||
"""Test adding a group, then getting it."""
|
||||
await self.add_group(admin_server, group_name)
|
||||
response = await self.get_group(admin_server, [group_name])
|
||||
assert response.status_code == 200
|
||||
# We might as well try to deserialize the group.
|
||||
group = ClientSecretGroup.model_validate(response.json())
|
||||
assert group.group_name == group_name
|
||||
|
||||
@pytest.mark.parametrize("parent,child", [("parent", "child")])
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_nested_group(
|
||||
self, admin_server: AdminServer, parent: str, child: str
|
||||
) -> None:
|
||||
"""Test adding a group with a parent group."""
|
||||
await self.add_group(admin_server, parent)
|
||||
await self.add_group(admin_server, child, parent)
|
||||
|
||||
response = await self.get_group(admin_server, [parent])
|
||||
assert response.status_code == 200
|
||||
|
||||
parent_group = ClientSecretGroup.model_validate(response.json())
|
||||
assert parent_group.group_name == parent
|
||||
assert len(parent_group.children) == 1
|
||||
assert parent_group.children[0].group_name == child
|
||||
|
||||
response = await self.get_group(admin_server, [parent, child])
|
||||
assert response.status_code == 200
|
||||
|
||||
child_group = ClientSecretGroup.model_validate(response.json())
|
||||
assert child_group.group_name == child
|
||||
assert child_group.parent_group is not None
|
||||
assert child_group.parent_group.group_name == parent
|
||||
assert child_group.path == "/parent/child"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"secret_name,group_name,parent_name",
|
||||
[("test", "group", None), ("test", "child", "parent")],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_secret_to_group(
|
||||
self,
|
||||
admin_server: AdminServer,
|
||||
secret_name: str,
|
||||
group_name: str,
|
||||
parent_name: str | None,
|
||||
) -> None:
|
||||
"""Test adding a secret to a group."""
|
||||
resp = await self.add_secret(admin_server, secret_name)
|
||||
assert resp.status_code == 200
|
||||
|
||||
groups = [group_name]
|
||||
if parent_name:
|
||||
await self.add_group(admin_server, parent_name)
|
||||
groups = [parent_name, group_name]
|
||||
await self.add_group(admin_server, group_name, parent_name)
|
||||
|
||||
resp = await self.add_secret_to_group(admin_server, secret_name, groups)
|
||||
assert resp.status_code == 200
|
||||
|
||||
resp = await self.get_group(admin_server, groups)
|
||||
assert resp.status_code == 200
|
||||
|
||||
group = ClientSecretGroup.model_validate(resp.json())
|
||||
assert len(group.entries) == 1
|
||||
assert group.entries[0].name == secret_name
|
||||
|
||||
@pytest.mark.parametrize("groups", [["group1", "group2", "group3"]])
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_group_flat(
|
||||
self, admin_server: AdminServer, groups: list[str]
|
||||
) -> None:
|
||||
"""Test getting a list of groups with no recursion."""
|
||||
for group in groups:
|
||||
await self.add_group(admin_server, group)
|
||||
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.groups) == len(groups)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_group_tree(self, admin_server: AdminServer) -> None:
|
||||
"""Test getting a list of groups where recursion exists."""
|
||||
await self.add_group(admin_server, "root")
|
||||
await self.add_group(admin_server, "level1", "root")
|
||||
await self.add_group(admin_server, "level2", "level1")
|
||||
await self.add_secret(admin_server, "secret1")
|
||||
await self.add_secret(admin_server, "secret2", "/root/level1")
|
||||
await self.add_secret(admin_server, "secret3", "/root/level1")
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
# we expect this to be a tree now
|
||||
assert len(group_list.ungrouped) == 1
|
||||
assert len(group_list.groups) == 1
|
||||
assert group_list.groups[0].group_name == "root"
|
||||
assert len(group_list.groups[0].children) == 1
|
||||
assert len(group_list.groups[0].children[0].children) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_move_secret_to_root(self, admin_server: AdminServer) -> None:
|
||||
"""Test moving a secret to the root."""
|
||||
await self.add_group(admin_server, "secretgroup")
|
||||
await self.add_secret(admin_server, "secret1", "/secretgroup")
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.ungrouped) == 0
|
||||
|
||||
await self.add_secret_to_group(admin_server, "secret1", None)
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.ungrouped) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_secret_group(self, admin_server: AdminServer) -> None:
|
||||
"""Test deleting a secret group."""
|
||||
await self.add_group(admin_server, "secretgroup")
|
||||
await self.add_group(admin_server, "othergroup")
|
||||
|
||||
await self.add_secret(admin_server, "secret1", "/secretgroup")
|
||||
await self.add_secret(admin_server, "secret2", "/secretgroup")
|
||||
await self.add_secret(admin_server, "secret3", "/secretgroup")
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.groups) == 2
|
||||
|
||||
response = await self.delete_secret_group(admin_server, "/secretgroup")
|
||||
assert response.status_code == 200
|
||||
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.groups) == 1
|
||||
assert len(group_list.ungrouped) == 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_nest_group(self, admin_server: AdminServer) -> None:
|
||||
"""Test moving a group below another group."""
|
||||
await self.add_group(admin_server, "secretgroup")
|
||||
await self.add_group(admin_server, "othergroup")
|
||||
await self.add_group(admin_server, "nested", "/othergroup")
|
||||
await self.add_secret(admin_server, "testsecret", "/secretgroup")
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.groups) == 2
|
||||
response = await self.move_secret_group(
|
||||
admin_server, "/secretgroup", "/othergroup/nested"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.groups) == 1
|
||||
assert group_list.groups[0].group_name == "othergroup"
|
||||
assert len(group_list.groups[0].children) == 1
|
||||
assert len(group_list.groups[0].children[0].children) == 1
|
||||
assert group_list.groups[0].children[0].children[0].group_name == "secretgroup"
|
||||
assert len(group_list.groups[0].children[0].children[0].entries) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_nested_group_by_path(self, admin_server: AdminServer) -> None:
|
||||
"""Test adding a group directly by path"""
|
||||
await self.add_group(admin_server, "/secretgroup")
|
||||
await self.add_group(admin_server, "/secretgroup/othergroup")
|
||||
await self.add_group(admin_server, "/secretgroup/othergroup/nestedgroup")
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.groups) == 1
|
||||
assert len(group_list.groups[0].children) == 1
|
||||
assert len(group_list.groups[0].children[0].children) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unnest_group(self, admin_server: AdminServer) -> None:
|
||||
"""Test moving a deeply nested group back to the root."""
|
||||
await self.add_group(admin_server, "/secretgroup")
|
||||
await self.add_group(admin_server, "/secretgroup/othergroup")
|
||||
await self.add_group(admin_server, "/secretgroup/othergroup/nestedgroup")
|
||||
await self.add_secret(
|
||||
admin_server, "secret1", "/secretgroup/othergroup/nestedgroup"
|
||||
)
|
||||
await self.add_secret(
|
||||
admin_server, "secret2", "/secretgroup/othergroup/nestedgroup"
|
||||
)
|
||||
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.groups) == 1
|
||||
|
||||
move_resp = await self.move_secret_group(
|
||||
admin_server, "/secretgroup/othergroup/nestedgroup", "/"
|
||||
)
|
||||
assert move_resp.status_code == 200
|
||||
|
||||
response = await self.get_groups(admin_server)
|
||||
assert response.status_code == 200
|
||||
group_list = ClientSecretGroupList.model_validate(response.json())
|
||||
assert len(group_list.groups) == 2
|
||||
target_group = next(
|
||||
filter(lambda x: x.group_name == "nestedgroup", group_list.groups)
|
||||
)
|
||||
assert len(target_group.entries) == 2
|
||||
assert target_group.path == "/nestedgroup"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"group_name,description,parent",
|
||||
[
|
||||
(("test", "test"), ("before", "after"), (None, None)),
|
||||
(("test", "newname"), ("descr", "descr"), ("parent", "parent")),
|
||||
(("test", "test"), ("descr", "descr"), ("oldparent", "newparent")),
|
||||
(("test", "test"), ("descr", "descr"), ("oldparent", None)),
|
||||
(("oldname", "newname"), ("before", "after"), ("oldparent", "newparent")),
|
||||
],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_group_update(
|
||||
self,
|
||||
admin_server: AdminServer,
|
||||
group_name: tuple[str, str],
|
||||
description: tuple[str, str],
|
||||
parent: tuple[str | None, str | None],
|
||||
) -> None:
|
||||
"""Test updating a group"""
|
||||
name_b, name_a = group_name
|
||||
descr_b, descr_a = description
|
||||
parent_b, parent_a = parent
|
||||
if parent_b:
|
||||
await self.add_group(admin_server, parent_b, None)
|
||||
|
||||
if parent_a and parent_a != parent_b:
|
||||
await self.add_group(admin_server, parent_a, None)
|
||||
elif not parent_a:
|
||||
parent_a = "/"
|
||||
|
||||
await self.add_group(admin_server, name_b, parent_b, descr_b)
|
||||
|
||||
group_path = name_b
|
||||
if parent_b:
|
||||
group_path = f"{parent_b}/{name_b}"
|
||||
|
||||
resp = await self.update_secret_group(
|
||||
admin_server, name_a, group_path, description=descr_a, parent=parent_a
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
group = ClientSecretGroup.model_validate(resp.json())
|
||||
assert group.group_name == name_a
|
||||
assert group.description == descr_a
|
||||
if parent_a and parent_a != "/":
|
||||
assert group.parent_group is not None
|
||||
assert group.parent_group.group_name == parent_a
|
||||
else:
|
||||
assert group.parent_group is None
|
||||
|
||||
Reference in New Issue
Block a user