Files

98 lines
4.6 KiB
Python

import importlib.util
import sqlite3
import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from types import SimpleNamespace
import pytest
ROOT=Path(__file__).resolve().parents[2]
sys.path.insert(0,str(ROOT/'work/mi-fitness-mcp-cn/src'))
from mi_fitness_mcp.services.query_service import QueryService
@pytest.mark.parametrize('method',['_aggregate_by_week','_aggregate_by_month'])
def test_latest_aggregation_uses_latest_date_instead_of_sum(method):
service=QueryService(None,'synthetic')
result=getattr(service,method)([{'date':'2026-10-16','value':9},{'date':'2026-10-15','value':2}],'latest')
assert result[0]['value']==9
def runtime():
spec=importlib.util.spec_from_file_location('audit_runtime',ROOT/'outputs/nas-deployment/runtime.py')
module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
return module
def test_repeated_login_for_same_account_keeps_sync_but_multiple_accounts_are_rejected(tmp_path):
db=tmp_path/'health.sqlite'
with sqlite3.connect(db) as c:
c.execute('CREATE TABLE api_keys (key TEXT,user_id TEXT,region TEXT,created_at TEXT,revoked INTEGER)')
c.executemany('INSERT INTO api_keys VALUES (?,?,?,?,0)',[('old','owner','cn','2026-10-15'),('new','owner','cn','2026-10-16')])
assert runtime().load_single_key(db)=='new'
with sqlite3.connect(db) as c:c.execute("INSERT INTO api_keys VALUES ('other','another-owner','cn','2026-10-17',0)")
with pytest.raises(RuntimeError):runtime().load_single_key(db)
def test_cached_credential_does_not_repeat_expensive_unlock_or_bypass_revocation(tmp_path,monkeypatch):
from mi_fitness_mcp import api
from mi_fitness_mcp.storage import Database
from starlette.requests import Request
db=Database(tmp_path/'health.sqlite')
with db._get_connection() as c:
c.execute('CREATE TABLE api_keys (key TEXT,user_id TEXT,region TEXT,pass_token TEXT,revoked INTEGER,last_used_at TEXT)')
c.execute("INSERT INTO api_keys VALUES ('synthetic-key','owner','cn','',0,NULL)")
c.commit()
state=SimpleNamespace(default_context=None,db=db,config=None,contexts={},key_contexts={},key_context_lock=threading.RLock())
request=Request({'type':'http','headers':[(b'x-api-key',b'synthetic-key')],'app':SimpleNamespace(state=state)})
calls=[]
clock=[100.0]
monkeypatch.setattr(api,'time',SimpleNamespace(monotonic=lambda:clock[0]))
def unlock(key):
calls.append(key)
time.sleep(0.05)
return 'synthetic-account-token'
monkeypatch.setattr(api,'load_api_key_secret',unlock)
monkeypatch.setattr(api,'_build_context',lambda *args:SimpleNamespace(query_service=object()))
barrier=threading.Barrier(2)
def resolve():
barrier.wait()
return api._resolve_context(request)
with ThreadPoolExecutor(max_workers=2) as pool:
list(pool.map(lambda _:resolve(),range(2)))
assert len(calls)==1, '每个请求都执行昂贵的密钥环解锁,概览五个请求被串行拖慢'
clock[0]+=61
api._resolve_context(request)
assert len(calls)==2, '短期缓存到期后必须重新读取密钥环'
with db._get_connection() as c:
c.execute("UPDATE api_keys SET revoked=1 WHERE key='synthetic-key'");c.commit()
with pytest.raises(api.HTTPException):api._resolve_context(request)
state.default_context=SimpleNamespace(query_service=object())
bad=Request({'type':'http','headers':[(b'x-api-key',b'unknown-key')],'app':SimpleNamespace(state=state)})
with pytest.raises(api.HTTPException):api._resolve_context(bad)
@pytest.mark.asyncio
async def test_ambiguous_key_prefix_cannot_revoke_multiple_authorizations(tmp_path,monkeypatch):
from mi_fitness_mcp import api
from mi_fitness_mcp.storage import Database
from starlette.requests import Request
db=Database(tmp_path/'keys.sqlite')
with db._get_connection() as c:
c.execute('CREATE TABLE api_keys (key TEXT,revoked INTEGER)')
c.executemany('INSERT INTO api_keys VALUES (?,0)',[('mif_sk_abcde0000',),('mif_sk_abcde1111',)])
c.commit()
deleted=[]
monkeypatch.setattr(api,'_require_admin',lambda request:None)
monkeypatch.setattr(api,'delete_api_key_secret',deleted.append)
request=Request({'type':'http','headers':[],'app':SimpleNamespace(state=SimpleNamespace(db=db))})
with pytest.raises(api.HTTPException) as error:
await api.revoke_key('mif_sk_abcde',request)
assert error.value.status_code==409
assert not deleted
with db._get_connection() as c:assert c.execute('SELECT sum(revoked) FROM api_keys').fetchone()[0]==0