diff --git a/CLAUDE.md b/CLAUDE.md index 23784c83..29f7c1c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,8 +81,15 @@ Odoo content-hashes the compiled bundle URL (`/web/assets//...`). When CSS - **fusion_clock** is currently being modified in Cursor — always read files fresh before editing, don't assume you know the current state ## Workflow -- Local dev: `docker exec odoo-dev-app odoo -d fusion-dev -u --stop-after-init` -- Local URL: http://localhost:8069 +- Local dev: `docker exec odoo-modsdev-app odoo -d fusion-dev -u --stop-after-init` +- Local URL: http://localhost:8082 +- **Running module tests requires ephemeral ports.** The dev container's main Odoo process holds 8069 and 8072; a `docker exec ... odoo --test-enable` will die with `Address already in use` unless you also pass `--http-port=0 --gevent-port=0`. This is because Odoo 19 forces `http_spawn()` when `--test-enable` is set, even when `--no-http` is passed. Canonical test invocation: + ```bash + docker exec odoo-modsdev-app odoo -d fusion-dev --test-enable --test-tags / \ + -u --stop-after-init --http-port=0 --gevent-port=0 2>&1 | tail -60 + ``` +- **`fusion_centralize_billing` tests run on odoo-trial (VM 316).** Local dev is Community and cannot install this module. Use `bash scripts/fcb_test_on_trial.sh` from the repo root. The script uses `--http-port 8070` to avoid the port 8069 conflict with the live odoo-trial-app container. Pass = `FCB_EXIT=0`. Takes ~1-2 min. +- **Python deps not bundled with `odoo:19` image:** `user_agents` (used by `fusion_login_audit`), and likely others. Install ephemerally with `docker exec -u 0 odoo-modsdev-app pip install --break-system-packages`. The install is LOST when the container is recreated (e.g. `docker compose up -d` after a compose edit). When this happens, the symptom is `ModuleNotFoundError` deep in the auth or report code. Re-run the pip install. A persistent fix would be a custom Dockerfile or a startup hook on the compose service — not done yet. - Test before deploying. Edit existing files — don't create unnecessary new ones. ## PDF Preview — Prefer fusion_pdf_preview Over Downloads/New-Tab diff --git a/fusion_centralize_billing/models/service.py b/fusion_centralize_billing/models/service.py index 953eac7a..5dc83a1e 100644 --- a/fusion_centralize_billing/models/service.py +++ b/fusion_centralize_billing/models/service.py @@ -54,3 +54,11 @@ class FusionBillingService(models.Model): raw = secrets.token_urlsafe(32) self.api_key_hash = hashlib.sha256(raw.encode()).hexdigest() return raw + + @api.model + def _match_api_key(self, raw_key): + """Return the active service whose stored hash matches raw_key, else empty recordset.""" + if not raw_key: + return self.browse() + key_hash = hashlib.sha256(raw_key.encode()).hexdigest() + return self.search([('api_key_hash', '=', key_hash), ('active', '=', True)], limit=1) diff --git a/fusion_centralize_billing/tests/__init__.py b/fusion_centralize_billing/tests/__init__.py new file mode 100644 index 00000000..7399d626 --- /dev/null +++ b/fusion_centralize_billing/tests/__init__.py @@ -0,0 +1 @@ +from . import test_identity diff --git a/fusion_centralize_billing/tests/test_identity.py b/fusion_centralize_billing/tests/test_identity.py new file mode 100644 index 00000000..7b24ec12 --- /dev/null +++ b/fusion_centralize_billing/tests/test_identity.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from odoo.tests.common import TransactionCase, tagged + + +@tagged('post_install', '-at_install') +class TestServiceApiKey(TransactionCase): + + def setUp(self): + super().setUp() + self.Service = self.env['fusion.billing.service'].sudo() + self.service = self.Service.create({'name': 'NexaCloud', 'code': 'nexacloud'}) + + def test_generate_and_match_api_key(self): + raw = self.service.action_generate_api_key() + self.assertTrue(raw and len(raw) >= 20) + self.assertTrue(self.service.api_key_hash) + self.assertNotEqual(raw, self.service.api_key_hash) # only the hash is stored + matched = self.Service._match_api_key(raw) + self.assertEqual(matched, self.service) + + def test_match_api_key_rejects_unknown_and_inactive(self): + raw = self.service.action_generate_api_key() + self.assertFalse(self.Service._match_api_key('nope-not-a-key')) + self.service.active = False + self.assertFalse(self.Service._match_api_key(raw)) diff --git a/scripts/fcb_test_on_trial.sh b/scripts/fcb_test_on_trial.sh index c8006357..a8805503 100644 --- a/scripts/fcb_test_on_trial.sh +++ b/scripts/fcb_test_on_trial.sh @@ -23,5 +23,5 @@ ssh -o ConnectTimeout=40 "${PVE}" "qm guest exec ${VMID} --timeout 90 -- bash -l 2>&1 | sed -n 's/.*"out-data" : "\(.*\)",/\1/p' | sed 's/\\n/\n/g' echo ">> upgrade + test on Enterprise 19 (db=trial, --no-http)" -ssh -o ConnectTimeout=40 "${PVE}" "qm guest exec ${VMID} --timeout 600 -- bash -lc 'docker exec odoo-trial-app odoo -d trial -u ${MODULE} --no-http --workers 0 --test-enable --test-tags /${MODULE} --stop-after-init >/tmp/fcb_test.log 2>&1; echo FCB_EXIT=\$?; grep -iE \"FAIL|ERROR|tested in|Ran |assert\" /tmp/fcb_test.log | grep -viE \"fusion_plating|fusion_tasks|not installable|not loaded\" | tail -30'" \ +ssh -o ConnectTimeout=40 "${PVE}" "qm guest exec ${VMID} --timeout 600 -- bash -lc 'docker exec odoo-trial-app odoo -d trial -u ${MODULE} --no-http --http-port 8070 --workers 0 --test-enable --test-tags /${MODULE} --stop-after-init >/tmp/fcb_test.log 2>&1; echo FCB_EXIT=\$?; grep -iE \"FAIL|ERROR|tested in|Ran |assert\" /tmp/fcb_test.log | grep -viE \"fusion_plating|fusion_tasks|not installable|not loaded\" | tail -30'" \ 2>&1 | sed -n 's/.*"out-data" : "\(.*\)",/\1/p' | sed 's/\\n/\n/g'