Skip to content

Commit 0b0c5be

Browse files
authored
Merge pull request #521 from alleyinteractive/copilot/add-rest-api-route-tests
Add REST API route integration tests
2 parents 2f12e26 + 1ff973c commit 0b0c5be

1 file changed

Lines changed: 188 additions & 0 deletions

File tree

tests/Feature/RestApiTest.php

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
namespace Alley\WP_New_Relic_Transactions\Tests\Feature;
3+
4+
use Alley\WP_New_Relic_Transactions\Tests\TestCase;
5+
use PHPUnit\Framework\Attributes\PreserveGlobalState;
6+
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
7+
use WP_REST_Response;
8+
9+
/**
10+
* Tests for REST API route transaction naming.
11+
*
12+
* Runs in separate processes because each test defines the REST_REQUEST constant,
13+
* which cannot be redefined once set in PHP.
14+
*
15+
* Visit {@see https://mantle.alley.co/testing/test-framework.html} to learn more.
16+
*/
17+
#[RunTestsInSeparateProcesses]
18+
#[PreserveGlobalState(false)]
19+
class RestApiTest extends TestCase {
20+
/**
21+
* Mock New Relic instance for testing transaction naming.
22+
*
23+
* @var \Alley\WP_New_Relic_Transactions\Tests\MockNewRelic
24+
*/
25+
protected $nr;
26+
27+
protected function setUp(): void {
28+
parent::setUp();
29+
30+
$GLOBALS['wp_new_relic_transactions_plugin']->named = false;
31+
$this->nr = $GLOBALS['mock_new_relic'];
32+
$this->nr->reset();
33+
34+
// REST_REQUEST must be defined for the plugin's rest_routes() to fire,
35+
// since Mantle replaces rest_api_loaded() which is normally responsible
36+
// for defining it.
37+
define( 'REST_REQUEST', true );
38+
39+
// Register test REST routes. These callbacks run when Mantle fires
40+
// rest_api_init() during each request's replace_rest_api() call.
41+
add_action(
42+
'rest_api_init',
43+
function () {
44+
register_rest_route(
45+
'test-plugin/v1',
46+
'/items/(?P<id>[\d]+)',
47+
[
48+
'methods' => [ 'GET', 'POST' ],
49+
'callback' => fn() => new WP_REST_Response( [ 'id' => 1 ], 200 ),
50+
'permission_callback' => '__return_true',
51+
],
52+
);
53+
54+
register_rest_route(
55+
'test-plugin/v1',
56+
'/posts/(?P<post_id>[\d]+)/comments/(?P<comment_id>[\d]+)',
57+
[
58+
'methods' => 'GET',
59+
'callback' => fn() => new WP_REST_Response( [], 200 ),
60+
'permission_callback' => '__return_true',
61+
],
62+
);
63+
64+
register_rest_route(
65+
'test-plugin/v1',
66+
'/media/(?P<path>(?:[^/]+/)*(?:[^/]+))',
67+
[
68+
'methods' => 'GET',
69+
'callback' => fn() => new WP_REST_Response( [], 200 ),
70+
'permission_callback' => '__return_true',
71+
],
72+
);
73+
74+
register_rest_route(
75+
'test-plugin/v1',
76+
'/placement/(?P<player>(?:[^/]+))?',
77+
[
78+
'methods' => 'GET',
79+
'callback' => fn() => new WP_REST_Response( [], 200 ),
80+
'permission_callback' => '__return_true',
81+
],
82+
);
83+
84+
register_rest_route(
85+
'test-plugin/v1',
86+
'/(?P<type>(posts|pages))/(?P<slug>[^/]+)',
87+
[
88+
'methods' => 'GET',
89+
'callback' => fn() => new WP_REST_Response( [], 200 ),
90+
'permission_callback' => '__return_true',
91+
],
92+
);
93+
},
94+
);
95+
}
96+
97+
/**
98+
* Test that a simple route with a single ID parameter is named correctly.
99+
*/
100+
public function test_single_param_route_naming(): void {
101+
$this->get( '/wp-json/test-plugin/v1/items/123' );
102+
103+
$this->assertSame( 'GET /test-plugin/v1/items/<id>', $this->nr->name );
104+
$this->assertSame( '/test-plugin/v1/items/(?P<id>[\d]+)', $this->nr->params['wp-api-route'] );
105+
$this->assertSame( 'true', $this->nr->params['wp-api'] );
106+
}
107+
108+
/**
109+
* Test that a route with multiple parameters is named correctly.
110+
*/
111+
public function test_multiple_params_route_naming(): void {
112+
$this->get( '/wp-json/test-plugin/v1/posts/5/comments/42' );
113+
114+
$this->assertSame( 'GET /test-plugin/v1/posts/<post_id>/comments/<comment_id>', $this->nr->name );
115+
$this->assertSame( '/test-plugin/v1/posts/(?P<post_id>[\d]+)/comments/(?P<comment_id>[\d]+)', $this->nr->params['wp-api-route'] );
116+
}
117+
118+
/**
119+
* Test that a route with nested parentheses in the regex is named correctly.
120+
*/
121+
public function test_nested_parentheses_route_naming(): void {
122+
$this->get( '/wp-json/test-plugin/v1/media/uploads/2024/image.jpg' );
123+
124+
$this->assertSame( 'GET /test-plugin/v1/media/<path>', $this->nr->name );
125+
}
126+
127+
/**
128+
* Test that a route with an optional parameter and nested parens is named correctly.
129+
*/
130+
public function test_optional_nested_param_route_naming(): void {
131+
$this->get( '/wp-json/test-plugin/v1/placement/my-player' );
132+
133+
$this->assertSame( 'GET /test-plugin/v1/placement/<player>', $this->nr->name );
134+
}
135+
136+
/**
137+
* Test that a route with a nested alternation group is named correctly.
138+
*/
139+
public function test_alternation_group_route_naming(): void {
140+
$this->get( '/wp-json/test-plugin/v1/posts/hello-world' );
141+
142+
$this->assertSame( 'GET /test-plugin/v1/<type>/<slug>', $this->nr->name );
143+
}
144+
145+
/**
146+
* Test that a POST request to a route is named with the correct HTTP method.
147+
*/
148+
public function test_post_method_route_naming(): void {
149+
$this->post( '/wp-json/test-plugin/v1/items/7' );
150+
151+
$this->assertSame( 'POST /test-plugin/v1/items/<id>', $this->nr->name );
152+
}
153+
154+
/**
155+
* Test that a route without any parameters is named as-is.
156+
*/
157+
public function test_route_without_params_naming(): void {
158+
add_action(
159+
'rest_api_init',
160+
function () {
161+
register_rest_route(
162+
'test-plugin/v1',
163+
'/status',
164+
[
165+
'methods' => 'GET',
166+
'callback' => fn() => new WP_REST_Response( [ 'ok' => true ], 200 ),
167+
'permission_callback' => '__return_true',
168+
],
169+
);
170+
},
171+
);
172+
173+
$this->get( '/wp-json/test-plugin/v1/status' );
174+
175+
$this->assertSame( 'GET /test-plugin/v1/status', $this->nr->name );
176+
}
177+
178+
/**
179+
* Test that the transaction is only named once even if multiple REST routes
180+
* could match (plugin should only set the name on the first dispatch).
181+
*/
182+
public function test_transaction_named_only_once(): void {
183+
$this->get( '/wp-json/test-plugin/v1/items/123' );
184+
185+
// The 'named' flag on the plugin should be true after the first dispatch.
186+
$this->assertTrue( $GLOBALS['wp_new_relic_transactions_plugin']->named );
187+
}
188+
}

0 commit comments

Comments
 (0)