feature
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user