From bc83889fc71c8a688cf10d085ac3f5464358a8d3 Mon Sep 17 00:00:00 2001 From: Rodolpho Lima Date: Tue, 25 Nov 2025 19:26:10 +0100 Subject: [PATCH 1/2] [IMP] add evaluation for request.env - add evaluation for odoo.http.request (Request class) - add env variable to Request class and its evaluation (Environment class or None) --- server/src/core/python_arch_builder_hooks.rs | 19 +++++++++-- server/src/core/python_arch_eval_hooks.rs | 33 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/server/src/core/python_arch_builder_hooks.rs b/server/src/core/python_arch_builder_hooks.rs index 77a95304..505f7d78 100644 --- a/server/src/core/python_arch_builder_hooks.rs +++ b/server/src/core/python_arch_builder_hooks.rs @@ -38,14 +38,27 @@ static arch_class_hooks: Lazy> = Lazy::new(|| {vec![ let mut range = symbol.borrow().range().clone(); let slots = symbol.borrow().get_symbol(&(vec![], vec![Sy!("__slots__")]), u32::MAX); if slots.len() == 1 { - if slots.len() == 1 { - range = slots[0].borrow().range().clone(); - } + range = slots[0].borrow().range().clone(); } symbol.borrow_mut().add_new_variable(session, Sy!("env"), &range); } } }, + PythonArchClassHook { + odoo_entry: true, + trees: vec![ + (Sy!("15.3"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("http")], vec![Sy!("Request")])) + ], + func: |session: &mut SessionInfo, _entry_point: &Rc>, request_class: Rc>| { + // ----------- Request.env ------------ + let has_env = !request_class.borrow().get_content_symbol(&Sy!("env"), u32::MAX).symbols.is_empty(); + if has_env { + return; + } + let range = request_class.borrow().range().clone(); + request_class.borrow_mut().add_new_variable(session, Sy!("env"), &range); + } + }, PythonArchClassHook { odoo_entry: true, trees: vec![ diff --git a/server/src/core/python_arch_eval_hooks.rs b/server/src/core/python_arch_eval_hooks.rs index ef5b7b8d..f11cdaaf 100644 --- a/server/src/core/python_arch_eval_hooks.rs +++ b/server/src/core/python_arch_eval_hooks.rs @@ -64,6 +64,39 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v env.set_doc_string(Some(S!(""))); } }}, + PythonArchEvalFileHook {odoo_entry: true, + // todo? before 15.3: request: WebRequest + trees: vec![(Sy!("15.3"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("http")], vec![Sy!("request")]))], + if_exist_only: true, + func: |_odoo: &mut SessionInfo, _entry: &Rc>, file_symbol: Rc>, symbol: Rc>| { + // --------- request: Request --------- + let request_class = file_symbol.borrow().get_symbol(&(vec![], vec![Sy!("Request")]), u32::MAX); + let Some(request_class) = request_class.last() else { + return; + }; + let mut request = symbol.borrow_mut(); + request.set_evaluations(vec![Evaluation::eval_from_symbol(&Rc::downgrade(request_class), Some(true))]); + }}, + PythonArchEvalFileHook {odoo_entry: true, + trees: vec![(Sy!("15.3"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("http")], vec![Sy!("Request"), Sy!("env")]))], + if_exist_only: true, + func: |odoo: &mut SessionInfo, _entry: &Rc>, file_symbol: Rc>, symbol: Rc>| { + // --------- Request.env: Environment | None --------- + let env_file = odoo.sync_odoo.get_symbol(odoo.sync_odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("api")], vec![]), u32::MAX); + let Some(env_file) = env_file.last() else { + return; + }; + let env_class = env_file.borrow().get_symbol(&(vec![], vec![Sy!("Environment")]), u32::MAX); + let Some(env_class) = env_class.last() else { + return; + }; + let mut request_env = symbol.borrow_mut(); + request_env.set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(env_class), Some(true)), + Evaluation::new_none() + ]); + file_symbol.borrow_mut().add_dependency(&mut env_file.borrow_mut(), BuildSteps::ARCH_EVAL, BuildSteps::ARCH); + }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("BaseModel"), Sy!("ids")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("BaseModel"), Sy!("ids")]))], From c9ba24d3c775ceec881712ff6d78d392d856e1cc Mon Sep 17 00:00:00 2001 From: Rodolpho Lima Date: Wed, 3 Dec 2025 14:09:39 +0100 Subject: [PATCH 2/2] [IMP] add tests for request.env --- server/tests/data/addons/module_1/__init__.py | 3 +- .../addons/module_1/controllers/__init__.py | 2 + .../addons/module_1/controllers/test_http.py | 36 ++++++ server/tests/test_hooks.rs | 122 ++++++++++++++++++ 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 server/tests/data/addons/module_1/controllers/__init__.py create mode 100644 server/tests/data/addons/module_1/controllers/test_http.py create mode 100644 server/tests/test_hooks.rs diff --git a/server/tests/data/addons/module_1/__init__.py b/server/tests/data/addons/module_1/__init__.py index f49c07d5..d3f3705d 100644 --- a/server/tests/data/addons/module_1/__init__.py +++ b/server/tests/data/addons/module_1/__init__.py @@ -1,2 +1,3 @@ from . import models -from . import data \ No newline at end of file +from . import data +from . import controllers \ No newline at end of file diff --git a/server/tests/data/addons/module_1/controllers/__init__.py b/server/tests/data/addons/module_1/controllers/__init__.py new file mode 100644 index 00000000..d109d73e --- /dev/null +++ b/server/tests/data/addons/module_1/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import test_http diff --git a/server/tests/data/addons/module_1/controllers/test_http.py b/server/tests/data/addons/module_1/controllers/test_http.py new file mode 100644 index 00000000..53c78f08 --- /dev/null +++ b/server/tests/data/addons/module_1/controllers/test_http.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from odoo import http +from odoo.http import request + + +class TestHttpController(http.Controller): + + @http.route('/test/request', type='http', auth='public') + def test_request_type(self): + """Test that request has correct type.""" + # Hovering over 'request' should show Request class + req = request + + # Hovering over 'request.env' should show Environment | None + env = request.env + + # Accessing models via request.env + if request.env: + partner = request.env['res.partner'] + users = request.env['res.users'] + + return "OK" + + @http.route('/test/request/search', type='http', auth='user') + def test_request_env_usage(self): + """Test using request.env in typical scenarios.""" + # Direct model access + partners = request.env['res.partner'].search([]) + + # With sudo + admin_partners = request.env['res.partner'].sudo().search([]) + + # Accessing user + current_user = request.env.user + + return "OK" diff --git a/server/tests/test_hooks.rs b/server/tests/test_hooks.rs new file mode 100644 index 00000000..21c38e38 --- /dev/null +++ b/server/tests/test_hooks.rs @@ -0,0 +1,122 @@ +use std::env; +use std::path::PathBuf; +use std::cell::RefCell; +use std::rc::Rc; + +use odoo_ls_server::core::odoo::SyncOdoo; +use odoo_ls_server::core::symbols::symbol::Symbol; +use odoo_ls_server::core::file_mgr::FileInfo; +use odoo_ls_server::utils::PathSanitizer; +use odoo_ls_server::threads::SessionInfo; + +mod setup; +mod test_utils; + +/// Test that odoo.http.request and request.env hooks work correctly. +/// Tests hover and definition. +#[test] +fn test_request_hooks() { + let (mut odoo, config) = setup::setup::setup_server(true); + let mut session = setup::setup::create_init_session(&mut odoo, config); + + // Check Odoo version (request hooks require 15.3+) + if session.sync_odoo.version_major < 15 || (session.sync_odoo.version_major == 15 && session.sync_odoo.version_minor < 3) { + panic!("Skipping test_request_hooks: Odoo version < 15.3"); + } + + let test_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/data/addons/module_1/controllers/test_http.py") + .sanitize(); + + let Some(file_symbol) = SyncOdoo::get_symbol_of_opened_file(&mut session, &PathBuf::from(&test_file)) else { + panic!("Failed to get file symbol for {}", test_file); + }; + + let file_mgr = session.sync_odoo.get_file_mgr(); + let file_info = file_mgr.borrow().get_file_info(&test_file).unwrap(); + + test_request_type_hover(&mut session, &file_symbol, &file_info); + test_request_env_definition(&mut session, &file_symbol, &file_info); +} + +/// Test that hovering over 'request' shows Request class +/// and that hovering over 'request.env' shows Environment type +fn test_request_type_hover( + session: &mut SessionInfo, + file_symbol: &Rc>, + file_info: &Rc> +) { + // Test 1: Hover over 'request' variable (line 11: req = request) + // Should show Request class + let hover_text = test_utils::get_hover_markdown(session, file_symbol, file_info, 11, 14) + .expect("Should get hover text for request"); + + assert!( + hover_text.contains("Request"), + "Hover over 'request' should show Request class. Got: {}", + hover_text + ); + + // Test 2: Hover over 'request.env' (line 14: env = request.env) + // Should show Environment | None + let hover_text = test_utils::get_hover_markdown(session, file_symbol, file_info, 14, 21) + .expect("Should get hover text for request.env"); + + assert!( + hover_text.contains("Environment"), + "Hover over 'request.env' should show Environment type. Got: {}", + hover_text + ); + assert!( + hover_text.contains("None"), + "Hover over 'request.env' should show None. Got: {}", + hover_text + ); +} + +/// Test that request.env provides correct definitions +fn test_request_env_definition( + session: &mut SessionInfo, + file_symbol: &Rc>, + file_info: &Rc> +) { + // Test 1: Go-to-definition on 'request' import (line 3: from odoo.http import request) + // Should navigate to request variable in odoo.http + let definitions = test_utils::get_definition_locs(session, file_symbol, file_info, 2, 22); + + assert!( + !definitions.is_empty(), + "Should find definition for 'request' import" + ); + + // Verify it points to odoo/http.py or similar + let target_uri = &definitions[0].target_uri.to_string(); + assert!( + target_uri.contains("odoo/http.py"), + "Definition should point to http module. Got: {:?}", + target_uri + ); + + // Test 2: Go-to-definition on 'request.env' (line 15: env = request.env) + // Should navigate to Environment class or env attribute + let definitions = test_utils::get_definition_locs(session, file_symbol, file_info, 14, 21); + + assert!( + !definitions.is_empty(), + "Should find definition for 'request.env'" + ); + + // Test 3: Hover on request.env.user (line 34: current_user = request.env.user) + // Should show proper type for user + let hover_text = test_utils::get_hover_markdown(session, file_symbol, file_info, 33, 35); + + if let Some(text) = hover_text { + // env.user should have a type (typically res.users or User) + assert!( + !text.is_empty(), + "request.env.user should have hover information" + ); + } +} + +