Skip to content

Commit f345575

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-4373' into PR_2025_12_11_muntianu
2 parents 9f18dec + 4a3595b commit f345575

File tree

2 files changed

+228
-15
lines changed

2 files changed

+228
-15
lines changed

app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,27 +104,79 @@ private function loadCustomersData(array $customerIdentifiers): void
104104
};
105105
$offset = 0;
106106
for ($chunk = $getChuck($offset); !empty($chunk); $offset += $pageSize, $chunk = $getChuck($offset)) {
107-
$customerWebsites = array_reduce($chunk, function ($customerWebsiteByEmail, $customer) {
108-
$customerWebsiteByEmail[$customer['email']][] = $customer['website_id'];
109-
return $customerWebsiteByEmail;
110-
}, []);
107+
$customerWebsites = $this->buildCustomerWebsitesMap($chunk);
111108
$chunkSelect = clone $select;
112109
$chunkSelect->where($customerTableId . '.email IN (?)', array_keys($customerWebsites));
113110
$customers = $collection->getConnection()->fetchAll($chunkSelect);
114111
foreach ($customers as $customer) {
115-
$this->addCustomerByArray($customer);
116-
if ($this->configShare->isGlobalScope() &&
117-
!in_array((int) $customer['website_id'], $customerWebsites[$customer['email']], true)
118-
) {
119-
foreach ($customerWebsites[$customer['email']] as $websiteId) {
120-
$customer['website_id'] = $websiteId;
121-
$this->addCustomerByArray($customer);
122-
}
123-
}
112+
$this->processCustomerData($customer, $customerWebsites);
124113
}
125114
}
126115
}
127116

117+
/**
118+
* Build customer websites map from chunk of customer identifiers.
119+
*
120+
* @param array $chunk
121+
* @return array
122+
*/
123+
private function buildCustomerWebsitesMap(array $chunk): array
124+
{
125+
return array_reduce($chunk, function ($customerWebsiteByEmail, $customer) {
126+
$email = isset($customer['email']) ? mb_strtolower(trim($customer['email'])) : '';
127+
$customerWebsiteByEmail[$email][] = $customer['website_id'];
128+
return $customerWebsiteByEmail;
129+
}, []);
130+
}
131+
132+
/**
133+
* Process customer data from database and handle global scope logic.
134+
*
135+
* @param array $customer
136+
* @param array $customerWebsites
137+
* @return void
138+
*/
139+
private function processCustomerData(array $customer, array $customerWebsites): void
140+
{
141+
$email = isset($customer['email']) ? mb_strtolower(trim($customer['email'])) : '';
142+
$customer['email'] = $email;
143+
$this->addCustomerByArray($customer);
144+
145+
if ($this->shouldProcessGlobalScope($email, $customer, $customerWebsites)) {
146+
$this->processGlobalScopeCustomer($customer, $customerWebsites[$email]);
147+
}
148+
}
149+
150+
/**
151+
* Check if customer should be processed for global scope.
152+
*
153+
* @param string $email
154+
* @param array $customer
155+
* @param array $customerWebsites
156+
* @return bool
157+
*/
158+
private function shouldProcessGlobalScope(string $email, array $customer, array $customerWebsites): bool
159+
{
160+
return $this->configShare->isGlobalScope()
161+
&& isset($customerWebsites[$email])
162+
&& !in_array((int) $customer['website_id'], $customerWebsites[$email], true);
163+
}
164+
165+
/**
166+
* Process customer for all websites in global scope.
167+
*
168+
* @param array $customer
169+
* @param array $websiteIds
170+
* @return void
171+
*/
172+
private function processGlobalScopeCustomer(array $customer, array $websiteIds): void
173+
{
174+
foreach ($websiteIds as $websiteId) {
175+
$customer['website_id'] = $websiteId;
176+
$this->addCustomerByArray($customer);
177+
}
178+
}
179+
128180
/**
129181
* Add a customer by an array
130182
*

app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/Customer/StorageTest.php

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,91 @@ public static function customerDataProvider(): array
108108
];
109109
}
110110

111+
/**
112+
* Test prepareCustomers with uppercase email in Global scope.
113+
*
114+
* @throws Exception
115+
*/
116+
public function testPrepareCustomersWithUppercaseEmailGlobalScope(): void
117+
{
118+
$customersToFind = [
119+
['email' => '[email protected]', 'website_id' => 1],
120+
];
121+
$customersData = [
122+
['email' => '[email protected]', 'website_id' => 1, 'entity_id' => 100, 'store_id' => 1],
123+
];
124+
125+
$this->mockCustomerCollectionForGlobalScope($customersData);
126+
$this->storage->prepareCustomers($customersToFind);
127+
128+
$this->assertEquals(100, $this->storage->getCustomerId('[email protected]', 1));
129+
$this->assertEquals(100, $this->storage->getCustomerId('[email protected]', 1));
130+
}
131+
132+
/**
133+
* Test prepareCustomers with uppercase email in Website scope.
134+
*
135+
* @throws Exception
136+
*/
137+
public function testPrepareCustomersWithUppercaseEmailWebsiteScope(): void
138+
{
139+
$customersToFind = [
140+
['email' => '[email protected]', 'website_id' => 1],
141+
];
142+
$customersData = [
143+
['email' => '[email protected]', 'website_id' => 1, 'entity_id' => 100, 'store_id' => 1],
144+
];
145+
146+
$this->mockCustomerCollectionForWebsiteScope($customersData);
147+
$this->storage->prepareCustomers($customersToFind);
148+
149+
$this->assertEquals(100, $this->storage->getCustomerId('[email protected]', 1));
150+
}
151+
152+
/**
153+
* Test prepareCustomers with mixed case emails in Global scope.
154+
*
155+
* @throws Exception
156+
*/
157+
public function testPrepareCustomersWithMixedCaseEmailsGlobalScope(): void
158+
{
159+
$customersToFind = [
160+
['email' => '[email protected]', 'website_id' => 1],
161+
['email' => '[email protected]', 'website_id' => 2],
162+
];
163+
$customersData = [
164+
['email' => '[email protected]', 'website_id' => 1, 'entity_id' => 200, 'store_id' => 1],
165+
['email' => '[email protected]', 'website_id' => 2, 'entity_id' => 201, 'store_id' => 2],
166+
];
167+
168+
$this->mockCustomerCollectionForGlobalScope($customersData);
169+
$this->storage->prepareCustomers($customersToFind);
170+
171+
$this->assertEquals(200, $this->storage->getCustomerId('[email protected]', 1));
172+
$this->assertEquals(201, $this->storage->getCustomerId('[email protected]', 2));
173+
}
174+
175+
/**
176+
* Test email normalization consistency when building customer websites map.
177+
*
178+
* @throws Exception
179+
*/
180+
public function testEmailNormalizationConsistency(): void
181+
{
182+
$customersToFind = [
183+
['email' => '[email protected]', 'website_id' => 1],
184+
];
185+
$customersData = [
186+
['email' => '[email protected]', 'website_id' => 1, 'entity_id' => 300, 'store_id' => 1],
187+
];
188+
189+
$this->mockCustomerCollectionForGlobalScope($customersData);
190+
$this->storage->prepareCustomers($customersToFind);
191+
192+
$this->assertEquals(300, $this->storage->getCustomerId('[email protected]', 1));
193+
$this->assertEquals(300, $this->storage->getCustomerId('[email protected]', 1));
194+
}
195+
111196
/**
112197
* Mock the customer collection to return specific data.
113198
*
@@ -134,18 +219,94 @@ private function mockCustomerCollection(array $customersData): void
134219
->willReturn(true);
135220
}
136221

222+
/**
223+
* Mock the customer collection for Global scope tests.
224+
*
225+
* @param array $customersData
226+
* @throws Exception
227+
*/
228+
private function mockCustomerCollectionForGlobalScope(array $customersData): void
229+
{
230+
$selectMock = $this->getMockBuilder(Select::class)
231+
->disableOriginalConstructor()
232+
->onlyMethods(['getPart', 'where'])
233+
->getMock();
234+
235+
$selectMock->expects($this->atLeastOnce())
236+
->method('getPart')
237+
->willReturn(['main_table' => 'customer_entity']);
238+
239+
$selectMock->method('where')
240+
->willReturnSelf();
241+
242+
$this->customerCollectionMock->expects($this->atLeastOnce())
243+
->method('removeAttributeToSelect')
244+
->willReturnSelf();
245+
246+
$this->customerCollectionMock->expects($this->atLeastOnce())
247+
->method('getSelect')
248+
->willReturn($selectMock);
249+
250+
$this->customerCollectionMock->expects($this->atLeastOnce())
251+
->method('getConnection')
252+
->willReturn($this->mockConnection($customersData));
253+
254+
$this->configShareMock->expects($this->atLeastOnce())
255+
->method('isGlobalScope')
256+
->willReturn(true);
257+
}
258+
259+
/**
260+
* Mock the customer collection for Website scope tests.
261+
*
262+
* @param array $customersData
263+
* @throws Exception
264+
*/
265+
private function mockCustomerCollectionForWebsiteScope(array $customersData): void
266+
{
267+
$selectMock = $this->getMockBuilder(Select::class)
268+
->disableOriginalConstructor()
269+
->onlyMethods(['getPart', 'where'])
270+
->getMock();
271+
272+
$selectMock->expects($this->atLeastOnce())
273+
->method('getPart')
274+
->willReturn(['main_table' => 'customer_entity']);
275+
276+
$selectMock->method('where')
277+
->willReturnSelf();
278+
279+
$this->customerCollectionMock->expects($this->atLeastOnce())
280+
->method('removeAttributeToSelect')
281+
->willReturnSelf();
282+
283+
$this->customerCollectionMock->expects($this->atLeastOnce())
284+
->method('getSelect')
285+
->willReturn($selectMock);
286+
287+
$this->customerCollectionMock->expects($this->atLeastOnce())
288+
->method('getConnection')
289+
->willReturn($this->mockConnection($customersData));
290+
291+
$this->configShareMock->expects($this->atLeastOnce())
292+
->method('isGlobalScope')
293+
->willReturn(false);
294+
}
295+
137296
/**
138297
* Mock the database connection to return specific customer data.
139298
*
140299
* @param array $customersData
141300
* @return MockObject
301+
* @throws Exception
142302
*/
143303
private function mockConnection(array $customersData): MockObject
144304
{
145-
$this->connectionMock->expects($this->once())
305+
$connectionMock = $this->createMock(AdapterInterface::class);
306+
$connectionMock->expects($this->once())
146307
->method('fetchAll')
147308
->willReturn($customersData);
148309

149-
return $this->connectionMock;
310+
return $connectionMock;
150311
}
151312
}

0 commit comments

Comments
 (0)