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