chore: import upstream snapshot with attribution
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Auth module tests."""
|
||||
@@ -0,0 +1,230 @@
|
||||
"""Tests for auth.store module."""
|
||||
|
||||
import sqlite3
|
||||
import threading
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from cua_cli.auth.store import (
|
||||
CredentialStore,
|
||||
clear_credentials,
|
||||
get_api_key,
|
||||
require_api_key,
|
||||
save_api_key,
|
||||
)
|
||||
|
||||
|
||||
class TestCredentialStore:
|
||||
"""Tests for CredentialStore class."""
|
||||
|
||||
def test_init_creates_database(self, tmp_path):
|
||||
"""Test that initializing creates the database file."""
|
||||
db_path = tmp_path / "test.db"
|
||||
CredentialStore(db_path)
|
||||
|
||||
assert db_path.exists()
|
||||
|
||||
def test_init_creates_parent_directory(self, tmp_path):
|
||||
"""Test that initializing creates parent directories."""
|
||||
db_path = tmp_path / "nested" / "dir" / "test.db"
|
||||
CredentialStore(db_path)
|
||||
|
||||
assert db_path.parent.exists()
|
||||
assert db_path.exists()
|
||||
|
||||
def test_set_and_get(self, tmp_path):
|
||||
"""Test setting and getting a value."""
|
||||
store = CredentialStore(tmp_path / "test.db")
|
||||
|
||||
store.set("test_key", "test_value")
|
||||
result = store.get("test_key")
|
||||
|
||||
assert result == "test_value"
|
||||
|
||||
def test_get_nonexistent_key(self, tmp_path):
|
||||
"""Test getting a nonexistent key returns None."""
|
||||
store = CredentialStore(tmp_path / "test.db")
|
||||
|
||||
result = store.get("nonexistent")
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_set_overwrites_existing(self, tmp_path):
|
||||
"""Test that setting an existing key overwrites the value."""
|
||||
store = CredentialStore(tmp_path / "test.db")
|
||||
|
||||
store.set("key", "value1")
|
||||
store.set("key", "value2")
|
||||
result = store.get("key")
|
||||
|
||||
assert result == "value2"
|
||||
|
||||
def test_delete_existing_key(self, tmp_path):
|
||||
"""Test deleting an existing key returns True."""
|
||||
store = CredentialStore(tmp_path / "test.db")
|
||||
|
||||
store.set("key", "value")
|
||||
result = store.delete("key")
|
||||
|
||||
assert result is True
|
||||
assert store.get("key") is None
|
||||
|
||||
def test_delete_nonexistent_key(self, tmp_path):
|
||||
"""Test deleting a nonexistent key returns False."""
|
||||
store = CredentialStore(tmp_path / "test.db")
|
||||
|
||||
result = store.delete("nonexistent")
|
||||
|
||||
assert result is False
|
||||
|
||||
def test_clear_removes_all(self, tmp_path):
|
||||
"""Test that clear removes all values."""
|
||||
store = CredentialStore(tmp_path / "test.db")
|
||||
|
||||
store.set("key1", "value1")
|
||||
store.set("key2", "value2")
|
||||
store.clear()
|
||||
|
||||
assert store.get("key1") is None
|
||||
assert store.get("key2") is None
|
||||
|
||||
def test_wal_mode_enabled(self, tmp_path):
|
||||
"""Test that WAL mode is enabled for concurrent access."""
|
||||
db_path = tmp_path / "test.db"
|
||||
CredentialStore(db_path)
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
try:
|
||||
cursor = conn.execute("PRAGMA journal_mode")
|
||||
mode = cursor.fetchone()[0]
|
||||
assert mode.lower() == "wal"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_concurrent_access(self, tmp_path):
|
||||
"""Test that concurrent access works with WAL mode."""
|
||||
db_path = tmp_path / "test.db"
|
||||
store = CredentialStore(db_path)
|
||||
results = []
|
||||
errors = []
|
||||
|
||||
def writer():
|
||||
try:
|
||||
for i in range(10):
|
||||
store.set(f"key_{i}", f"value_{i}")
|
||||
except Exception as e:
|
||||
errors.append(e)
|
||||
|
||||
def reader():
|
||||
try:
|
||||
for i in range(10):
|
||||
result = store.get(f"key_{i}")
|
||||
if result is not None:
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
errors.append(e)
|
||||
|
||||
threads = [
|
||||
threading.Thread(target=writer),
|
||||
threading.Thread(target=reader),
|
||||
threading.Thread(target=reader),
|
||||
]
|
||||
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
assert len(errors) == 0
|
||||
|
||||
|
||||
class TestGetApiKey:
|
||||
"""Tests for get_api_key function."""
|
||||
|
||||
def test_returns_env_var_first(self, monkeypatch, tmp_path):
|
||||
"""Test that environment variable takes precedence."""
|
||||
# Set up a store with a different value
|
||||
with patch("cua_cli.auth.store.CREDENTIALS_DB", tmp_path / "test.db"):
|
||||
with patch("cua_cli.auth.store._store", None):
|
||||
monkeypatch.setenv("CUA_API_KEY", "env-key")
|
||||
save_api_key("stored-key")
|
||||
|
||||
result = get_api_key()
|
||||
|
||||
assert result == "env-key"
|
||||
|
||||
def test_falls_back_to_stored(self, monkeypatch, tmp_path):
|
||||
"""Test that stored key is used when env var not set."""
|
||||
with patch("cua_cli.auth.store.CREDENTIALS_DB", tmp_path / "test.db"):
|
||||
with patch("cua_cli.auth.store._store", None):
|
||||
monkeypatch.delenv("CUA_API_KEY", raising=False)
|
||||
save_api_key("stored-key")
|
||||
|
||||
result = get_api_key()
|
||||
|
||||
assert result == "stored-key"
|
||||
|
||||
def test_returns_none_when_not_found(self, monkeypatch, tmp_path):
|
||||
"""Test that None is returned when no key is found."""
|
||||
with patch("cua_cli.auth.store.CREDENTIALS_DB", tmp_path / "test.db"):
|
||||
with patch("cua_cli.auth.store._store", None):
|
||||
monkeypatch.delenv("CUA_API_KEY", raising=False)
|
||||
|
||||
result = get_api_key()
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestSaveApiKey:
|
||||
"""Tests for save_api_key function."""
|
||||
|
||||
def test_saves_api_key(self, tmp_path, monkeypatch):
|
||||
"""Test that API key is saved correctly."""
|
||||
with patch("cua_cli.auth.store.CREDENTIALS_DB", tmp_path / "test.db"):
|
||||
with patch("cua_cli.auth.store._store", None):
|
||||
monkeypatch.delenv("CUA_API_KEY", raising=False)
|
||||
|
||||
save_api_key("my-api-key")
|
||||
result = get_api_key()
|
||||
|
||||
assert result == "my-api-key"
|
||||
|
||||
|
||||
class TestClearCredentials:
|
||||
"""Tests for clear_credentials function."""
|
||||
|
||||
def test_clears_all_credentials(self, tmp_path, monkeypatch):
|
||||
"""Test that all credentials are cleared."""
|
||||
with patch("cua_cli.auth.store.CREDENTIALS_DB", tmp_path / "test.db"):
|
||||
with patch("cua_cli.auth.store._store", None):
|
||||
monkeypatch.delenv("CUA_API_KEY", raising=False)
|
||||
|
||||
save_api_key("my-api-key")
|
||||
clear_credentials()
|
||||
result = get_api_key()
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestRequireApiKey:
|
||||
"""Tests for require_api_key function."""
|
||||
|
||||
def test_returns_key_when_available(self, monkeypatch):
|
||||
"""Test that key is returned when available."""
|
||||
monkeypatch.setenv("CUA_API_KEY", "my-api-key")
|
||||
|
||||
result = require_api_key()
|
||||
|
||||
assert result == "my-api-key"
|
||||
|
||||
def test_raises_when_not_available(self, monkeypatch, tmp_path):
|
||||
"""Test that RuntimeError is raised when no key available."""
|
||||
with patch("cua_cli.auth.store.CREDENTIALS_DB", tmp_path / "test.db"):
|
||||
with patch("cua_cli.auth.store._store", None):
|
||||
monkeypatch.delenv("CUA_API_KEY", raising=False)
|
||||
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
require_api_key()
|
||||
|
||||
assert "No API key configured" in str(exc_info.value)
|
||||
assert "cua auth login" in str(exc_info.value)
|
||||
Reference in New Issue
Block a user