2026-09-01 00:16:17 +07:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use Illuminate\Http\Client\Request;
|
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
|
use Livewire\Livewire;
|
|
|
|
|
use Modules\AiAgent\Filament\Pages\ManageSuggestionMisses;
|
|
|
|
|
use Spatie\Permission\Models\Permission;
|
|
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
|
config([
|
|
|
|
|
'services.bnfexpress.ai_api_url' => 'https://bnfexpress.test',
|
|
|
|
|
'services.bnfexpress.client_id' => 'ev_admin',
|
|
|
|
|
'services.bnfexpress.client_secret' => 'test-secret',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Permission::findOrCreate('manage_ai_agent', 'web');
|
|
|
|
|
|
|
|
|
|
$this->admin = User::factory()->create()->givePermissionTo(['manage_ai_agent']);
|
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a user without manage_ai_agent cannot access it', function () {
|
|
|
|
|
$this->actingAs(User::factory()->create());
|
|
|
|
|
|
|
|
|
|
expect(ManageSuggestionMisses::canAccess())->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('it renders and lists misses from the client', function () {
|
|
|
|
|
Http::fake(['bnfexpress.test/*' => Http::response([
|
|
|
|
|
['id' => 1, 'text_norm' => 'ev charging cost', 'lang' => 'en', 'syllables' => 3, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
])]);
|
|
|
|
|
|
|
|
|
|
Livewire::test(ManageSuggestionMisses::class)
|
|
|
|
|
->assertOk()
|
|
|
|
|
->loadTable()
|
|
|
|
|
->assertSee('ev charging cost');
|
|
|
|
|
|
|
|
|
|
Http::assertSent(fn (Request $request) => str_contains($request->url(), '/admin/suggestion-misses') && $request->method() === 'GET');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('dismiss calls dismissSuggestionMiss and shows a success notification', function () {
|
|
|
|
|
Http::fake(['bnfexpress.test/*' => fn (Request $request) => match ($request->method()) {
|
|
|
|
|
'DELETE' => Http::response([]),
|
|
|
|
|
default => Http::response([
|
|
|
|
|
['id' => 1, 'text_norm' => 'to dismiss', 'lang' => 'en', 'syllables' => 2, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
]),
|
|
|
|
|
}]);
|
|
|
|
|
|
|
|
|
|
Livewire::test(ManageSuggestionMisses::class)
|
|
|
|
|
->loadTable()
|
|
|
|
|
->callTableAction('dismiss', 1)
|
|
|
|
|
->assertNotified('Miss dismissed');
|
|
|
|
|
|
|
|
|
|
Http::assertSent(fn (Request $request) => $request->method() === 'DELETE' && str_contains($request->url(), '/admin/suggestion-misses/1'));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a failed dismiss surfaces the gateway detail message', function () {
|
|
|
|
|
Http::fake(['bnfexpress.test/*' => fn (Request $request) => match ($request->method()) {
|
|
|
|
|
'DELETE' => Http::response(['detail' => 'Suggestion miss not found.'], 404),
|
|
|
|
|
default => Http::response([
|
|
|
|
|
['id' => 1, 'text_norm' => 'to dismiss', 'lang' => 'en', 'syllables' => 2, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
]),
|
|
|
|
|
}]);
|
|
|
|
|
|
|
|
|
|
Livewire::test(ManageSuggestionMisses::class)
|
|
|
|
|
->loadTable()
|
|
|
|
|
->callTableAction('dismiss', 1)
|
|
|
|
|
->assertNotified('Failed to dismiss miss');
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-03 20:42:56 +07:00
|
|
|
test('promote bulk action prefills the bulk-add modal with the selected misses\' text and language', function () {
|
|
|
|
|
Http::fake(['bnfexpress.test/*' => Http::response([
|
|
|
|
|
['id' => 1, 'text_norm' => 'ev charging cost', 'lang' => 'my', 'syllables' => 3, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
['id' => 2, 'text_norm' => 'nearest charger', 'lang' => 'my', 'syllables' => 2, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
])]);
|
|
|
|
|
|
|
|
|
|
Livewire::test(ManageSuggestionMisses::class)
|
|
|
|
|
->loadTable()
|
|
|
|
|
->mountTableBulkAction('promote', [1, 2])
|
|
|
|
|
->assertTableBulkActionDataSet([
|
|
|
|
|
'items_raw' => "ev charging cost\nnearest charger",
|
|
|
|
|
'lang' => 'my',
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('promote bulk action calls batchCreateSuggestions with the (possibly edited) phrases and shows the result', function () {
|
2026-09-01 00:16:17 +07:00
|
|
|
Http::fake(['bnfexpress.test/*' => fn (Request $request) => match (true) {
|
2026-09-03 20:42:56 +07:00
|
|
|
str_contains($request->url(), '/admin/suggestions/batch') => Http::response(['created' => 2, 'skipped' => 0, 'trie_rebuilt' => true]),
|
|
|
|
|
str_contains($request->url(), '/admin/suggestion-misses/batch') => Http::response(['deleted' => 2, 'skipped' => 0]),
|
2026-09-01 00:16:17 +07:00
|
|
|
default => Http::response([
|
|
|
|
|
['id' => 1, 'text_norm' => 'a', 'lang' => 'en', 'syllables' => 1, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
['id' => 2, 'text_norm' => 'b', 'lang' => 'en', 'syllables' => 1, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
]),
|
|
|
|
|
}]);
|
|
|
|
|
|
|
|
|
|
Livewire::test(ManageSuggestionMisses::class)
|
|
|
|
|
->loadTable()
|
2026-09-03 20:42:56 +07:00
|
|
|
->callTableBulkAction('promote', [1, 2], data: ['items_raw' => "a\nb\nc", 'lang' => 'my', 'intent' => null])
|
2026-09-01 00:16:17 +07:00
|
|
|
->assertNotified('2 promoted, 0 skipped');
|
|
|
|
|
|
2026-09-03 20:42:56 +07:00
|
|
|
Http::assertSent(fn (Request $request) => str_contains($request->url(), '/admin/suggestions/batch')
|
|
|
|
|
&& $request['items'] === [
|
|
|
|
|
['text' => 'a', 'lang' => 'my'],
|
|
|
|
|
['text' => 'b', 'lang' => 'my'],
|
|
|
|
|
['text' => 'c', 'lang' => 'my'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Http::assertSent(fn (Request $request) => $request->method() === 'DELETE'
|
|
|
|
|
&& str_contains($request->url(), '/admin/suggestion-misses/batch')
|
|
|
|
|
&& $request['miss_ids'] === [1, 2]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('promoting deletes the original misses after a successful promote', function () {
|
|
|
|
|
Http::fake(['bnfexpress.test/*' => fn (Request $request) => match (true) {
|
|
|
|
|
str_contains($request->url(), '/admin/suggestions/batch') => Http::response(['created' => 1, 'skipped' => 0, 'trie_rebuilt' => true]),
|
|
|
|
|
str_contains($request->url(), '/admin/suggestion-misses/batch') => Http::response(['deleted' => 1, 'skipped' => 0]),
|
|
|
|
|
default => Http::response([
|
|
|
|
|
['id' => 1, 'text_norm' => 'ev charging cost', 'lang' => 'en', 'syllables' => 3, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
]),
|
|
|
|
|
}]);
|
|
|
|
|
|
|
|
|
|
Livewire::test(ManageSuggestionMisses::class)
|
|
|
|
|
->loadTable()
|
|
|
|
|
->callTableBulkAction('promote', [1], data: ['items_raw' => 'ev charging cost', 'lang' => 'en', 'intent' => null])
|
|
|
|
|
->assertNotified('1 promoted, 0 skipped');
|
|
|
|
|
|
|
|
|
|
Http::assertSent(fn (Request $request) => $request->method() === 'DELETE'
|
|
|
|
|
&& str_contains($request->url(), '/admin/suggestion-misses/batch')
|
|
|
|
|
&& $request['miss_ids'] === [1]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a failed cleanup delete after a successful promote surfaces a separate warning notification', function () {
|
|
|
|
|
Http::fake(['bnfexpress.test/*' => fn (Request $request) => match (true) {
|
|
|
|
|
str_contains($request->url(), '/admin/suggestions/batch') => Http::response(['created' => 1, 'skipped' => 0, 'trie_rebuilt' => true]),
|
|
|
|
|
str_contains($request->url(), '/admin/suggestion-misses/batch') => Http::response(['detail' => 'boom'], 500),
|
|
|
|
|
default => Http::response([
|
|
|
|
|
['id' => 1, 'text_norm' => 'ev charging cost', 'lang' => 'en', 'syllables' => 3, 'was_used' => false, 'created_at' => now()->toIso8601String()],
|
|
|
|
|
]),
|
|
|
|
|
}]);
|
|
|
|
|
|
|
|
|
|
// assertNotified() pulls (and clears) every queued notification on its
|
|
|
|
|
// first call, so only one chained call can see anything — check the
|
|
|
|
|
// warning here; the success title is covered by the other promote tests.
|
|
|
|
|
Livewire::test(ManageSuggestionMisses::class)
|
|
|
|
|
->loadTable()
|
|
|
|
|
->callTableBulkAction('promote', [1], data: ['items_raw' => 'ev charging cost', 'lang' => 'en', 'intent' => null])
|
|
|
|
|
->assertNotified('Promoted, but failed to remove the original misses');
|
2026-09-01 00:16:17 +07:00
|
|
|
});
|