mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 21:32:00 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import case
|
||||
from . import test_onboarding
|
||||
from . import test_onboarding_concurrency
|
||||
|
|
|
|||
40
odoo-bringout-oca-ocb-onboarding/onboarding/tests/case.py
Normal file
40
odoo-bringout-oca-ocb-onboarding/onboarding/tests/case.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TransactionCaseOnboarding(TransactionCase):
|
||||
def assert_step_is_done(self, step, also_with_company=None):
|
||||
self.assertIn(
|
||||
step.current_progress_step_id.step_state, {'done', 'just_done'},
|
||||
f'Expected done-like current state for step {step.id} for {step.env.company.name}')
|
||||
self.assertEqual(step.current_progress_step_id.step_state, step.current_step_state)
|
||||
if also_with_company:
|
||||
self.assert_step_is_done(step.with_company(also_with_company))
|
||||
|
||||
def assert_step_is_not_done(self, step, also_with_company=None):
|
||||
self.assertIn(
|
||||
step.current_progress_step_id.step_state, {'not_done', False},
|
||||
f'Expected "not_done" current state for step {step.id} for {step.env.company.name}')
|
||||
self.assertEqual(step.current_step_state, 'not_done')
|
||||
if also_with_company:
|
||||
self.assert_step_is_not_done(step.with_company(also_with_company))
|
||||
|
||||
def assert_onboarding_is_done(self, onboarding, also_with_company=None):
|
||||
self.assertIn(
|
||||
onboarding.current_progress_id.onboarding_state, {'done', 'just_done'},
|
||||
f'Expected done-like current state for onboarding "{onboarding.name}" '
|
||||
f'for "{onboarding.env.company.name}"')
|
||||
self.assertEqual(onboarding.current_progress_id.onboarding_state,
|
||||
onboarding.current_onboarding_state)
|
||||
if also_with_company:
|
||||
self.assert_onboarding_is_done(onboarding.with_company(also_with_company))
|
||||
|
||||
def assert_onboarding_is_not_done(self, onboarding, also_with_company=None):
|
||||
self.assertIn(
|
||||
onboarding.current_progress_id.onboarding_state, {'not_done', False},
|
||||
f'Expected `"not_done"` or `False` current state for onboarding {onboarding.name} '
|
||||
f'for {onboarding.env.company.name}')
|
||||
self.assertEqual(onboarding.current_onboarding_state, 'not_done')
|
||||
if also_with_company:
|
||||
self.assert_onboarding_is_not_done(onboarding.with_company(also_with_company))
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.addons.onboarding.tests.case import TransactionCaseOnboarding
|
||||
|
||||
|
||||
class TestOnboardingCommon(TransactionCase):
|
||||
class TestOnboardingCommon(TransactionCaseOnboarding):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
|
@ -18,12 +18,12 @@ class TestOnboardingCommon(TransactionCase):
|
|||
})
|
||||
cls.user_admin.company_ids |= cls.company_2
|
||||
|
||||
cls.onboarding_1 = cls.env['onboarding.onboarding'].create([
|
||||
cls.onboarding_1, cls.onboarding_2 = cls.env['onboarding.onboarding'].create([
|
||||
{
|
||||
'name': 'Test Onboarding 1',
|
||||
'name': f'Test Onboarding {onboarding_id}',
|
||||
'route_name': f'onboarding{onboarding_id}',
|
||||
'is_per_company': False,
|
||||
'route_name': 'onboarding1'
|
||||
}
|
||||
} for onboarding_id in range(2)
|
||||
])
|
||||
|
||||
# create a fake action for step opening
|
||||
|
|
@ -36,49 +36,22 @@ class TestOnboardingCommon(TransactionCase):
|
|||
cls.onboarding_1_step_1, cls.onboarding_1_step_2 = cls.env['onboarding.onboarding.step'].create([
|
||||
{
|
||||
'title': f'Test Onboarding 1 - Step {step_n}',
|
||||
'onboarding_id': cls.onboarding_1.id,
|
||||
'onboarding_ids': [cls.onboarding_1.id],
|
||||
'is_per_company': False,
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
|
||||
}
|
||||
for step_n in range(1, 3)
|
||||
])
|
||||
|
||||
# Add one of these in onboarding_2, and an "original" one
|
||||
cls.onboarding_2.step_ids = [cls.onboarding_1_step_1.id]
|
||||
cls.onboarding_2_step_2 = cls.env['onboarding.onboarding.step'].create([{
|
||||
'title': 'Test Onboarding 2 - Step 2',
|
||||
'onboarding_ids': [cls.onboarding_2.id],
|
||||
'is_per_company': False,
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
|
||||
}])
|
||||
# Create progress records as would happen through the controller
|
||||
cls.onboarding_1.with_company(cls.company_1)._search_or_create_progress()
|
||||
|
||||
def assert_step_is_done(self, step, also_with_company=None):
|
||||
self.assertIn(
|
||||
step.current_progress_step_id.step_state, {'done', 'just_done'},
|
||||
f'Expected done-like current state for step {step.id} for {step.env.company}')
|
||||
self.assertEqual(step.current_progress_step_id.step_state, step.current_step_state)
|
||||
if also_with_company:
|
||||
self.assert_step_is_done(step.with_company(also_with_company))
|
||||
|
||||
def assert_step_is_not_done(self, step, also_with_company=None):
|
||||
self.assertIn(
|
||||
step.current_progress_step_id.step_state, {'not_done', False},
|
||||
f'Expected "not_done" current state for step {step.id} for {step.env.company}')
|
||||
self.assertEqual(step.current_step_state, 'not_done')
|
||||
if also_with_company:
|
||||
self.assert_step_is_not_done(step.with_company(also_with_company))
|
||||
|
||||
def assert_onboarding_is_done(self, onboarding, also_with_company=None):
|
||||
self.assertIn(
|
||||
onboarding.current_progress_id.onboarding_state, {'done', 'just_done'},
|
||||
f'Expected done-like current state for onboarding {onboarding.name} '
|
||||
f'for {onboarding.env.company}')
|
||||
self.assertEqual(onboarding.current_progress_id.onboarding_state,
|
||||
onboarding.current_onboarding_state)
|
||||
if also_with_company:
|
||||
self.assert_onboarding_is_done(onboarding.with_company(also_with_company))
|
||||
|
||||
def assert_onboarding_is_not_done(self, onboarding, also_with_company=None):
|
||||
self.assertEqual(
|
||||
onboarding.current_progress_id.onboarding_state, 'not_done',
|
||||
f'Expected "not_done" current state for onboarding {onboarding.name} '
|
||||
f'for {onboarding.env.company}')
|
||||
self.assertEqual(onboarding.current_onboarding_state, 'not_done')
|
||||
if also_with_company:
|
||||
self.assert_onboarding_is_not_done(onboarding.with_company(also_with_company))
|
||||
(cls.onboarding_1 + cls.onboarding_2).with_company(cls.company_1)._search_or_create_progress()
|
||||
|
||||
def activate_company(self, company):
|
||||
self.onboarding_1_step_1 = self.onboarding_1_step_1.with_company(company)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import unittest
|
||||
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.onboarding.tests.common import TestOnboardingCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestOnboarding(TestOnboardingCommon):
|
||||
|
|
@ -12,7 +20,8 @@ class TestOnboarding(TestOnboardingCommon):
|
|||
self.onboarding_1.current_progress_id._get_and_update_onboarding_state(),
|
||||
{self.onboarding_1_step_1.id: 'not_done', self.onboarding_1_step_2.id: 'not_done'})
|
||||
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.assertEqual(self.onboarding_1_step_1.action_set_just_done(), self.onboarding_1_step_1,
|
||||
"The onboarding step just validated should have been returned.")
|
||||
# Test completed step state consolidation from `just_done` to `done`
|
||||
self.assertDictEqual(
|
||||
self.onboarding_1.current_progress_id._get_and_update_onboarding_state(),
|
||||
|
|
@ -21,6 +30,8 @@ class TestOnboarding(TestOnboardingCommon):
|
|||
self.onboarding_1.current_progress_id._get_and_update_onboarding_state(),
|
||||
{self.onboarding_1_step_1.id: 'done', self.onboarding_1_step_2.id: 'not_done'})
|
||||
self.assert_step_is_done(self.onboarding_1_step_1, self.company_2)
|
||||
self.assertFalse(self.onboarding_1_step_1.action_set_just_done(),
|
||||
"The onboarding step already validated should not have been returned.")
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1, self.company_2)
|
||||
|
||||
self.onboarding_1_step_2.action_set_just_done()
|
||||
|
|
@ -42,7 +53,8 @@ class TestOnboarding(TestOnboardingCommon):
|
|||
# Adding new step resets onboarding state to 'not_done' even if closed
|
||||
onboarding_1_step_3 = self.env['onboarding.onboarding.step'].create({
|
||||
'title': 'Test Onboarding 1 - Step 3',
|
||||
'onboarding_id': self.onboarding_1.id,
|
||||
'onboarding_ids': [self.onboarding_1.id],
|
||||
'is_per_company': False,
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
|
||||
})
|
||||
self.assert_step_is_not_done(onboarding_1_step_3)
|
||||
|
|
@ -62,7 +74,8 @@ class TestOnboarding(TestOnboardingCommon):
|
|||
# Adding new step resets onboarding state to 'not_done'
|
||||
self.env['onboarding.onboarding.step'].create({
|
||||
'title': 'Test Onboarding 1 - Step 4',
|
||||
'onboarding_id': self.onboarding_1.id,
|
||||
'onboarding_ids': [self.onboarding_1.id],
|
||||
'is_per_company': False,
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
|
||||
})
|
||||
|
||||
|
|
@ -79,14 +92,16 @@ class TestOnboarding(TestOnboardingCommon):
|
|||
# Completing onboarding as company_1
|
||||
self.assertEqual(self.env.company, self.company_1)
|
||||
|
||||
# Updating onboarding to per-company
|
||||
self.onboarding_1.is_per_company = True
|
||||
# Updating onboarding (and steps) to per-company
|
||||
self.onboarding_1_step_1.is_per_company = True
|
||||
|
||||
# Required after progress reset (simulate role of controller)
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.assert_step_is_done(self.onboarding_1_step_1)
|
||||
|
||||
self.assertFalse(self.onboarding_1_step_2.is_per_company)
|
||||
self.onboarding_1_step_2.action_set_just_done()
|
||||
self.assert_onboarding_is_done(self.onboarding_1)
|
||||
|
||||
|
|
@ -95,19 +110,14 @@ class TestOnboarding(TestOnboardingCommon):
|
|||
# First access from company_2
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
|
||||
# Blank state for company 2
|
||||
# Blank state for company 2 for step 1
|
||||
self.assert_step_is_not_done(self.onboarding_1_step_1)
|
||||
# But step 2 is done
|
||||
self.assert_step_is_done(self.onboarding_1_step_2)
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1)
|
||||
|
||||
# But no change for company 1
|
||||
self.assert_step_is_done(self.onboarding_1_step_1.with_company(self.company_1))
|
||||
self.assert_onboarding_is_done(self.onboarding_1.with_company(self.company_1))
|
||||
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.assert_step_is_done(self.onboarding_1_step_1)
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1)
|
||||
self.onboarding_1_step_2.with_company(self.company_2).action_set_just_done()
|
||||
self.assert_step_is_done(self.onboarding_1_step_2)
|
||||
self.assert_onboarding_is_done(self.onboarding_1)
|
||||
|
||||
# is_onboarding_closed status is also company-independent
|
||||
|
|
@ -116,51 +126,113 @@ class TestOnboarding(TestOnboardingCommon):
|
|||
self.assertFalse(self.onboarding_1.with_company(self.company_1).current_progress_id.is_onboarding_closed)
|
||||
|
||||
def test_onboarding_to_company_change(self):
|
||||
"""Checks that changing onboarding to per-company resets completions states.
|
||||
"""
|
||||
""" Checks that changing an onboarding step to per-company resets
|
||||
completion states."""
|
||||
# Completing onboarding as company_1
|
||||
self.assertEqual(self.env.company, self.company_1)
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.onboarding_1_step_2.action_set_just_done()
|
||||
self.assert_onboarding_is_done(self.onboarding_1)
|
||||
|
||||
# Updating onboarding to per-company
|
||||
self.onboarding_1.is_per_company = True
|
||||
# Updating onboarding step 1 to per-company
|
||||
self.onboarding_1_step_1.is_per_company = True
|
||||
self.assertTrue(self.onboarding_1.is_per_company)
|
||||
# Required after progress reset (simulate role of controller)
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1)
|
||||
|
||||
def test_no_crash_on_multiple_progress_records(self):
|
||||
existing_progress = self.env['onboarding.progress'].search([
|
||||
('onboarding_id', '=', self.onboarding_1.id), ('company_id', '=', False)
|
||||
])
|
||||
self.assertEqual(len(existing_progress), 1)
|
||||
def test_onboarding_shared_steps(self):
|
||||
self.onboarding_2_step_2.action_set_just_done()
|
||||
self.assert_step_is_done(self.onboarding_2_step_2)
|
||||
# Completing common step is also required to be "done"
|
||||
self.assert_onboarding_is_not_done(self.onboarding_2)
|
||||
|
||||
extra_progress = self.env['onboarding.progress'].create({
|
||||
'onboarding_id': self.onboarding_1.id,
|
||||
'company_id': False
|
||||
})
|
||||
|
||||
self.env['onboarding.progress.step'].create([{
|
||||
'step_id': self.onboarding_1_step_1.id,
|
||||
'progress_id': progress.id
|
||||
} for progress in (existing_progress, extra_progress)
|
||||
])
|
||||
|
||||
nb_progress = self.env['onboarding.progress'].search([
|
||||
('onboarding_id', '=', self.onboarding_1.id), ('company_id', '=', False)], count=True)
|
||||
nb_progress_steps = self.env['onboarding.progress.step'].search([
|
||||
('step_id', '=', self.onboarding_1_step_1.id)], count=True)
|
||||
|
||||
# Even though multiple onboarding progress (& steps) records exist
|
||||
self.assertEqual(nb_progress, 2)
|
||||
self.assertEqual(nb_progress_steps, 2)
|
||||
|
||||
# no error is raised, and so we can interact
|
||||
_ = self.onboarding_1_step_1.current_progress_step_id
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1)
|
||||
self.assert_onboarding_is_done(self.onboarding_2)
|
||||
|
||||
# Same with onboarding progress
|
||||
_ = self.onboarding_1.current_progress_id
|
||||
self.onboarding_1.action_close()
|
||||
@mute_logger('odoo.sql_db')
|
||||
def test_progress_no_company_uniqueness(self):
|
||||
"""Check that there cannot be two progress records created for
|
||||
the same onboarding when it is configured to be completed only
|
||||
once for the whole db and not per-company (is_per_company=False).
|
||||
NB: Postgresql UNIQUE constraint failures raise IntegrityErrors.
|
||||
"""
|
||||
self.assertFalse(self.onboarding_1.current_progress_id.company_id)
|
||||
with self.assertRaises(IntegrityError):
|
||||
self.env['onboarding.progress'].create({
|
||||
'onboarding_id': self.onboarding_1.id,
|
||||
'company_id': False
|
||||
})
|
||||
|
||||
@mute_logger('odoo.sql_db')
|
||||
def test_progress_per_company_uniqueness(self):
|
||||
"""Check that there cannot be two progress records created for
|
||||
the same company and the same onboarding when the onboarding is
|
||||
configured to be completed per-company.
|
||||
See also ``test_progress_no_company_uniqueness``
|
||||
"""
|
||||
# Updating onboarding to per-company
|
||||
self.onboarding_1_step_1.is_per_company = True
|
||||
# Create an onboarding_progress (simulate role of controller)
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
self.env['onboarding.progress'].create({
|
||||
'onboarding_id': self.onboarding_1.id,
|
||||
'company_id': self.env.company.id
|
||||
})
|
||||
|
||||
def test_onboarding_step_without_onboarding(self):
|
||||
self.step_initially_w_o_onboarding = self.env['onboarding.onboarding.step'].create({
|
||||
'title': 'Step Initially Without Onboarding',
|
||||
})
|
||||
self.assertEqual(self.step_initially_w_o_onboarding.current_step_state, 'not_done')
|
||||
self.step_initially_w_o_onboarding.action_set_just_done()
|
||||
|
||||
self.assert_step_is_done(self.step_initially_w_o_onboarding)
|
||||
|
||||
self.onboarding_3 = self.env['onboarding.onboarding'].create({
|
||||
'name': 'Test Onboarding 3',
|
||||
'route_name': 'onboarding3',
|
||||
})
|
||||
self.onboarding_3._search_or_create_progress()
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
self.step_initially_w_o_onboarding.onboarding_ids = [Command.link(self.onboarding_3.id)]
|
||||
|
||||
self.step_initially_w_o_onboarding.write({
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step'
|
||||
})
|
||||
self.step_initially_w_o_onboarding.onboarding_ids = [Command.link(self.onboarding_3.id)]
|
||||
|
||||
with self.subTest('Progress records are recreated for companies with completed steps'):
|
||||
# Onboarding is done as only step was already done by company 1
|
||||
self.assert_onboarding_is_done(self.onboarding_3)
|
||||
|
||||
# Not by company 2
|
||||
self.onboarding_3.with_company(self.company_2)._search_or_create_progress()
|
||||
self.assert_onboarding_is_not_done(self.onboarding_3.with_company(self.company_2))
|
||||
|
||||
# But it can
|
||||
self.step_initially_w_o_onboarding.with_company(self.company_2).action_set_just_done()
|
||||
self.assert_onboarding_is_done(self.onboarding_3.with_company(self.company_2))
|
||||
|
||||
@unittest.skip("Company deletion can fail because of other foreign key constraints.")
|
||||
def test_remove_company_with_progress(self):
|
||||
user = mail_new_test_user(
|
||||
self.env,
|
||||
login='erp_manager',
|
||||
groups="base.group_erp_manager",
|
||||
)
|
||||
self.onboarding_1_step_1.is_per_company = True
|
||||
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
self.onboarding_1.with_company(self.company_2)._search_or_create_progress()
|
||||
self.assertEqual(len(self.onboarding_1.progress_ids), 2)
|
||||
|
||||
# group_erp_manager has no access to onboardng, compute_current_progress is the focus of this test
|
||||
self.company_2.with_user(user).unlink()
|
||||
self.onboarding_1._compute_current_progress()
|
||||
self.assertEqual(len(self.onboarding_1.progress_ids), 1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import psycopg2.errors
|
||||
|
||||
from odoo import api
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.tests.common import get_db_name, tagged, BaseCase
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('-standard', '-at_install', 'post_install')
|
||||
class TestOnboardingConcurrency(BaseCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.registry = Registry(get_db_name())
|
||||
cls.addClassCleanup(cls.cleanUpClass)
|
||||
|
||||
with cls.registry.cursor() as cr:
|
||||
env = api.Environment(cr, api.SUPERUSER_ID, {})
|
||||
cls.onboarding_id = env['onboarding.onboarding'].create([
|
||||
{
|
||||
'name': 'Test Onboarding Concurrent',
|
||||
'is_per_company': False,
|
||||
'route_name': 'onboarding_concurrent'
|
||||
}
|
||||
]).id
|
||||
|
||||
@classmethod
|
||||
def cleanUpClass(cls):
|
||||
with cls.registry.cursor() as cr:
|
||||
env = api.Environment(cr, api.SUPERUSER_ID, {})
|
||||
env['onboarding.onboarding'].browse(cls.onboarding_id).unlink()
|
||||
env['onboarding.progress'].search([
|
||||
('onboarding_id', '=', cls.onboarding_id)
|
||||
]).unlink()
|
||||
|
||||
@mute_logger('odoo.sql_db')
|
||||
def test_concurrent_create_progress(self):
|
||||
barrier = threading.Barrier(2)
|
||||
|
||||
def run():
|
||||
with self.registry.cursor() as cr:
|
||||
env = api.Environment(cr, api.SUPERUSER_ID, {})
|
||||
onboarding = env['onboarding.onboarding'].search([
|
||||
('id', '=', self.onboarding_id)
|
||||
])
|
||||
# There is no progress record
|
||||
self.assertFalse(env['onboarding.progress'].search([
|
||||
('onboarding_id', '=', self.onboarding_id)
|
||||
]))
|
||||
barrier.wait(timeout=2)
|
||||
try:
|
||||
onboarding._create_progress()
|
||||
except psycopg2.errors.UniqueViolation:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
future_1 = executor.submit(run)
|
||||
future_2 = executor.submit(run)
|
||||
raised_1 = future_1.result(timeout=3)
|
||||
raised_2 = future_2.result(timeout=3)
|
||||
|
||||
with self.registry.cursor() as cr:
|
||||
env = api.Environment(cr, api.SUPERUSER_ID, {})
|
||||
self.assertEqual(
|
||||
len(env['onboarding.progress'].search([('onboarding_id', '=', self.onboarding_id)])),
|
||||
1,
|
||||
"Exactly one thread should have been able to create a record."
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
raised_1 + raised_2,
|
||||
1,
|
||||
"Exactly one thread should have raised a UniqueViolation error even though "
|
||||
"there was no progress record at the start of its transaction."
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue