framework-m 0.5.2__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {framework_m-0.5.2 → framework_m-0.6.1}/.gitignore +7 -1
- {framework_m-0.5.2 → framework_m-0.6.1}/CHANGELOG.md +21 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/PKG-INFO +4 -4
- {framework_m-0.5.2 → framework_m-0.6.1}/README.md +1 -1
- {framework_m-0.5.2 → framework_m-0.6.1}/alembic/env.py +1 -1
- {framework_m-0.5.2 → framework_m-0.6.1}/examples/run_server.py +4 -4
- {framework_m-0.5.2 → framework_m-0.6.1}/pyproject.toml +4 -4
- framework_m-0.6.1/tests/adapters/auth/test_oauth_adapter.py +137 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/adapters/read_model/test_projector.py +12 -13
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/conftest.py +13 -4
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/core/interfaces/test_read_model.py +2 -3
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_api_endpoints.py +11 -12
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_child_table_integration.py +1 -2
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_crud_flow.py +6 -6
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_jobs_events_webhooks.py +20 -18
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_migration_flow.py +5 -5
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_override_migration.py +2 -3
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_postgres_flow.py +8 -8
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_real_http_client.py +29 -9
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_workflow_lifecycle.py +4 -5
- framework_m-0.6.1/tests/test_meta.py +29 -0
- framework_m-0.5.2/src/framework_m/adapters/__init__.py +0 -9
- framework_m-0.5.2/src/framework_m/adapters/audit/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/audit/database_audit.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/audit/file_audit.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/auth/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/auth/citadel_policy.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/auth/federated_identity.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/auth/local_identity.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/auth/rbac_permission.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/auth/session_store.py +0 -421
- framework_m-0.5.2/src/framework_m/adapters/auth/strategies.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/cache/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/cache/redis_cache.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/db/__init__.py +0 -2
- framework_m-0.5.2/src/framework_m/adapters/db/connection.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/db/field_registry.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/db/generic_repository.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/db/migration.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/db/outbox_repository.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/db/repository_factory.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/db/schema_mapper.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/db/session.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/db/sqlalchemy_adapter.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/db/table_registry.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/email/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/email/processor.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/email/queue_adapter.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/email/sender_adapter.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/events/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/events/inmemory_event_bus.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/events/nats_event_bus.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/factory.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/i18n/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/jobs/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/jobs/job_logger.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/jobs/outbox_worker.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/jobs/schedules.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/jobs/taskiq_adapter.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/print/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/print/gotenberg_adapter.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/print/jinja_adapter.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/read_model/__init__.py +0 -10
- framework_m-0.5.2/src/framework_m/adapters/read_model/projector.py +0 -132
- framework_m-0.5.2/src/framework_m/adapters/socket/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/socket/nats_socket.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/storage/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/storage/local.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/storage/memory.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/storage/s3.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/tenant.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/__init__.py +0 -4
- framework_m-0.5.2/src/framework_m/adapters/web/activity_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/api_key_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/app.py +0 -2
- framework_m-0.5.2/src/framework_m/adapters/web/auth_middleware.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/auth_router.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/auth_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/dtos.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/error_handler.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/file_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/job_router.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/magic_link_routes.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/web/meta_router.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/meta_router_extended.py +0 -6
- framework_m-0.5.2/src/framework_m/adapters/web/meta_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/metadata_router.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/middleware/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/middleware/locale.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/web/middleware_registry.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/web/notification_routes.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/web/oauth_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/print_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/rpc_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/session_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/settings_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/share_routes.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/socket.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/web/user_data_routes.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/web/workflow_router.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/webhooks/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/webhooks/delivery.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/webhooks/listener.py +0 -229
- framework_m-0.5.2/src/framework_m/adapters/workflow/__init__.py +0 -5
- framework_m-0.5.2/src/framework_m/adapters/workflow/internal_workflow.py +0 -7
- framework_m-0.5.2/src/framework_m/adapters/workflow/temporal_adapter.py +0 -7
- framework_m-0.5.2/src/framework_m/core/__init__.py +0 -3
- framework_m-0.5.2/src/framework_m/core/config.py +0 -17
- framework_m-0.5.2/src/framework_m/core/container.py +0 -5
- framework_m-0.5.2/src/framework_m/core/decorators.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/__init__.py +0 -2
- framework_m-0.5.2/src/framework_m/core/doctypes/activity_log.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/api_key.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/custom_permission.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/document_share.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/email_queue.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/error_log.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/file.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/job_log.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/notification.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/print_format.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/recent_document.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/scheduled_job.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/session.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/social_account.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/system_settings.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/tenant_translation.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/todo.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/translation.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/user.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/webhook.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/webhook_log.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/workflow.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/workflow_state.py +0 -5
- framework_m-0.5.2/src/framework_m/core/doctypes/workflow_transition.py +0 -5
- framework_m-0.5.2/src/framework_m/core/domain/__init__.py +0 -3
- framework_m-0.5.2/src/framework_m/core/domain/base_controller.py +0 -3
- framework_m-0.5.2/src/framework_m/core/domain/base_doctype.py +0 -3
- framework_m-0.5.2/src/framework_m/core/domain/mixins.py +0 -17
- framework_m-0.5.2/src/framework_m/core/domain/naming_counter.py +0 -3
- framework_m-0.5.2/src/framework_m/core/domain/outbox.py +0 -3
- framework_m-0.5.2/src/framework_m/core/events.py +0 -5
- framework_m-0.5.2/src/framework_m/core/exceptions.py +0 -29
- framework_m-0.5.2/src/framework_m/core/interfaces/__init__.py +0 -110
- framework_m-0.5.2/src/framework_m/core/interfaces/audit.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/auth_context.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/authentication.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/cache.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/email_queue.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/email_sender.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/event_bus.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/i18n.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/identity.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/job_queue.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/notification.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/oauth.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/permission.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/print.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/repository.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/search.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/session.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/socket.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/storage.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/tenant.py +0 -5
- framework_m-0.5.2/src/framework_m/core/interfaces/workflow.py +0 -5
- framework_m-0.5.2/src/framework_m/core/permission_lookup.py +0 -5
- framework_m-0.5.2/src/framework_m/core/permissions.py +0 -5
- framework_m-0.5.2/src/framework_m/core/pii.py +0 -5
- framework_m-0.5.2/src/framework_m/core/registry.py +0 -3
- framework_m-0.5.2/src/framework_m/core/rls.py +0 -5
- framework_m-0.5.2/src/framework_m/core/rpc_registry.py +0 -5
- framework_m-0.5.2/src/framework_m/core/security.py +0 -5
- framework_m-0.5.2/src/framework_m/core/services/__init__.py +0 -0
- framework_m-0.5.2/src/framework_m/core/services/user_manager.py +0 -5
- framework_m-0.5.2/src/framework_m/core/system_context.py +0 -5
- framework_m-0.5.2/src/framework_m/core/types/__init__.py +0 -2
- framework_m-0.5.2/src/framework_m/core/types/job_context.py +0 -5
- framework_m-0.5.2/src/framework_m/core/unit_of_work.py +0 -5
- {framework_m-0.5.2 → framework_m-0.6.1}/alembic/README +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/alembic/script.py.mako +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/alembic.ini +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/docs/ports.md +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/examples/api_example.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/src/framework_m/__init__.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/src/framework_m/cli/__init__.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/src/framework_m/cli/main.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/src/framework_m/py.typed +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/architecture/fixtures/ci_dummy_index.html +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/architecture/test_frontend_bootstrap.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/architecture/test_package_separation.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/core/interfaces/test_report_engine.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/helpers/__init__.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/__init__.py +0 -0
- {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_app_ports.py +0 -0
|
@@ -113,8 +113,14 @@ yarn-error.log*
|
|
|
113
113
|
|
|
114
114
|
# Generated Documentation
|
|
115
115
|
docs/developer/generated/
|
|
116
|
-
docs/developer/
|
|
116
|
+
docs/developer/generated-core/
|
|
117
|
+
docs/user/generated/
|
|
117
118
|
docs/machine/
|
|
119
|
+
docs/adr/index.md
|
|
120
|
+
docs/adr/_sidebar.json
|
|
121
|
+
docs/rfcs/index.md
|
|
122
|
+
docs/rfcs/_sidebar.json
|
|
123
|
+
docs/rfcs/_category_.json
|
|
118
124
|
|
|
119
125
|
# Website
|
|
120
126
|
website/.docusaurus/
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## framework-m v0.6.1
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- remove hardcoded publishConfig registry (b197c97)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## framework-m v0.6.0
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- granular generation, flat sidebar, and monorepo sync (576fb81)
|
|
15
|
+
- add configurable basename and stabilize frontend tests (b57e6a4)
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
- shell integration gaps (6a31f82)
|
|
20
|
+
- resolve broken links in docusaurus build (a162fd1)
|
|
21
|
+
- correct hydration errors and refine routing logic (192dac2)
|
|
22
|
+
|
|
23
|
+
|
|
3
24
|
## framework-m v0.5.2
|
|
4
25
|
|
|
5
26
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: framework-m
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: A modular, metadata-driven business application framework
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/castlecraft/framework-m
|
|
6
6
|
Project-URL: Documentation, https://gitlab.com/castlecraft/framework-m#readme
|
|
@@ -17,8 +17,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.12
|
|
20
|
-
Requires-Dist: framework-m-core>=0.
|
|
21
|
-
Requires-Dist: framework-m-standard>=0.
|
|
20
|
+
Requires-Dist: framework-m-core>=0.9.0
|
|
21
|
+
Requires-Dist: framework-m-standard>=0.6.0
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
24
24
|
# Framework M
|
|
@@ -126,7 +126,7 @@ git clone https://gitlab.com/castlecraft/framework-m.git
|
|
|
126
126
|
cd framework-m
|
|
127
127
|
|
|
128
128
|
# Install dependencies
|
|
129
|
-
uv sync --all-extras
|
|
129
|
+
uv sync --all-packages --all-extras --all-groups
|
|
130
130
|
|
|
131
131
|
# Run tests
|
|
132
132
|
uv run pytest
|
|
@@ -19,7 +19,7 @@ from alembic import context
|
|
|
19
19
|
# Framework M imports for auto-generation support
|
|
20
20
|
# Uncomment and customize based on your app:
|
|
21
21
|
# from framework_m.core.registry import MetaRegistry
|
|
22
|
-
# from
|
|
22
|
+
# from framework_m_standard.adapters.db import SchemaMapper
|
|
23
23
|
# from your_app.doctypes import * # Import all DocTypes to register them
|
|
24
24
|
|
|
25
25
|
# this is the Alembic Config object, which provides
|
|
@@ -12,13 +12,13 @@ Then test with:
|
|
|
12
12
|
from typing import Any, ClassVar
|
|
13
13
|
|
|
14
14
|
import uvicorn
|
|
15
|
+
from framework_m_standard.adapters.web.meta_router import create_meta_router
|
|
16
|
+
from framework_m_standard.adapters.web.meta_routes import meta_routes_router
|
|
17
|
+
from framework_m_standard.adapters.web.rpc_routes import rpc_router
|
|
15
18
|
from litestar import Litestar, Response
|
|
16
19
|
from litestar.status_codes import HTTP_403_FORBIDDEN
|
|
17
20
|
from litestar.types import ASGIApp, Receive, Scope, Send
|
|
18
21
|
|
|
19
|
-
from framework_m.adapters.web.meta_router import create_meta_router
|
|
20
|
-
from framework_m.adapters.web.meta_routes import meta_routes_router
|
|
21
|
-
from framework_m.adapters.web.rpc_routes import rpc_router
|
|
22
22
|
from framework_m.core.domain.base_doctype import BaseDocType, Field
|
|
23
23
|
from framework_m.core.exceptions import PermissionDeniedError
|
|
24
24
|
from framework_m.core.interfaces.auth_context import UserContext
|
|
@@ -133,4 +133,4 @@ if __name__ == "__main__":
|
|
|
133
133
|
print("")
|
|
134
134
|
|
|
135
135
|
app = create_test_app()
|
|
136
|
-
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
136
|
+
uvicorn.run(app, host="0.0.0.0", port=8000) # nosec B104
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "framework-m"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.1"
|
|
4
4
|
description = "A modular, metadata-driven business application framework"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "Apache-2.0" }
|
|
@@ -21,8 +21,8 @@ classifiers = [
|
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
dependencies = [
|
|
24
|
-
"framework-m-core>=0.
|
|
25
|
-
"framework-m-standard>=0.
|
|
24
|
+
"framework-m-core>=0.9.0",
|
|
25
|
+
"framework-m-standard>=0.6.0",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[tool.uv.sources]
|
|
@@ -39,7 +39,7 @@ dev = [
|
|
|
39
39
|
"testcontainers>=3.7.0",
|
|
40
40
|
"httpx>=0.27.0",
|
|
41
41
|
"aiosqlite>=0.20.0",
|
|
42
|
-
"framework-m-studio>=0.
|
|
42
|
+
"framework-m-studio>=0.7.0",
|
|
43
43
|
# Release tools
|
|
44
44
|
"python-semantic-release>=9.0.0",
|
|
45
45
|
"build>=1.0.0",
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Tests for OAuthAdapter (Indie Mode OAuth2 via authlib).
|
|
2
|
+
|
|
3
|
+
TDD scope (work-item-20 / section 1):
|
|
4
|
+
- Authorization URL generation for common providers (Google/GitHub).
|
|
5
|
+
- Callback handling that hydrates framework models for linking/auth context.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
from urllib.parse import parse_qs, urlparse
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.asyncio
|
|
17
|
+
async def test_oauth_adapter_generates_google_authorization_url() -> None:
|
|
18
|
+
"""OAuthAdapter should generate a valid Google authorization URL."""
|
|
19
|
+
from framework_m_standard.adapters.auth.oauth import OAuthAdapter
|
|
20
|
+
|
|
21
|
+
adapter = OAuthAdapter(
|
|
22
|
+
provider_name="google",
|
|
23
|
+
client_id="google-client-id",
|
|
24
|
+
client_secret="google-client-secret",
|
|
25
|
+
authorization_url="https://accounts.google.com/o/oauth2/v2/auth",
|
|
26
|
+
token_url="https://oauth2.googleapis.com/token",
|
|
27
|
+
userinfo_url="https://www.googleapis.com/oauth2/v3/userinfo",
|
|
28
|
+
scope="openid email profile",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
url = await adapter.get_authorization_url(
|
|
32
|
+
state="state-123",
|
|
33
|
+
redirect_uri="https://app.example.com/api/v1/auth/oauth/google/callback",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
parsed = urlparse(url)
|
|
37
|
+
query = parse_qs(parsed.query)
|
|
38
|
+
|
|
39
|
+
assert parsed.scheme == "https"
|
|
40
|
+
assert parsed.netloc == "accounts.google.com"
|
|
41
|
+
assert query["client_id"][0] == "google-client-id"
|
|
42
|
+
assert query["state"][0] == "state-123"
|
|
43
|
+
assert query["redirect_uri"][0].endswith("/oauth/google/callback")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.asyncio
|
|
47
|
+
async def test_oauth_adapter_generates_github_authorization_url() -> None:
|
|
48
|
+
"""OAuthAdapter should generate a valid GitHub authorization URL."""
|
|
49
|
+
from framework_m_standard.adapters.auth.oauth import OAuthAdapter
|
|
50
|
+
|
|
51
|
+
adapter = OAuthAdapter(
|
|
52
|
+
provider_name="github",
|
|
53
|
+
client_id="github-client-id",
|
|
54
|
+
client_secret="github-client-secret",
|
|
55
|
+
authorization_url="https://github.com/login/oauth/authorize",
|
|
56
|
+
token_url="https://github.com/login/oauth/access_token",
|
|
57
|
+
userinfo_url="https://api.github.com/user",
|
|
58
|
+
scope="read:user user:email",
|
|
59
|
+
emails_url="https://api.github.com/user/emails",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
url = await adapter.get_authorization_url(
|
|
63
|
+
state="state-456",
|
|
64
|
+
redirect_uri="https://app.example.com/api/v1/auth/oauth/github/callback",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
parsed = urlparse(url)
|
|
68
|
+
query = parse_qs(parsed.query)
|
|
69
|
+
|
|
70
|
+
assert parsed.scheme == "https"
|
|
71
|
+
assert parsed.netloc == "github.com"
|
|
72
|
+
assert query["client_id"][0] == "github-client-id"
|
|
73
|
+
assert query["state"][0] == "state-456"
|
|
74
|
+
assert query["scope"][0] == "read:user user:email"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.asyncio
|
|
78
|
+
async def test_oauth_adapter_handle_callback_hydrates_social_account_and_user_context(
|
|
79
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""handle_callback should exchange code, fetch profile, and hydrate models."""
|
|
82
|
+
from framework_m_core.interfaces.oauth import OAuth2Token, OAuth2UserInfo
|
|
83
|
+
from framework_m_standard.adapters.auth.oauth import OAuthAdapter
|
|
84
|
+
|
|
85
|
+
adapter = OAuthAdapter(
|
|
86
|
+
provider_name="google",
|
|
87
|
+
client_id="google-client-id",
|
|
88
|
+
client_secret="google-client-secret",
|
|
89
|
+
authorization_url="https://accounts.google.com/o/oauth2/v2/auth",
|
|
90
|
+
token_url="https://oauth2.googleapis.com/token",
|
|
91
|
+
userinfo_url="https://www.googleapis.com/oauth2/v3/userinfo",
|
|
92
|
+
scope="openid email profile",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async def fake_exchange_code(*, code: str, redirect_uri: str) -> OAuth2Token:
|
|
96
|
+
assert code == "oauth-code-123"
|
|
97
|
+
assert redirect_uri.endswith("/oauth/google/callback")
|
|
98
|
+
return await asyncio.sleep(
|
|
99
|
+
0, result=OAuth2Token(access_token="token-abc", token_type="Bearer")
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
async def fake_get_user_info(token: OAuth2Token) -> OAuth2UserInfo:
|
|
103
|
+
assert token.access_token == "token-abc"
|
|
104
|
+
return await asyncio.sleep(
|
|
105
|
+
0,
|
|
106
|
+
result=OAuth2UserInfo(
|
|
107
|
+
provider="google",
|
|
108
|
+
provider_user_id="provider-user-1",
|
|
109
|
+
email="jane@example.com",
|
|
110
|
+
display_name="Jane Doe",
|
|
111
|
+
avatar_url="https://cdn.example.com/avatar.png",
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
monkeypatch.setattr(adapter, "exchange_code", fake_exchange_code)
|
|
116
|
+
monkeypatch.setattr(adapter, "get_user_info", fake_get_user_info)
|
|
117
|
+
|
|
118
|
+
result = await adapter.handle_callback(
|
|
119
|
+
code="oauth-code-123",
|
|
120
|
+
redirect_uri="https://app.example.com/api/v1/auth/oauth/google/callback",
|
|
121
|
+
local_user_id="user-123",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
assert result.token.access_token == "token-abc"
|
|
125
|
+
assert result.user_info.provider_user_id == "provider-user-1"
|
|
126
|
+
|
|
127
|
+
social_account = result.social_account
|
|
128
|
+
assert social_account.provider == "google"
|
|
129
|
+
assert social_account.provider_user_id == "provider-user-1"
|
|
130
|
+
assert social_account.user_id == "user-123"
|
|
131
|
+
assert social_account.email == "jane@example.com"
|
|
132
|
+
|
|
133
|
+
user_context = result.user_context
|
|
134
|
+
assert user_context.id == "user-123"
|
|
135
|
+
assert user_context.email == "jane@example.com"
|
|
136
|
+
assert user_context.name == "Jane Doe"
|
|
137
|
+
assert "User" in user_context.roles
|
|
@@ -7,8 +7,7 @@ from typing import Any
|
|
|
7
7
|
from unittest.mock import AsyncMock
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
|
-
|
|
11
|
-
from framework_m.core.interfaces.event_bus import Event
|
|
10
|
+
from framework_m_core.interfaces.event_bus import Event
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class TestProjectorProtocolStructure:
|
|
@@ -16,19 +15,19 @@ class TestProjectorProtocolStructure:
|
|
|
16
15
|
|
|
17
16
|
def test_projector_protocol_exists(self) -> None:
|
|
18
17
|
"""Projector should be importable from adapters."""
|
|
19
|
-
from
|
|
18
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
20
19
|
|
|
21
20
|
assert Projector is not None
|
|
22
21
|
|
|
23
22
|
def test_projector_has_handle_method(self) -> None:
|
|
24
23
|
"""Projector should define handle() method for events."""
|
|
25
|
-
from
|
|
24
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
26
25
|
|
|
27
26
|
assert hasattr(Projector, "handle")
|
|
28
27
|
|
|
29
28
|
def test_projector_has_subscribe_method(self) -> None:
|
|
30
29
|
"""Projector should define subscribe() for event bus registration."""
|
|
31
|
-
from
|
|
30
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
32
31
|
|
|
33
32
|
assert hasattr(Projector, "subscribe")
|
|
34
33
|
|
|
@@ -36,7 +35,7 @@ class TestProjectorProtocolStructure:
|
|
|
36
35
|
"""Projector should define event_patterns for topics to subscribe to."""
|
|
37
36
|
from unittest.mock import AsyncMock
|
|
38
37
|
|
|
39
|
-
from
|
|
38
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
40
39
|
|
|
41
40
|
mock_read_model = AsyncMock()
|
|
42
41
|
projector = Projector(read_model=mock_read_model)
|
|
@@ -66,7 +65,7 @@ class TestProjectorEventHandling:
|
|
|
66
65
|
self, mock_read_model: Any
|
|
67
66
|
) -> None:
|
|
68
67
|
"""handle() should delegate to read_model.project()."""
|
|
69
|
-
from
|
|
68
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
70
69
|
|
|
71
70
|
projector = Projector(read_model=mock_read_model)
|
|
72
71
|
event = Event(type="doc.created", data={"doctype": "Invoice", "id": "123"})
|
|
@@ -80,7 +79,7 @@ class TestProjectorEventHandling:
|
|
|
80
79
|
self, mock_read_model: Any, mock_event_bus: Any
|
|
81
80
|
) -> None:
|
|
82
81
|
"""subscribe() should register event patterns with event bus."""
|
|
83
|
-
from
|
|
82
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
84
83
|
|
|
85
84
|
projector = Projector(
|
|
86
85
|
read_model=mock_read_model,
|
|
@@ -97,7 +96,7 @@ class TestProjectorEventHandling:
|
|
|
97
96
|
self, mock_read_model: Any, mock_event_bus: Any
|
|
98
97
|
) -> None:
|
|
99
98
|
"""subscribe() should store subscription IDs for later unsubscribe."""
|
|
100
|
-
from
|
|
99
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
101
100
|
|
|
102
101
|
projector = Projector(
|
|
103
102
|
read_model=mock_read_model,
|
|
@@ -114,7 +113,7 @@ class TestProjectorEventHandling:
|
|
|
114
113
|
self, mock_read_model: Any, mock_event_bus: Any
|
|
115
114
|
) -> None:
|
|
116
115
|
"""unsubscribe() should remove all event bus subscriptions."""
|
|
117
|
-
from
|
|
116
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
118
117
|
|
|
119
118
|
projector = Projector(
|
|
120
119
|
read_model=mock_read_model,
|
|
@@ -141,7 +140,7 @@ class TestProjectorFiltering:
|
|
|
141
140
|
@pytest.mark.asyncio
|
|
142
141
|
async def test_handle_filters_by_doctype(self, mock_read_model: Any) -> None:
|
|
143
142
|
"""handle() should filter events by doctype if configured."""
|
|
144
|
-
from
|
|
143
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
145
144
|
|
|
146
145
|
projector = Projector(
|
|
147
146
|
read_model=mock_read_model,
|
|
@@ -165,7 +164,7 @@ class TestProjectorFiltering:
|
|
|
165
164
|
self, mock_read_model: Any
|
|
166
165
|
) -> None:
|
|
167
166
|
"""handle() should process all events if no doctype_filter."""
|
|
168
|
-
from
|
|
167
|
+
from framework_m_standard.adapters.read_model.projector import Projector
|
|
169
168
|
|
|
170
169
|
projector = Projector(read_model=mock_read_model)
|
|
171
170
|
|
|
@@ -183,6 +182,6 @@ class TestProjectorExports:
|
|
|
183
182
|
|
|
184
183
|
def test_exported_from_adapters_read_model(self) -> None:
|
|
185
184
|
"""Projector should be importable from adapters.read_model."""
|
|
186
|
-
from
|
|
185
|
+
from framework_m_standard.adapters.read_model import Projector
|
|
187
186
|
|
|
188
187
|
assert Projector is not None
|
|
@@ -7,11 +7,15 @@ This module provides shared fixtures for testing Framework M:
|
|
|
7
7
|
- Database fixtures for integration testing
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import os
|
|
10
11
|
from collections.abc import AsyncGenerator
|
|
11
12
|
from datetime import UTC, datetime
|
|
12
13
|
from typing import Any
|
|
13
14
|
|
|
14
15
|
import pytest
|
|
16
|
+
from framework_m_core.container import Container
|
|
17
|
+
from framework_m_core.domain.base_controller import BaseController
|
|
18
|
+
from framework_m_core.registry import MetaRegistry
|
|
15
19
|
from sqlalchemy import MetaData
|
|
16
20
|
from sqlalchemy.ext.asyncio import (
|
|
17
21
|
AsyncEngine,
|
|
@@ -21,9 +25,14 @@ from sqlalchemy.ext.asyncio import (
|
|
|
21
25
|
)
|
|
22
26
|
|
|
23
27
|
from framework_m import DocType, Field
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def pytest_configure(config: Any) -> None:
|
|
31
|
+
"""Configure environment for testing."""
|
|
32
|
+
# Ensure a default JWT secret is available to satisfy fail-fast validation
|
|
33
|
+
if not os.environ.get("FRAMEWORK_M_JWT_SECRET"):
|
|
34
|
+
os.environ["FRAMEWORK_M_JWT_SECRET"] = "test-secret-min-32-chars-long-security"
|
|
35
|
+
|
|
27
36
|
|
|
28
37
|
# =============================================================================
|
|
29
38
|
# Sample DocTypes for Testing
|
|
@@ -210,7 +219,7 @@ async def clean_tables(
|
|
|
210
219
|
) -> AsyncGenerator[MetaData, None]:
|
|
211
220
|
"""Create a clean metadata and drop all tables after test."""
|
|
212
221
|
# Reset table registry before each test
|
|
213
|
-
from
|
|
222
|
+
from framework_m_standard.adapters.db.table_registry import TableRegistry
|
|
214
223
|
|
|
215
224
|
table_registry = TableRegistry()
|
|
216
225
|
table_registry.reset()
|
|
@@ -7,8 +7,7 @@ from typing import Any
|
|
|
7
7
|
from unittest.mock import AsyncMock
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
|
-
|
|
11
|
-
from framework_m.core.interfaces.event_bus import Event
|
|
10
|
+
from framework_m_core.interfaces.event_bus import Event
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class TestReadModelProtocolStructure:
|
|
@@ -107,7 +106,7 @@ class TestReadModelExports:
|
|
|
107
106
|
|
|
108
107
|
def test_exported_from_interfaces_init(self) -> None:
|
|
109
108
|
"""ReadModelProtocol should be exported from core.interfaces."""
|
|
110
|
-
from
|
|
109
|
+
from framework_m_core.interfaces import ReadModelProtocol
|
|
111
110
|
|
|
112
111
|
assert ReadModelProtocol is not None
|
|
113
112
|
|
|
@@ -7,16 +7,15 @@ Covers: validation, permissions, RLS filtering, immutable docs.
|
|
|
7
7
|
from typing import Any, ClassVar
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
|
+
from framework_m_core.domain.base_doctype import BaseDocType, Field
|
|
11
|
+
from framework_m_core.exceptions import PermissionDeniedError
|
|
12
|
+
from framework_m_core.interfaces.auth_context import UserContext
|
|
13
|
+
from framework_m_core.registry import MetaRegistry
|
|
10
14
|
from litestar import Litestar, Response
|
|
11
15
|
from litestar.status_codes import HTTP_403_FORBIDDEN
|
|
12
16
|
from litestar.testing import TestClient
|
|
13
17
|
from litestar.types import ASGIApp, Receive, Scope, Send
|
|
14
18
|
|
|
15
|
-
from framework_m.core.domain.base_doctype import BaseDocType, Field
|
|
16
|
-
from framework_m.core.exceptions import PermissionDeniedError
|
|
17
|
-
from framework_m.core.interfaces.auth_context import UserContext
|
|
18
|
-
from framework_m.core.registry import MetaRegistry
|
|
19
|
-
|
|
20
19
|
# =============================================================================
|
|
21
20
|
# Test DocTypes
|
|
22
21
|
# =============================================================================
|
|
@@ -104,7 +103,7 @@ class TestCrudEndpointValidation:
|
|
|
104
103
|
@pytest.fixture
|
|
105
104
|
def app(self) -> Litestar:
|
|
106
105
|
"""Create test app with CRUD routes."""
|
|
107
|
-
from
|
|
106
|
+
from framework_m_standard.adapters.web.meta_router import create_meta_router
|
|
108
107
|
|
|
109
108
|
router = create_meta_router()
|
|
110
109
|
return Litestar(
|
|
@@ -154,7 +153,7 @@ class TestCrudEndpointPermissions:
|
|
|
154
153
|
@pytest.fixture
|
|
155
154
|
def app_employee(self) -> Litestar:
|
|
156
155
|
"""Create test app with Employee role."""
|
|
157
|
-
from
|
|
156
|
+
from framework_m_standard.adapters.web.meta_router import create_meta_router
|
|
158
157
|
|
|
159
158
|
router = create_meta_router()
|
|
160
159
|
return Litestar(
|
|
@@ -168,7 +167,7 @@ class TestCrudEndpointPermissions:
|
|
|
168
167
|
@pytest.fixture
|
|
169
168
|
def app_admin(self) -> Litestar:
|
|
170
169
|
"""Create test app with Admin role."""
|
|
171
|
-
from
|
|
170
|
+
from framework_m_standard.adapters.web.meta_router import create_meta_router
|
|
172
171
|
|
|
173
172
|
router = create_meta_router()
|
|
174
173
|
return Litestar(
|
|
@@ -216,7 +215,7 @@ class TestCrudRlsFiltering:
|
|
|
216
215
|
@pytest.fixture
|
|
217
216
|
def app_user1(self) -> Litestar:
|
|
218
217
|
"""Create test app for user 1."""
|
|
219
|
-
from
|
|
218
|
+
from framework_m_standard.adapters.web.meta_router import create_meta_router
|
|
220
219
|
|
|
221
220
|
router = create_meta_router()
|
|
222
221
|
return Litestar(
|
|
@@ -250,7 +249,7 @@ class TestRpcEndpointIntegration:
|
|
|
250
249
|
@pytest.fixture
|
|
251
250
|
def app(self) -> Litestar:
|
|
252
251
|
"""Create test app with RPC routes."""
|
|
253
|
-
from
|
|
252
|
+
from framework_m_standard.adapters.web.rpc_routes import rpc_router
|
|
254
253
|
|
|
255
254
|
return Litestar(
|
|
256
255
|
route_handlers=[rpc_router],
|
|
@@ -265,8 +264,8 @@ class TestRpcEndpointIntegration:
|
|
|
265
264
|
|
|
266
265
|
def test_rpc_function_callable(self, client: TestClient[Litestar]) -> None:
|
|
267
266
|
"""Should be able to call registered RPC function."""
|
|
268
|
-
from
|
|
269
|
-
from
|
|
267
|
+
from framework_m_core.decorators import rpc
|
|
268
|
+
from framework_m_core.rpc_registry import RpcRegistry
|
|
270
269
|
|
|
271
270
|
registry = RpcRegistry.get_instance()
|
|
272
271
|
|
|
@@ -13,10 +13,9 @@ from typing import ClassVar
|
|
|
13
13
|
from uuid import uuid4
|
|
14
14
|
|
|
15
15
|
import pytest
|
|
16
|
+
from framework_m_core.domain.base_doctype import BaseDocType
|
|
16
17
|
from pydantic import Field
|
|
17
18
|
|
|
18
|
-
from framework_m.core.domain.base_doctype import BaseDocType
|
|
19
|
-
|
|
20
19
|
# ============================================================================
|
|
21
20
|
# Test DocTypes
|
|
22
21
|
# ============================================================================
|
|
@@ -8,18 +8,18 @@ from __future__ import annotations
|
|
|
8
8
|
from collections.abc import AsyncGenerator
|
|
9
9
|
|
|
10
10
|
import pytest
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
from framework_m import DocType, Field
|
|
14
|
-
from framework_m.adapters.db import (
|
|
11
|
+
from framework_m_core.registry import MetaRegistry
|
|
12
|
+
from framework_m_standard.adapters.db import (
|
|
15
13
|
ConnectionFactory,
|
|
16
14
|
RepositoryFactory,
|
|
17
15
|
SessionFactory,
|
|
18
16
|
TableRegistry,
|
|
19
17
|
initialize_database,
|
|
20
18
|
)
|
|
21
|
-
from
|
|
22
|
-
from
|
|
19
|
+
from framework_m_standard.adapters.db.schema_mapper import SchemaMapper
|
|
20
|
+
from sqlalchemy import MetaData
|
|
21
|
+
|
|
22
|
+
from framework_m import DocType, Field
|
|
23
23
|
|
|
24
24
|
# =============================================================================
|
|
25
25
|
# Test DocType
|