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