feat: bot
This commit is contained in:
@@ -356,4 +356,51 @@ class AdminAgentPortalTest extends TestCase
|
||||
])
|
||||
->assertStatus(405);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function admin_can_send_agent_telegram_test(): void
|
||||
{
|
||||
config(['coruna.telegram.bot_token' => 'system-token']);
|
||||
\Illuminate\Support\Facades\Http::fake([
|
||||
'api.telegram.org/*' => \Illuminate\Support\Facades\Http::response(['ok' => true], 200),
|
||||
]);
|
||||
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$agent = User::query()->create([
|
||||
'username' => 'tgagent',
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
'chat_id' => '-200',
|
||||
]);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->post(route('admin.agents.telegramTest'), [
|
||||
'agent_id' => $agent->id,
|
||||
'chat_id' => '-300',
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 0);
|
||||
|
||||
\Illuminate\Support\Facades\Http::assertSent(function ($request) {
|
||||
$text = (string) ($request->data()['text'] ?? '');
|
||||
|
||||
return str_contains($request->url(), '/botsystem-token/')
|
||||
&& ($request->data()['chat_id'] ?? null) === '-300'
|
||||
&& str_contains($text, 'Telegram 测试')
|
||||
&& str_contains($text, 'tgagent');
|
||||
});
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function agent_telegram_test_requires_chat_id(): void
|
||||
{
|
||||
config(['coruna.telegram.bot_token' => 'system-token']);
|
||||
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$this->actingAs($admin, 'admin')
|
||||
->post(route('admin.agents.telegramTest'), [])
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 1)
|
||||
->assertJsonPath('msg', '未填写 Chat ID');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,16 @@ class SystemAdminTest extends TestCase
|
||||
public function super_admin_can_save_settings(): void
|
||||
{
|
||||
$super = $this->superAdmin();
|
||||
\Illuminate\Support\Facades\Http::fake(function ($request) {
|
||||
if (str_contains($request->url(), 'getMe')) {
|
||||
return \Illuminate\Support\Facades\Http::response([
|
||||
'ok' => true,
|
||||
'result' => ['id' => 1, 'is_bot' => true, 'username' => 'test_bot'],
|
||||
], 200);
|
||||
}
|
||||
|
||||
return \Illuminate\Support\Facades\Http::response(['ok' => true], 200);
|
||||
});
|
||||
|
||||
$this->actingAs($super, 'admin')
|
||||
->post(route('admin.system.settings.update'), [
|
||||
@@ -75,10 +85,13 @@ class SystemAdminTest extends TestCase
|
||||
'panel_agent_hosts' => 'agent.example.com',
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 0);
|
||||
->assertJsonPath('code', 0)
|
||||
->assertJsonPath('data.bot_username', '@test_bot');
|
||||
|
||||
$this->assertSame('bot:token', Setting::query()->where('key', 'telegram.bot_token')->value('value'));
|
||||
$this->assertSame('test_bot', Setting::query()->where('key', 'telegram.bot_username')->value('value'));
|
||||
$this->assertSame('bot:token', config('coruna.telegram.bot_token'));
|
||||
$this->assertSame('test_bot', config('coruna.telegram.bot_username'));
|
||||
$this->assertSame(5, (int) config('coruna.channels.max_per_agent'));
|
||||
$this->assertSame(['cdn1.example.com', 'cdn2.example.com'], config('coruna.channel_domains'));
|
||||
$this->assertSame(
|
||||
@@ -138,4 +151,85 @@ class SystemAdminTest extends TestCase
|
||||
|
||||
$this->assertDatabaseHas('admins', ['id' => $super->id]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function super_admin_can_send_telegram_test(): void
|
||||
{
|
||||
config([
|
||||
'coruna.telegram.bot_token' => 'saved-token',
|
||||
'coruna.telegram.owner_chat_id' => '100',
|
||||
]);
|
||||
\Illuminate\Support\Facades\Http::fake([
|
||||
'api.telegram.org/*' => \Illuminate\Support\Facades\Http::response(['ok' => true], 200),
|
||||
]);
|
||||
|
||||
$this->actingAs($this->superAdmin(), 'admin')
|
||||
->post(route('admin.system.settings.telegramTest'), [
|
||||
'chat_id' => '-1009',
|
||||
'bot_token' => 'form-token',
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 0);
|
||||
|
||||
\Illuminate\Support\Facades\Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/botform-token/')
|
||||
&& ($request->data()['chat_id'] ?? null) === '-1009'
|
||||
&& str_contains((string) ($request->data()['text'] ?? ''), 'Telegram 测试');
|
||||
});
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function telegram_test_reports_telegram_error(): void
|
||||
{
|
||||
config(['coruna.telegram.bot_token' => 'saved-token']);
|
||||
\Illuminate\Support\Facades\Http::fake([
|
||||
'api.telegram.org/*' => \Illuminate\Support\Facades\Http::response([
|
||||
'ok' => false,
|
||||
'description' => 'Bad Request: chat not found',
|
||||
], 400),
|
||||
]);
|
||||
|
||||
$this->actingAs($this->superAdmin(), 'admin')
|
||||
->post(route('admin.system.settings.telegramTest'), [
|
||||
'chat_id' => '999',
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 1)
|
||||
->assertJsonPath('msg', 'Bad Request: chat not found');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function normal_admin_cannot_test_official_telegram(): void
|
||||
{
|
||||
$this->actingAs($this->normalAdmin(), 'admin')
|
||||
->post(route('admin.system.settings.telegramTest'), ['chat_id' => '1'])
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function settings_page_shows_bot_username_from_getMe(): void
|
||||
{
|
||||
config([
|
||||
'coruna.telegram.bot_token' => 'saved-token',
|
||||
'coruna.telegram.bot_username' => '',
|
||||
]);
|
||||
\Illuminate\Support\Facades\Http::fake(function ($request) {
|
||||
if (str_contains($request->url(), 'getMe')) {
|
||||
return \Illuminate\Support\Facades\Http::response([
|
||||
'ok' => true,
|
||||
'result' => ['username' => 'ops_bot'],
|
||||
], 200);
|
||||
}
|
||||
|
||||
return \Illuminate\Support\Facades\Http::response(['ok' => true], 200);
|
||||
});
|
||||
|
||||
$this->actingAs($this->superAdmin(), 'admin')
|
||||
->get(route('admin.system.settings.index'))
|
||||
->assertOk()
|
||||
->assertSee('@ops_bot')
|
||||
->assertSee('t.me/ops_bot?startgroup=1', false);
|
||||
|
||||
$this->assertSame('ops_bot', Setting::query()->where('key', 'telegram.bot_username')->value('value'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use PHPUnit\Framework\Attributes\Test;
|
||||
use SergiX44\Nutgram\Nutgram;
|
||||
use SergiX44\Nutgram\Telegram\Properties\ChatMemberStatus;
|
||||
use SergiX44\Nutgram\Telegram\Properties\ChatType;
|
||||
use SergiX44\Nutgram\Telegram\Properties\UpdateType;
|
||||
use SergiX44\Nutgram\Testing\FakeNutgram;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -386,14 +387,12 @@ class TelegramBotTest extends TestCase
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
'chat_id' => (string) self::AGENT_CHAT,
|
||||
'bot_token' => '111:AAA',
|
||||
]);
|
||||
$agentB = User::query()->create([
|
||||
'username' => 'agent_b',
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
'chat_id' => '-100111',
|
||||
'bot_token' => '222:BBB',
|
||||
]);
|
||||
|
||||
$chA = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
||||
@@ -418,8 +417,6 @@ class TelegramBotTest extends TestCase
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
app(TelegramBotContext::class)->setAgent($agentA);
|
||||
|
||||
/** @var FakeNutgram $bot */
|
||||
$bot = app(Nutgram::class);
|
||||
$bot->willReceivePartial([
|
||||
@@ -471,7 +468,6 @@ class TelegramBotTest extends TestCase
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
'chat_id' => (string) self::AGENT_CHAT,
|
||||
'bot_token' => '111:AAA',
|
||||
]);
|
||||
Channel::query()->create([
|
||||
'channel_id' => 'cccccccccccccccccccccccccccccccc',
|
||||
@@ -526,18 +522,17 @@ class TelegramBotTest extends TestCase
|
||||
|
||||
$history2 = $bot2->getRequestHistory();
|
||||
$transferMsg = FakeNutgram::getActualData(array_values($history2[array_key_last($history2)])[0]);
|
||||
$this->assertStringContainsString('仅限官方', $transferMsg['text'] ?? '');
|
||||
$this->assertStringContainsString('无权操作该地址', $transferMsg['text'] ?? '');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function agent_help_hides_transfer(): void
|
||||
public function agent_help_lists_transfer_for_own_devices(): void
|
||||
{
|
||||
$agent = User::query()->create([
|
||||
'username' => 'agent_help',
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
'chat_id' => (string) self::AGENT_CHAT,
|
||||
'bot_token' => '111:AAA',
|
||||
]);
|
||||
app(TelegramBotContext::class)->setAgent($agent);
|
||||
|
||||
@@ -557,46 +552,150 @@ class TelegramBotTest extends TestCase
|
||||
$history = $bot->getRequestHistory();
|
||||
$msg = FakeNutgram::getActualData(array_values($history[array_key_last($history)])[0]);
|
||||
$this->assertStringContainsString('/data', $msg['text'] ?? '');
|
||||
$this->assertStringContainsString('不支持 /transfer', $msg['text'] ?? '');
|
||||
$this->assertStringNotContainsString('/transfer <fromAddress>', $msg['text'] ?? '');
|
||||
$this->assertStringContainsString('/transfer <fromAddress>', $msg['text'] ?? '');
|
||||
$this->assertStringNotContainsString('仅可查看/转出本代理渠道设备', $msg['text'] ?? '');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function agent_webhook_requires_secret_and_config(): void
|
||||
public function agent_can_transfer_own_device_address(): void
|
||||
{
|
||||
$bare = User::query()->create([
|
||||
'username' => 'agent_hook_bare',
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
]);
|
||||
$this->postJson('/hooks/telegram/agent/'.$bare->id, ['update_id' => 1])
|
||||
->assertNotFound();
|
||||
|
||||
$agent = User::query()->create([
|
||||
'username' => 'agent_hook',
|
||||
'username' => 'agent_xfer',
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
'bot_token' => '111:AAA',
|
||||
'chat_id' => (string) self::AGENT_CHAT,
|
||||
]);
|
||||
$this->assertTrue($agent->fresh()->hasTelegramBot());
|
||||
$channelId = 'cccccccccccccccccccccccccccccccc';
|
||||
Channel::query()->create([
|
||||
'channel_id' => $channelId,
|
||||
'user_id' => $agent->id,
|
||||
'status' => 1,
|
||||
]);
|
||||
$device = \App\Models\Device::query()->create([
|
||||
'device_id' => 'dev-agent-xfer',
|
||||
'channel_id' => $channelId,
|
||||
]);
|
||||
\App\Models\WalletAddress::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'address' => 'TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH',
|
||||
'chain_type' => 'TRON',
|
||||
'source' => 'imToken',
|
||||
]);
|
||||
|
||||
$this->postJson('/hooks/telegram/agent/'.$agent->id, ['update_id' => 1], [
|
||||
'X-Telegram-Bot-Api-Secret-Token' => 'wrong',
|
||||
])->assertForbidden();
|
||||
$mock = Mockery::mock(TransferService::class);
|
||||
$mock->shouldReceive('handle')
|
||||
->once()
|
||||
->andReturn([
|
||||
'ok' => true,
|
||||
'txid' => 'agentbeef',
|
||||
'from' => 'TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH',
|
||||
'to' => 'TPayoutAddressxxxxxxxxxxxxxxxxxxxxxx',
|
||||
'amount' => '1',
|
||||
'asset' => 'USDT',
|
||||
]);
|
||||
$this->app->instance(TransferService::class, $mock);
|
||||
|
||||
$secret = TelegramBotContext::webhookSecretFor($agent->fresh());
|
||||
$this->call(
|
||||
'POST',
|
||||
'/hooks/telegram/agent/'.$agent->id,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
'HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN' => $secret,
|
||||
/** @var FakeNutgram $bot */
|
||||
$bot = app(Nutgram::class);
|
||||
$bot->willReceivePartial([
|
||||
'status' => ChatMemberStatus::ADMINISTRATOR->value,
|
||||
'user' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'AgentAdmin'],
|
||||
'can_manage_chat' => true,
|
||||
]);
|
||||
$bot->hearMessage([
|
||||
'text' => '/transfer TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH USDT 1',
|
||||
'chat' => ['id' => self::AGENT_CHAT, 'type' => ChatType::SUPERGROUP->value],
|
||||
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'AgentAdmin'],
|
||||
])->reply();
|
||||
|
||||
$history = $bot->getRequestHistory();
|
||||
$last = FakeNutgram::getActualData(array_values($history[array_key_last($history)])[0]);
|
||||
$this->assertStringContainsString('agentbeef', $last['text'] ?? '');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function agent_webhook_route_is_removed(): void
|
||||
{
|
||||
$this->postJson('/hooks/telegram/agent/1', ['update_id' => 1])
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function joining_a_group_announces_chat_id(): void
|
||||
{
|
||||
$chatId = -1005556667778;
|
||||
|
||||
/** @var FakeNutgram $bot */
|
||||
$bot = app(Nutgram::class);
|
||||
$bot->hearUpdateType(UpdateType::MY_CHAT_MEMBER, [
|
||||
'chat' => [
|
||||
'id' => $chatId,
|
||||
'type' => ChatType::SUPERGROUP->value,
|
||||
'title' => '代理测试群',
|
||||
],
|
||||
json_encode(['update_id' => 1])
|
||||
)->assertNoContent();
|
||||
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'Admin'],
|
||||
'date' => time(),
|
||||
'old_chat_member' => [
|
||||
'status' => ChatMemberStatus::LEFT->value,
|
||||
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
|
||||
],
|
||||
'new_chat_member' => [
|
||||
'status' => ChatMemberStatus::MEMBER->value,
|
||||
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
|
||||
],
|
||||
])->reply();
|
||||
|
||||
$bot->assertCalled('sendMessage', 1);
|
||||
$history = $bot->getRequestHistory();
|
||||
$last = FakeNutgram::getActualData(array_values($history[array_key_last($history)])[0]);
|
||||
$this->assertStringContainsString((string) $chatId, $last['text'] ?? '');
|
||||
$this->assertStringContainsString('Chat ID', $last['text'] ?? '');
|
||||
$this->assertStringContainsString('代理测试群', $last['text'] ?? '');
|
||||
$copy = (string) data_get($last, 'reply_markup.inline_keyboard.0.0.copy_text.text', '');
|
||||
$this->assertSame((string) $chatId, $copy);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function promoting_or_leaving_does_not_announce_chat_id(): void
|
||||
{
|
||||
/** @var FakeNutgram $bot */
|
||||
$bot = app(Nutgram::class);
|
||||
|
||||
$bot->hearUpdateType(UpdateType::MY_CHAT_MEMBER, [
|
||||
'chat' => ['id' => -1001, 'type' => ChatType::SUPERGROUP->value, 'title' => 'x'],
|
||||
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'Admin'],
|
||||
'date' => time(),
|
||||
'old_chat_member' => [
|
||||
'status' => ChatMemberStatus::MEMBER->value,
|
||||
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
|
||||
],
|
||||
'new_chat_member' => [
|
||||
'status' => ChatMemberStatus::ADMINISTRATOR->value,
|
||||
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
|
||||
],
|
||||
])->reply();
|
||||
$bot->assertCalled('sendMessage', 0);
|
||||
|
||||
$this->app->forgetInstance(Nutgram::class);
|
||||
$this->app->forgetInstance('nutgram');
|
||||
$this->app->forgetInstance('telegram');
|
||||
$this->app->forgetInstance(FakeNutgram::class);
|
||||
|
||||
/** @var FakeNutgram $bot2 */
|
||||
$bot2 = app(Nutgram::class);
|
||||
$bot2->hearUpdateType(UpdateType::MY_CHAT_MEMBER, [
|
||||
'chat' => ['id' => -1001, 'type' => ChatType::SUPERGROUP->value, 'title' => 'x'],
|
||||
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'Admin'],
|
||||
'date' => time(),
|
||||
'old_chat_member' => [
|
||||
'status' => ChatMemberStatus::MEMBER->value,
|
||||
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
|
||||
],
|
||||
'new_chat_member' => [
|
||||
'status' => ChatMemberStatus::LEFT->value,
|
||||
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
|
||||
],
|
||||
])->reply();
|
||||
$bot2->assertCalled('sendMessage', 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class TelegramNotifierRoutingTest extends TestCase
|
||||
use RefreshDatabase;
|
||||
|
||||
#[Test]
|
||||
public function agent_device_notifies_agent_bot(): void
|
||||
public function agent_device_notifies_agent_chat(): void
|
||||
{
|
||||
config([
|
||||
'coruna.telegram.bot_token' => 'system-token',
|
||||
@@ -27,7 +27,6 @@ class TelegramNotifierRoutingTest extends TestCase
|
||||
'username' => 'agent_tg',
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
'bot_token' => 'agent-token',
|
||||
'chat_id' => '200',
|
||||
]);
|
||||
$channelId = 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||
@@ -46,9 +45,18 @@ class TelegramNotifierRoutingTest extends TestCase
|
||||
app(TelegramNotifier::class)->notifyNewDevice('dev-agent-1', '16.0', '1.2.3.4');
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/botagent-token/')
|
||||
&& ($request->data()['chat_id'] ?? null) === '200';
|
||||
$text = (string) ($request->data()['text'] ?? '');
|
||||
|
||||
return str_contains($request->url(), '/botsystem-token/')
|
||||
&& ($request->data()['chat_id'] ?? null) === '200'
|
||||
&& str_contains($text, 'ChannelID')
|
||||
&& str_contains($text, 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee');
|
||||
});
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/botsystem-token/')
|
||||
&& ($request->data()['chat_id'] ?? null) === '100';
|
||||
});
|
||||
Http::assertSentCount(2);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -75,9 +83,14 @@ class TelegramNotifierRoutingTest extends TestCase
|
||||
app(TelegramNotifier::class)->notifyNewDevice('dev-official-1', '16.0', '1.2.3.4');
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
$text = (string) ($request->data()['text'] ?? '');
|
||||
|
||||
return str_contains($request->url(), '/botsystem-token/')
|
||||
&& ($request->data()['chat_id'] ?? null) === '100';
|
||||
&& ($request->data()['chat_id'] ?? null) === '100'
|
||||
&& str_contains($text, 'ChannelID')
|
||||
&& str_contains($text, 'ffffffffffffffffffffffffffffffff');
|
||||
});
|
||||
Http::assertSentCount(1);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -109,8 +122,13 @@ class TelegramNotifierRoutingTest extends TestCase
|
||||
app(TelegramNotifier::class)->notifyNewDevice('dev-fallback', '16.0', '1.2.3.4');
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
$text = (string) ($request->data()['text'] ?? '');
|
||||
|
||||
return str_contains($request->url(), '/botsystem-token/')
|
||||
&& ($request->data()['chat_id'] ?? null) === '100';
|
||||
&& ($request->data()['chat_id'] ?? null) === '100'
|
||||
&& str_contains($text, 'ChannelID')
|
||||
&& str_contains($text, '12121212121212121212121212121212');
|
||||
});
|
||||
Http::assertSentCount(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user