fix: use standard odoo jsonrpc protocol
This commit is contained in:
@@ -1,2 +1,5 @@
|
||||
/ticketing.ini
|
||||
/tickets_state.json
|
||||
/WAPT/certificate.crt
|
||||
/WAPT/manifest.sha256
|
||||
/WAPT/signature.sha256
|
||||
|
||||
@@ -46,6 +46,8 @@ webhook_url = CHANGE_ME_ROCKET_CHAT_WEBHOOK_URL
|
||||
|
||||
[odoo]
|
||||
url = https://CHANGE_ME_ODOO_HOST
|
||||
database = CHANGE_ME_ODOO_DATABASE
|
||||
username = CHANGE_ME_ODOO_USERNAME
|
||||
api_key = CHANGE_ME_ODOO_API_KEY
|
||||
model = helpdesk.ticket
|
||||
|
||||
@@ -82,24 +84,41 @@ Hors WAPT, le script ne peut pas etre lance tel quel sans fournir les objets et
|
||||
|
||||
Le runtime lit et ecrit ce fichier dans `WAPT.private_dir`. Il n'est pas versionne dans le depot.
|
||||
|
||||
La cle API est utilisee comme mot de passe d'API Odoo pour authentifier `username`. Il est aussi possible de renseigner `uid` dans la section `[odoo]` pour eviter l'appel d'authentification.
|
||||
|
||||
## Protocole Odoo
|
||||
|
||||
Le script utilise l'endpoint configure sous la forme `<odoo.url>/jsonrpc` avec un payload JSON-RPC direct :
|
||||
Le script utilise l'endpoint configure sous la forme `<odoo.url>/jsonrpc` avec le protocole JSON-RPC Odoo standard.
|
||||
|
||||
Authentification :
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "call",
|
||||
"params": {
|
||||
"model": "helpdesk.ticket",
|
||||
"method": "search_read",
|
||||
"args": [[], ["id", "write_date"]]
|
||||
"service": "common",
|
||||
"method": "authenticate",
|
||||
"args": ["database", "username", "api_key", {}]
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
Il faut donc que l'instance Odoo expose bien un endpoint compatible avec ce format. Si l'instance attend le protocole externe Odoo standard `execute_kw`, il faudra adapter `fetch_odoo_tickets()`.
|
||||
Lecture des tickets :
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "call",
|
||||
"params": {
|
||||
"service": "object",
|
||||
"method": "execute_kw",
|
||||
"args": ["database", "uid", "api_key", "helpdesk.ticket", "search_read", [[]], {"fields": ["id", "write_date"]}]
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -116,6 +135,6 @@ python3 -m unittest discover -s tests
|
||||
|
||||
## Prochaines ameliorations recommandees
|
||||
|
||||
1. Confirmer en environnement cible que l'endpoint Odoo accepte bien le payload JSON-RPC direct documente ci-dessus.
|
||||
1. Confirmer en environnement cible que l'utilisateur Odoo configure a bien acces au modele `helpdesk.ticket`.
|
||||
2. Completer les metadonnees `WAPT/control` selon les conventions internes Comitari.
|
||||
3. Ajouter un test d'integration manuel ou automatise sur une instance Odoo de recette.
|
||||
|
||||
+18
@@ -93,3 +93,21 @@ Ce fichier consigne les interventions Codex sur le projet afin de garder une tra
|
||||
- Ajout de tests avec mocks `requests` sur `fetch_odoo_tickets()` et `send_to_rocket()`.
|
||||
- Ajout d'un fichier `WAPT/control` minimal.
|
||||
- Documentation du payload JSON-RPC Odoo attendu dans `README.md`.
|
||||
- Ajout des artefacts WAPT generes `certificate.crt`, `manifest.sha256` et `signature.sha256` dans `.gitignore`.
|
||||
|
||||
### Correction protocole Odoo JSON-RPC
|
||||
|
||||
- Analyse d'une erreur `RPC.jsonrpc() missing 1 required positional argument: 'service'` lors de `wapt-get audit`.
|
||||
- Remplacement du payload direct `model/method/args` par le protocole JSON-RPC Odoo standard :
|
||||
- `service=common`, `method=authenticate` pour obtenir le `uid` ;
|
||||
- `service=object`, `method=execute_kw` pour appeler `helpdesk.ticket.search_read`.
|
||||
- Ajout des champs `odoo.database` et `odoo.username` dans `ticketing.ini.example`.
|
||||
- Support optionnel de `odoo.uid` pour eviter l'authentification si l'identifiant utilisateur est deja connu.
|
||||
- Mise a jour des tests unitaires pour verifier les appels `authenticate` et `execute_kw`.
|
||||
|
||||
### Ajustement WAPT/control
|
||||
|
||||
- Passage de `target_os` de `windows` a `all` dans `WAPT/control`.
|
||||
- Incrementation de la version WAPT de `0.1.0-2` a `0.1.0-3`.
|
||||
- Attention : `target_os` fait partie des attributs signes, le paquet doit etre regenere/resigne avant publication.
|
||||
- Regle projet retenue : incrementer `WAPT/control:version` a chaque nouvelle version du paquet.
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
package : comi-odoo-ticketing
|
||||
version : 0.1.0-2
|
||||
version : 0.1.0-3
|
||||
architecture : all
|
||||
section : base
|
||||
priority : optional
|
||||
@@ -11,7 +11,7 @@ depends :
|
||||
conflicts :
|
||||
maturity : PROD
|
||||
locale : all
|
||||
target_os : windows
|
||||
target_os : all
|
||||
min_wapt_version :
|
||||
sources :
|
||||
installed_size :
|
||||
|
||||
@@ -52,9 +52,16 @@ def load_config():
|
||||
|
||||
required_options = [
|
||||
("odoo", "url"),
|
||||
("odoo", "database"),
|
||||
("odoo", "api_key"),
|
||||
("rocket", "webhook_url"),
|
||||
]
|
||||
if not (
|
||||
conf_wapt.has_option("odoo", "uid")
|
||||
or conf_wapt.has_option("odoo", "username")
|
||||
):
|
||||
required_options.append(("odoo", "username"))
|
||||
|
||||
missing_options = [
|
||||
"%s.%s" % (section, option)
|
||||
for section, option in required_options
|
||||
@@ -85,31 +92,27 @@ def get_http_timeout(conf_wapt):
|
||||
return conf_wapt.getint("http", "timeout", fallback=DEFAULT_TIMEOUT)
|
||||
|
||||
|
||||
def build_odoo_payload(conf_wapt):
|
||||
def build_odoo_payload(service, method, args):
|
||||
return {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "call",
|
||||
"params": {
|
||||
"model": conf_wapt.get("odoo", "model", fallback="helpdesk.ticket"),
|
||||
"method": "search_read",
|
||||
"args": [[], ["id", "write_date"]],
|
||||
"service": service,
|
||||
"method": method,
|
||||
"args": args,
|
||||
},
|
||||
"id": 1,
|
||||
}
|
||||
|
||||
|
||||
def fetch_odoo_tickets(conf_wapt):
|
||||
def call_odoo_jsonrpc(conf_wapt, service, method, args):
|
||||
odoo_url = conf_wapt.get("odoo", "url").rstrip("/")
|
||||
endpoint = "%s/jsonrpc" % odoo_url
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer %s" % conf_wapt.get("odoo", "api_key"),
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
endpoint,
|
||||
json=build_odoo_payload(conf_wapt),
|
||||
headers=headers,
|
||||
json=build_odoo_payload(service, method, args),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=get_http_timeout(conf_wapt),
|
||||
)
|
||||
response.raise_for_status()
|
||||
@@ -123,6 +126,45 @@ def fetch_odoo_tickets(conf_wapt):
|
||||
return response_data["result"]
|
||||
|
||||
|
||||
def get_odoo_uid(conf_wapt):
|
||||
if conf_wapt.has_option("odoo", "uid"):
|
||||
return conf_wapt.getint("odoo", "uid")
|
||||
|
||||
uid = call_odoo_jsonrpc(
|
||||
conf_wapt,
|
||||
"common",
|
||||
"authenticate",
|
||||
[
|
||||
conf_wapt.get("odoo", "database"),
|
||||
conf_wapt.get("odoo", "username"),
|
||||
conf_wapt.get("odoo", "api_key"),
|
||||
{},
|
||||
],
|
||||
)
|
||||
if not uid:
|
||||
raise Exception("Authentification Odoo impossible")
|
||||
return uid
|
||||
|
||||
|
||||
def fetch_odoo_tickets(conf_wapt):
|
||||
uid = get_odoo_uid(conf_wapt)
|
||||
|
||||
return call_odoo_jsonrpc(
|
||||
conf_wapt,
|
||||
"object",
|
||||
"execute_kw",
|
||||
[
|
||||
conf_wapt.get("odoo", "database"),
|
||||
uid,
|
||||
conf_wapt.get("odoo", "api_key"),
|
||||
conf_wapt.get("odoo", "model", fallback="helpdesk.ticket"),
|
||||
"search_read",
|
||||
[[]],
|
||||
{"fields": ["id", "write_date"]},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def load_tickets_state(state_path):
|
||||
if not os.path.exists(state_path):
|
||||
return {}
|
||||
|
||||
+25
-6
@@ -33,6 +33,8 @@ def make_config():
|
||||
config = ConfigParser()
|
||||
config.add_section("odoo")
|
||||
config.set("odoo", "url", "https://odoo.example.test")
|
||||
config.set("odoo", "database", "test-db")
|
||||
config.set("odoo", "username", "test@example.test")
|
||||
config.set("odoo", "api_key", "test-api-key")
|
||||
config.set("odoo", "model", "helpdesk.ticket")
|
||||
config.add_section("rocket")
|
||||
@@ -82,19 +84,24 @@ class TicketingStateTest(unittest.TestCase):
|
||||
self.assertEqual(ticketing.load_tickets_state(state_path), expected_state)
|
||||
|
||||
@patch("ticketing_setup.requests.post")
|
||||
def test_fetch_odoo_tickets_uses_timeout_and_returns_result(self, post):
|
||||
response = Mock()
|
||||
response.json.return_value = {
|
||||
def test_fetch_odoo_tickets_authenticates_and_returns_result(self, post):
|
||||
auth_response = Mock()
|
||||
auth_response.json.return_value = {"result": 7}
|
||||
tickets_response = Mock()
|
||||
tickets_response.json.return_value = {
|
||||
"result": [{"id": 42, "write_date": "2026-07-24 12:00:00"}],
|
||||
}
|
||||
post.return_value = response
|
||||
post.side_effect = [auth_response, tickets_response]
|
||||
|
||||
tickets = ticketing.fetch_odoo_tickets(make_config())
|
||||
|
||||
self.assertEqual(tickets, [{"id": 42, "write_date": "2026-07-24 12:00:00"}])
|
||||
response.raise_for_status.assert_called_once_with()
|
||||
post.assert_called_once()
|
||||
auth_response.raise_for_status.assert_called_once_with()
|
||||
tickets_response.raise_for_status.assert_called_once_with()
|
||||
self.assertEqual(post.call_count, 2)
|
||||
self.assertEqual(post.call_args.kwargs["timeout"], 5)
|
||||
self.assertEqual(post.call_args.kwargs["json"]["params"]["service"], "object")
|
||||
self.assertEqual(post.call_args.kwargs["json"]["params"]["method"], "execute_kw")
|
||||
|
||||
@patch("ticketing_setup.requests.post")
|
||||
def test_fetch_odoo_tickets_rejects_jsonrpc_error(self, post):
|
||||
@@ -105,6 +112,18 @@ class TicketingStateTest(unittest.TestCase):
|
||||
with self.assertRaisesRegex(Exception, "Erreur Odoo JSON-RPC"):
|
||||
ticketing.fetch_odoo_tickets(make_config())
|
||||
|
||||
@patch("ticketing_setup.requests.post")
|
||||
def test_fetch_odoo_tickets_can_use_configured_uid(self, post):
|
||||
config = make_config()
|
||||
config.set("odoo", "uid", "12")
|
||||
response = Mock()
|
||||
response.json.return_value = {"result": []}
|
||||
post.return_value = response
|
||||
|
||||
self.assertEqual(ticketing.fetch_odoo_tickets(config), [])
|
||||
self.assertEqual(post.call_count, 1)
|
||||
self.assertEqual(post.call_args.kwargs["json"]["params"]["args"][1], 12)
|
||||
|
||||
@patch("ticketing_setup.requests.post")
|
||||
def test_send_to_rocket_raises_for_http_errors(self, post):
|
||||
response = Mock()
|
||||
|
||||
@@ -3,6 +3,8 @@ webhook_url = CHANGE_ME_ROCKET_CHAT_WEBHOOK_URL
|
||||
|
||||
[odoo]
|
||||
url = https://CHANGE_ME_ODOO_HOST
|
||||
database = CHANGE_ME_ODOO_DATABASE
|
||||
username = CHANGE_ME_ODOO_USERNAME
|
||||
api_key = CHANGE_ME_ODOO_API_KEY
|
||||
model = helpdesk.ticket
|
||||
|
||||
|
||||
Reference in New Issue
Block a user