Skip to content

Commit 1d86276

Browse files
committed
Widen test coverage.
1 parent b29437b commit 1d86276

4 files changed

Lines changed: 133 additions & 0 deletions

File tree

tests/test_api.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,65 @@ async def test_deactivate_users_mode(client):
433433
r2 = await client.post("/deactivate", json={"license_id": lid, "app_id": "*", "user_principal": "bob@co.com"})
434434
assert r2.status_code == 404
435435
assert "Active activation not found" in r2.json()["detail"]
436+
437+
438+
@pytest.mark.asyncio
439+
async def test_activate_reactivates_after_deactivate(client):
440+
"""Re-activating a previously-released identity reuses (un-revokes) its row."""
441+
lid = (await _issue_license(client, restriction="activations", activation_limit=1))["license_id"]
442+
assert (await _activate(client, lid, machine_id="m1")).status_code == 200
443+
444+
deact = await client.post("/deactivate", json={"license_id": lid, "app_id": "*", "machine_id": "m1"})
445+
assert deact.status_code == 204
446+
447+
r = await _activate(client, lid, machine_id="m1")
448+
assert r.status_code == 200
449+
assert r.json()["active_count"] == 1
450+
451+
452+
# ---------------------------------------------------------------------------
453+
# Admin auth + not-found branches
454+
# ---------------------------------------------------------------------------
455+
456+
457+
@pytest.mark.asyncio
458+
async def test_admin_requires_configured_key(client):
459+
"""With a bearer token but no admin key configured server-side → 503."""
460+
from locksmith.core.config import settings
461+
462+
original = settings.admin_api_key
463+
settings.admin_api_key = ""
464+
try:
465+
resp = await client.post("/licenses", json={}, headers={"Authorization": "Bearer anything"})
466+
assert resp.status_code == 503
467+
assert "not configured" in resp.json()["detail"].lower()
468+
finally:
469+
settings.admin_api_key = original
470+
471+
472+
@pytest.mark.asyncio
473+
async def test_get_unknown_license_returns_404(client):
474+
from locksmith.core.config import settings
475+
476+
original = settings.admin_api_key
477+
settings.admin_api_key = "testkey123"
478+
try:
479+
resp = await client.get("/licenses/no-such-id", headers={"Authorization": "Bearer testkey123"})
480+
assert resp.status_code == 404
481+
assert "not found" in resp.json()["detail"].lower()
482+
finally:
483+
settings.admin_api_key = original
484+
485+
486+
@pytest.mark.asyncio
487+
async def test_revoke_unknown_license_returns_404(client):
488+
from locksmith.core.config import settings
489+
490+
original = settings.admin_api_key
491+
settings.admin_api_key = "testkey123"
492+
try:
493+
resp = await client.delete("/licenses/no-such-id", headers={"Authorization": "Bearer testkey123"})
494+
assert resp.status_code == 404
495+
assert "not found" in resp.json()["detail"].lower()
496+
finally:
497+
settings.admin_api_key = original

tests/test_keys.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ def test_save_keypair_succeeds_without_permission_bit_assertions_on_non_posix(ke
7272
assert loaded_pubkey == pubkey
7373

7474

75+
def test_save_keypair_non_posix_branch(keypair, tmp_path, monkeypatch):
76+
"""Force the non-POSIX path (plain write_bytes, no fd/chmod) on a POSIX host.
77+
78+
Only ``keys``'s view of ``os`` reports ``name == 'nt'``; the real ``os`` is left
79+
alone so ``pathlib`` still produces a usable ``PosixPath`` on this host.
80+
"""
81+
import locksmith.core.keys as keys
82+
83+
class _NonPosixOS:
84+
name = "nt"
85+
86+
def __getattr__(self, attr):
87+
return getattr(os, attr)
88+
89+
monkeypatch.setattr(keys, "os", _NonPosixOS())
90+
pubkey, privkey = keypair
91+
priv_path, pub_path = save_keypair(pubkey, privkey, tmp_path / "keys")
92+
93+
assert priv_path.exists()
94+
assert pub_path.exists()
95+
assert rsa.PrivateKey.load_pkcs1(priv_path.read_bytes()) == privkey
96+
assert rsa.PublicKey.load_pkcs1(pub_path.read_bytes()) == pubkey
97+
98+
7599
def test_load_pkcs1_raises_for_corrupted_key_files(tmp_path):
76100
priv_path = tmp_path / "id_rsa"
77101
pub_path = tmp_path / "id_rsa.pub"

tests/test_signer.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,36 @@ async def test_verify_only_signer_cannot_sign(verify_only_signer):
347347
lic = _make_license()
348348
with pytest.raises(ValueError, match="no private key"):
349349
await sign_license(lic, verify_only_signer)
350+
351+
352+
# ---------------------------------------------------------------------------
353+
# Defensive / edge branches
354+
# ---------------------------------------------------------------------------
355+
356+
357+
@pytest.mark.asyncio
358+
async def test_naive_datetimes_treated_as_utc(file_signer):
359+
"""valid_from / expires_at without tzinfo are assumed to be UTC during validation."""
360+
lic = _make_license(time_policy=TimePolicy.LIMITED)
361+
naive_utc = datetime.now(UTC).replace(tzinfo=None)
362+
lic.valid_from = naive_utc - timedelta(hours=1)
363+
lic.expires_at = naive_utc + timedelta(hours=1)
364+
await sign_license(lic, file_signer) # re-sign so the signature matches the mutated payload
365+
await validate_license(lic, file_signer) # currently valid → must not raise
366+
367+
368+
@pytest.mark.asyncio
369+
async def test_specific_policy_requires_app_version(file_signer):
370+
lic = _make_license(version_policy=VersionPolicy.SPECIFIC, locked_version="2.0.0")
371+
await sign_license(lic, file_signer)
372+
with pytest.raises(LicenseVersionError, match="app_version is required"):
373+
await validate_license(lic, file_signer) # no app_version supplied
374+
375+
376+
@pytest.mark.asyncio
377+
async def test_entitlement_version_range_requires_app_version(file_signer):
378+
ent = Entitlement(app_id="com.app", min_version="2.0.0")
379+
lic = _make_license(entitlements=[ent])
380+
await sign_license(lic, file_signer)
381+
with pytest.raises(LicenseVersionError, match="app_version is required"):
382+
await validate_license(lic, file_signer, app_id="com.app") # matches, but no app_version

tests/test_store.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Tests for store helpers not reachable through the API layer."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
import locksmith.core.store as store
8+
9+
10+
def test_get_session_raises_when_not_initialised(monkeypatch):
11+
"""get_session() refuses to hand out sessions before init_db() has run."""
12+
monkeypatch.setattr(store, "_async_session", None)
13+
with pytest.raises(RuntimeError, match="Database not initialised"):
14+
store.get_session()

0 commit comments

Comments
 (0)