feat(03-01): backend API endpoints — channels, billing, usage, and audit logger enhancement
- Create channels.py: HMAC-signed OAuth state generation/verification, Slack OAuth install/callback, WhatsApp manual connect, test message endpoint - Create billing.py: Stripe Checkout session, billing portal session, webhook handler with idempotency (StripeEvent table), subscription lifecycle management - Update usage.py: add _aggregate_rows_by_agent and _aggregate_rows_by_provider helpers (unit-testable without DB), complete usage endpoints - Fix audit.py: rename 'metadata' attribute to 'event_metadata' (SQLAlchemy 2.0 DeclarativeBase reserves 'metadata') - Enhance runner.py: audit log now includes prompt_tokens, completion_tokens, total_tokens, cost_usd, provider in LLM call metadata - Update api/__init__.py to export all new routers - All 27 unit tests passing
This commit is contained in:
72
tests/unit/test_slack_oauth.py
Normal file
72
tests/unit/test_slack_oauth.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Unit tests for Slack OAuth state generation and verification.
|
||||
|
||||
Tests:
|
||||
- generate_oauth_state produces a base64-encoded string containing tenant_id
|
||||
- verify_oauth_state returns the correct tenant_id for a valid state
|
||||
- verify_oauth_state raises ValueError for a tampered state
|
||||
- verify_oauth_state raises ValueError for a state signed with wrong secret
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from shared.api.channels import generate_oauth_state, verify_oauth_state
|
||||
|
||||
_SECRET = "test-hmac-secret-do-not-use-in-production"
|
||||
_TENANT_ID = "550e8400-e29b-41d4-a716-446655440000"
|
||||
|
||||
|
||||
def test_generate_oauth_state_is_string() -> None:
|
||||
"""generate_oauth_state returns a non-empty string."""
|
||||
state = generate_oauth_state(tenant_id=_TENANT_ID, secret=_SECRET)
|
||||
assert isinstance(state, str)
|
||||
assert len(state) > 0
|
||||
|
||||
|
||||
def test_generate_oauth_state_contains_tenant_id() -> None:
|
||||
"""
|
||||
generate_oauth_state embeds the tenant_id in the state payload.
|
||||
Verifying the state should return the original tenant_id.
|
||||
"""
|
||||
state = generate_oauth_state(tenant_id=_TENANT_ID, secret=_SECRET)
|
||||
recovered = verify_oauth_state(state=state, secret=_SECRET)
|
||||
assert recovered == _TENANT_ID
|
||||
|
||||
|
||||
def test_verify_oauth_state_valid() -> None:
|
||||
"""verify_oauth_state returns correct tenant_id for a freshly generated state."""
|
||||
state = generate_oauth_state(tenant_id=_TENANT_ID, secret=_SECRET)
|
||||
result = verify_oauth_state(state=state, secret=_SECRET)
|
||||
assert result == _TENANT_ID
|
||||
|
||||
|
||||
def test_verify_oauth_state_tampered() -> None:
|
||||
"""verify_oauth_state raises ValueError if the state payload is tampered."""
|
||||
state = generate_oauth_state(tenant_id=_TENANT_ID, secret=_SECRET)
|
||||
|
||||
# Tamper: append garbage to the state string
|
||||
tampered = state + "TAMPERED"
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
verify_oauth_state(state=tampered, secret=_SECRET)
|
||||
|
||||
|
||||
def test_verify_oauth_state_wrong_secret() -> None:
|
||||
"""verify_oauth_state raises ValueError if verified with the wrong secret."""
|
||||
state = generate_oauth_state(tenant_id=_TENANT_ID, secret=_SECRET)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
verify_oauth_state(state=state, secret="wrong-secret")
|
||||
|
||||
|
||||
def test_generate_oauth_state_nonce_differs() -> None:
|
||||
"""Two calls to generate_oauth_state produce different states (random nonce)."""
|
||||
state1 = generate_oauth_state(tenant_id=_TENANT_ID, secret=_SECRET)
|
||||
state2 = generate_oauth_state(tenant_id=_TENANT_ID, secret=_SECRET)
|
||||
# Different nonce means different state tokens
|
||||
assert state1 != state2
|
||||
# Both must still verify correctly
|
||||
assert verify_oauth_state(state=state1, secret=_SECRET) == _TENANT_ID
|
||||
assert verify_oauth_state(state=state2, secret=_SECRET) == _TENANT_ID
|
||||
Reference in New Issue
Block a user