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
15 changes: 15 additions & 0 deletions edi_endpoint_oca/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ Configuration

Go to "EDI -> Config -> Endpoints".

Exec modes
----------

Each endpoint must pick an "Exec mode" that decides how the incoming
request is turned into work for the EDI framework:

- **Create exchange record** (default): persists the raw HTTP body as a
new exchange record on the configured backend / exchange type and
returns ``{"status": "queued", "id": <identifier>}`` with HTTP 200.
Use this for "receive and queue" endpoints — no per-endpoint code
snippet is required, and request validation (e.g. JSON Schema) is
handled by the endpoint mixin before the handler runs.
- **Execute code**: runs the user-provided code snippet, giving full
control over how the request is processed and what is returned.

Bug Tracker
===========

Expand Down
11 changes: 11 additions & 0 deletions edi_endpoint_oca/demo/edi_backend_demo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,15 @@ record = endpoint.create_exchange_record()
result = {"response": Response("Created record: %s" % record.identifier)}
</field>
</record>

<record id="edi_endpoint_demo_2" model="edi.endpoint">
<field name="backend_id" ref="edi_backend_demo" />
<field name="backend_type_id" ref="edi_core_oca.demo_edi_backend_type" />
<field name="exchange_type_id" ref="edi_exchange_type_demo" />
<field name="name">EDI Demo Endpoint 2</field>
<field name="route">/demo/create</field>
<field name="request_method">POST</field>
<field name="request_content_type">application/json</field>
<field name="exec_mode">create_exchange_record</field>
</record>
</odoo>
20 changes: 20 additions & 0 deletions edi_endpoint_oca/models/edi_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ class EDIEndpoint(models.Model):
comodel_name="edi.exchange.type",
domain="[('backend_type_id','=', backend_type_id)]",
)
exec_mode = fields.Selection(default="create_exchange_record")

def _selection_exec_mode(self):
return super()._selection_exec_mode() + [
("create_exchange_record", self.env._("Create exchange record")),
]

def _handle_exec__create_exchange_record(self, request):
"""Persist the raw HTTP body as an exchange record and acknowledge.

Covers the "receive and queue" case for incoming EDI endpoints,
avoiding the need for a per-endpoint code snippet.
"""
record = self.create_exchange_record(
file_content=request.httprequest.get_data(),
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simahawk shouldn't create_exchange_record set the edi_exchange_state to input_received if file_content is provided?
or perhaps it should immediately call exchange_receive in such case?

To me it seems obvious, I don't get why we would want it in a separate step given the file_content is already set.

return {
"payload": {"status": "queued", "id": record.identifier},
"status_code": 200,
}

def create_exchange_record(self, file_content=None, encoding="utf-8", **vals):
"""Create an EDI exchange record from current endpoint.
Expand Down
14 changes: 14 additions & 0 deletions edi_endpoint_oca/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
Go to "EDI -\> Config -\> Endpoints".

## Exec modes

Each endpoint must pick an "Exec mode" that decides how the incoming
request is turned into work for the EDI framework:

- **Create exchange record** (default): persists the raw HTTP body as a
new exchange record on the configured backend / exchange type and
returns `{"status": "queued", "id": <identifier>}` with HTTP 200. Use
this for "receive and queue" endpoints — no per-endpoint code snippet
is required, and request validation (e.g. JSON Schema) is handled by
the endpoint mixin before the handler runs.
- **Execute code**: runs the user-provided code snippet, giving full
control over how the request is processed and what is returned.
40 changes: 29 additions & 11 deletions edi_endpoint_oca/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -380,44 +380,62 @@ <h1>EDI endpoint</h1>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a><ul>
<li><a class="reference internal" href="#exec-modes" id="toc-entry-2">Exec modes</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
<p>Go to “EDI -&gt; Config -&gt; Endpoints”.</p>
<div class="section" id="exec-modes">
<h3><a class="toc-backref" href="#toc-entry-2">Exec modes</a></h3>
<p>Each endpoint must pick an “Exec mode” that decides how the incoming
request is turned into work for the EDI framework:</p>
<ul class="simple">
<li><strong>Create exchange record</strong> (default): persists the raw HTTP body as a
new exchange record on the configured backend / exchange type and
returns <tt class="docutils literal">{&quot;status&quot;: &quot;queued&quot;, &quot;id&quot;: &lt;identifier&gt;}</tt> with HTTP 200.
Use this for “receive and queue” endpoints — no per-endpoint code
snippet is required, and request validation (e.g. JSON Schema) is
handled by the endpoint mixin before the handler runs.</li>
<li><strong>Execute code</strong>: runs the user-provided code snippet, giving full
control over how the request is processed and what is returned.</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<h2><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/edi-framework/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/edi-framework/issues/new?body=module:%20edi_endpoint_oca%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
<h2><a class="toc-backref" href="#toc-entry-4">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<h3><a class="toc-backref" href="#toc-entry-5">Authors</a></h3>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
<ul class="simple">
<li>Simone Orsi &lt;<a class="reference external" href="mailto:simone.orsi&#64;camptocamp.com">simone.orsi&#64;camptocamp.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand Down
26 changes: 26 additions & 0 deletions edi_endpoint_oca/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ def _setup_records(cls):
)
or cls._get_endpoint()
)
cls.endpoint_create_record = (
cls.Endpoint.search(
[
("route", "=", "/edi/demo/create"),
("backend_type_id", "=", cls.backend_type.id),
("exchange_type_id", "=", cls.exchange_type.id),
],
limit=1,
)
or cls._get_endpoint_create_record()
)

@classmethod
def _get_backend_type(cls):
Expand Down Expand Up @@ -103,6 +114,21 @@ def _get_endpoint(cls):
}
)

@classmethod
def _get_endpoint_create_record(cls):
return cls.env["edi.endpoint"].create(
{
"name": "EDI Demo Endpoint 2",
"backend_id": cls.backend.id,
"backend_type_id": cls.backend_type.id,
"exchange_type_id": cls.exchange_type.id,
"route": "/demo/create",
"request_method": "POST",
"request_content_type": "application/json",
"exec_mode": "create_exchange_record",
}
)


class EDIEndpointCommonTestCase(TransactionCase, EDIEndpointTestMixin):
@classmethod
Expand Down
6 changes: 3 additions & 3 deletions edi_endpoint_oca/tests/test_edi_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ def test_route(self):

def test_endpoint_count(self):
backend = self.endpoint.backend_id
self.assertEqual(backend.endpoints_count, 1)
initial = backend.endpoints_count
rec = self.endpoint.copy(
{
"route": "/another",
}
)
self.assertEqual(backend.endpoints_count, 2)
self.assertEqual(backend.endpoints_count, initial + 1)
rec.active = False
self.assertEqual(backend.endpoints_count, 1)
self.assertEqual(backend.endpoints_count, initial)

def test_archive_check(self):
backend = self.endpoint.backend_id
Expand Down
30 changes: 25 additions & 5 deletions edi_endpoint_oca/tests/test_edi_endpoint_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
# @author: Simone Orsi <simone.orsi@camptocamp.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import base64
import json
import os
import unittest

from odoo.tests.common import HttpCase
from odoo.tests import HttpCase, RecordCapturer

from .common import EDIEndpointTestMixin

Expand All @@ -17,18 +19,18 @@ def setUpClass(cls):
super().setUpClass()
cls._setup_env()
cls._setup_records()
# Sync only the endpoint under test to avoid re-registering unrelated
# Sync only the endpoints under test to avoid re-registering unrelated
# demo routes that may already exist in the route registry.
cls.endpoint._handle_registry_sync()
(cls.endpoint | cls.endpoint_create_record)._handle_registry_sync()

def tearDown(self):
# Clear routing cache so each test starts clean
self.env.registry.clear_cache("routing")
super().tearDown()

def _make_request(self, route, headers=None):
def _make_request(self, route, headers=None, data=None):
headers = dict(headers or {})
return self.url_open(route, headers=headers, timeout=60)
return self.url_open(route, headers=headers, data=data, timeout=60)

def test_call1(self):
endpoint = "/edi/demo/try"
Expand All @@ -39,3 +41,21 @@ def test_call1(self):
response = self._make_request(endpoint)
self.assertEqual(response.status_code, 200)
self.assertIn("Created record:", response.content.decode())

def test_handle_exec_create_exchange_record(self):
self.authenticate("admin", "admin")
body = json.dumps({"hello": "world"}).encode()
with RecordCapturer(self.env["edi.exchange.record"], []) as capture:
response = self._make_request(
"/edi/demo/create",
headers={"Content-Type": "application/json"},
data=body,
)
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload["status"], "queued")
self.assertEqual(len(capture.records), 1)
record = capture.records
self.assertEqual(record.identifier, payload["id"])
self.assertEqual(record.edi_exchange_state, "new")
self.assertEqual(base64.b64decode(record.exchange_file), body)
Loading