# Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime from freezegun import freeze_time import psycopg2 import json from odoo.tools import mute_logger from odoo.exceptions import UserError from odoo.tests.common import new_test_user from odoo.addons.spreadsheet_edition.tests.spreadsheet_test_case import SpreadsheetTestCase class SpreadsheetMixinTest(SpreadsheetTestCase): def test_cannot_dispatch_with_invalid_parent_revision(self): spreadsheet = self.env["spreadsheet.test"].create({}) revision_payload = self.new_revision_data(spreadsheet) is_accepted = spreadsheet.dispatch_spreadsheet_message(revision_payload) self.assertTrue(is_accepted, "the first revision should be accepted") revision_payload = self.new_revision_data(spreadsheet, serverRevisionId="something") is_accepted = spreadsheet.dispatch_spreadsheet_message(revision_payload) self.assertFalse(is_accepted) def test_cannot_delete_revision_in_a_chain(self): spreadsheet = self.env["spreadsheet.test"].create({}) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) revision = spreadsheet.spreadsheet_revision_ids[1] with self.assertRaises(psycopg2.errors.UniqueViolation), mute_logger("odoo.sql_db"): revision.unlink() def test_save_spreadsheet_snapshot(self): spreadsheet = self.env["spreadsheet.test"].create({}) current_revision_uuid = spreadsheet.current_revision_uuid snapshot = {"sheets": [], "revisionId": spreadsheet.current_revision_uuid} spreadsheet.save_spreadsheet_snapshot(snapshot) self.assertNotEqual(spreadsheet.current_revision_uuid, current_revision_uuid) self.assertEqual( json.loads(spreadsheet._get_spreadsheet_serialized_snapshot()), dict(snapshot, revisionId=spreadsheet.current_revision_uuid), ) def test_save_spreadsheet_snapshot_with_invalid_revision_id(self): spreadsheet = self.env["spreadsheet.test"].create({}) snapshot = {"sheets": [], "revisionId": spreadsheet.current_revision_uuid} # one revision is saved in the meantime (concurrently) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) with self.assertRaises(UserError): spreadsheet.save_spreadsheet_snapshot(snapshot) def test_rename_revision(self): spreadsheet = self.env["spreadsheet.test"].create({}) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) revision = spreadsheet.spreadsheet_revision_ids[0] self.assertEqual(revision.name, False) spreadsheet.rename_revision(revision.id, "new revision name") self.assertEqual(revision.name, "new revision name") def test_get_spreadsheet_history(self): with freeze_time("2020-02-02"): spreadsheet = self.env["spreadsheet.test"].create({}) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) self.snapshot( spreadsheet, spreadsheet.current_revision_uuid, "snapshot-revision-id", {"sheets": [], "revisionId": "snapshot-revision-id"}, ) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) user = spreadsheet.create_uid data = spreadsheet.get_spreadsheet_history() revisions = data["revisions"] self.assertEqual(len(revisions), 3) for revision in revisions: self.assertEqual(revision["timestamp"], datetime(2020, 2, 2, 0, 0, 0)) self.assertEqual(revision["user"], (user.id, user.name)) # from snapshot data = spreadsheet.get_spreadsheet_history(True) revisions = data["revisions"] self.assertEqual(len(revisions), 1) self.assertEqual(revisions[0]["timestamp"], datetime(2020, 2, 2, 0, 0, 0)) self.assertEqual(revisions[0]["user"], (user.id, user.name)) def test_currency_passed_to_spreadsheet_history(self): spreadsheet = self.env["spreadsheet.test"].create({}) company_eur = self.env["res.company"].create({"currency_id": self.env.ref("base.EUR").id, "name": "EUR"}) company_gbp = self.env["res.company"].create({"currency_id": self.env.ref("base.GBP").id, "name": "GBP"}) data = spreadsheet.with_company(company_eur).get_spreadsheet_history() self.assertEqual(data["default_currency"]["code"], "EUR") self.assertEqual(data["default_currency"]["symbol"], "€") data = spreadsheet.with_company(company_gbp).get_spreadsheet_history() self.assertEqual(data["default_currency"]["code"], "GBP") self.assertEqual(data["default_currency"]["symbol"], "£") def test_get_spreadsheet_base_user_access_right_history(self): user = new_test_user(self.env, login="test", groups="base.group_user,test_spreadsheet.group_spreadsheet_test") spreadsheet = self.env["spreadsheet.test"].create({}) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) spreadsheet.invalidate_recordset() data = spreadsheet.with_user(user).get_spreadsheet_history() self.assertEqual(len(data["revisions"]), 1) def test_empty_spreadsheet_server_revision_id(self): spreadsheet = self.env["spreadsheet.test"].create({}) self.assertEqual(spreadsheet.current_revision_uuid, "START_REVISION") self.assertEqual( spreadsheet.with_context(bin_size=True).current_revision_uuid, "START_REVISION" ) def test_no_data_server_revision_id(self): spreadsheet = self.env["spreadsheet.test"].create({}) spreadsheet.spreadsheet_snapshot = False spreadsheet.spreadsheet_data = False self.assertEqual(spreadsheet.current_revision_uuid, "START_REVISION") def test_last_revision_is_server_revision_id(self): spreadsheet = self.env["spreadsheet.test"].create({}) revision_data = self.new_revision_data(spreadsheet) next_revision_id = revision_data["nextRevisionId"] spreadsheet.dispatch_spreadsheet_message(revision_data) self.assertEqual(spreadsheet.current_revision_uuid, next_revision_id) # dispatch new revision on top revision_data = self.new_revision_data(spreadsheet) next_revision_id = revision_data["nextRevisionId"] spreadsheet.dispatch_spreadsheet_message(revision_data) self.assertEqual(spreadsheet.current_revision_uuid, next_revision_id) def test_snapshotted_server_revision_id(self): spreadsheet = self.env["spreadsheet.test"].create({}) snapshot_revision_id = "snapshot-id" self.snapshot( spreadsheet, spreadsheet.current_revision_uuid, snapshot_revision_id, {"revisionId": snapshot_revision_id}, ) self.assertEqual(spreadsheet.current_revision_uuid, snapshot_revision_id) self.assertEqual( spreadsheet.with_context(bin_size=True).current_revision_uuid, snapshot_revision_id ) # dispatch revision after snapshot revision_data = self.new_revision_data(spreadsheet) next_revision_id = revision_data["nextRevisionId"] spreadsheet.dispatch_spreadsheet_message(revision_data) self.assertEqual(spreadsheet.current_revision_uuid, next_revision_id) def test_get_spreadsheets(self): spreadsheet = self.env["spreadsheet.test"].create({}) self.assertEqual( self.env["spreadsheet.test"].get_spreadsheets(), { "records": [{ "id": spreadsheet.id, "display_name": spreadsheet.display_name, "display_thumbnail": False }], "total": 1, } ) def test_get_spreadsheets_limit(self): self.env["spreadsheet.test"].create({}) self.env["spreadsheet.test"].create({}) result = self.env["spreadsheet.test"].get_spreadsheets([], limit=1) self.assertEqual(len(result["records"]), 1) self.assertEqual(result["total"], 2) def test_get_spreadsheets_domain(self): first = self.env["spreadsheet.test"].create({}) self.env["spreadsheet.test"].create({}) result = self.env["spreadsheet.test"].get_spreadsheets([("id", "=", first.id)], limit=1) self.assertEqual(len(result["records"]), 1) self.assertEqual(result["records"][0]["id"], first.id) self.assertEqual(result["total"], 1) def test_get_selector_spreadsheet_models(self): result = self.env["spreadsheet.mixin"].get_selector_spreadsheet_models() self.assertTrue(any(r["model"] == "spreadsheet.test" for r in result)) def test_action_open_new_spreadsheet(self): action = self.env["spreadsheet.test"].action_open_new_spreadsheet() spreadsheet = self.env["spreadsheet.test"].browse(action["params"]["spreadsheet_id"]) self.assertTrue(spreadsheet.exists()) def test_keep_original_spreadsheet_author_with_multiple_users(self): test_user_1 = new_test_user(self.env, login="test_user_1", groups="base.group_user,test_spreadsheet.group_spreadsheet_test") test_user_2 = new_test_user(self.env, login="test_user_2", groups="base.group_user,test_spreadsheet.group_spreadsheet_test") test_user_3 = new_test_user(self.env, login="test_user_3", groups="base.group_user,test_spreadsheet.group_spreadsheet_test") spreadsheet = self.env["spreadsheet.test"].with_user(test_user_1).create({}) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) spreadsheet.with_user(test_user_2).dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) spreadsheet.with_user(test_user_3).dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) copy = spreadsheet.with_user(self.env.ref("base.user_admin")).copy() revisions = copy.spreadsheet_revision_ids self.assertEqual(revisions[0].author_id, test_user_1) self.assertEqual(revisions[1].author_id, test_user_2) self.assertEqual(revisions[2].author_id, test_user_3) spreadsheet.with_user(self.env.ref("base.user_admin")).dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) self.assertEqual(spreadsheet.sudo().spreadsheet_revision_ids[-1].author_id, self.env.ref("base.user_admin")) def test_keep_original_spreadsheet_revision_timestamps(self): with freeze_time("2025-01-01 12:00:00"): spreadsheet = self.env["spreadsheet.test"].create({}) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) spreadsheet.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) with freeze_time("2025-01-01 23:00:00"): copy = spreadsheet.copy() revisions = copy.spreadsheet_revision_ids self.assertEqual(revisions[0].revision_date, datetime(2025, 1, 1, 12, 0, 0)) self.assertEqual(revisions[1].revision_date, datetime(2025, 1, 1, 12, 0, 0)) self.assertEqual(revisions[2].revision_date, datetime(2025, 1, 1, 12, 0, 0)) copy.dispatch_spreadsheet_message(self.new_revision_data(spreadsheet)) self.assertEqual( copy.spreadsheet_revision_ids[-1].revision_date, datetime(2025, 1, 1, 23, 0, 0), ) def test_get_search_arch(self): # default search view arch = '' self.env["ir.ui.view"].create({ "name": "Search View", "model": "res.partner", "type": "search", "arch": arch, }) action = self.env["ir.actions.act_window"].create({ "name": "Action2", "res_model": "res.partner", }) xml_id = "spreadsheet_test_partner_search_view" module = "test_spreadsheet_edition" self.env["ir.model.data"].create({ "name": xml_id, "model": "ir.actions.act_window", "module": module, "res_id": action.id, }) xml_id = f"{module}.{xml_id}" result = self.env["spreadsheet.mixin"].get_search_view_archs([xml_id]) self.assertEqual(result, {"res.partner": [arch]}) # with a custom search view custom_arch = '' custom_search_view = self.env["ir.ui.view"].create({ "name": "Custom Search View", "model": "res.partner", "type": "search", "arch": custom_arch, }) action.search_view_id = custom_search_view result = self.env["spreadsheet.mixin"].get_search_view_archs([xml_id]) self.assertEqual(result, {"res.partner": [custom_arch]}) def test_cell_thread_naming(self): spreadsheet1 = self.env["spreadsheet.test"].create({}) spreadsheet2 = self.env["spreadsheet.test"].create({}) threads = self.env["spreadsheet.cell.thread"].create([ {"dummy_id": spreadsheet1.id}, {"dummy_id": spreadsheet2.id}, ]) thread1, thread2 = threads self.assertEqual( threads.mapped("display_name"), # compute in batch [thread1.display_name, thread2.display_name] )