- Rename MCPToken to UserToken across models, views, and tests - Update URL names from mcp-token-* to token-* - Add Daedalus/Pallas integration design doc (v2) - Switch docker-compose to build local mnemosyne:local image via shared build config instead of pulling from git.helu.ca
78 lines
2.9 KiB
Python
78 lines
2.9 KiB
Python
"""Tests for the UserToken model."""
|
|
|
|
from datetime import timedelta
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase
|
|
from django.utils import timezone
|
|
|
|
from mcp_server.models import UserToken, hash_token
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class UserTokenModelTest(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(
|
|
username="alice", email="alice@example.com", password="pw"
|
|
)
|
|
|
|
def test_create_token_returns_plaintext_and_stores_hash(self):
|
|
token, plaintext = UserToken.objects.create_token(user=self.user, name="t")
|
|
self.assertTrue(plaintext)
|
|
self.assertGreater(len(plaintext), 20)
|
|
# Database stores hash, not plaintext
|
|
self.assertEqual(len(token.token_hash), 64)
|
|
self.assertNotEqual(token.token_hash, plaintext)
|
|
self.assertEqual(token.token_hash, hash_token(plaintext))
|
|
|
|
def test_token_hash_never_equals_plaintext(self):
|
|
# Regression guard: if anyone ever wires plaintext back into token_hash,
|
|
# this fails.
|
|
token, plaintext = UserToken.objects.create_token(user=self.user, name="t")
|
|
self.assertNotIn(plaintext, token.token_hash)
|
|
|
|
def test_active_token_is_valid(self):
|
|
token, _ = UserToken.objects.create_token(user=self.user, name="t")
|
|
self.assertTrue(token.is_valid)
|
|
|
|
def test_inactive_token_not_valid(self):
|
|
token, _ = UserToken.objects.create_token(user=self.user, name="t")
|
|
token.is_active = False
|
|
token.save()
|
|
self.assertFalse(token.is_valid)
|
|
|
|
def test_expired_token_not_valid(self):
|
|
token, _ = UserToken.objects.create_token(
|
|
user=self.user,
|
|
name="t",
|
|
expires_at=timezone.now() - timedelta(hours=1),
|
|
)
|
|
self.assertFalse(token.is_valid)
|
|
|
|
def test_unrestricted_permits_all(self):
|
|
token, _ = UserToken.objects.create_token(user=self.user, name="t")
|
|
self.assertTrue(token.can_use_tool("anything"))
|
|
|
|
def test_tool_whitelist(self):
|
|
token, _ = UserToken.objects.create_token(
|
|
user=self.user, name="t", allowed_tools=["search"]
|
|
)
|
|
self.assertTrue(token.can_use_tool("search"))
|
|
self.assertFalse(token.can_use_tool("get_chunk"))
|
|
|
|
def test_record_usage(self):
|
|
token, _ = UserToken.objects.create_token(user=self.user, name="t")
|
|
self.assertIsNone(token.last_used_at)
|
|
token.record_usage()
|
|
token.refresh_from_db()
|
|
self.assertIsNotNone(token.last_used_at)
|
|
|
|
def test_masked_token_is_hash_prefix(self):
|
|
token, plaintext = UserToken.objects.create_token(user=self.user, name="t")
|
|
masked = token.get_masked_token()
|
|
self.assertTrue(masked.startswith("tok_…"))
|
|
self.assertIn(token.token_hash[:8], masked)
|
|
# Plaintext must never leak through the masked display
|
|
self.assertNotIn(plaintext, masked)
|