Skip to content

Commit 6a03740

Browse files
committed
fix: include remaining test modifications
1 parent 57bfb82 commit 6a03740

1 file changed

Lines changed: 149 additions & 3 deletions

File tree

tests/Feature/MonitorSqlQueryWorkflowTest.php

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use App\Ai\Agents\SqlQueryAssistant;
66
use App\Jobs\ProcessDataExportJob;
7+
use App\Models\AiMemoryProfile;
78
use App\Models\ConnectionPermission;
89
use App\Models\DatabaseConnection;
910
use App\Models\Permission;
@@ -136,10 +137,134 @@ public function test_ai_generate_returns_structured_payload_with_confirmation()
136137
'explanation',
137138
'tables_used',
138139
'confidence',
140+
'conversation_id',
141+
'dialect',
142+
'memory_applied' => ['short_term', 'long_term'],
139143
'requires_confirmation',
140144
'suggested_visualization' => ['type', 'x_axis', 'y_axis', 'reason'],
141145
]);
142146
$response->assertJsonPath('requires_confirmation', true);
147+
$response->assertJsonPath('dialect', 'pgsql');
148+
$response->assertJsonPath('memory_applied.short_term', true);
149+
$response->assertJsonPath('conversation_id', fn (mixed $value): bool => is_string($value) && $value !== '');
150+
151+
$profile = AiMemoryProfile::query()
152+
->where('user_id', $user->id)
153+
->where('connection_id', $connection->id)
154+
->first();
155+
156+
$this->assertNotNull($profile);
157+
$this->assertIsArray($profile?->preferred_tables);
158+
}
159+
160+
public function test_ai_generate_continues_existing_conversation_for_same_user()
161+
{
162+
SqlQueryAssistant::fake([
163+
[
164+
'sql' => 'SELECT * FROM customers LIMIT 10',
165+
'explanation' => 'First response.',
166+
'tables_used' => ['customers'],
167+
'confidence' => 'high',
168+
'suggested_visualization' => [
169+
'type' => 'table',
170+
'x_axis' => null,
171+
'y_axis' => null,
172+
'reason' => 'Rows are tabular.',
173+
],
174+
],
175+
[
176+
'sql' => 'SELECT COUNT(*) AS total FROM customers',
177+
'explanation' => 'Second response.',
178+
'tables_used' => ['customers'],
179+
'confidence' => 'high',
180+
'suggested_visualization' => [
181+
'type' => 'kpi',
182+
'x_axis' => null,
183+
'y_axis' => 'total',
184+
'reason' => 'Single metric.',
185+
],
186+
],
187+
]);
188+
189+
$user = $this->createUserWithPermission('queries.ai_generate');
190+
$connection = $this->createConnection();
191+
$this->grantTableAccess($user, $connection, 'customers');
192+
193+
$first = $this->actingAs($user)->postJson('/queries/ai-generate', [
194+
'connection_id' => $connection->id,
195+
'question' => 'List customers',
196+
'selected_tables' => ['customers'],
197+
]);
198+
199+
$first->assertOk();
200+
$conversationId = $first->json('conversation_id');
201+
$this->assertIsString($conversationId);
202+
203+
$second = $this->actingAs($user)->postJson('/queries/ai-generate', [
204+
'connection_id' => $connection->id,
205+
'question' => 'Now count them',
206+
'conversation_id' => $conversationId,
207+
'selected_tables' => ['customers'],
208+
]);
209+
210+
$second->assertOk();
211+
$second->assertJsonPath('conversation_id', $conversationId);
212+
}
213+
214+
public function test_ai_generate_rejects_conversation_id_from_another_user()
215+
{
216+
SqlQueryAssistant::fake([
217+
[
218+
'sql' => 'SELECT * FROM customers LIMIT 10',
219+
'explanation' => 'First response.',
220+
'tables_used' => ['customers'],
221+
'confidence' => 'high',
222+
'suggested_visualization' => [
223+
'type' => 'table',
224+
'x_axis' => null,
225+
'y_axis' => null,
226+
'reason' => 'Rows are tabular.',
227+
],
228+
],
229+
[
230+
'sql' => 'SELECT * FROM customers LIMIT 5',
231+
'explanation' => 'Other user response.',
232+
'tables_used' => ['customers'],
233+
'confidence' => 'medium',
234+
'suggested_visualization' => [
235+
'type' => 'table',
236+
'x_axis' => null,
237+
'y_axis' => null,
238+
'reason' => 'Rows are tabular.',
239+
],
240+
],
241+
]);
242+
243+
$owner = $this->createUserWithPermission('queries.ai_generate');
244+
$intruder = $this->createUserWithPermission('queries.ai_generate');
245+
$connection = $this->createConnection();
246+
$this->grantTableAccess($owner, $connection, 'customers');
247+
$this->grantTableAccess($intruder, $connection, 'customers');
248+
249+
$first = $this->actingAs($owner)->postJson('/queries/ai-generate', [
250+
'connection_id' => $connection->id,
251+
'question' => 'List customers',
252+
'selected_tables' => ['customers'],
253+
]);
254+
255+
$first->assertOk();
256+
$conversationId = $first->json('conversation_id');
257+
$this->assertIsString($conversationId);
258+
259+
$rejected = $this->actingAs($intruder)->postJson('/queries/ai-generate', [
260+
'connection_id' => $connection->id,
261+
'question' => 'Reuse that conversation',
262+
'conversation_id' => $conversationId,
263+
'selected_tables' => ['customers'],
264+
]);
265+
266+
$rejected->assertStatus(422);
267+
$rejected->assertJsonPath('message', 'The provided conversation_id is invalid for this user.');
143268
}
144269

145270
public function test_exports_can_be_queued_in_xlsx()
@@ -201,6 +326,25 @@ public function test_query_execute_returns_sanitized_message_on_engine_error()
201326
$response->assertJsonPath('message', 'The SQL engine returned a sanitized error response.');
202327
}
203328

329+
public function test_query_execute_blocks_dialect_mismatch_for_mysql_connection()
330+
{
331+
$user = $this->createUserWithPermission('queries.execute');
332+
$connection = $this->createConnection(driver: 'mysql');
333+
$this->grantTableAccess($user, $connection, 'customers');
334+
335+
$response = $this->actingAs($user)->postJson('/queries/execute', [
336+
'connection_id' => $connection->id,
337+
'sql' => "SELECT * FROM customers WHERE name ILIKE '%john%'",
338+
]);
339+
340+
$response->assertStatus(422);
341+
$response->assertJsonPath('message', 'SQL validation failed.');
342+
$this->assertTrue(
343+
collect($response->json('errors', []))
344+
->contains(fn (mixed $error): bool => is_string($error) && str_contains($error, 'Dialect mismatch'))
345+
);
346+
}
347+
204348
private function createUserWithPermission(string $permissionKey): User
205349
{
206350
$permission = Permission::query()->firstOrCreate([
@@ -221,13 +365,15 @@ private function createUserWithPermission(string $permissionKey): User
221365
return $user;
222366
}
223367

224-
private function createConnection(): DatabaseConnection
368+
private function createConnection(string $driver = 'pgsql'): DatabaseConnection
225369
{
370+
$defaultPort = $driver === 'pgsql' ? 5432 : 3306;
371+
226372
return DatabaseConnection::query()->create([
227373
'name' => 'Local PG',
228-
'driver' => 'pgsql',
374+
'driver' => $driver,
229375
'host' => '127.0.0.1',
230-
'port' => 5432,
376+
'port' => $defaultPort,
231377
'database' => 'demo',
232378
'username' => 'readonly',
233379
'password' => 'secret',

0 commit comments

Comments
 (0)