[FIX] mail, bus: fix mock sendone/many crash during tests

The mock of sendone/many was relying on the bus_service. Since owl.Component.env
is changing during tests setup, the bus_service was not always defined. In order
to make this more reliable, the longpolling/poll route is now mocked, returning a
promise that can be resolved by the sendone/many methods.

closes odoo/odoo#97298

Related: odoo/enterprise#30059
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2022-08-05 16:35:45 +02:00
parent 19fa3c35a3
commit d998c54feb
5 changed files with 81 additions and 32 deletions
@@ -0,0 +1,62 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
import { makeDeferred } from "@web/../tests/helpers/utils";
patch(MockServer.prototype, 'bus', {
init() {
this._super(...arguments);
this.pendingLongpollingPromise = null;
this.notificationsToBeResolved = [];
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
async _performRPC(route, args) {
if (route === '/longpolling/poll') {
const longpollingPromise = makeDeferred();
if (this.notificationsToBeResolved.length) {
longpollingPromise.resolve(this.notificationsToBeResolved);
this.notificationsToBeResolved = [];
} else {
this.pendingLongpollingPromise = longpollingPromise;
}
return longpollingPromise;
}
return this._super(route, args);
},
/**
* Simulates `_sendone` on `bus.bus`.
*
* @param {string} channel
* @param {string} notificationType
* @param {any} message
*/
_mockBusBus__sendone(channel, notificationType, message) {
this._mockBusBus__sendmany([[channel, notificationType, message]]);
},
/**
* Simulates `_sendmany` on `bus.bus`.
*
* @param {Array} notifications
*/
_mockBusBus__sendmany(notifications) {
const values = [];
for (const notification of notifications) {
const [type, payload] = notification.slice(1, notification.length);
values.push({ message: { payload, type } });
}
if (this.pendingLongpollingPromise) {
this.pendingLongpollingPromise.resolve(values);
this.pendingLongpollingPromise = null;
} else {
this.notificationsToBeResolved.push(...values);
}
},
});
@@ -578,29 +578,6 @@ patch(MockServer.prototype, 'mail', {
// Private Mocked Methods
//--------------------------------------------------------------------------
/**
* Simulates `_sendone` on `bus.bus`.
*
* @param {string} channel
* @param {string} notificationType
* @param {any} message
*/
_mockBusBus__sendone(channel, notificationType, message) {
this._mockBusBus__sendmany([[channel, notificationType, message]]);
},
/**
* Simulates `_sendmany` on `bus.bus`.
*
* @param {Array} notifications
*/
_mockBusBus__sendmany(notifications) {
const values = [];
for (const notification of notifications) {
const [type, payload] = notification.slice(1, notification.length);
values.push({ payload, type });
}
owl.Component.env.services['bus_service'].trigger('notification', values);
},
/**
* Simulates `_attachment_format` on `ir.attachment`.
*
@@ -18,7 +18,8 @@ import { registry } from '@web/core/registry';
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { createWebClient } from "@web/../tests/webclient/helpers";
const WEBCLIENT_LOAD_ROUTES = [
const ROUTES_TO_IGNORE = [
'/longpolling/poll',
'/web/webclient/load_menus',
'/web/dataset/call_kw/res.users/load_views',
'/web/dataset/call_kw/res.users/systray_get_activities'
@@ -90,7 +91,6 @@ function setupMessagingServiceRegistries({
const originalService = busService.start(...arguments);
Object.assign(originalService, {
_beep() {}, // Do nothing
_poll() {}, // Do nothing
_registerWindowUnload() {}, // Do nothing
updateOption() {},
});
@@ -156,5 +156,5 @@ function setupMessagingServiceRegistries({
export {
getWebClientReady,
WEBCLIENT_LOAD_ROUTES,
ROUTES_TO_IGNORE,
};
@@ -1,7 +1,7 @@
/** @odoo-module **/
import { start, startServer } from '@mail/../tests/helpers/test_utils';
import { WEBCLIENT_LOAD_ROUTES } from '@mail/../tests/helpers/webclient_setup';
import { ROUTES_TO_IGNORE } from '@mail/../tests/helpers/webclient_setup';
import testUtils from 'web.test_utils';
import { clickEdit, patchWithCleanup, selectDropdownItem } from '@web/../tests/helpers/utils';
@@ -21,7 +21,7 @@ QUnit.test('list activity widget with no activity', async function (assert) {
mockRPC: function (route, args) {
if (
args.method !== 'get_views' &&
!['/mail/init_messaging', '/mail/load_message_failures', '/longpolling/im_status', ...WEBCLIENT_LOAD_ROUTES].includes(route)
!['/mail/init_messaging', '/mail/load_message_failures', '/longpolling/im_status', ...ROUTES_TO_IGNORE].includes(route)
) {
assert.step(route);
}
@@ -70,7 +70,7 @@ QUnit.test('list activity widget with activities', async function (assert) {
mockRPC: function (route, args) {
if (
args.method !== 'get_views' &&
!['/mail/init_messaging', '/mail/load_message_failures', '/longpolling/im_status', ...WEBCLIENT_LOAD_ROUTES].includes(route)
!['/mail/init_messaging', '/mail/load_message_failures', '/longpolling/im_status', ...ROUTES_TO_IGNORE].includes(route)
) {
assert.step(route);
}
@@ -115,7 +115,7 @@ QUnit.test('list activity widget with exception', async function (assert) {
mockRPC: function (route, args) {
if (
args.method !== 'get_views' &&
!['/mail/init_messaging', '/mail/load_message_failures', '/longpolling/im_status', ...WEBCLIENT_LOAD_ROUTES].includes(route)
!['/mail/init_messaging', '/mail/load_message_failures', '/longpolling/im_status', ...ROUTES_TO_IGNORE].includes(route)
) {
assert.step(route);
}
@@ -172,7 +172,7 @@ QUnit.test('list activity widget: open dropdown', async function (assert) {
mockRPC: function (route, args) {
if (
args.method !== 'get_views' &&
!['/mail/init_messaging', '/mail/load_message_failures', '/longpolling/im_status', ...WEBCLIENT_LOAD_ROUTES].includes(route)
!['/mail/init_messaging', '/mail/load_message_failures', '/longpolling/im_status', ...ROUTES_TO_IGNORE].includes(route)
) {
assert.step(args.method || route);
}
+11 -1
View File
@@ -52,6 +52,7 @@ import Widget from "web.Widget";
import { uiService } from "@web/core/ui/ui_service";
import { ClientActionAdapter, ViewAdapter } from "@web/legacy/action_adapters";
import { commandService } from "@web/core/commands/command_service";
import { ConnectionAbortedError } from "@web/core/network/rpc_service";
import { CustomFavoriteItem } from "@web/search/favorite_menu/custom_favorite_item";
import { standaloneAdapter } from "web.OwlCompatibility";
@@ -215,7 +216,7 @@ export async function addLegacyMockEnvironment(env, legacyParams = {}) {
const legacyMockServer = new LegacyMockServer(legacyParams.models, { widget });
await legacyMockServer.setup();
const originalRPC = env.services.rpc;
env.services.rpc = async (...args) => {
const rpc = async (...args) => {
try {
return await originalRPC(...args);
} catch (e) {
@@ -226,6 +227,15 @@ export async function addLegacyMockEnvironment(env, legacyParams = {}) {
}
}
};
env.services.rpc = function () {
let rejectFn;
const rpcProm = new Promise((resolve, reject) => {
rejectFn = reject;
rpc(...arguments).then(resolve).catch(reject);
});
rpcProm.abort = () => rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));
return rpcProm;
};
}
}