Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions spp_dci_server/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ Dependencies
Changelog
=========

19.0.2.0.4
~~~~~~~~~~

- Return signed DCI ``on-search`` envelopes from registry alias stubs
(disability, crvs, farmer) instead of HTTP 501; per-item ``rjct`` with
``ACTION_NOT_SUPPORTED``.

19.0.2.0.0
~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion spp_dci_server/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{ # pylint: disable=pointless-statement
"name": "OpenSPP DCI Server",
"summary": "DCI API server infrastructure with FastAPI routers",
"version": "19.0.2.0.3",
"version": "19.0.2.0.4",
"category": "OpenSPP/Integration",
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
Expand Down
4 changes: 4 additions & 0 deletions spp_dci_server/readme/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 19.0.2.0.4

- Return signed DCI ``on-search`` envelopes from registry alias stubs (disability, crvs, farmer) instead of HTTP 501; per-item ``rjct`` with ``ACTION_NOT_SUPPORTED``.

### 19.0.2.0.0

- Initial migration to OpenSPP2
158 changes: 75 additions & 83 deletions spp_dci_server/routers/registry_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,88 @@
"""

import logging
from datetime import datetime
import uuid
from datetime import UTC, datetime
from typing import Annotated

from odoo.api import Environment

from odoo.addons.fastapi.dependencies import odoo_env
from odoo.addons.spp_dci.schemas import DCIEnvelope
from odoo.addons.spp_dci.schemas.constants import MsgHeaderStatusReasonCode
from odoo.addons.spp_dci.schemas.search import (
SearchRequest,
SearchResponse,
SearchResponseItem,
)
from odoo.addons.spp_dci.services import build_signed_envelope

from fastapi import APIRouter, Depends, status
from fastapi import APIRouter, Depends, HTTPException, status

from ..middleware.signature import verify_bearer_token

_logger = logging.getLogger(__name__)


def _build_stub_search_envelope(
request_envelope: DCIEnvelope,
env: Environment,
not_implemented_message: str,
) -> DCIEnvelope:
"""Return a signed DCI on-search envelope with per-item rjct stubs."""
try:
search_request = SearchRequest.model_validate(request_envelope.message)
except Exception as e:
_logger.error("Invalid SearchRequest message: %s", str(e))
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid search request message: {str(e)}",
) from e

response_items = []
for req_item in search_request.search_request:
response_items.append(
SearchResponseItem(
reference_id=req_item.reference_id,
timestamp=datetime.now(UTC),
status="rjct",
status_reason_code=MsgHeaderStatusReasonCode.ACTION_NOT_SUPPORTED.value,
status_reason_message=not_implemented_message,
data=None,
pagination=None,
locale=req_item.locale,
)
)

search_response = SearchResponse(
transaction_id=search_request.transaction_id,
correlation_id=str(uuid.uuid4()),
search_response=response_items,
)
response_message = search_response.model_dump(mode="json", exclude_none=True)

return build_signed_envelope(
env,
request_envelope.header,
response_message,
status_code="rjct",
status_reason_code=MsgHeaderStatusReasonCode.ACTION_NOT_SUPPORTED.value,
status_reason_message=not_implemented_message,
completed_count=0,
)


# Disability Registry router
disability_router = APIRouter(tags=["Disability Registry"], prefix="/disability/registry")


@disability_router.post(
"/sync/search",
response_model=SearchResponse,
response_model=DCIEnvelope,
response_model_exclude_none=True,
status_code=status.HTTP_501_NOT_IMPLEMENTED,
)
async def disability_sync_search(
request: SearchRequest,
request_envelope: DCIEnvelope,
env: Annotated[Environment, Depends(odoo_env)],
_bearer_token: Annotated[str, Depends(verify_bearer_token)],
):
Expand All @@ -54,29 +105,10 @@ async def disability_sync_search(
For now, returns "not implemented" response for SPDCI compliance testing.
"""
_logger.warning("Disability Registry search endpoint called but not yet implemented")

# Build response items with "not implemented" status
response_items = []
for req_item in request.search_request:
response_items.append(
SearchResponseItem(
reference_id=req_item.reference_id,
timestamp=datetime.utcnow(),
status="rjct",
status_reason_code=MsgHeaderStatusReasonCode.ACTION_NOT_SUPPORTED.value,
status_reason_message=(
"Disability Registry not yet implemented. Will be available in spp_dci_server_disability module."
),
data=None,
pagination=None,
locale=req_item.locale,
)
)

return SearchResponse(
transaction_id=request.transaction_id,
correlation_id=None,
search_response=response_items,
return _build_stub_search_envelope(
request_envelope,
env,
"Disability Registry not yet implemented. Will be available in spp_dci_server_disability module.",
)


Expand All @@ -99,7 +131,7 @@ async def disability_sync_notify(
return {
"status": "rjct",
"message": "Disability Registry not yet implemented",
"timestamp": datetime.utcnow().isoformat(),
"timestamp": datetime.now(UTC).isoformat(),
}


Expand All @@ -109,12 +141,11 @@ async def disability_sync_notify(

@crvs_router.post(
"/sync/search",
response_model=SearchResponse,
response_model=DCIEnvelope,
response_model_exclude_none=True,
status_code=status.HTTP_501_NOT_IMPLEMENTED,
)
async def crvs_sync_search(
request: SearchRequest,
request_envelope: DCIEnvelope,
env: Annotated[Environment, Depends(odoo_env)],
_bearer_token: Annotated[str, Depends(verify_bearer_token)],
):
Expand All @@ -127,29 +158,10 @@ async def crvs_sync_search(
For now, returns "not implemented" response for SPDCI compliance testing.
"""
_logger.warning("Civil Registry search endpoint called but not yet implemented")

# Build response items with "not implemented" status
response_items = []
for req_item in request.search_request:
response_items.append(
SearchResponseItem(
reference_id=req_item.reference_id,
timestamp=datetime.utcnow(),
status="rjct",
status_reason_code=MsgHeaderStatusReasonCode.ACTION_NOT_SUPPORTED.value,
status_reason_message=(
"Civil Registry not yet implemented. Will be available in spp_dci_server_crvs module."
),
data=None,
pagination=None,
locale=req_item.locale,
)
)

return SearchResponse(
transaction_id=request.transaction_id,
correlation_id=None,
search_response=response_items,
return _build_stub_search_envelope(
request_envelope,
env,
"Civil Registry not yet implemented. Will be available in spp_dci_server_crvs module.",
)


Expand All @@ -172,7 +184,7 @@ async def crvs_sync_notify(
return {
"status": "rjct",
"message": "Civil Registry not yet implemented",
"timestamp": datetime.utcnow().isoformat(),
"timestamp": datetime.now(UTC).isoformat(),
}


Expand All @@ -182,12 +194,11 @@ async def crvs_sync_notify(

@farmer_router.post(
"/sync/search",
response_model=SearchResponse,
response_model=DCIEnvelope,
response_model_exclude_none=True,
status_code=status.HTTP_501_NOT_IMPLEMENTED,
)
async def farmer_sync_search(
request: SearchRequest,
request_envelope: DCIEnvelope,
env: Annotated[Environment, Depends(odoo_env)],
_bearer_token: Annotated[str, Depends(verify_bearer_token)],
):
Expand All @@ -200,29 +211,10 @@ async def farmer_sync_search(
For now, returns "not implemented" response for SPDCI compliance testing.
"""
_logger.warning("Farmer Registry search endpoint called but not yet implemented")

# Build response items with "not implemented" status
response_items = []
for req_item in request.search_request:
response_items.append(
SearchResponseItem(
reference_id=req_item.reference_id,
timestamp=datetime.utcnow(),
status="rjct",
status_reason_code=MsgHeaderStatusReasonCode.ACTION_NOT_SUPPORTED.value,
status_reason_message=(
"Farmer Registry not yet implemented. Will be available in spp_dci_server_farmer module."
),
data=None,
pagination=None,
locale=req_item.locale,
)
)

return SearchResponse(
transaction_id=request.transaction_id,
correlation_id=None,
search_response=response_items,
return _build_stub_search_envelope(
request_envelope,
env,
"Farmer Registry not yet implemented. Will be available in spp_dci_server_farmer module.",
)


Expand All @@ -245,5 +237,5 @@ async def farmer_sync_notify(
return {
"status": "rjct",
"message": "Farmer Registry not yet implemented",
"timestamp": datetime.utcnow().isoformat(),
"timestamp": datetime.now(UTC).isoformat(),
}
8 changes: 8 additions & 0 deletions spp_dci_server/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,14 @@ <h2><a class="toc-backref" href="#toc-entry-1">Changelog</a></h2>
</div>
</div>
<div class="section" id="section-1">
<h1>19.0.2.0.4</h1>
<ul class="simple">
<li>Return signed DCI <tt class="docutils literal">on-search</tt> envelopes from registry alias stubs
(disability, crvs, farmer) instead of HTTP 501; per-item <tt class="docutils literal">rjct</tt> with
<tt class="docutils literal">ACTION_NOT_SUPPORTED</tt>.</li>
</ul>
</div>
<div class="section" id="section-2">
<h1>19.0.2.0.0</h1>
<ul class="simple">
<li>Initial migration to OpenSPP2</li>
Expand Down
29 changes: 25 additions & 4 deletions spp_dci_server/tests/test_callback_routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
from odoo.tests import tagged

from odoo.addons.spp_dci.schemas.constants import MsgHeaderStatusReasonCode
from odoo.addons.spp_dci.schemas.envelope import DCIMessageHeader
from odoo.addons.spp_dci.schemas.search import (
SearchCriteria,
SearchRequest,
SearchRequestItem,
SearchResponse,
)
from odoo.addons.spp_dci.schemas import DCIEnvelope

from .common import DCIServerCommon

Expand Down Expand Up @@ -211,8 +214,24 @@ def _build_search_request(self, n_items=1):
)
return SearchRequest(transaction_id="txn-alias", search_request=items)

def _build_search_envelope(self, n_items=1):
search_request = self._build_search_request(n_items=n_items)
header = DCIMessageHeader(
message_id="msg-alias-test",
message_ts=datetime.now(UTC),
action="search",
sender_id="registry-witness",
receiver_id="openspp",
total_count=n_items,
)
return DCIEnvelope(
signature="",
header=header,
message=search_request.model_dump(mode="json"),
)

def test_search_stubs_return_per_item_rjct(self):
request = self._build_search_request(n_items=2)
request = self._build_search_envelope(n_items=2)
# Each alias mentions its corresponding spp_dci_server_* module in
# the rejection message so operators can find the missing addon.
expected_module = {
Expand All @@ -223,9 +242,11 @@ def test_search_stubs_return_per_item_rjct(self):
for registry, endpoint in self.search_endpoints.items():
with self.subTest(registry=registry):
response = _run(endpoint(request, self.env, _bearer_token="t"))
self.assertEqual(response.transaction_id, "txn-alias")
self.assertEqual(len(response.search_response), 2)
for item in response.search_response:
self.assertIsInstance(response, DCIEnvelope)
search_response = SearchResponse.model_validate(response.message)
self.assertEqual(search_response.transaction_id, "txn-alias")
self.assertEqual(len(search_response.search_response), 2)
for item in search_response.search_response:
self.assertEqual(item.status, "rjct")
self.assertEqual(
item.status_reason_code,
Expand Down