Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c62c7783d6 | ||
|
|
ecc1283de7 |
@@ -61,6 +61,14 @@ conflicting profile is preserved unless the reviewed fragment explicitly sets
|
|||||||
`on_conflict` to `update`. Credential bindings are added idempotently and are
|
`on_conflict` to `update`. Credential bindings are added idempotently and are
|
||||||
never removed merely because a package omits a credential reference.
|
never removed merely because a package omits a credential reference.
|
||||||
|
|
||||||
|
Mail also registers a module-owned infrastructure dependency provider. Its
|
||||||
|
authorized Ops inventory lists every persisted SMTP endpoint and legacy SMTP
|
||||||
|
profile by stable non-secret reference, state and scope, together with numeric
|
||||||
|
credential-binding evidence. Before the host deployer changes or removes
|
||||||
|
`mail.smtp`, it requires a fresh, complete inventory from the same installation
|
||||||
|
and displays these dependencies in the plan. The inventory never contains
|
||||||
|
transport credentials or decrypted envelope data.
|
||||||
|
|
||||||
Preflight does not prove SMTP reachability. After apply, use the normal Mail
|
Preflight does not prove SMTP reachability. After apply, use the normal Mail
|
||||||
profile test and Ops health surfaces. If the receipt says SMTP is unavailable,
|
profile test and Ops health surfaces. If the receipt says SMTP is unavailable,
|
||||||
is invalid, or is not mounted, import is blocked with an operator-facing
|
is invalid, or is not mounted, import is blocked with an operator-facing
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.24",
|
"version": "0.1.26",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.24",
|
"version": "0.1.26",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.18",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.24",
|
"version": "0.1.26",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "webui/src/index.ts",
|
"main": "webui/src/index.ts",
|
||||||
|
|||||||
+2
-2
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-mail"
|
name = "govoplan-mail"
|
||||||
version = "0.1.24"
|
version = "0.1.26"
|
||||||
description = "GovOPlaN mail module with backend and WebUI integration."
|
description = "GovOPlaN mail module with backend and WebUI integration."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
authors = [{ name = "GovOPlaN" }]
|
authors = [{ name = "GovOPlaN" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core>=0.1.18",
|
"govoplan-core>=0.1.42",
|
||||||
"pydantic>=2,<3",
|
"pydantic>=2,<3",
|
||||||
"redis>=5,<6",
|
"redis>=5,<6",
|
||||||
"SQLAlchemy>=2,<3",
|
"SQLAlchemy>=2,<3",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from dataclasses import dataclass, replace
|
|||||||
import re
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import or_, select
|
from sqlalchemy import func, or_, select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from govoplan_core.core.configuration_packages import (
|
from govoplan_core.core.configuration_packages import (
|
||||||
@@ -24,6 +24,8 @@ from govoplan_core.core.configuration_packages import (
|
|||||||
from govoplan_core.core.infrastructure_capabilities import (
|
from govoplan_core.core.infrastructure_capabilities import (
|
||||||
InfrastructureCapability,
|
InfrastructureCapability,
|
||||||
InfrastructureCapabilityReceipt,
|
InfrastructureCapabilityReceipt,
|
||||||
|
InfrastructureDependency,
|
||||||
|
InfrastructureDependencyProvider,
|
||||||
)
|
)
|
||||||
from govoplan_core.security.credential_envelopes import (
|
from govoplan_core.security.credential_envelopes import (
|
||||||
CredentialAccessContext,
|
CredentialAccessContext,
|
||||||
@@ -51,6 +53,9 @@ from govoplan_mail.backend.server_hierarchy import (
|
|||||||
|
|
||||||
|
|
||||||
MAIL_CONFIGURATION_CAPABILITY = "mail.configuration"
|
MAIL_CONFIGURATION_CAPABILITY = "mail.configuration"
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY = (
|
||||||
|
"infrastructure.dependency_inventory.mail"
|
||||||
|
)
|
||||||
SMTP_PROFILE_FRAGMENT = "smtp_profile"
|
SMTP_PROFILE_FRAGMENT = "smtp_profile"
|
||||||
_SYSTEM_CONFIGURATION_SCOPES = frozenset(
|
_SYSTEM_CONFIGURATION_SCOPES = frozenset(
|
||||||
{"system:settings:write", "system:governance:write"}
|
{"system:settings:write", "system:governance:write"}
|
||||||
@@ -132,8 +137,12 @@ class _ProfileTarget:
|
|||||||
on_conflict: str
|
on_conflict: str
|
||||||
|
|
||||||
|
|
||||||
class SqlMailConfigurationProvider(ConfigurationProvider):
|
class SqlMailConfigurationProvider(
|
||||||
|
ConfigurationProvider,
|
||||||
|
InfrastructureDependencyProvider,
|
||||||
|
):
|
||||||
module_id = "mail"
|
module_id = "mail"
|
||||||
|
capability_ids = ("mail.smtp",)
|
||||||
|
|
||||||
def describe(self) -> ConfigurationProviderDescription:
|
def describe(self) -> ConfigurationProviderDescription:
|
||||||
return ConfigurationProviderDescription(
|
return ConfigurationProviderDescription(
|
||||||
@@ -204,6 +213,84 @@ class SqlMailConfigurationProvider(ConfigurationProvider):
|
|||||||
item for item in import_result.diagnostics if item.severity == "blocker"
|
item for item in import_result.diagnostics if item.severity == "blocker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def infrastructure_dependencies(self) -> tuple[InfrastructureDependency, ...]:
|
||||||
|
with get_database().session() as session:
|
||||||
|
return _smtp_infrastructure_dependencies(session)
|
||||||
|
|
||||||
|
|
||||||
|
def _smtp_infrastructure_dependencies(
|
||||||
|
session: Session,
|
||||||
|
) -> tuple[InfrastructureDependency, ...]:
|
||||||
|
endpoints = tuple(
|
||||||
|
session.execute(
|
||||||
|
select(MailServerEndpoint)
|
||||||
|
.where(MailServerEndpoint.protocol == "smtp")
|
||||||
|
.order_by(MailServerEndpoint.id)
|
||||||
|
).scalars()
|
||||||
|
)
|
||||||
|
binding_counts = {
|
||||||
|
str(server_id): int(count)
|
||||||
|
for server_id, count in session.execute(
|
||||||
|
select(
|
||||||
|
MailServerCredentialBinding.server_id,
|
||||||
|
func.count(MailServerCredentialBinding.id),
|
||||||
|
).group_by(MailServerCredentialBinding.server_id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dependencies: list[InfrastructureDependency] = []
|
||||||
|
endpoint_profile_ids: set[str] = set()
|
||||||
|
for endpoint in endpoints:
|
||||||
|
endpoint_profile_ids.add(endpoint.profile_id)
|
||||||
|
dependencies.append(
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="mail.smtp",
|
||||||
|
module_id="mail",
|
||||||
|
dependency_type="smtp_endpoint",
|
||||||
|
dependency_ref=mail_server_ref(endpoint.id) or f"mail:{endpoint.id}",
|
||||||
|
state="active" if endpoint.is_active else "inactive",
|
||||||
|
scope=str(endpoint.scope_type or "tenant"),
|
||||||
|
summary=(
|
||||||
|
"A persisted Mail SMTP endpoint is bound to the deployment relay."
|
||||||
|
),
|
||||||
|
metrics={
|
||||||
|
"credential_binding_count": binding_counts.get(endpoint.id, 0),
|
||||||
|
"default_endpoint": int(bool(endpoint.is_default)),
|
||||||
|
},
|
||||||
|
required_action=(
|
||||||
|
"Rebind, migrate, or explicitly retire this SMTP endpoint and its credential-envelope references before changing the relay capability."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
legacy_profiles = tuple(
|
||||||
|
session.execute(
|
||||||
|
select(MailServerProfile)
|
||||||
|
.where(MailServerProfile.smtp_config.is_not(None))
|
||||||
|
.order_by(MailServerProfile.id)
|
||||||
|
).scalars()
|
||||||
|
)
|
||||||
|
for profile in legacy_profiles:
|
||||||
|
if profile.id in endpoint_profile_ids or not dict(profile.smtp_config or {}):
|
||||||
|
continue
|
||||||
|
dependencies.append(
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="mail.smtp",
|
||||||
|
module_id="mail",
|
||||||
|
dependency_type="legacy_smtp_profile",
|
||||||
|
dependency_ref=f"mail-profile:{profile.id}",
|
||||||
|
state="active" if profile.is_active else "inactive",
|
||||||
|
scope=str(profile.scope_type or "tenant"),
|
||||||
|
summary=(
|
||||||
|
"A persisted legacy Mail profile still contains SMTP transport configuration."
|
||||||
|
),
|
||||||
|
metrics={"credential_binding_count": 0},
|
||||||
|
required_action=(
|
||||||
|
"Migrate or explicitly retire this legacy profile before changing the relay capability."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return tuple(dependencies)
|
||||||
|
|
||||||
|
|
||||||
def _preflight_smtp_profile(
|
def _preflight_smtp_profile(
|
||||||
session: Session,
|
session: Session,
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ from govoplan_core.core.modules import (
|
|||||||
ModuleManifest,
|
ModuleManifest,
|
||||||
NavItem,
|
NavItem,
|
||||||
PermissionDefinition,
|
PermissionDefinition,
|
||||||
|
ProductAvailabilityExplanation,
|
||||||
ProductAreaContribution,
|
ProductAreaContribution,
|
||||||
|
ProductSurfaceContribution,
|
||||||
QuickAccessTool,
|
QuickAccessTool,
|
||||||
RoleTemplate,
|
RoleTemplate,
|
||||||
)
|
)
|
||||||
@@ -46,7 +48,10 @@ from govoplan_core.core.provider_governance import (
|
|||||||
from govoplan_core.core.search import SearchSourceProviderRegistration
|
from govoplan_core.core.search import SearchSourceProviderRegistration
|
||||||
from govoplan_core.core.views import ViewSurface
|
from govoplan_core.core.views import ViewSurface
|
||||||
from govoplan_core.db.base import Base
|
from govoplan_core.db.base import Base
|
||||||
from govoplan_mail.backend.configuration_provider import MAIL_CONFIGURATION_CAPABILITY
|
from govoplan_mail.backend.configuration_provider import (
|
||||||
|
MAIL_CONFIGURATION_CAPABILITY,
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY,
|
||||||
|
)
|
||||||
from govoplan_mail.backend.documentation import (
|
from govoplan_mail.backend.documentation import (
|
||||||
documentation_configuration_states,
|
documentation_configuration_states,
|
||||||
documentation_topics,
|
documentation_topics,
|
||||||
@@ -445,7 +450,7 @@ POP3_PROVIDER = ExternalProviderDeclaration(
|
|||||||
manifest = ModuleManifest(
|
manifest = ModuleManifest(
|
||||||
id="mail",
|
id="mail",
|
||||||
name="Mail",
|
name="Mail",
|
||||||
version="0.1.24",
|
version="0.1.26",
|
||||||
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
||||||
optional_dependencies=("campaigns", "addresses", "calendar", "postbox", "search"),
|
optional_dependencies=("campaigns", "addresses", "calendar", "postbox", "search"),
|
||||||
provides_interfaces=(
|
provides_interfaces=(
|
||||||
@@ -562,6 +567,39 @@ manifest = ModuleManifest(
|
|||||||
order=40,
|
order=40,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
product_surfaces=(
|
||||||
|
ProductSurfaceContribution(
|
||||||
|
id="communication.messages",
|
||||||
|
module_id="mail",
|
||||||
|
label="i18n:govoplan-core.product_surface.messages",
|
||||||
|
description="i18n:govoplan-core.product_surface.messages_description",
|
||||||
|
icon="mail",
|
||||||
|
entry_path="/messages",
|
||||||
|
route_path="/mail",
|
||||||
|
surface_ids=("mail.nav.mail", "mail.route.mail"),
|
||||||
|
presentations=("task", "reader"),
|
||||||
|
search_source_ids=("mail.mailbox_messages",),
|
||||||
|
help_context_ids=("mail.quick_access.messages",),
|
||||||
|
documentation_topic_ids=("mail.quick-access-and-product-area",),
|
||||||
|
required_any=("mail:mailbox:read",),
|
||||||
|
aliases=("/inbox",),
|
||||||
|
order=10,
|
||||||
|
unavailable=ProductAvailabilityExplanation(
|
||||||
|
reason="authorization",
|
||||||
|
title="i18n:govoplan-core.product_surface.messages_unavailable",
|
||||||
|
description="i18n:govoplan-core.product_surface.messages_unavailable_description",
|
||||||
|
resolution="i18n:govoplan-core.product_surface.messages_unavailable_resolution",
|
||||||
|
responsible_role="i18n:govoplan-core.access_administrator",
|
||||||
|
),
|
||||||
|
degraded=ProductAvailabilityExplanation(
|
||||||
|
reason="provider_degraded",
|
||||||
|
title="i18n:govoplan-core.product_surface.messages_degraded",
|
||||||
|
description="i18n:govoplan-core.product_surface.messages_degraded_description",
|
||||||
|
resolution="i18n:govoplan-core.product_surface.messages_degraded_resolution",
|
||||||
|
responsible_role="i18n:govoplan-core.integration_operator",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
quick_access_tools=(
|
quick_access_tools=(
|
||||||
QuickAccessTool(
|
QuickAccessTool(
|
||||||
id="mail.messages",
|
id="mail.messages",
|
||||||
@@ -607,6 +645,7 @@ manifest = ModuleManifest(
|
|||||||
),
|
),
|
||||||
capability_factories={
|
capability_factories={
|
||||||
MAIL_CONFIGURATION_CAPABILITY: _configuration_provider,
|
MAIL_CONFIGURATION_CAPABILITY: _configuration_provider,
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY: _configuration_provider,
|
||||||
"mail.campaign_delivery": lambda context: __import__("govoplan_mail.backend.capabilities", fromlist=["campaign_capability"]).campaign_capability(context),
|
"mail.campaign_delivery": lambda context: __import__("govoplan_mail.backend.capabilities", fromlist=["campaign_capability"]).campaign_capability(context),
|
||||||
CAPABILITY_MAIL_DELIVERY_OUTBOX: lambda context: __import__(
|
CAPABILITY_MAIL_DELIVERY_OUTBOX: lambda context: __import__(
|
||||||
"govoplan_mail.backend.capabilities",
|
"govoplan_mail.backend.capabilities",
|
||||||
@@ -843,7 +882,9 @@ manifest = ModuleManifest(
|
|||||||
"The Mail configuration provider reads the validated mail.smtp deployment capability and derives authoritative endpoint metadata. "
|
"The Mail configuration provider reads the validated mail.smtp deployment capability and derives authoritative endpoint metadata. "
|
||||||
"Missing non-secret transport fields are collected by preflight; authentication is represented only by an existing credential-envelope id. "
|
"Missing non-secret transport fields are collected by preflight; authentication is represented only by an existing credential-envelope id. "
|
||||||
"Tenant scope is the default, system scope requires system authority, conflicting profiles are preserved unless update is explicitly reviewed, "
|
"Tenant scope is the default, system scope requires system authority, conflicting profiles are preserved unless update is explicitly reviewed, "
|
||||||
"and an unchanged second apply is a no-op. SMTP reachability remains a separate Mail profile test."
|
"and an unchanged second apply is a no-op. Before the host deployer changes or removes mail.smtp, Mail inventories every persisted SMTP endpoint, "
|
||||||
|
"legacy SMTP profile, and credential-binding count through the non-secret Ops dependency report. Missing, stale, or incomplete inventory blocks apply; "
|
||||||
|
"SMTP reachability remains a separate Mail profile test."
|
||||||
),
|
),
|
||||||
layer="configured",
|
layer="configured",
|
||||||
documentation_types=("admin",),
|
documentation_types=("admin",),
|
||||||
@@ -875,7 +916,9 @@ manifest = ModuleManifest(
|
|||||||
"Der Mail-Konfigurationsprovider liest die validierte Bereitstellungsfähigkeit mail.smtp und übernimmt deren maßgebliche Endpunktdaten. "
|
"Der Mail-Konfigurationsprovider liest die validierte Bereitstellungsfähigkeit mail.smtp und übernimmt deren maßgebliche Endpunktdaten. "
|
||||||
"Fehlende nicht geheime Transportangaben werden im Preflight abgefragt; Authentifizierung wird ausschließlich durch die ID eines vorhandenen Zugangsdaten-Umschlags referenziert. "
|
"Fehlende nicht geheime Transportangaben werden im Preflight abgefragt; Authentifizierung wird ausschließlich durch die ID eines vorhandenen Zugangsdaten-Umschlags referenziert. "
|
||||||
"Mandantenbezug ist der Standard, Systembezug erfordert Systemberechtigung, abweichende vorhandene Profile bleiben ohne ausdrücklich geprüfte Aktualisierung unverändert, "
|
"Mandantenbezug ist der Standard, Systembezug erfordert Systemberechtigung, abweichende vorhandene Profile bleiben ohne ausdrücklich geprüfte Aktualisierung unverändert, "
|
||||||
"und eine unveränderte zweite Anwendung bleibt wirkungslos. Die SMTP-Erreichbarkeit wird weiterhin separat im Mail-Profil getestet."
|
"und eine unveränderte zweite Anwendung bleibt wirkungslos. Bevor der Host-Deployer mail.smtp ändert oder entfernt, inventarisiert Mail alle gespeicherten SMTP-Endpunkte, "
|
||||||
|
"älteren SMTP-Profile und die Anzahl ihrer Zugangsdatenbindungen im nicht geheimen Ops-Abhängigkeitsbericht. Ein fehlendes, veraltetes oder unvollständiges Inventar blockiert die Anwendung; "
|
||||||
|
"die SMTP-Erreichbarkeit wird weiterhin separat im Mail-Profil getestet."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -893,6 +936,9 @@ manifest = ModuleManifest(
|
|||||||
body=(
|
body=(
|
||||||
"Mail contributes its full mailbox to Communication. When Quick Access is enabled, its compact provider surface "
|
"Mail contributes its full mailbox to Communication. When Quick Access is enabled, its compact provider surface "
|
||||||
"appears inside the shared Messages drawer alongside independent Postbox and future chat contributions. "
|
"appears inside the shared Messages drawer alongside independent Postbox and future chat contributions. "
|
||||||
|
"Mail also contributes to the stable Messages product entry: `/messages` and its migration alias `/inbox` open "
|
||||||
|
"Mail only when its mailbox route is currently authorized and visible in the effective View; otherwise another "
|
||||||
|
"available Messages owner is selected or the shell explains how access can be restored. "
|
||||||
"Recent entries open the exact authorized profile, folder, and message; Drafts resolves the configured or "
|
"Recent entries open the exact authorized profile, folder, and message; Drafts resolves the configured or "
|
||||||
"provider-detected Drafts folder, and the full Mail page keeps the versioned Quick Access return context. "
|
"provider-detected Drafts folder, and the full Mail page keeps the versioned Quick Access return context. "
|
||||||
"When an active Case requests a selection, choosing a recent message returns only its tenant-bound profile, "
|
"When an active Case requests a selection, choosing a recent message returns only its tenant-bound profile, "
|
||||||
@@ -914,6 +960,9 @@ manifest = ModuleManifest(
|
|||||||
"body": (
|
"body": (
|
||||||
"Mail ordnet das vollständige Postfach Kommunikation zu. Ist der Schnellzugriff aktiviert, erscheint die kompakte "
|
"Mail ordnet das vollständige Postfach Kommunikation zu. Ist der Schnellzugriff aktiviert, erscheint die kompakte "
|
||||||
"Mail-Oberfläche gemeinsam mit unabhängigen Beiträgen aus Postbox und künftig Chat unter Nachrichten. "
|
"Mail-Oberfläche gemeinsam mit unabhängigen Beiträgen aus Postbox und künftig Chat unter Nachrichten. "
|
||||||
|
"Mail trägt außerdem zum stabilen Produkteinstieg Nachrichten bei: `/messages` und der Migrationsalias `/inbox` "
|
||||||
|
"öffnen Mail nur, wenn die Postfachroute aktuell berechtigt und in der wirksamen View sichtbar ist. Andernfalls "
|
||||||
|
"wählt die Shell einen anderen verfügbaren Nachrichten-Eigentümer oder erklärt die Wiederherstellung des Zugriffs. "
|
||||||
"Aktuelle Einträge öffnen das genaue berechtigte Profil, den Ordner und die Nachricht; Entwürfe verwendet "
|
"Aktuelle Einträge öffnen das genaue berechtigte Profil, den Ordner und die Nachricht; Entwürfe verwendet "
|
||||||
"den konfigurierten oder vom Anbieter erkannten Entwurfsordner. Die vollständige Mail-Seite erhält den "
|
"den konfigurierten oder vom Anbieter erkannten Entwurfsordner. Die vollständige Mail-Seite erhält den "
|
||||||
"versionierten Rücksprungkontext. Verfassen öffnet bewusst die konfigurierte Mail-Anwendung des Benutzers, "
|
"versionierten Rücksprungkontext. Verfassen öffnet bewusst die konfigurierte Mail-Anwendung des Benutzers, "
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from govoplan_core.db.session import configure_database, reset_database
|
|||||||
from govoplan_core.security.credential_envelopes import CredentialEnvelope
|
from govoplan_core.security.credential_envelopes import CredentialEnvelope
|
||||||
from govoplan_mail.backend.configuration_provider import (
|
from govoplan_mail.backend.configuration_provider import (
|
||||||
MAIL_CONFIGURATION_CAPABILITY,
|
MAIL_CONFIGURATION_CAPABILITY,
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY,
|
||||||
SqlMailConfigurationProvider,
|
SqlMailConfigurationProvider,
|
||||||
)
|
)
|
||||||
from govoplan_mail.backend.db.models import (
|
from govoplan_mail.backend.db.models import (
|
||||||
@@ -168,9 +169,30 @@ class MailConfigurationProviderTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_provider_is_registered_and_describes_receipt_bound_fragment(self) -> None:
|
def test_provider_is_registered_and_describes_receipt_bound_fragment(self) -> None:
|
||||||
self.assertIn(MAIL_CONFIGURATION_CAPABILITY, manifest.capability_factories)
|
self.assertIn(MAIL_CONFIGURATION_CAPABILITY, manifest.capability_factories)
|
||||||
|
self.assertIn(
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY,
|
||||||
|
manifest.capability_factories,
|
||||||
|
)
|
||||||
description = self.provider.describe()
|
description = self.provider.describe()
|
||||||
self.assertEqual(("smtp_profile",), description.fragment_types)
|
self.assertEqual(("smtp_profile",), description.fragment_types)
|
||||||
|
|
||||||
|
def test_inventory_reports_actual_smtp_endpoint_and_credential_binding(self) -> None:
|
||||||
|
fragment = ConfigurationPackageFragment(
|
||||||
|
module_id="mail",
|
||||||
|
fragment_type="smtp_profile",
|
||||||
|
fragment_id="inventory-smtp",
|
||||||
|
payload={"credential_envelope_id": "credential-1"},
|
||||||
|
)
|
||||||
|
self.provider.apply(fragment, {}, self.context)
|
||||||
|
|
||||||
|
dependencies = self.provider.infrastructure_dependencies()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(dependencies))
|
||||||
|
self.assertEqual("mail.smtp", dependencies[0].capability_id)
|
||||||
|
self.assertEqual("smtp_endpoint", dependencies[0].dependency_type)
|
||||||
|
self.assertEqual(1, dependencies[0].metrics["credential_binding_count"])
|
||||||
|
self.assertNotIn("test-mail", str(dependencies[0].to_dict()))
|
||||||
|
|
||||||
def test_apply_is_idempotent_and_binds_existing_credential_envelope(self) -> None:
|
def test_apply_is_idempotent_and_binds_existing_credential_envelope(self) -> None:
|
||||||
fragment = ConfigurationPackageFragment(
|
fragment = ConfigurationPackageFragment(
|
||||||
module_id="mail",
|
module_id="mail",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.24",
|
"version": "0.1.26",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.24",
|
"version": "0.1.26",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.24",
|
"version": "0.1.26",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
import { createElement, lazy } from "react";
|
import { createElement, lazy } from "react";
|
||||||
import type { MailDevMailboxUiCapability, MailProfilesUiCapability, PlatformWebModule, QuickAccessToolsUiCapability } from "@govoplan/core-webui";
|
import { messagesProductSurfaceTranslations, type MailDevMailboxUiCapability, type MailProfilesUiCapability, type PlatformWebModule, type QuickAccessToolsUiCapability } from "@govoplan/core-webui";
|
||||||
import { MailProfilePolicyEditor, MailProfileScopeManager } from "./features/mail/MailProfileManagement";
|
import { MailProfilePolicyEditor, MailProfileScopeManager } from "./features/mail/MailProfileManagement";
|
||||||
import { validateMailPolicy } from "./features/mail/mailPolicyValidation";
|
import { validateMailPolicy } from "./features/mail/mailPolicyValidation";
|
||||||
import { mailCredentialReferenceSelectors } from "./features/mail/mailReferenceProviders";
|
import { mailCredentialReferenceSelectors } from "./features/mail/mailReferenceProviders";
|
||||||
@@ -14,8 +14,8 @@ const mailboxRead = ["mail:mailbox:read"];
|
|||||||
const bounceRead = ["mail:bounce:read", "mail:bounce:manage"];
|
const bounceRead = ["mail:bounce:read", "mail:bounce:manage"];
|
||||||
const legacyImportAccess = ["mail:pop3:import", "mail:pop3:manage"];
|
const legacyImportAccess = ["mail:pop3:import", "mail:pop3:manage"];
|
||||||
const translations = {
|
const translations = {
|
||||||
en: generatedTranslations.en,
|
en: { ...generatedTranslations.en, ...messagesProductSurfaceTranslations.en },
|
||||||
de: generatedTranslations.de
|
de: { ...generatedTranslations.de, ...messagesProductSurfaceTranslations.de }
|
||||||
};
|
};
|
||||||
const mailQuickAccessTools: QuickAccessToolsUiCapability = {
|
const mailQuickAccessTools: QuickAccessToolsUiCapability = {
|
||||||
tools: [
|
tools: [
|
||||||
|
|||||||
Reference in New Issue
Block a user