Skip to content

Commit 6181515

Browse files
committed
AC-14567: MView mechanism silently ignores errors on trigger execution
Test coverage for Mview/View/Subscription
1 parent 74d5449 commit 6181515

File tree

1 file changed

+240
-0
lines changed

1 file changed

+240
-0
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All rights reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Mview\View\Test\Integration;
9+
10+
use Magento\Framework\App\ResourceConnection;
11+
use Magento\Framework\DB\Ddl\Trigger;
12+
use Magento\Framework\DB\Ddl\TriggerFactory;
13+
use Magento\Framework\Mview\Config;
14+
use Magento\Framework\Mview\View\CollectionInterface;
15+
use Magento\Framework\Mview\View\Subscription;
16+
use Magento\Framework\Mview\View\SubscriptionStatementPostprocessorInterface;
17+
use Magento\Framework\Mview\ViewInterface;
18+
use Magento\TestFramework\Helper\Bootstrap;
19+
use PHPUnit\Framework\TestCase;
20+
21+
/**
22+
* Integration test for \Magento\Framework\Mview\View\Subscription
23+
*
24+
* @magentoDbIsolation disabled
25+
*/
26+
class SubscriptionTest extends TestCase
27+
{
28+
/**
29+
* @var Subscription
30+
*/
31+
private $subscription;
32+
33+
/**
34+
* @var ResourceConnection
35+
*/
36+
private $resource;
37+
38+
/**
39+
* @var TriggerFactory
40+
*/
41+
private $triggerFactory;
42+
43+
/**
44+
* @var CollectionInterface
45+
*/
46+
private $viewCollection;
47+
48+
/**
49+
* @var ViewInterface
50+
*/
51+
private $view;
52+
53+
/**
54+
* @var Config
55+
*/
56+
private $mviewConfig;
57+
58+
/**
59+
* @var SubscriptionStatementPostprocessorInterface
60+
*/
61+
private $statementPostprocessor;
62+
63+
/**
64+
* @inheritdoc
65+
*/
66+
protected function setUp(): void
67+
{
68+
$objectManager = Bootstrap::getObjectManager();
69+
$this->resource = $objectManager->get(ResourceConnection::class);
70+
$this->triggerFactory = $objectManager->get(TriggerFactory::class);
71+
$this->viewCollection = $objectManager->get(CollectionInterface::class);
72+
$this->mviewConfig = $objectManager->get(Config::class);
73+
$this->statementPostprocessor = $objectManager->get(SubscriptionStatementPostprocessorInterface::class);
74+
75+
// Create a test view
76+
$this->view = $objectManager->create(ViewInterface::class);
77+
$this->view->setId('test_view')
78+
->setData('subscriptions', [
79+
'catalog_product_entity' => [
80+
'name' => 'catalog_product_entity',
81+
'column' => 'entity_id',
82+
'subscription_model' => null,
83+
'processor' => \Magento\Framework\Mview\View\AdditionalColumnsProcessor\DefaultProcessor::class
84+
]
85+
]);
86+
87+
// Create changelog for the view
88+
$changelog = $objectManager->create(\Magento\Framework\Mview\View\Changelog::class);
89+
$changelog->setViewId('test_view');
90+
$changelog->create();
91+
92+
// Set up view state
93+
$state = $objectManager->create(\Magento\Framework\Mview\View\StateInterface::class);
94+
$state->setViewId('test_view')
95+
->setMode(\Magento\Framework\Mview\View\StateInterface::MODE_ENABLED)
96+
->setStatus(\Magento\Framework\Mview\View\StateInterface::STATUS_IDLE)
97+
->save();
98+
99+
$this->view->setState($state);
100+
101+
// Configure the view in Mview configuration
102+
$configData = $objectManager->get(\Magento\Framework\Mview\Config\Data::class);
103+
$configData->merge([
104+
'test_view' => [
105+
'view_id' => 'test_view',
106+
'action_class' => \Magento\Framework\Indexer\Action\Dummy::class,
107+
'group' => 'indexer',
108+
'subscriptions' => [
109+
'catalog_product_entity' => [
110+
'name' => 'catalog_product_entity',
111+
'column' => 'entity_id',
112+
'subscription_model' => null,
113+
'processor' => \Magento\Framework\Mview\View\AdditionalColumnsProcessor\DefaultProcessor::class
114+
]
115+
]
116+
]
117+
]);
118+
119+
$this->subscription = new Subscription(
120+
$this->resource,
121+
$this->triggerFactory,
122+
$this->viewCollection,
123+
$this->view,
124+
'catalog_product_entity',
125+
'entity_id',
126+
['updated_at'],
127+
[],
128+
$this->mviewConfig,
129+
$this->statementPostprocessor
130+
);
131+
}
132+
133+
/**
134+
* @inheritdoc
135+
*/
136+
protected function tearDown(): void
137+
{
138+
// Clean up changelog table
139+
$changelog = $this->view->getChangelog();
140+
if ($changelog) {
141+
$changelog->drop();
142+
}
143+
144+
// Clean up state
145+
$state = $this->view->getState();
146+
if ($state) {
147+
$state->delete();
148+
}
149+
}
150+
151+
/**
152+
* Test creating database triggers
153+
*/
154+
public function testCreateTriggers(): void
155+
{
156+
// Create triggers
157+
$this->subscription->create();
158+
159+
// Verify triggers were created
160+
$connection = $this->resource->getConnection();
161+
$triggers = $this->subscription->getTriggers();
162+
163+
foreach ($triggers as $trigger) {
164+
$triggerName = $trigger->getName();
165+
$result = $connection->fetchOne(
166+
"SELECT TRIGGER_NAME FROM information_schema.TRIGGERS WHERE TRIGGER_NAME = ?",
167+
[$triggerName]
168+
);
169+
$this->assertNotEmpty(
170+
$result,
171+
sprintf('Trigger %s was not created', $triggerName)
172+
);
173+
}
174+
}
175+
176+
/**
177+
* Test removing database triggers
178+
*/
179+
public function testRemoveTriggers(): void
180+
{
181+
// First create triggers
182+
$this->subscription->create();
183+
184+
// Get trigger names before removal
185+
$triggers = $this->subscription->getTriggers();
186+
$triggerNames = array_map(function ($trigger) {
187+
return $trigger->getName();
188+
}, $triggers);
189+
190+
// Remove triggers
191+
$this->subscription->remove();
192+
193+
// Verify triggers were removed
194+
$connection = $this->resource->getConnection();
195+
foreach ($triggerNames as $triggerName) {
196+
$this->assertFalse(
197+
$connection->isTableExists($triggerName),
198+
sprintf('Trigger %s was not removed', $triggerName)
199+
);
200+
}
201+
}
202+
203+
/**
204+
* Test trigger statements for ignored columns
205+
*/
206+
public function testTriggerStatementsWithIgnoredColumns(): void
207+
{
208+
$this->subscription->create();
209+
$triggers = $this->subscription->getTriggers();
210+
211+
// Find the UPDATE trigger
212+
$updateTrigger = null;
213+
foreach ($triggers as $trigger) {
214+
if ($trigger->getEvent() === Trigger::EVENT_UPDATE) {
215+
$updateTrigger = $trigger;
216+
break;
217+
}
218+
}
219+
220+
$this->assertNotNull($updateTrigger, 'UPDATE trigger not found');
221+
222+
// Verify the trigger statements contain the ignored column check
223+
$statements = $updateTrigger->getStatements();
224+
$this->assertNotEmpty($statements, 'Trigger has no statements');
225+
226+
// Check that updated_at is NOT in the list of columns being checked
227+
$hasIgnoredColumnCheck = true;
228+
foreach ($statements as $statement) {
229+
if (strpos($statement, 'NOT(NEW.`updated_at` <=> OLD.`updated_at`)') !== false) {
230+
$hasIgnoredColumnCheck = false;
231+
break;
232+
}
233+
}
234+
235+
$this->assertTrue(
236+
$hasIgnoredColumnCheck,
237+
'Trigger contains check for ignored column'
238+
);
239+
}
240+
}

0 commit comments

Comments
 (0)