173 lines
5.8 KiB
PHP
173 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Device;
|
|
use App\Models\TokenviewEvent;
|
|
use App\Models\WalletAddress;
|
|
use App\Services\Tokenview\TokenviewClient;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\TestCase;
|
|
|
|
class TokenviewWebhookTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function sign(string $body, string $key): string
|
|
{
|
|
return hash_hmac('sha256', $body, $key);
|
|
}
|
|
|
|
private function seedMonitoredAddress(array $overrides = []): WalletAddress
|
|
{
|
|
$device = Device::query()->create([
|
|
'device_id' => 'dev-tv-1',
|
|
'channel_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
]);
|
|
|
|
return WalletAddress::query()->create(array_merge([
|
|
'device_id' => $device->id,
|
|
'address' => '0xab5c66752a9e8167967685f1450532fb96d5d24f',
|
|
'chain_type' => 'ETH',
|
|
'source' => 'imToken',
|
|
'eth' => 1.0,
|
|
'usdt' => 10.0,
|
|
'monitor' => 1,
|
|
], $overrides));
|
|
}
|
|
|
|
#[Test]
|
|
public function webhook_rejects_bad_signature_when_sign_key_configured(): void
|
|
{
|
|
config(['coruna.tokenview.sign_key' => 'secret-sign']);
|
|
$body = json_encode(['address' => '0xabc', 'txid' => '0x1', 'coin' => 'ETH', 'value' => '1']);
|
|
|
|
$this->call(
|
|
'POST',
|
|
'/hooks/tokenview',
|
|
[],
|
|
[],
|
|
[],
|
|
[
|
|
'CONTENT_TYPE' => 'application/json',
|
|
'HTTP_X_TOKENVIEW_SIGNATURE' => 'deadbeef',
|
|
],
|
|
$body
|
|
)->assertStatus(401);
|
|
}
|
|
|
|
#[Test]
|
|
public function webhook_applies_inbound_delta_and_dedupes_txid(): void
|
|
{
|
|
config(['coruna.tokenview.sign_key' => 'secret-sign']);
|
|
$addr = $this->seedMonitoredAddress();
|
|
|
|
$payload = [
|
|
'address' => '0xAb5c66752a9e8167967685f1450532fb96d5d24f',
|
|
'txid' => '0xdf244cbc60f4220e5d90de0833b647bd7f376f5132314a1672dd9b5128302659',
|
|
'coin' => 'ETH',
|
|
'value' => '0.5',
|
|
'tokenSymbol' => 'USDT',
|
|
'tokenValue' => '100',
|
|
];
|
|
$body = json_encode($payload);
|
|
$headers = [
|
|
'CONTENT_TYPE' => 'application/json',
|
|
'HTTP_X_TOKENVIEW_SIGNATURE' => $this->sign($body, 'secret-sign'),
|
|
];
|
|
|
|
$this->call('POST', '/hooks/tokenview', [], [], [], $headers, $body)
|
|
->assertOk()
|
|
->assertSee('ok');
|
|
|
|
$addr->refresh();
|
|
$this->assertEqualsWithDelta(1.5, (float) $addr->eth, 0.0000001);
|
|
$this->assertEqualsWithDelta(110.0, (float) $addr->usdt, 0.0000001);
|
|
$this->assertSame(1, TokenviewEvent::query()->count());
|
|
|
|
// Retry same event — no double apply
|
|
$this->call('POST', '/hooks/tokenview', [], [], [], $headers, $body)
|
|
->assertOk();
|
|
|
|
$addr->refresh();
|
|
$this->assertEqualsWithDelta(1.5, (float) $addr->eth, 0.0000001);
|
|
$this->assertEqualsWithDelta(110.0, (float) $addr->usdt, 0.0000001);
|
|
$this->assertSame(1, TokenviewEvent::query()->count());
|
|
}
|
|
|
|
#[Test]
|
|
public function webhook_ignores_address_when_monitor_off(): void
|
|
{
|
|
config(['coruna.tokenview.sign_key' => '']);
|
|
$addr = $this->seedMonitoredAddress(['monitor' => 0, 'eth' => 2.0]);
|
|
|
|
$payload = [
|
|
'address' => $addr->address,
|
|
'txid' => '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
'coin' => 'ETH',
|
|
'value' => '3',
|
|
];
|
|
|
|
$this->postJson('/hooks/tokenview', $payload)->assertOk()->assertSee('ok');
|
|
|
|
$addr->refresh();
|
|
$this->assertEqualsWithDelta(2.0, (float) $addr->eth, 0.0000001);
|
|
$this->assertSame(0, TokenviewEvent::query()->count());
|
|
}
|
|
|
|
#[Test]
|
|
public function monitor_toggle_calls_tokenview_add_and_remove(): void
|
|
{
|
|
config(['coruna.tokenview.api_key' => 'test-key']);
|
|
Http::fake([
|
|
'services.tokenview.io/*' => Http::response(['code' => 1, 'msg' => 'success', 'data' => null], 200),
|
|
]);
|
|
|
|
$admin = \App\Models\Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$addr = $this->seedMonitoredAddress(['monitor' => 0]);
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->putJson(route('admin.addresses.update', $addr), ['monitor' => 1])
|
|
->assertOk()
|
|
->assertJsonPath('data.monitor', 1);
|
|
|
|
Http::assertSent(function ($request) {
|
|
return str_contains($request->url(), '/monitor/address/add/eth/')
|
|
&& str_contains($request->url(), strtolower('0xab5c66752a9e8167967685f1450532fb96d5d24f'));
|
|
});
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->putJson(route('admin.addresses.update', $addr), ['monitor' => 0])
|
|
->assertOk();
|
|
|
|
Http::assertSent(function ($request) {
|
|
return str_contains($request->url(), '/monitor/address/remove/eth/');
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function set_webhook_command_posts_url(): void
|
|
{
|
|
config([
|
|
'coruna.tokenview.api_key' => 'test-key',
|
|
'coruna.tokenview.base_url' => 'https://services.tokenview.io/vipapi',
|
|
'app.url' => 'https://example.test',
|
|
]);
|
|
Http::fake([
|
|
'services.tokenview.io/*' => Http::response(['code' => 1, 'msg' => 'success'], 200),
|
|
]);
|
|
|
|
$this->artisan('tokenview:set-webhook')
|
|
->assertSuccessful();
|
|
|
|
Http::assertSent(function ($request) {
|
|
return str_contains($request->url(), '/monitor/setwebhookurl')
|
|
&& $request->body() === 'https://example.test/hooks/tokenview';
|
|
});
|
|
|
|
$this->assertTrue(app(TokenviewClient::class)->enabled());
|
|
}
|
|
}
|