feat(api): lift identity + self-revoke to /openapi/v1/account (Phase C.9-10)

GET /v1/me moves to GET /openapi/v1/account. DELETE
/v1/oauth/authorizations/self moves to DELETE
/openapi/v1/account/sessions/self. Both classes (AccountApi,
AccountSessionsSelfApi) are now in controllers/openapi/account.py and
re-registered on service_api_ns at the legacy paths.

service_api/oauth.py is now nothing but legacy re-mount declarations
(20 lines). All in-place handler logic has moved to openapi/. Phase F
will delete the file and the legacy mounts together.

Helper functions (_load_memberships, _pick_default_workspace,
_workspace_payload, _account_payload) move with the AccountApi class.

Plan: docs/superpowers/plans/2026-04-26-openapi-migration.md (in difyctl repo).
This commit is contained in:
GareArc
2026-04-26 23:50:15 -07:00
parent e93821af46
commit b7bd9c19ed
4 changed files with 220 additions and 148 deletions

View File

@ -0,0 +1,73 @@
"""Phase C steps 910: identity + self-revoke moved to /openapi/v1/account.
Legacy /v1/me + /v1/oauth/authorizations/self stay mounted via
re-registration in service_api/oauth.py.
"""
import builtins
import pytest
from flask import Flask
from flask.views import MethodView
from controllers.openapi import bp as openapi_bp
from controllers.openapi.account import AccountApi, AccountSessionsSelfApi
from controllers.service_api import bp as service_api_bp
if not hasattr(builtins, "MethodView"):
builtins.MethodView = MethodView # type: ignore[attr-defined]
@pytest.fixture
def dual_app() -> Flask:
app = Flask(__name__)
app.config["TESTING"] = True
app.register_blueprint(service_api_bp)
app.register_blueprint(openapi_bp)
return app
def _rule(app: Flask, path: str):
return next(r for r in app.url_map.iter_rules() if r.rule == path)
def test_account_route_registered(dual_app: Flask):
rules = {r.rule for r in dual_app.url_map.iter_rules()}
assert "/openapi/v1/account" in rules
def test_legacy_me_route_registered(dual_app: Flask):
rules = {r.rule for r in dual_app.url_map.iter_rules()}
assert "/v1/me" in rules
def test_account_and_me_dispatch_to_same_class(dual_app: Flask):
new = _rule(dual_app, "/openapi/v1/account")
legacy = _rule(dual_app, "/v1/me")
assert dual_app.view_functions[new.endpoint].view_class is AccountApi
assert dual_app.view_functions[legacy.endpoint].view_class is AccountApi
def test_account_sessions_self_route_registered(dual_app: Flask):
rules = {r.rule for r in dual_app.url_map.iter_rules()}
assert "/openapi/v1/account/sessions/self" in rules
def test_legacy_oauth_authorizations_self_route_registered(dual_app: Flask):
rules = {r.rule for r in dual_app.url_map.iter_rules()}
assert "/v1/oauth/authorizations/self" in rules
def test_sessions_self_paths_dispatch_to_same_class(dual_app: Flask):
new = _rule(dual_app, "/openapi/v1/account/sessions/self")
legacy = _rule(dual_app, "/v1/oauth/authorizations/self")
assert dual_app.view_functions[new.endpoint].view_class is AccountSessionsSelfApi
assert dual_app.view_functions[legacy.endpoint].view_class is AccountSessionsSelfApi
def test_account_methods(dual_app: Flask):
rule = _rule(dual_app, "/openapi/v1/account")
assert "GET" in rule.methods
def test_sessions_self_methods(dual_app: Flask):
rule = _rule(dual_app, "/openapi/v1/account/sessions/self")
assert "DELETE" in rule.methods