@@ -56,6 +56,14 @@ def _create_invoice_with_attachment(self, pdf_content=b'%PDF-fake'):
5656 )
5757 return move , attachment
5858
59+ def _create_preview_wizard (self , move ):
60+ return self .env ['ai.preview.wizard' ].create (
61+ {
62+ 'move_id' : move .id ,
63+ 'preview_data' : json .dumps (self .mock_data ),
64+ }
65+ )
66+
5967 def test_cache_populated_after_extraction (self ):
6068 """Cache fields should be populated after a successful extraction."""
6169 move , attachment = self ._create_invoice_with_attachment ()
@@ -127,8 +135,23 @@ def test_cache_invalidated_on_attachment_change(self):
127135 # Cache should now reference the new attachment
128136 self .assertEqual (move .ai_last_extraction_attachment_id , new_attachment )
129137
130- def test_re_extract_clears_cache (self ):
131- """action_ai_re_extract should clear cache and trigger fresh extraction."""
138+ def test_sync_preview_returns_move_to_pending (self ):
139+ """Synchronous preview must not leave the move stuck in processing."""
140+ move , attachment = self ._create_invoice_with_attachment ()
141+
142+ with patch .object (
143+ type (move ),
144+ '_ai_trigger_extraction' ,
145+ return_value = self .mock_data ,
146+ ):
147+ result = move .action_ai_extract ()
148+
149+ self .assertEqual (result .get ('res_model' ), 'ai.preview.wizard' )
150+ self .assertEqual (move .ai_extraction_status , 'pending' )
151+ self .assertEqual (move .ai_last_extraction_attachment_id , attachment )
152+
153+ def test_re_extract_bypasses_cache (self ):
154+ """action_ai_re_extract should force a fresh extraction for the same attachment."""
132155 move , attachment = self ._create_invoice_with_attachment ()
133156
134157 # Populate cache
@@ -143,6 +166,21 @@ def test_re_extract_clears_cache(self):
143166 move .action_ai_re_extract ()
144167 mock_trigger .assert_called_once ()
145168
169+ def test_re_extract_blocked_keeps_cache (self ):
170+ """A blocked re-extract must not destroy the previously cached result."""
171+ move , attachment = self ._create_invoice_with_attachment ()
172+
173+ move .ai_last_extraction_data = json .dumps (self .mock_data )
174+ move .ai_last_extraction_attachment_id = attachment .id
175+ move .ai_extraction_status = 'processing'
176+
177+ result = move .action_ai_re_extract ()
178+
179+ self .assertEqual (result ['tag' ], 'display_notification' )
180+ self .assertEqual (move .ai_extraction_status , 'processing' )
181+ self .assertTrue (move .ai_last_extraction_data )
182+ self .assertEqual (move .ai_last_extraction_attachment_id , attachment )
183+
146184 def test_discard_keeps_cache (self ):
147185 """Discarding the preview wizard should keep the cache intact."""
148186 move , attachment = self ._create_invoice_with_attachment ()
@@ -155,6 +193,24 @@ def test_discard_keeps_cache(self):
155193 self .assertTrue (move .ai_last_extraction_data )
156194 self .assertEqual (move .ai_last_extraction_attachment_id , attachment )
157195
196+ def test_discard_preserves_pending_status (self ):
197+ """Discard should not mutate the sync-preview pending state."""
198+ move , _attachment = self ._create_invoice_with_attachment ()
199+ move .ai_extraction_status = 'pending'
200+
201+ self ._create_preview_wizard (move ).action_discard ()
202+
203+ self .assertEqual (move .ai_extraction_status , 'pending' )
204+
205+ def test_discard_preserves_done_status (self ):
206+ """Discarding an already-applied preview must not downgrade done."""
207+ move , _attachment = self ._create_invoice_with_attachment ()
208+ move .ai_extraction_status = 'done'
209+
210+ self ._create_preview_wizard (move ).action_discard ()
211+
212+ self .assertEqual (move .ai_extraction_status , 'done' )
213+
158214 def test_cache_not_populated_on_failure (self ):
159215 """Cache should not be populated when extraction fails."""
160216 move , attachment = self ._create_invoice_with_attachment ()
0 commit comments