PluginProbe
Extendify / 3.1.0
Extendify v3.1.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / tests / Integration / QuickEdit / Controllers / WCProductControllerTest.php

WCProductControllerTest.php in Extendify 3.1.0, at tests/Integration/QuickEdit/Controllers/WCProductControllerTest.php

350 lines 11.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Extendify\Tests\Integration\QuickEdit\Controllers;
4
5 use Extendify\QuickEdit\Controllers\WCProductController;
6 use WP_UnitTestCase;
7
8 class WCProductControllerTest extends WP_UnitTestCase
9 {
10 public function setUp(): void
11 {
12 parent::setUp();
13 $this->loginAsAdmin();
14 }
15
16 public function test_permission_callback_requires_admin_capability()
17 {
18 wp_set_current_user(0);
19 $this->assertFalse(WCProductController::permissionCallback());
20
21 $sub = self::factory()->user->create(['role' => 'subscriber']);
22 wp_set_current_user($sub);
23 $this->assertFalse(WCProductController::permissionCallback());
24
25 $editor = self::factory()->user->create(['role' => 'editor']);
26 wp_set_current_user($editor);
27 $this->assertFalse(WCProductController::permissionCallback());
28
29 $admin = self::factory()->user->create(['role' => 'administrator']);
30 wp_set_current_user($admin);
31 $this->assertTrue(WCProductController::permissionCallback());
32 }
33
34 public function test_get_without_product_id_returns_400()
35 {
36 $res = WCProductController::handle($this->getRequest([]));
37 $this->assertSame(400, $res->get_status());
38 $this->assertSame('product_id required', $res->get_data()['error']);
39 }
40
41 public function test_get_with_non_product_post_returns_400()
42 {
43 $postId = self::factory()->post->create();
44
45 $res = WCProductController::handle($this->getRequest(['product_id' => $postId]));
46
47 $this->assertSame(400, $res->get_status());
48 $this->assertSame('not a product', $res->get_data()['error']);
49 }
50
51 public function test_get_returns_name_short_description_prices_and_image_meta()
52 {
53 $pid = $this->createProduct([
54 'post_title' => 'Hat',
55 'post_excerpt' => 'A summary',
56 'post_content' => 'The long description.',
57 ]);
58 $product = wc_get_product($pid);
59 $product->set_regular_price('20');
60 $product->set_sale_price('15');
61 $product->save();
62
63 $res = WCProductController::handle($this->getRequest(['product_id' => $pid]));
64
65 $this->assertSame(200, $res->get_status());
66 $data = $res->get_data();
67 $this->assertSame('Hat', $data['name']);
68 $this->assertSame('A summary', $data['short_description']);
69 $this->assertSame('The long description.', $data['description']);
70 $this->assertSame('20', $data['regular_price']);
71 $this->assertSame('15', $data['sale_price']);
72 $this->assertSame(0, $data['image_id']);
73 $this->assertSame('', $data['image_url']);
74 }
75
76 public function test_post_updates_name()
77 {
78 $pid = $this->createProduct(['post_title' => 'Old']);
79
80 $res = WCProductController::handle($this->postRequest([
81 'product_id' => $pid,
82 'field' => 'name',
83 'value' => 'New title',
84 ]));
85
86 $this->assertSame(200, $res->get_status());
87 $this->assertTrue($res->get_data()['ok']);
88 $this->assertSame('New title', get_post($pid)->post_title);
89 }
90
91 public function test_post_name_sanitizes_html()
92 {
93 $pid = $this->createProduct();
94
95 WCProductController::handle($this->postRequest([
96 'product_id' => $pid,
97 'field' => 'name',
98 'value' => '<script>x</script>Clean',
99 ]));
100
101 $this->assertSame('Clean', get_post($pid)->post_title);
102 }
103
104 public function test_post_updates_short_description()
105 {
106 $pid = $this->createProduct(['post_excerpt' => 'old']);
107
108 $res = WCProductController::handle($this->postRequest([
109 'product_id' => $pid,
110 'field' => 'short_description',
111 'value' => '<p>A <strong>rich</strong> excerpt</p>',
112 ]));
113
114 $this->assertSame(200, $res->get_status());
115 $this->assertStringContainsString('<strong>rich</strong>', get_post($pid)->post_excerpt);
116 }
117
118 public function test_post_updates_description()
119 {
120 $pid = $this->createProduct(['post_content' => 'old long description']);
121
122 $res = WCProductController::handle($this->postRequest([
123 'product_id' => $pid,
124 'field' => 'description',
125 'value' => '<p>A <strong>rich</strong> long description</p>',
126 ]));
127
128 $this->assertSame(200, $res->get_status());
129 $this->assertStringContainsString('<strong>rich</strong>', get_post($pid)->post_content);
130 }
131
132 public function test_post_description_sanitizes_disallowed_html()
133 {
134 $pid = $this->createProduct();
135
136 WCProductController::handle($this->postRequest([
137 'product_id' => $pid,
138 'field' => 'description',
139 'value' => '<p>safe</p><script>x</script>',
140 ]));
141
142 $this->assertStringNotContainsString('<script', get_post($pid)->post_content);
143 $this->assertStringContainsString('<p>safe</p>', get_post($pid)->post_content);
144 }
145
146 public function test_post_price_requires_object_value()
147 {
148 $pid = $this->createProduct();
149
150 $res = WCProductController::handle($this->postRequest([
151 'product_id' => $pid,
152 'field' => 'price',
153 'value' => '10',
154 ]));
155
156 $this->assertSame(400, $res->get_status());
157 $this->assertSame('price expects {regular, sale}', $res->get_data()['error']);
158 }
159
160 public function test_post_price_sets_regular_and_sale_and_displays_lower()
161 {
162 $pid = $this->createProduct();
163
164 $res = WCProductController::handle($this->postRequest([
165 'product_id' => $pid,
166 'field' => 'price',
167 'value' => ['regular' => '50', 'sale' => '30'],
168 ]));
169
170 $this->assertSame(200, $res->get_status());
171 $product = wc_get_product($pid);
172 $this->assertSame('50', $product->get_regular_price());
173 $this->assertSame('30', $product->get_sale_price());
174 // _price tracks the displayed price; 30 < 50 ⇒ sale wins.
175 $this->assertSame('30', $product->get_price());
176 }
177
178 public function test_post_price_with_empty_sale_uses_regular_for_displayed()
179 {
180 $pid = $this->createProduct();
181
182 WCProductController::handle($this->postRequest([
183 'product_id' => $pid,
184 'field' => 'price',
185 'value' => ['regular' => '40', 'sale' => ''],
186 ]));
187
188 $product = wc_get_product($pid);
189 $this->assertSame('40', $product->get_regular_price());
190 $this->assertSame('', $product->get_sale_price());
191 $this->assertSame('40', $product->get_price());
192 }
193
194 public function test_post_image_requires_positive_attachment_id()
195 {
196 $pid = $this->createProduct();
197
198 $res = WCProductController::handle($this->postRequest([
199 'product_id' => $pid,
200 'field' => 'image',
201 'value' => 0,
202 ]));
203
204 $this->assertSame(400, $res->get_status());
205 $this->assertSame('attachment_id required', $res->get_data()['error']);
206 }
207
208 public function test_post_image_calls_set_post_thumbnail_and_returns_ok()
209 {
210 $pid = $this->createProduct();
211 // wp_get_attachment_image (set_post_thumbnail's pre-check) needs a real
212 // attached file. create_upload_object writes a stub PNG to uploads.
213 $attId = self::factory()->attachment->create_upload_object(
214 $this->writeStubPng()
215 );
216
217 $res = WCProductController::handle($this->postRequest([
218 'product_id' => $pid,
219 'field' => 'image',
220 'value' => $attId,
221 ]));
222
223 $this->assertSame(200, $res->get_status());
224 $this->assertSame($attId, (int) get_post_thumbnail_id($pid));
225 }
226
227 public function test_post_image_rejects_nonexistent_attachment_id()
228 {
229 $pid = $this->createProduct();
230
231 $res = WCProductController::handle($this->postRequest([
232 'product_id' => $pid,
233 'field' => 'image',
234 'value' => 999999,
235 ]));
236
237 $this->assertSame(400, $res->get_status());
238 $this->assertSame('not an image attachment', $res->get_data()['error']);
239 $this->assertSame(0, (int) get_post_thumbnail_id($pid));
240 }
241
242 public function test_post_image_rejects_non_image_attachment()
243 {
244 $pid = $this->createProduct();
245 $attId = self::factory()->attachment->create_upload_object($this->writeStubText());
246
247 $res = WCProductController::handle($this->postRequest([
248 'product_id' => $pid,
249 'field' => 'image',
250 'value' => $attId,
251 ]));
252
253 $this->assertSame(400, $res->get_status());
254 $this->assertSame('not an image attachment', $res->get_data()['error']);
255 $this->assertSame(0, (int) get_post_thumbnail_id($pid));
256 }
257
258 public function test_post_unknown_field_returns_400()
259 {
260 $pid = $this->createProduct();
261
262 $res = WCProductController::handle($this->postRequest([
263 'product_id' => $pid,
264 'field' => 'bogus',
265 'value' => 'x',
266 ]));
267
268 $this->assertSame(400, $res->get_status());
269 $this->assertStringStartsWith('unknown field', $res->get_data()['error']);
270 }
271
272 public function test_post_forbidden_when_user_cannot_edit_post()
273 {
274 $pid = $this->createProduct();
275 $sub = self::factory()->user->create(['role' => 'subscriber']);
276 wp_set_current_user($sub);
277
278 $res = WCProductController::handle($this->postRequest([
279 'product_id' => $pid,
280 'field' => 'name',
281 'value' => 'x',
282 ]));
283
284 $this->assertSame(403, $res->get_status());
285 $this->assertSame('cannot edit this product', $res->get_data()['error']);
286 }
287
288 public function test_init_hooks_registerRoutes_into_rest_api_init()
289 {
290 remove_all_filters('rest_api_init');
291 WCProductController::init();
292
293 $this->assertNotFalse(
294 has_action('rest_api_init', [WCProductController::class, 'registerRoutes'])
295 );
296 }
297
298 private function createProduct(array $args = []): int
299 {
300 return self::factory()->post->create(array_merge([
301 'post_type' => 'product',
302 'post_title' => 'Test Product',
303 'post_status' => 'publish',
304 ], $args));
305 }
306
307 private function getRequest(array $params): \WP_REST_Request
308 {
309 $req = new \WP_REST_Request('GET', '/extendify/v1/quick-edit/product');
310 foreach ($params as $k => $v) {
311 $req->set_param($k, $v);
312 }
313 return $req;
314 }
315
316 private function postRequest(array $params): \WP_REST_Request
317 {
318 $req = new \WP_REST_Request('POST', '/extendify/v1/quick-edit/product');
319 foreach ($params as $k => $v) {
320 $req->set_param($k, $v);
321 }
322 return $req;
323 }
324
325 private function loginAsAdmin(): void
326 {
327 $admin = self::factory()->user->create(['role' => 'administrator']);
328 wp_set_current_user($admin);
329 }
330
331 private function writeStubPng(): string
332 {
333 // 1×1 transparent PNG, base64-decoded.
334 $bytes = base64_decode(
335 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
336 );
337 $path = wp_tempnam('qe-test-stub') . '.png';
338 file_put_contents($path, $bytes);
339 return $path;
340 }
341
342 private function writeStubText(): string
343 {
344 // A real, non-image upload: the attachment exists but isn't an image.
345 $path = wp_tempnam('qe-test-stub') . '.txt';
346 file_put_contents($path, 'not an image');
347 return $path;
348 }
349 }
350