297 lines
12 KiB
PHP
297 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Admin;
|
|
use App\Models\Channel;
|
|
use App\Models\Device;
|
|
use App\Models\PluginSession;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\TestCase;
|
|
|
|
class PluginSessionPageTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
#[Test]
|
|
public function admin_can_list_whatsapp_and_telegram_sessions(): void
|
|
{
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$device = Device::query()->create([
|
|
'device_id' => 'dev-sess',
|
|
'channel_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
'has_whatsapp' => true,
|
|
'has_telegram' => true,
|
|
]);
|
|
PluginSession::query()->create([
|
|
'device_id' => $device->id,
|
|
'device_key' => $device->device_id,
|
|
'kind' => PluginSession::KIND_WHATSAPP,
|
|
'account_id' => '15551230000',
|
|
'phone' => '15551230000',
|
|
'payload' => [
|
|
'userId' => '15551230000',
|
|
'nickname' => 'wa-nick',
|
|
'whatsappVersion' => '2.24.0',
|
|
'clientStaticKeypairBase64' => 'QUJD',
|
|
],
|
|
]);
|
|
PluginSession::query()->create([
|
|
'device_id' => $device->id,
|
|
'device_key' => $device->device_id,
|
|
'kind' => PluginSession::KIND_TELEGRAM,
|
|
'account_id' => '987654',
|
|
'payload' => [
|
|
'user_id' => '987654',
|
|
'state' => ['records' => []],
|
|
'db_sqlite' => str_repeat('A', 400),
|
|
],
|
|
]);
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->get(route('admin.whatsapp.index'))
|
|
->assertOk()
|
|
->assertSee('WS 参数');
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.whatsapp.data'))
|
|
->assertOk()
|
|
->assertJsonPath('count', 1)
|
|
->assertJsonPath('data.0.account_id', '15551230000')
|
|
->assertJsonPath('data.0.nickname', 'wa-nick')
|
|
->assertJsonPath('data.0.has_keystore', true)
|
|
->assertJsonMissingPath('data.0.payload_json');
|
|
|
|
$tg = $this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.telegram.data'))
|
|
->assertOk()
|
|
->assertJsonPath('count', 1)
|
|
->assertJsonPath('data.0.account_id', '987654')
|
|
->assertJsonPath('data.0.has_db', true)
|
|
->json();
|
|
$this->assertArrayNotHasKey('payload_json', $tg['data'][0] ?? []);
|
|
|
|
$tgPayload = $this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.sessions.payload', PluginSession::query()->where('kind', PluginSession::KIND_TELEGRAM)->first()))
|
|
->assertOk()
|
|
->json('data.payload_json');
|
|
$this->assertStringContainsString(str_repeat('A', 400), (string) $tgPayload);
|
|
$this->assertStringNotContainsString('400 bytes', (string) $tgPayload);
|
|
|
|
$tgRow = PluginSession::query()->where('kind', PluginSession::KIND_TELEGRAM)->first();
|
|
$download = $this->actingAs($admin, 'admin')
|
|
->get(route('admin.sessions.download', $tgRow))
|
|
->assertOk();
|
|
$this->assertStringContainsString('attachment', (string) $download->headers->get('content-disposition'));
|
|
$this->assertStringContainsString(str_repeat('A', 400), $download->streamedContent());
|
|
}
|
|
|
|
#[Test]
|
|
public function agent_only_sees_own_channel_sessions(): void
|
|
{
|
|
$agentA = User::query()->create(['username' => 'a', 'password' => 'secret12', 'status' => 1]);
|
|
$agentB = User::query()->create(['username' => 'b', 'password' => 'secret12', 'status' => 1]);
|
|
$chA = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
|
|
$chB = 'cccccccccccccccccccccccccccccccc';
|
|
Channel::query()->create(['channel_id' => $chA, 'user_id' => $agentA->id, 'status' => 1]);
|
|
Channel::query()->create(['channel_id' => $chB, 'user_id' => $agentB->id, 'status' => 1]);
|
|
|
|
$devA = Device::query()->create(['device_id' => 'dev-a', 'channel_id' => $chA]);
|
|
$devB = Device::query()->create(['device_id' => 'dev-b', 'channel_id' => $chB]);
|
|
PluginSession::query()->create([
|
|
'device_id' => $devA->id,
|
|
'device_key' => 'dev-a',
|
|
'kind' => PluginSession::KIND_TELEGRAM,
|
|
'account_id' => 'aaa',
|
|
'payload' => ['user_id' => 'aaa'],
|
|
]);
|
|
PluginSession::query()->create([
|
|
'device_id' => $devB->id,
|
|
'device_key' => 'dev-b',
|
|
'kind' => PluginSession::KIND_TELEGRAM,
|
|
'account_id' => 'bbb',
|
|
'payload' => ['user_id' => 'bbb'],
|
|
]);
|
|
|
|
$this->actingAs($agentA, 'agent')
|
|
->getJson(route('user.telegram.data'))
|
|
->assertOk()
|
|
->assertJsonPath('count', 1)
|
|
->assertJsonPath('data.0.account_id', 'aaa');
|
|
|
|
$own = PluginSession::query()->where('account_id', 'aaa')->first();
|
|
$other = PluginSession::query()->where('account_id', 'bbb')->first();
|
|
$this->actingAs($agentA, 'agent')
|
|
->getJson(route('user.sessions.payload', $own))
|
|
->assertOk()
|
|
->assertJsonPath('code', 0);
|
|
$this->actingAs($agentA, 'agent')
|
|
->getJson(route('user.sessions.payload', $other))
|
|
->assertForbidden();
|
|
$this->actingAs($agentA, 'agent')
|
|
->get(route('user.sessions.download', $own))
|
|
->assertOk();
|
|
$this->actingAs($agentA, 'agent')
|
|
->get(route('user.sessions.download', $other))
|
|
->assertForbidden();
|
|
}
|
|
|
|
#[Test]
|
|
public function admin_can_export_sessions_as_zip(): void
|
|
{
|
|
Storage::fake('local');
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$device = Device::query()->create([
|
|
'device_id' => 'dev-export',
|
|
'channel_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
]);
|
|
PluginSession::query()->create([
|
|
'device_id' => $device->id,
|
|
'device_key' => $device->device_id,
|
|
'kind' => PluginSession::KIND_TELEGRAM,
|
|
'account_id' => '111',
|
|
'payload' => ['user_id' => '111', 'state' => 'abc'],
|
|
]);
|
|
PluginSession::query()->create([
|
|
'device_id' => $device->id,
|
|
'device_key' => $device->device_id,
|
|
'kind' => PluginSession::KIND_TELEGRAM,
|
|
'account_id' => '222',
|
|
'payload' => ['user_id' => '222', 'state' => 'def'],
|
|
]);
|
|
|
|
$resp = $this->actingAs($admin, 'admin')
|
|
->get(route('admin.sessions.export', ['kind' => '1']))
|
|
->assertOk();
|
|
$this->assertStringContainsString('attachment', (string) $resp->headers->get('content-disposition'));
|
|
$this->assertSame('application/zip', $resp->headers->get('content-type'));
|
|
$this->assertSame('2', $resp->headers->get('x-export-count'));
|
|
|
|
// Verify the ZIP contains two JSON files.
|
|
$body = $resp->streamedContent();
|
|
$tmp = tempnam(sys_get_temp_dir(), 'test_zip_');
|
|
file_put_contents($tmp, $body);
|
|
$zip = new \ZipArchive();
|
|
$this->assertTrue($zip->open($tmp) === true);
|
|
$this->assertSame(2, $zip->numFiles);
|
|
$names = [];
|
|
for ($i = 0; $i < $zip->numFiles; $i++) {
|
|
$names[] = $zip->getNameIndex($i);
|
|
}
|
|
$zip->close();
|
|
@unlink($tmp);
|
|
$this->assertCount(2, $names);
|
|
foreach ($names as $name) {
|
|
$this->assertStringContainsString('tg-', $name);
|
|
$this->assertStringEndsWith('.json', $name);
|
|
}
|
|
}
|
|
|
|
#[Test]
|
|
public function export_returns_error_when_no_data(): void
|
|
{
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.sessions.export', ['kind' => '1']))
|
|
->assertStatus(422)
|
|
->assertJsonPath('code', 1);
|
|
}
|
|
|
|
#[Test]
|
|
public function list_and_export_respect_created_date_range(): void
|
|
{
|
|
Storage::fake('local');
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$device = Device::query()->create([
|
|
'device_id' => 'dev-range',
|
|
'channel_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
]);
|
|
|
|
$old = Carbon::now()->subDays(10)->startOfDay();
|
|
$mid = Carbon::now()->subDays(2)->startOfDay();
|
|
$new = Carbon::now()->startOfDay();
|
|
|
|
foreach ([['id' => 'old', 'at' => $old], ['id' => 'mid', 'at' => $mid], ['id' => 'new', 'at' => $new]] as $row) {
|
|
$session = PluginSession::query()->create([
|
|
'device_id' => $device->id,
|
|
'device_key' => $device->device_id,
|
|
'kind' => PluginSession::KIND_TELEGRAM,
|
|
'account_id' => $row['id'],
|
|
'payload' => ['user_id' => $row['id']],
|
|
]);
|
|
// created_at is guarded, so set it directly on the model and save.
|
|
$session->created_at = $row['at'];
|
|
$session->updated_at = $row['at'];
|
|
$session->save();
|
|
}
|
|
|
|
$ymd = static fn (Carbon $c) => $c->format('Y-m-d');
|
|
|
|
// from = mid day => only mid + new remain (old excluded).
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.telegram.data', ['created_from' => $ymd($mid)]))
|
|
->assertOk()
|
|
->assertJsonPath('count', 2);
|
|
|
|
// to = mid day => only old + mid remain (new excluded).
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.telegram.data', ['created_to' => $ymd($mid)]))
|
|
->assertOk()
|
|
->assertJsonPath('count', 2);
|
|
|
|
// both = exactly mid day => only mid remains.
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.telegram.data', [
|
|
'created_from' => $ymd($mid),
|
|
'created_to' => $ymd($mid),
|
|
]))
|
|
->assertOk()
|
|
->assertJsonPath('count', 1)
|
|
->assertJsonPath('data.0.account_id', 'mid');
|
|
|
|
// Export honors the same range and reports the narrowed count.
|
|
$resp = $this->actingAs($admin, 'admin')
|
|
->get(route('admin.sessions.export', [
|
|
'kind' => '1',
|
|
'created_from' => $ymd($mid),
|
|
'created_to' => $ymd($mid),
|
|
]))
|
|
->assertOk();
|
|
$this->assertSame('1', $resp->headers->get('x-export-count'));
|
|
}
|
|
|
|
#[Test]
|
|
public function agent_export_respects_channel_scope(): void
|
|
{
|
|
Storage::fake('local');
|
|
$agentA = User::query()->create(['username' => 'a', 'password' => 'secret12', 'status' => 1]);
|
|
$chA = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
|
|
$chB = 'cccccccccccccccccccccccccccccccc';
|
|
Channel::query()->create(['channel_id' => $chA, 'user_id' => $agentA->id, 'status' => 1]);
|
|
Channel::query()->create(['channel_id' => $chB, 'user_id' => User::query()->create(['username' => 'b', 'password' => 'secret12', 'status' => 1])->id, 'status' => 1]);
|
|
|
|
$devA = Device::query()->create(['device_id' => 'dev-a', 'channel_id' => $chA]);
|
|
$devB = Device::query()->create(['device_id' => 'dev-b', 'channel_id' => $chB]);
|
|
PluginSession::query()->create([
|
|
'device_id' => $devA->id, 'device_key' => 'dev-a',
|
|
'kind' => PluginSession::KIND_TELEGRAM, 'account_id' => 'aaa',
|
|
'payload' => ['user_id' => 'aaa'],
|
|
]);
|
|
PluginSession::query()->create([
|
|
'device_id' => $devB->id, 'device_key' => 'dev-b',
|
|
'kind' => PluginSession::KIND_TELEGRAM, 'account_id' => 'bbb',
|
|
'payload' => ['user_id' => 'bbb'],
|
|
]);
|
|
|
|
$resp = $this->actingAs($agentA, 'agent')
|
|
->get(route('user.sessions.export', ['kind' => '1']))
|
|
->assertOk();
|
|
$this->assertSame('1', $resp->headers->get('x-export-count'));
|
|
}
|
|
}
|