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
19 changes: 16 additions & 3 deletions server/src/core/python_arch_builder_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,27 @@ static arch_class_hooks: Lazy<Vec<PythonArchClassHook>> = 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<RefCell<EntryPoint>>, request_class: Rc<RefCell<Symbol>>| {
// ----------- 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![
Expand Down
33 changes: 33 additions & 0 deletions server/src/core/python_arch_eval_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,39 @@ static arch_eval_file_hooks: Lazy<Vec<PythonArchEvalFileHook>> = 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<RefCell<EntryPoint>>, file_symbol: Rc<RefCell<Symbol>>, symbol: Rc<RefCell<Symbol>>| {
// --------- 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<RefCell<EntryPoint>>, file_symbol: Rc<RefCell<Symbol>>, symbol: Rc<RefCell<Symbol>>| {
// --------- 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")]))],
Expand Down
3 changes: 2 additions & 1 deletion server/tests/data/addons/module_1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import models
from . import data
from . import data
from . import controllers
2 changes: 2 additions & 0 deletions server/tests/data/addons/module_1/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import test_http
36 changes: 36 additions & 0 deletions server/tests/data/addons/module_1/controllers/test_http.py
Original file line number Diff line number Diff line change
@@ -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"
122 changes: 122 additions & 0 deletions server/tests/test_hooks.rs
Original file line number Diff line number Diff line change
@@ -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<RefCell<Symbol>>,
file_info: &Rc<RefCell<FileInfo>>
) {
// 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<RefCell<Symbol>>,
file_info: &Rc<RefCell<FileInfo>>
) {
// 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"
);
}
}