Files
dify/api/tests/unit_tests/controllers/openapi/auth/test_data.py
GareArc 9b25980b09 feat(openapi): redesign auth pipeline — one pipeline per token type with PipelineRouter
Replace the single mutable-context Pipeline with a two-phase, condition-driven
system dispatched by token type.

New architecture:
- TokenType(StrEnum) replaces source: str on AuthContext / TokenKind
- AuthPipeline: pure prepare→auth step runner; no guard()
- PipelineRoute: binds AuthPipeline to an optional required_edition gate
- PipelineRouter: single guard() entry point; runs edition/license/token-type
  pre-gates then dispatches to the registered pipeline for the token type
- Cond / When: composable predicates for conditional step dispatch
- AuthData: frozen Pydantic model produced by the prepare phase; carries
  token_id so endpoints don't need to call get_auth_ctx() for identity fields
- Edition enum + current_edition(): CE / EE / SAAS discriminator

Two pipelines in composition.py:
- account_pipeline  — OAUTH_ACCOUNT tokens
- external_sso_pipeline — OAUTH_EXTERNAL_SSO tokens (EE enforced at route level)

All /openapi/v1 endpoints migrated to auth_router.guard().
Old context.py, steps.py, strategies.py, surface_gate.py deleted.
WORKSPACE_READ scope added; cached_verdicts renamed to membership_cache.
2026-05-26 03:16:28 -07:00

117 lines
3.0 KiB
Python

import uuid
from unittest.mock import patch
import pytest
from pydantic import ValidationError
from controllers.openapi.auth.data import (
AuthData,
Edition,
ExternalIdentity,
RequestContext,
current_edition,
)
from libs.oauth_bearer import Scope, TokenType
# --- Edition / current_edition ---
def test_current_edition_saas():
with patch("controllers.openapi.auth.data.dify_config") as cfg:
cfg.EDITION = "CLOUD"
cfg.ENTERPRISE_ENABLED = True
assert current_edition() == Edition.SAAS
def test_current_edition_ee():
with patch("controllers.openapi.auth.data.dify_config") as cfg:
cfg.EDITION = "SELF_HOSTED"
cfg.ENTERPRISE_ENABLED = True
assert current_edition() == Edition.EE
def test_current_edition_ce():
with patch("controllers.openapi.auth.data.dify_config") as cfg:
cfg.EDITION = "SELF_HOSTED"
cfg.ENTERPRISE_ENABLED = False
assert current_edition() == Edition.CE
# --- ExternalIdentity ---
def test_external_identity_frozen():
ei = ExternalIdentity(email="a@b.com", issuer="idp")
with pytest.raises(ValidationError):
ei.email = "other@b.com" # type: ignore[misc]
def test_external_identity_issuer_optional():
ei = ExternalIdentity(email="a@b.com")
assert ei.issuer is None
# --- RequestContext ---
def test_request_context_frozen():
ctx = RequestContext(
token_type=TokenType.OAUTH_ACCOUNT,
path_params={"app_id": "123"},
)
with pytest.raises(ValidationError):
ctx.token_type = TokenType.OAUTH_EXTERNAL_SSO # type: ignore[misc]
def test_request_context_scope_optional():
ctx = RequestContext(token_type=TokenType.OAUTH_ACCOUNT, path_params={})
assert ctx.scope is None
# --- AuthData ---
def test_auth_data_frozen():
data = AuthData(
token_type=TokenType.OAUTH_ACCOUNT,
token_hash="abc",
scopes=frozenset({Scope.FULL}),
)
with pytest.raises(ValidationError):
data.token_type = TokenType.OAUTH_EXTERNAL_SSO # type: ignore[misc]
def test_auth_data_account_id_optional():
data = AuthData(
token_type=TokenType.OAUTH_EXTERNAL_SSO,
token_hash="abc",
scopes=frozenset({Scope.APPS_RUN}),
external_identity=ExternalIdentity(email="u@sso.com"),
)
assert data.account_id is None
def test_auth_data_external_identity_none_for_account():
data = AuthData(
token_type=TokenType.OAUTH_ACCOUNT,
account_id=uuid.uuid4(),
token_hash="abc",
scopes=frozenset({Scope.FULL}),
)
assert data.external_identity is None
def test_auth_data_tenants_default_empty():
data = AuthData(
token_type=TokenType.OAUTH_ACCOUNT,
token_hash="abc",
scopes=frozenset(),
)
assert data.tenants == {}
def test_auth_data_token_id_optional():
data = AuthData(
token_type=TokenType.OAUTH_ACCOUNT,
token_hash="abc",
scopes=frozenset(),
)
assert data.token_id is None