Skip to content

Commit ff16051

Browse files
committed
Add unit tests to cover changes
1 parent ec8c3b8 commit ff16051

File tree

4 files changed

+431
-9
lines changed

4 files changed

+431
-9
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
<?php
2+
/**
3+
* Integration tests for the API_Request class.
4+
*
5+
* @package WordPress\AI\Tests\Integration\Includes
6+
*/
7+
8+
namespace WordPress\AI\Tests\Integration\Includes;
9+
10+
use WordPress\AI\API_Request;
11+
use WP_Error;
12+
use WP_UnitTestCase;
13+
14+
/**
15+
* API_Request test case.
16+
*
17+
* @since 0.1.0
18+
*/
19+
class API_RequestTest extends WP_UnitTestCase {
20+
21+
/**
22+
* Test that constructor sets provider and model correctly.
23+
*
24+
* @since 0.1.0
25+
*/
26+
public function test_constructor_sets_provider_and_model() {
27+
$request = new API_Request( 'openai', 'gpt-4' );
28+
29+
$reflection = new \ReflectionClass( $request );
30+
$provider_property = $reflection->getProperty( 'provider' );
31+
$provider_property->setAccessible( true );
32+
$model_property = $reflection->getProperty( 'model' );
33+
$model_property->setAccessible( true );
34+
35+
$this->assertEquals( 'openai', $provider_property->getValue( $request ), 'Provider should be set' );
36+
$this->assertEquals( 'gpt-4', $model_property->getValue( $request ), 'Model should be set' );
37+
}
38+
39+
/**
40+
* Test that constructor handles empty strings.
41+
*
42+
* @since 0.1.0
43+
*/
44+
public function test_constructor_handles_empty_strings() {
45+
$request = new API_Request( '', '' );
46+
47+
$reflection = new \ReflectionClass( $request );
48+
$provider_property = $reflection->getProperty( 'provider' );
49+
$provider_property->setAccessible( true );
50+
$model_property = $reflection->getProperty( 'model' );
51+
$model_property->setAccessible( true );
52+
53+
$this->assertEquals( '', $provider_property->getValue( $request ), 'Provider should be empty string' );
54+
$this->assertEquals( '', $model_property->getValue( $request ), 'Model should be empty string' );
55+
}
56+
57+
/**
58+
* Test that is_client_available() checks for AiClient class.
59+
*
60+
* @since 0.1.0
61+
*/
62+
public function test_is_client_available_checks_class() {
63+
$request = new API_Request();
64+
65+
$reflection = new \ReflectionClass( $request );
66+
$method = $reflection->getMethod( 'is_client_available' );
67+
$method->setAccessible( true );
68+
69+
$result = $method->invoke( $request );
70+
71+
// The result depends on whether AiClient class exists in the test environment.
72+
$this->assertIsBool( $result, 'Should return a boolean' );
73+
}
74+
75+
/**
76+
* Test that generate_text() returns error when client is not available.
77+
*
78+
* @since 0.1.0
79+
*/
80+
public function test_generate_text_returns_error_when_client_unavailable() {
81+
$request = new API_Request();
82+
83+
// Mock the is_client_available method to return false.
84+
$mock = $this->getMockBuilder( API_Request::class )
85+
->onlyMethods( array( 'is_client_available' ) )
86+
->getMock();
87+
88+
$mock->expects( $this->once() )
89+
->method( 'is_client_available' )
90+
->willReturn( false );
91+
92+
$result = $mock->generate_text( 'test prompt' );
93+
94+
$this->assertInstanceOf( WP_Error::class, $result, 'Should return WP_Error when client unavailable' );
95+
$this->assertEquals( 'ai_client_not_available', $result->get_error_code(), 'Error code should be ai_client_not_available' );
96+
}
97+
98+
/**
99+
* Test that sanitize_choice() sanitizes text correctly.
100+
*
101+
* @since 0.1.0
102+
*/
103+
public function test_sanitize_choice_sanitizes_text() {
104+
$request = new API_Request();
105+
106+
$reflection = new \ReflectionClass( $request );
107+
$method = $reflection->getMethod( 'sanitize_choice' );
108+
$method->setAccessible( true );
109+
110+
// Test trimming quotes.
111+
$result = $method->invoke( $request, '"Test Title"' );
112+
$this->assertEquals( 'Test Title', $result, 'Should remove double quotes' );
113+
114+
$result = $method->invoke( $request, "'Test Title'" );
115+
$this->assertEquals( 'Test Title', $result, 'Should remove single quotes' );
116+
117+
// Test trimming whitespace.
118+
$result = $method->invoke( $request, ' Test Title ' );
119+
$this->assertEquals( 'Test Title', $result, 'Should trim whitespace' );
120+
121+
// Test sanitize_text_field behavior (removes HTML).
122+
$result = $method->invoke( $request, '<script>alert("test")</script>Test Title' );
123+
$this->assertEquals( 'Test Title', $result, 'Should sanitize HTML' );
124+
}
125+
126+
/**
127+
* Test that get_result() returns error for empty response.
128+
*
129+
* @since 0.1.0
130+
*/
131+
public function test_get_result_returns_error_for_empty_response() {
132+
$request = new API_Request();
133+
134+
$reflection = new \ReflectionClass( $request );
135+
$method = $reflection->getMethod( 'get_result' );
136+
$method->setAccessible( true );
137+
138+
$result = $method->invoke( $request, array() );
139+
140+
$this->assertInstanceOf( WP_Error::class, $result, 'Should return WP_Error for empty response' );
141+
$this->assertEquals( 'no_choices', $result->get_error_code(), 'Error code should be no_choices' );
142+
}
143+
144+
/**
145+
* Test that get_result() processes choices correctly.
146+
*
147+
* @since 0.1.0
148+
*/
149+
public function test_get_result_processes_choices() {
150+
$request = new API_Request();
151+
152+
$reflection = new \ReflectionClass( $request );
153+
$method = $reflection->getMethod( 'get_result' );
154+
$method->setAccessible( true );
155+
156+
$response = array(
157+
' Title 1 ',
158+
'"Title 2"',
159+
"'Title 3'",
160+
);
161+
162+
$result = $method->invoke( $request, $response );
163+
164+
$this->assertIsArray( $result, 'Should return an array' );
165+
$this->assertCount( 3, $result, 'Should have 3 choices' );
166+
$this->assertEquals( 'Title 1', $result[0], 'Should sanitize first choice' );
167+
$this->assertEquals( 'Title 2', $result[1], 'Should sanitize second choice' );
168+
$this->assertEquals( 'Title 3', $result[2], 'Should sanitize third choice' );
169+
}
170+
171+
/**
172+
* Test that process_model_config() processes string values.
173+
*
174+
* @since 0.1.0
175+
*/
176+
public function test_process_model_config_processes_string_values() {
177+
$request = new API_Request();
178+
179+
$reflection = new \ReflectionClass( $request );
180+
$method = $reflection->getMethod( 'process_model_config' );
181+
$method->setAccessible( true );
182+
183+
$options = array(
184+
'temperature' => '0.7',
185+
);
186+
187+
$result = $method->invoke( $request, $options );
188+
189+
$this->assertInstanceOf( \WordPress\AiClient\Providers\Models\DTO\ModelConfig::class, $result, 'Should return ModelConfig instance' );
190+
}
191+
192+
/**
193+
* Test that process_model_config() processes integer values.
194+
*
195+
* @since 0.1.0
196+
*/
197+
public function test_process_model_config_processes_integer_values() {
198+
$request = new API_Request();
199+
200+
$reflection = new \ReflectionClass( $request );
201+
$method = $reflection->getMethod( 'process_model_config' );
202+
$method->setAccessible( true );
203+
204+
$options = array(
205+
'candidateCount' => '5',
206+
);
207+
208+
$result = $method->invoke( $request, $options );
209+
210+
$this->assertInstanceOf( \WordPress\AiClient\Providers\Models\DTO\ModelConfig::class, $result, 'Should return ModelConfig instance' );
211+
}
212+
213+
/**
214+
* Test that process_model_config() processes boolean values.
215+
*
216+
* @since 0.1.0
217+
*/
218+
public function test_process_model_config_processes_boolean_values() {
219+
$request = new API_Request();
220+
221+
$reflection = new \ReflectionClass( $request );
222+
$method = $reflection->getMethod( 'process_model_config' );
223+
$method->setAccessible( true );
224+
225+
$options = array(
226+
'someBoolean' => 'true',
227+
);
228+
229+
// This will only work if 'someBoolean' is in the ModelConfig schema.
230+
// Otherwise it will be skipped. We'll just verify it doesn't error.
231+
$result = $method->invoke( $request, $options );
232+
233+
$this->assertInstanceOf( \WordPress\AiClient\Providers\Models\DTO\ModelConfig::class, $result, 'Should return ModelConfig instance' );
234+
}
235+
236+
/**
237+
* Test that process_model_config() skips invalid options.
238+
*
239+
* @since 0.1.0
240+
*/
241+
public function test_process_model_config_skips_invalid_options() {
242+
$request = new API_Request();
243+
244+
$reflection = new \ReflectionClass( $request );
245+
$method = $reflection->getMethod( 'process_model_config' );
246+
$method->setAccessible( true );
247+
248+
$options = array(
249+
'invalid_option' => 'value',
250+
'temperature' => '0.7',
251+
);
252+
253+
$result = $method->invoke( $request, $options );
254+
255+
$this->assertInstanceOf( \WordPress\AiClient\Providers\Models\DTO\ModelConfig::class, $result, 'Should return ModelConfig instance' );
256+
}
257+
}
258+

tests/Integration/Includes/Abilities/Title_GenerationTest.php

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ protected function load_feature_metadata(): array {
4141
public function register(): void {
4242
// No-op for testing.
4343
}
44+
45+
/**
46+
* Generates title suggestions from the given content.
47+
*
48+
* @since 0.1.0
49+
*
50+
* @param string $content The content to generate a title from.
51+
* @param int $n The number of titles to generate.
52+
* @return array|\WP_Error The generated titles, or a WP_Error if there was an error.
53+
*/
54+
public function generate_titles( string $content, int $n = 1 ) {
55+
// For testing, return mock titles.
56+
$titles = array();
57+
for ( $i = 1; $i <= $n; $i++ ) {
58+
$titles[] = "Generated Title {$i}";
59+
}
60+
return $titles;
61+
}
4462
}
4563

4664
/**
@@ -175,11 +193,10 @@ public function test_execute_callback_with_content() {
175193
$result = $method->invoke( $this->ability, $input );
176194

177195
$this->assertIsArray( $result, 'Result should be an array' );
178-
$this->assertEquals( 'title-generation', $result['feature_id'], 'Feature ID should match' );
179-
$this->assertEquals( 'Title Generation', $result['label'], 'Label should match' );
180-
$this->assertEquals( 'Generates title suggestions from content', $result['description'], 'Description should match' );
181-
$this->assertEquals( 'This is some test content.', $result['content'], 'Content should match input' );
182-
$this->assertEquals( 3, $result['n'], 'n should match input' );
196+
$this->assertArrayHasKey( 'titles', $result, 'Result should have titles key' );
197+
$this->assertIsArray( $result['titles'], 'Titles should be an array' );
198+
$this->assertCount( 3, $result['titles'], 'Should have 3 titles' );
199+
$this->assertEquals( 'Generated Title 1', $result['titles'][0], 'First title should match' );
183200
}
184201

185202
/**
@@ -207,8 +224,9 @@ public function test_execute_callback_with_post_id() {
207224
$result = $method->invoke( $this->ability, $input );
208225

209226
$this->assertIsArray( $result, 'Result should be an array' );
210-
$this->assertEquals( 'This is post content.', $result['content'], 'Content should come from post' );
211-
$this->assertEquals( $post_id, $result['post_id'], 'Post ID should match' );
227+
$this->assertArrayHasKey( 'titles', $result, 'Result should have titles key' );
228+
$this->assertIsArray( $result['titles'], 'Titles should be an array' );
229+
$this->assertCount( 2, $result['titles'], 'Should have 2 titles' );
212230
}
213231

214232
/**
@@ -263,7 +281,9 @@ public function test_execute_callback_uses_defaults() {
263281
$result = $method->invoke( $this->ability, $input );
264282

265283
$this->assertIsArray( $result, 'Result should be an array' );
266-
$this->assertEquals( 1, $result['n'], 'n should default to 1' );
284+
$this->assertArrayHasKey( 'titles', $result, 'Result should have titles key' );
285+
$this->assertIsArray( $result['titles'], 'Titles should be an array' );
286+
$this->assertCount( 1, $result['titles'], 'Should have 1 title by default' );
267287
}
268288

269289
/**
@@ -291,7 +311,10 @@ public function test_execute_callback_post_id_overrides_content() {
291311
$result = $method->invoke( $this->ability, $input );
292312

293313
$this->assertIsArray( $result, 'Result should be an array' );
294-
$this->assertEquals( 'Post content takes priority.', $result['content'], 'Post content should override provided content' );
314+
$this->assertArrayHasKey( 'titles', $result, 'Result should have titles key' );
315+
$this->assertIsArray( $result['titles'], 'Titles should be an array' );
316+
// The feature's generate_titles uses the post content, verified by titles being generated.
317+
$this->assertNotEmpty( $result['titles'], 'Should generate titles from post content' );
295318
}
296319

297320
/**

tests/Integration/Includes/Features/Example_Feature/Example_FeatureTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ public function test_get_ability_slug_returns_correct_format() {
7171
$this->assertStringStartsWith( 'ai/', $slug, 'Ability slug should start with ai/' );
7272
}
7373

74+
/**
75+
* Test that get_system_instruction() returns empty string for features without system instruction.
76+
*
77+
* @since 0.1.0
78+
*/
79+
public function test_get_system_instruction_returns_empty_for_features_without_instruction() {
80+
$feature = new Example_Feature();
81+
82+
$system_instruction = $feature->get_system_instruction();
83+
84+
$this->assertIsString( $system_instruction, 'System instruction should be a string' );
85+
$this->assertEquals( '', $system_instruction, 'System instruction should be empty for features without one' );
86+
}
87+
7488
/**
7589
* Test that footer content is added for logged-in users.
7690
*

0 commit comments

Comments
 (0)