1# -*- coding: utf-8 -*- 2# Part of Odoo. See LICENSE file for full copyright and licensing details. 3 4from contextlib import contextmanager 5 6import psycopg2 7import psycopg2.errorcodes 8 9import odoo 10from odoo.tests import common 11from odoo.tests.common import BaseCase 12 13ADMIN_USER_ID = common.ADMIN_USER_ID 14 15@contextmanager 16def environment(): 17 """ Return an environment with a new cursor for the current database; the 18 cursor is committed and closed after the context block. 19 """ 20 registry = odoo.registry(common.get_db_name()) 21 with registry.cursor() as cr: 22 yield odoo.api.Environment(cr, ADMIN_USER_ID, {}) 23 24 25def drop_sequence(code): 26 with environment() as env: 27 seq = env['ir.sequence'].search([('code', '=', code)]) 28 seq.unlink() 29 30 31class TestIrSequenceStandard(BaseCase): 32 """ A few tests for a 'Standard' (i.e. PostgreSQL) sequence. """ 33 34 def test_ir_sequence_create(self): 35 """ Try to create a sequence object. """ 36 with environment() as env: 37 seq = env['ir.sequence'].create({ 38 'code': 'test_sequence_type', 39 'name': 'Test sequence', 40 }) 41 self.assertTrue(seq) 42 43 def test_ir_sequence_search(self): 44 """ Try a search. """ 45 with environment() as env: 46 seqs = env['ir.sequence'].search([]) 47 self.assertTrue(seqs) 48 49 def test_ir_sequence_draw(self): 50 """ Try to draw a number. """ 51 with environment() as env: 52 n = env['ir.sequence'].next_by_code('test_sequence_type') 53 self.assertTrue(n) 54 55 def test_ir_sequence_draw_twice(self): 56 """ Try to draw a number from two transactions. """ 57 with environment() as env0: 58 with environment() as env1: 59 n0 = env0['ir.sequence'].next_by_code('test_sequence_type') 60 self.assertTrue(n0) 61 n1 = env1['ir.sequence'].next_by_code('test_sequence_type') 62 self.assertTrue(n1) 63 64 @classmethod 65 def tearDownClass(cls): 66 drop_sequence('test_sequence_type') 67 68 69class TestIrSequenceNoGap(BaseCase): 70 """ Copy of the previous tests for a 'No gap' sequence. """ 71 72 def test_ir_sequence_create_no_gap(self): 73 """ Try to create a sequence object. """ 74 with environment() as env: 75 seq = env['ir.sequence'].create({ 76 'code': 'test_sequence_type_2', 77 'name': 'Test sequence', 78 'implementation': 'no_gap', 79 }) 80 self.assertTrue(seq) 81 82 def test_ir_sequence_draw_no_gap(self): 83 """ Try to draw a number. """ 84 with environment() as env: 85 n = env['ir.sequence'].next_by_code('test_sequence_type_2') 86 self.assertTrue(n) 87 88 def test_ir_sequence_draw_twice_no_gap(self): 89 """ Try to draw a number from two transactions. 90 This is expected to not work. 91 """ 92 with environment() as env0: 93 with environment() as env1: 94 env1.cr._default_log_exceptions = False # Prevent logging a traceback 95 # NOTE: The error has to be an OperationalError 96 # s.t. the automatic request retry (service/model.py) works. 97 with self.assertRaises(psycopg2.OperationalError) as e: 98 n0 = env0['ir.sequence'].next_by_code('test_sequence_type_2') 99 self.assertTrue(n0) 100 n1 = env1['ir.sequence'].next_by_code('test_sequence_type_2') 101 self.assertEqual(e.exception.pgcode, psycopg2.errorcodes.LOCK_NOT_AVAILABLE, msg="postgresql returned an incorrect errcode") 102 103 @classmethod 104 def tearDownClass(cls): 105 drop_sequence('test_sequence_type_2') 106 107 108class TestIrSequenceChangeImplementation(BaseCase): 109 """ Create sequence objects and change their ``implementation`` field. """ 110 111 def test_ir_sequence_1_create(self): 112 """ Try to create a sequence object. """ 113 with environment() as env: 114 seq = env['ir.sequence'].create({ 115 'code': 'test_sequence_type_3', 116 'name': 'Test sequence', 117 }) 118 self.assertTrue(seq) 119 seq = env['ir.sequence'].create({ 120 'code': 'test_sequence_type_4', 121 'name': 'Test sequence', 122 'implementation': 'no_gap', 123 }) 124 self.assertTrue(seq) 125 126 def test_ir_sequence_2_write(self): 127 with environment() as env: 128 domain = [('code', 'in', ['test_sequence_type_3', 'test_sequence_type_4'])] 129 seqs = env['ir.sequence'].search(domain) 130 seqs.write({'implementation': 'standard'}) 131 seqs.write({'implementation': 'no_gap'}) 132 133 def test_ir_sequence_3_unlink(self): 134 with environment() as env: 135 domain = [('code', 'in', ['test_sequence_type_3', 'test_sequence_type_4'])] 136 seqs = env['ir.sequence'].search(domain) 137 seqs.unlink() 138 139 @classmethod 140 def tearDownClass(cls): 141 drop_sequence('test_sequence_type_3') 142 drop_sequence('test_sequence_type_4') 143 144 145class TestIrSequenceGenerate(BaseCase): 146 """ Create sequence objects and generate some values. """ 147 148 def test_ir_sequence_create(self): 149 """ Try to create a sequence object. """ 150 with environment() as env: 151 seq = env['ir.sequence'].create({ 152 'code': 'test_sequence_type_5', 153 'name': 'Test sequence', 154 }) 155 self.assertTrue(seq) 156 157 with environment() as env: 158 for i in range(1, 10): 159 n = env['ir.sequence'].next_by_code('test_sequence_type_5') 160 self.assertEqual(n, str(i)) 161 162 def test_ir_sequence_create_no_gap(self): 163 """ Try to create a sequence object. """ 164 with environment() as env: 165 seq = env['ir.sequence'].create({ 166 'code': 'test_sequence_type_6', 167 'name': 'Test sequence', 168 'implementation': 'no_gap', 169 }) 170 self.assertTrue(seq) 171 172 with environment() as env: 173 for i in range(1, 10): 174 n = env['ir.sequence'].next_by_code('test_sequence_type_6') 175 self.assertEqual(n, str(i)) 176 177 @classmethod 178 def tearDownClass(cls): 179 drop_sequence('test_sequence_type_5') 180 drop_sequence('test_sequence_type_6') 181 182 183class TestIrSequenceInit(common.TransactionCase): 184 185 def test_00(self): 186 """ test whether the read method returns the right number_next value 187 (from postgreSQL sequence and not ir_sequence value) 188 """ 189 # first creation of sequence (normal) 190 seq = self.env['ir.sequence'].create({ 191 'number_next': 1, 192 'company_id': 1, 193 'padding': 4, 194 'number_increment': 1, 195 'implementation': 'standard', 196 'name': 'test-sequence-00', 197 }) 198 # Call next() 4 times, and check the last returned value 199 seq.next_by_id() 200 seq.next_by_id() 201 seq.next_by_id() 202 n = seq.next_by_id() 203 self.assertEqual(n, "0004", 'The actual sequence value must be 4. reading : %s' % n) 204 # reset sequence to 1 by write() 205 seq.write({'number_next': 1}) 206 # Read the value of the current sequence 207 n = seq.next_by_id() 208 self.assertEqual(n, "0001", 'The actual sequence value must be 1. reading : %s' % n) 209