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