Write new secret manager using existing RSA logic

This commit is contained in:
2025-06-22 17:17:56 +02:00
parent 5985a726e3
commit 82ec7fabb4
34 changed files with 2042 additions and 640 deletions

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,67 @@
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
import httpx
from sshecret.backend import Client
from sshecret.crypto import generate_private_key, generate_public_key_string
from ..types import AdminServer
def make_test_key() -> str:
"""Generate a test key."""
private_key = generate_private_key()
return generate_public_key_string(private_key.public_key())
class BaseAdminTests:
"""Base admin test class."""
@asynccontextmanager
async def http_client(
self, admin_server: AdminServer, authenticate: bool = True
) -> AsyncIterator[httpx.AsyncClient]:
"""Run a client towards the admin rest api."""
admin_url, credentials = admin_server
username, password = credentials
headers: dict[str, str] | None = None
if authenticate:
async with httpx.AsyncClient(base_url=admin_url) as client:
response = await client.post(
"api/v1/token", data={"username": username, "password": password}
)
assert response.status_code == 200
data = response.json()
assert "access_token" in data
token = data["access_token"]
headers = {"Authorization": f"Bearer {token}"}
async with httpx.AsyncClient(base_url=admin_url, headers=headers) as client:
yield client
async def create_client(
self,
admin_server: AdminServer,
name: str,
public_key: str | None = None,
) -> Client:
"""Create a client."""
if not public_key:
public_key = make_test_key()
new_client = {
"name": name,
"public_key": public_key,
"sources": ["192.0.2.0/24"],
}
async with self.http_client(admin_server, True) as http_client:
response = await http_client.post("api/v1/clients/", json=new_client)
assert response.status_code == 200
data = response.json()
client = Client.model_validate(data)
return client

View File

@ -0,0 +1,184 @@
"""Tests of the admin interface."""
import allure
import pytest
from allure_commons.types import Severity
from ..types import AdminServer
from .base import BaseAdminTests
@allure.title("Admin API")
class TestAdminAPI(BaseAdminTests):
"""Tests of the Admin REST API."""
@allure.title("Test health test endpoint")
@allure.severity(Severity.TRIVIAL)
@pytest.mark.asyncio
async def test_health_check(
self, admin_server: tuple[str, tuple[str, str]]
) -> None:
"""Test admin login."""
async with self.http_client(admin_server, False) as client:
resp = await client.get("/health")
assert resp.status_code == 200
@allure.title("Test login over API")
@allure.severity(Severity.BLOCKER)
@pytest.mark.asyncio
async def test_admin_login(self, admin_server: AdminServer) -> None:
"""Test admin login."""
async with self.http_client(admin_server, False) as client:
resp = await client.get("api/v1/clients/")
assert resp.status_code == 401
async with self.http_client(admin_server, True) as client:
resp = await client.get("api/v1/clients/")
assert resp.status_code == 200
@allure.title("Admin API Client API")
class TestAdminApiClients(BaseAdminTests):
"""Test client routes."""
@allure.title("Test creating a client")
@allure.description("Ensure we can create a new client.")
@pytest.mark.asyncio
async def test_create_client(self, admin_server: AdminServer) -> None:
"""Test create_client."""
client = await self.create_client(admin_server, "testclient")
assert client.id is not None
assert client.name == "testclient"
@allure.title("Test reading clients")
@allure.description("Ensure we can retrieve a list of current clients.")
@pytest.mark.asyncio
async def test_get_clients(self, admin_server: AdminServer) -> None:
"""Test get_clients."""
client_names = ["test-db", "test-app", "test-www"]
for name in client_names:
await self.create_client(admin_server, name)
async with self.http_client(admin_server) as http_client:
resp = await http_client.get("api/v1/clients/")
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
assert len(data) == 3
for entry in data:
assert isinstance(entry, dict)
client_name = entry.get("name")
assert client_name in client_names
@allure.title("Test client deletion")
@allure.description("Ensure we can delete a client.")
@pytest.mark.asyncio
async def test_delete_client(self, admin_server: AdminServer) -> None:
"""Test delete_client."""
await self.create_client(admin_server, name="testclient")
async with self.http_client(admin_server) as http_client:
resp = await http_client.get("api/v1/clients/")
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
assert len(data) == 1
assert data[0]["name"] == "testclient"
resp = await http_client.delete("/api/v1/clients/testclient")
assert resp.status_code == 200
resp = await http_client.get("api/v1/clients/")
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
assert len(data) == 0
@allure.title("Test secret management")
class TestAdminApiSecrets(BaseAdminTests):
"""Test secret management."""
@allure.title("Test adding a secret")
@allure.description("Ensure that we can add a secret to a client.")
@pytest.mark.asyncio
async def test_add_secret(self, admin_server: AdminServer) -> None:
"""Test add_secret."""
await self.create_client(admin_server, name="testclient")
async with self.http_client(admin_server) as http_client:
data = {
"name": "testsecret",
"clients": ["testclient"],
"value": "secretstring",
}
resp = await http_client.post("api/v1/secrets/", json=data)
assert resp.status_code == 200
@allure.title("Test read a secret")
@allure.description("Ensure that we can retrieve a secret we have stored.")
@pytest.mark.asyncio
async def test_get_secret(self, admin_server: AdminServer) -> None:
"""Test get_secret."""
await self.test_add_secret(admin_server)
async with self.http_client(admin_server) as http_client:
resp = await http_client.get("api/v1/secrets/testsecret")
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, dict)
assert data["name"] == "testsecret"
assert data["secret"] == "secretstring"
assert "testclient" in data["clients"]
@allure.title("Test adding a secret with automatic value")
@allure.description(
"Test that we can add a secret where we let the system come up with the value of a given length."
)
@pytest.mark.asyncio
async def test_add_secret_auto(self, admin_server: AdminServer) -> None:
"""Test adding a secret with an auto-generated value."""
await self.create_client(admin_server, name="testclient")
async with self.http_client(admin_server) as http_client:
data = {
"name": "testsecret",
"clients": ["testclient"],
"value": {"auto_generate": True, "length": 17},
}
resp = await http_client.post("api/v1/secrets/", json=data)
assert resp.status_code == 200
resp = await http_client.get("api/v1/secrets/testsecret")
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, dict)
assert data["name"] == "testsecret"
assert len(data["secret"]) == 17
assert "testclient" in data["clients"]
@allure.title("Test updating a secret")
@allure.description("Test that we can update the value of a stored secret.")
@pytest.mark.asyncio
async def test_update_secret(self, admin_server: AdminServer) -> None:
"""Test updating secrets."""
await self.test_add_secret_auto(admin_server)
async with self.http_client(admin_server) as http_client:
resp = await http_client.put(
"api/v1/secrets/testsecret",
json={"value": "secret"},
)
assert resp.status_code == 200
resp = await http_client.get("api/v1/secrets/testsecret")
assert resp.status_code == 200
data = resp.json()
assert data["secret"] == "secret"
resp = await http_client.put(
"api/v1/secrets/testsecret",
json={"value": {"auto_generate": True, "length": 16}},
)
assert resp.status_code == 200
resp = await http_client.get("api/v1/secrets/testsecret")
assert resp.status_code == 200
data = resp.json()
assert len(data["secret"]) == 16

View File

@ -0,0 +1,634 @@
"""Test secret manager.
This package tests the rewritten secret manager system.
This is technically an integration test, as it requires the other subsystems to
run, but it uses the internal API rather than the exposed routes.
"""
import allure
import pytest
import pytest_asyncio
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sshecret_admin.core.settings import AdminServerSettings
from sshecret_admin.services.models import SecretGroup
from sshecret_admin.services.secret_manager import (
password_manager_context,
AsyncSecretContext,
InvalidSecretNameError,
InvalidGroupNameError,
)
from sshecret_admin.auth.models import Base, PasswordDB
from sshecret_admin.services.master_password import setup_master_password
# -------- global parameter sets start here -------- #
# -------- Fixtures start here -------- #
@pytest_asyncio.fixture(autouse=True)
async def create_admin_db(admin_server_settings: AdminServerSettings) -> None:
"""Create the database."""
engine = create_engine(admin_server_settings.admin_db)
Base.metadata.create_all(engine)
encr_master_password = setup_master_password(
settings=admin_server_settings, regenerate=True
)
with Session(engine) as session:
pwdb = PasswordDB(id=1, encrypted_password=encr_master_password)
session.add(pwdb)
session.commit()
@pytest_asyncio.fixture()
async def secrets_manager(admin_server_settings: AdminServerSettings):
"""Test that the context manager can be created."""
async with password_manager_context(
admin_server_settings, "TEST", "127.0.0.1"
) as manager:
yield manager
# -------- Tests start here -------- #
@allure.title("Adding entries")
@pytest.mark.parametrize("name,secret", [("testentry", "testsecret")])
class TestSecretsAddEntry:
"""Tests for the add_entry method."""
@pytest.mark.asyncio
async def test_add_entry(
self,
secrets_manager: AsyncSecretContext,
name: str,
secret: str,
) -> None:
"""Test add entry.
This tests add_entry and get_secret
"""
await secrets_manager.add_entry(name, secret)
stored_secret = await secrets_manager.get_secret(name)
assert stored_secret == secret
async def test_add_entry_duplicate(
self,
secrets_manager: AsyncSecretContext,
name: str,
secret: str,
) -> None:
"""Test adding an entry twice."""
await secrets_manager.add_entry(name, secret)
stored_secret = await secrets_manager.get_secret(name)
assert stored_secret == secret
with pytest.raises(InvalidSecretNameError):
await secrets_manager.add_entry(name, secret)
async def test_add_entry_with_group(
self,
secrets_manager: AsyncSecretContext,
name: str,
secret: str,
) -> None:
"""Test adding a secret with a group."""
group = "testgroup"
await secrets_manager.add_group(group)
await secrets_manager.add_entry(name, secret, group_path=group)
result = await secrets_manager.get_entry_group(name)
assert result == group
async def test_add_entry_with_nonexisting_group(
self,
secrets_manager: AsyncSecretContext,
name: str,
secret: str,
) -> None:
"""Test adding a secret where the group does not exist."""
group = "testgroup"
with pytest.raises(InvalidGroupNameError):
await secrets_manager.add_entry(name, secret, group_path=group)
async def test_add_entry_with_deep_path(
self,
secrets_manager: AsyncSecretContext,
name: str,
secret: str,
) -> None:
"""Test adding a secret to a nested group with a path specification."""
await secrets_manager.add_group("root")
await secrets_manager.add_group("nested", parent_group="root")
await secrets_manager.add_entry(name, secret, group_path="/root/nested")
group = await secrets_manager.get_secret_group("/root/nested")
assert group is not None
assert name in group.entries
async def test_overwrite_secret(
self, secrets_manager: AsyncSecretContext, name: str, secret: str
) -> None:
"""Test overwriting a secret."""
await secrets_manager.add_entry(name, secret)
stored_secret = await secrets_manager.get_secret(name)
assert stored_secret == secret
new_secret = "newsecret"
await secrets_manager.add_entry(name, new_secret, overwrite=True)
stored_secret = await secrets_manager.get_secret(name)
assert stored_secret == new_secret
@allure.title("Creating groups")
class TestSecretGroupCreation:
"""Test secret groups."""
@pytest.mark.parametrize(
"group_name", ["testgroup", "long group name with spaces", "blåbærgrød"]
)
@allure.title("Add a group name {group_name}")
@pytest.mark.asyncio
async def test_add_group(
self,
secrets_manager: AsyncSecretContext,
group_name: str,
) -> None:
"""Get adding a group."""
await secrets_manager.add_group(group_name)
groups = await secrets_manager.get_secret_groups()
assert len(groups) == 1
assert groups[0].name == group_name
@pytest.mark.asyncio
async def test_add_group_with_parent(
self, secrets_manager: AsyncSecretContext
) -> None:
"""Test add a group with a parent group."""
parent_name = "parent"
child_name = "child"
await secrets_manager.add_group(parent_name)
await secrets_manager.add_group(child_name, parent_group=parent_name)
parent_group = await secrets_manager.get_secret_group(f"/parent")
assert parent_group is not None
assert len(parent_group.children) == 1
assert parent_group.children[0].name == child_name
child_group = await secrets_manager.get_secret_group("/parent/child")
assert child_group is not None
assert child_group.name == child_name
assert child_group.parent_group is not None
assert child_group.parent_group.name == parent_name
assert len(child_group.children) == 0
@pytest.mark.asyncio
async def test_add_group_as_path(self, secrets_manager: AsyncSecretContext) -> None:
"""Add a nested group with path annotation."""
parent_name = "parent"
child_path = "/parent/child"
await secrets_manager.add_group(parent_name)
await secrets_manager.add_group(child_path)
parent_group = await secrets_manager.get_secret_group(f"/parent")
assert parent_group is not None
assert len(parent_group.children) == 1
assert parent_group.children[0].name == "child"
child_group = await secrets_manager.get_secret_group(child_path)
assert child_group is not None
@pytest.mark.asyncio
async def test_overlapping_names(self, secrets_manager: AsyncSecretContext) -> None:
"""Test having overlapping names in different groups."""
await secrets_manager.add_group("root")
with pytest.raises(InvalidGroupNameError):
await secrets_manager.add_group("/root")
await secrets_manager.add_group("/root/root")
group = await secrets_manager.get_secret_group("/root/root")
assert group is not None
assert group.name == "root"
@pytest.mark.asyncio
async def test_add_group_with_nonexisting_parent(
self, secrets_manager: AsyncSecretContext
) -> None:
"""Test adding a group with a nonexisting parent."""
with pytest.raises(InvalidGroupNameError):
await secrets_manager.add_group("orphan", parent_group="unknown")
@pytest.mark.asyncio
async def test_add_duplicate_group(
self, secrets_manager: AsyncSecretContext
) -> None:
"""Test adding the same group twice."""
await secrets_manager.add_group("snowflake")
with pytest.raises(InvalidGroupNameError):
await secrets_manager.add_group("snowflake")
@pytest.mark.parametrize(
"group_name,description", [("testgroup", "test description")]
)
@allure.title("Add a group name {group_name} with description {description}")
@pytest.mark.asyncio
async def test_add_group_with_description(
self,
secrets_manager: AsyncSecretContext,
group_name: str,
description: str,
) -> None:
"""Test adding a group with description."""
await secrets_manager.add_group(group_name, description)
result = await secrets_manager.get_secret_group(group_name)
assert result is not None
assert result.description == description
@pytest.mark.parametrize(
"groups",
[
[
("root", None, "root"),
("level1", "root", "/root/level1"),
("level2", "level1", "/root/level1/level2"),
],
[("flat1", None, "flat1"), ("flat2", None, "flat2")],
[
("stub", None, "stub"),
("root", None, "root"),
("nested", "root", "/root/nested"),
],
],
)
@allure.title("Listing groups")
class TestSecretGroupListing:
"""Tests for listing groups."""
@pytest_asyncio.fixture(autouse=True)
async def create_groups(
self,
secrets_manager: AsyncSecretContext,
groups: list[tuple[str, str | None, str]],
) -> None:
"""Pre-create groups."""
for name, parent_group, _path in groups:
await secrets_manager.add_group(name, parent_group=parent_group)
@pytest.mark.asyncio
async def test_get_secret_groups_list(
self,
secrets_manager: AsyncSecretContext,
groups: list[tuple[str, str | None, str]],
) -> None:
"""Test the flat get_secret_groups_list."""
# Create three levels of content
group_list = await secrets_manager.get_secret_group_list()
assert len(group_list) == len(groups)
@pytest.mark.asyncio
async def test_get_secret_groups(
self,
secrets_manager: AsyncSecretContext,
groups: list[tuple[str, str | None, str]],
) -> None:
"""Test the tree-oriented get_secretsgroups."""
group_map = dict([(group[0], group[1]) for group in sorted(groups)])
group_tree = await secrets_manager.get_secret_groups()
root_groups = [key for key, value in group_map.items() if value is None]
assert len(group_tree) == len(root_groups)
reconstructed_groups: list[tuple[str, str | None]] = []
def crawl_tree(item: SecretGroup) -> list[tuple[str, str | None]]:
"""Crawl a tree recursively."""
parent_group_name = None
if item.parent_group:
parent_group_name = item.parent_group.name
items: list[tuple[str, str | None]] = [(item.name, parent_group_name)]
for child in item.children:
items.extend(crawl_tree(child))
return items
for item in group_tree:
reconstructed_groups.extend(crawl_tree(item))
assert dict(sorted(reconstructed_groups)) == group_map
@pytest.mark.asyncio
async def test_get_secret_groups_with_secrets(
self,
secrets_manager: AsyncSecretContext,
groups: list[tuple[str, str | None, str]],
) -> None:
"""Test fetching groups where there are secrets in all groups."""
# We will create exactly two secrets in each group.
for group_name, _parent, path in groups:
await secrets_manager.add_entry(
f"{group_name}_1", f"{group_name}_secret_1", group_path=path
)
await secrets_manager.add_entry(
f"{group_name}_2", f"{group_name}_secret_2", group_path=path
)
group_list = await secrets_manager.get_secret_group_list()
for group in group_list:
assert len(group.entries) == 2
assert group.entries[0] == f"{group.name}_1"
assert group.entries[1] == f"{group.name}_2"
@pytest.mark.parametrize(
"query,expected,groups,children",
[
("MATCH", 1, [("MATCH", None), ("SOMETHINGELSE", None)], 0),
("MATCH", 1, [("root", None), ("MATCH", "root"), ("SOMETHINGELSE", "root")], 0),
("MATCH", 3, [("MATCH1", None), ("MATCH2", None), ("MATCH3", None)], 0),
("MATCH", 1, [("root", None), ("MATCH", "root"), ("CHILD", "MATCH")], 1),
(
"NOMATCH",
0,
[("foo", None), ("bar", None), ("foobar", "foo"), ("barfoo", "bar")],
0,
),
],
)
@allure.title("Searching in groups using patterns")
class TestGroupSearchPattern:
@pytest.mark.asyncio
async def test_group_list_pattern(
self,
secrets_manager: AsyncSecretContext,
query: str,
expected: int,
groups: list[tuple[str, str | None]],
children: int,
) -> None:
"""Test matching a pattern."""
for name, parent_group in groups:
await secrets_manager.add_group(name, parent_group=parent_group)
result = await secrets_manager.get_secret_group_list(pattern=query, regex=False)
assert len(result) == expected
@pytest.mark.asyncio
async def test_group_tree_pattern(
self,
secrets_manager: AsyncSecretContext,
query: str,
expected: int,
groups: list[tuple[str, str | None]],
children: int,
) -> None:
"""Test matching a pattern with a tree result."""
for name, parent_group in groups:
await secrets_manager.add_group(name, parent_group=parent_group)
result = await secrets_manager.get_secret_groups(pattern=query, regex=False)
assert len(result) == expected
if expected == 1 and children > 0:
assert len(result[0].children) == children
@allure.title("Modifying groups")
class TestGroupModification:
"""Test modifying groups."""
@pytest.mark.parametrize(
"group_name,parent,description",
[("test", None, "test description"), ("test", "root", "test_description")],
)
@pytest.mark.asyncio
async def test_set_group_description(
self,
secrets_manager: AsyncSecretContext,
group_name: str,
parent: str | None,
description: str,
) -> None:
"""Test setting a description on a group."""
if parent:
await secrets_manager.add_group(parent)
await secrets_manager.add_group(group_name, parent_group=parent)
path = group_name
if parent:
path = f"/{parent}/{group_name}"
group = await secrets_manager.get_secret_group(path)
assert group is not None
assert group.description is None
await secrets_manager.set_group_description(path, description)
group = await secrets_manager.get_secret_group(path)
assert group is not None
assert group.description == description
@pytest.mark.parametrize(
"groups,target_group, expected_path",
[
(
[("root", None), ("test", None)],
("test", "root"),
"/root/test",
),
(
[("root", None), ("test", "root")],
("/root/test", None),
"test",
),
([("test", None)], ("test", None), "test"),
],
)
@pytest.mark.asyncio
async def test_move_group(
self,
secrets_manager: AsyncSecretContext,
groups: list[tuple[str, str | None]],
target_group: tuple[str, str | None],
expected_path: str,
) -> None:
"""Test moving groups around."""
for group_name, parent_name in groups:
await secrets_manager.add_group(group_name, parent_group=parent_name)
group_name, target = target_group
await secrets_manager.move_group(group_name, target)
group = await secrets_manager.get_secret_group(expected_path)
assert group is not None
@allure.title("Deleting items")
class TestSecretManagerDeletions:
"""Test secret manager deletions."""
@pytest_asyncio.fixture(autouse=True)
async def create_test_data(self, secrets_manager: AsyncSecretContext) -> None:
"""Create some test data."""
groups = [
("root", None, "root"),
("level1", "root", "/root/level1"),
("level2", "level1", "/root/level1/level2"),
]
for n in range(2):
await secrets_manager.add_entry(f"ungrouped_{n}", "secret")
for group_name, parent_name, path in groups:
await secrets_manager.add_group(group_name, parent_group=parent_name)
for n in range(2):
await secrets_manager.add_entry(
f"{group_name}_{n}", "secret", group_path=path
)
@pytest.mark.parametrize(
"name,group_name",
[("root_1", "root"), ("level1_0", "/root/level1"), ("ungrouped_1", None)],
)
@allure.title("Delete secret {name in group {group_name}}")
@pytest.mark.asyncio
async def test_secret_deletion(
self, secrets_manager: AsyncSecretContext, name: str, group_name: str | None
) -> None:
"""Test secret deletion."""
if group_name:
group = await secrets_manager.get_secret_group(group_name)
assert group is not None
assert name in group.entries
secret = await secrets_manager.get_secret(name)
assert secret is not None
await secrets_manager.delete_entry(name)
secret = await secrets_manager.get_secret(name)
assert secret is None
if group_name:
group = await secrets_manager.get_secret_group(group_name)
assert group is not None
assert name not in group.entries
@pytest.mark.parametrize("name", ["NONEXISTING"])
@allure.title("Deleting non-existing entry {name}")
@pytest.mark.asyncio
async def test_nonexisting_entry(
self, secrets_manager: AsyncSecretContext, name: str
) -> None:
"""Test deleting something that doesn't exist."""
secret = await secrets_manager.get_secret(name)
assert secret is None
# Deleting something that is already deleted returns None
await secrets_manager.delete_entry(name)
@pytest.mark.parametrize("path", ["/root/level1"])
@allure.title("Deleting group {path}")
async def test_group_delete(
self, secrets_manager: AsyncSecretContext, path: str
) -> None:
"""Test deleting a group."""
group = await secrets_manager.get_secret_group(path)
assert group is not None
entries = list(group.entries)
await secrets_manager.delete_group(path)
group = await secrets_manager.get_secret_group(path)
assert group is None
for name in entries:
new_grouping = await secrets_manager.get_entry_group(name)
assert new_grouping is None
@allure.title("Other tests")
class TestSecretManagerOther:
"""Uncategorized tests to standardize module."""
@pytest.mark.asyncio
async def test_get_secret_nonexisting(
self, secrets_manager: AsyncSecretContext
) -> None:
"""Test get_secret with invalid name."""
result = await secrets_manager.get_secret("NOMATCH")
assert result is None
@pytest.mark.parametrize(
"group_name,num_grouped,num_ungrouped",
[("GROUP", 3, 3), ("GROUP", 3, 0), ("GROUP", 0, 0)],
)
@pytest.mark.asyncio
async def test_get_ungrouped_secrets(
self,
secrets_manager: AsyncSecretContext,
group_name: str,
num_grouped: int,
num_ungrouped: int,
) -> None:
"""Test get_ungrouped_secrets."""
await secrets_manager.add_group(group_name)
for n in range(num_ungrouped):
await secrets_manager.add_entry(f"ungrouped_{n}", "secret")
for n in range(num_grouped):
await secrets_manager.add_entry(
f"grouped_{n}", "secret", group_path="GROUP"
)
ungrouped = await secrets_manager.get_ungrouped_secrets()
assert len(ungrouped) == num_ungrouped
matching = [entry for entry in ungrouped if entry.startswith("ungrouped_")]
assert len(matching) == num_ungrouped
grouped_secrets = await secrets_manager.get_available_secrets(
group_path=group_name
)
assert len(grouped_secrets) == num_grouped
all_secrets = await secrets_manager.get_available_secrets()
assert len(all_secrets) == (num_ungrouped + num_grouped)
@pytest.mark.parametrize(
"entries", [[("test1", "secret1"), ("test2", "secret2")], []]
)
@pytest.mark.asyncio
async def test_get_available_secrets(
self, secrets_manager: AsyncSecretContext, entries: list[tuple[str, str]]
) -> None:
"""Test the get_available_secrets method."""
for name, secret in entries:
await secrets_manager.add_entry(name, secret)
entry_names = [entry[0] for entry in entries]
response = await secrets_manager.get_available_secrets()
assert len(response) == len(entries)
assert sorted(response) == sorted(entry_names)
async def test_get_secret_groups_none(
self, secrets_manager: AsyncSecretContext
) -> None:
"""Test get_secret_groups with no groups created."""
result = await secrets_manager.get_secret_groups()
assert len(result) == 0
result_flat = await secrets_manager.get_secret_group_list()
assert len(result_flat) == 0
@allure.title("Search for a group using regular expression")
async def test_group_regex_search(
self, secrets_manager: AsyncSecretContext
) -> None:
"""Search for entries with regular expressions."""
groups = [
"test1",
"test2",
"other",
"somethingelse",
]
for group in groups:
await secrets_manager.add_group(group)
results = await secrets_manager.get_secret_group_list(
pattern="^test", regex=True
)
assert len(results) == 2
for group in results:
assert group.name.startswith("test")