feat(service): huỷ dịch vụ, SSL, đổi mật khẩu hosting, lịch sử thanh toán theo hình thức
- Lịch sử thanh toán hoá đơn trả thêm `method` (tự động gia hạn / số dư / admin / chuyển khoản / hoàn tiền), bỏ đuôi `(method)` khỏi ghi chú - `DnsServer::panelUrl()` = https://<domain>/ dùng cho email cấp hosting và nút Plesk của khách - Order item trả thêm `uuid`/`type` của dịch vụ để FE dẫn tới trang quản lý - Yêu cầu huỷ dịch vụ, chứng chỉ SSL, lịch sử gia hạn, đổi mật khẩu hosting cho khách Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
a6b641015e
commit
b44a88bb52
@@ -32,6 +32,13 @@ class DnsServerDomainTest extends TestCase
|
||||
$this->assertSame('103.42.58.99', $server->publicHost());
|
||||
}
|
||||
|
||||
public function test_panel_url_is_https_on_public_host_without_port_or_script(): void
|
||||
{
|
||||
$server = new DnsServer(['ip_address' => '103.42.58.99', 'domain' => 'login.tgs.com.vn', 'panel_port' => 8880]);
|
||||
|
||||
$this->assertSame('https://login.tgs.com.vn/', $server->panelUrl());
|
||||
}
|
||||
|
||||
public function test_store_saves_domain_and_rejects_invalid_hostname(): void
|
||||
{
|
||||
$admin = $this->admin();
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\Admin\Product;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductCategories;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProductCategoryFilterTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_category_filter_applies_before_pagination(): void
|
||||
{
|
||||
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Full Administrator');
|
||||
|
||||
$wordpress = ProductCategories::create([
|
||||
'name' => 'HOSTING WORDPRESS',
|
||||
'slug' => 'wordpress-hosting',
|
||||
'type' => 'hosting',
|
||||
'is_active' => true,
|
||||
'sort_order' => 1,
|
||||
]);
|
||||
$other = ProductCategories::create([
|
||||
'name' => 'Other',
|
||||
'slug' => 'other',
|
||||
'type' => 'hosting',
|
||||
'is_active' => true,
|
||||
'sort_order' => 2,
|
||||
]);
|
||||
|
||||
$expected = Product::factory()->create(['product_categories_id' => $wordpress->id]);
|
||||
Product::factory()->count(16)->create(['product_categories_id' => $other->id]);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->getJson("/api/v1/administrator/products?per_page=15&product_categories_id={$wordpress->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.total', 1)
|
||||
->assertJsonCount(1, 'data.items')
|
||||
->assertJsonPath('data.items.0.id', $expected->id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\Admin\Service;
|
||||
|
||||
use App\Http\Resources\Api\Client\Service\ServiceClientDetailResource;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Product;
|
||||
use App\Models\Service;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Tests\TestCase;
|
||||
use ZipArchive;
|
||||
|
||||
class SslCertificateTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $admin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->assignRole('Full Administrator');
|
||||
}
|
||||
|
||||
private function service(string $productType = 'ssl'): Service
|
||||
{
|
||||
$product = Product::factory()->create(['product_type' => $productType, 'name' => 'SSL - Positive']);
|
||||
|
||||
return Service::create(['customer_id' => Customer::factory()->create()->id, 'product_id' => $product->id, 'status' => 'active']);
|
||||
}
|
||||
|
||||
private function zip(array $entries): UploadedFile
|
||||
{
|
||||
$path = tempnam(sys_get_temp_dir(), 'ssl') . '.zip';
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($path, ZipArchive::CREATE);
|
||||
foreach ($entries as $name => $content) {
|
||||
$zip->addFromString($name, $content);
|
||||
}
|
||||
$zip->close();
|
||||
|
||||
return new UploadedFile($path, 'cert.zip', 'application/zip', null, true);
|
||||
}
|
||||
|
||||
public function test_archive_is_extracted_to_text_files_and_visible_to_client(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
$base = "/api/v1/administrator/services/{$service->id}/ssl-info";
|
||||
|
||||
$this->actingAs($this->admin, 'admin')
|
||||
->post("{$base}/archive", ['archive' => $this->zip([
|
||||
'bundle/example.com.crt' => "-----BEGIN CERTIFICATE-----\nabc\n-----END CERTIFICATE-----",
|
||||
'../../evil.key' => 'key-data',
|
||||
'__MACOSX/._example.com.crt' => 'junk',
|
||||
'logo.png' => "\0\1binary",
|
||||
'dir/' => '',
|
||||
])], ['Accept' => 'application/json'])
|
||||
->assertOk()
|
||||
->assertJsonCount(2, 'data.files')
|
||||
->assertJsonPath('data.files.0.name', 'example.com.crt')
|
||||
->assertJsonPath('data.files.1.name', 'evil.key');
|
||||
|
||||
$this->actingAs($this->admin, 'admin')
|
||||
->putJson($base, [
|
||||
'fields' => [['label' => 'CNAME host', 'value' => '_abc.example.com']],
|
||||
'files' => [['name' => 'a.pem', 'content' => 'pem']],
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$this->actingAs($this->admin, 'admin')->getJson($base)
|
||||
->assertJsonPath('data.fields.0.value', '_abc.example.com')
|
||||
->assertJsonCount(1, 'data.files');
|
||||
|
||||
$this->assertStringNotContainsString('pem', (string) \DB::table('service_ssl_certificates')->value('data'));
|
||||
|
||||
$payload = (new ServiceClientDetailResource($service->fresh()))->toArray(new Request());
|
||||
$this->assertSame('a.pem', $payload['ssl_certificate']['files'][0]['name']);
|
||||
}
|
||||
|
||||
public function test_non_ssl_service_is_rejected(): void
|
||||
{
|
||||
$service = $this->service('domain');
|
||||
|
||||
$this->actingAs($this->admin, 'admin')
|
||||
->getJson("/api/v1/administrator/services/{$service->id}/ssl-info")
|
||||
->assertStatus(422);
|
||||
}
|
||||
|
||||
public function test_customer_downloads_own_certificate_zip_only(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
$service->sslCertificate()->create(['data' => [
|
||||
'fields' => [['label' => 'CNAME', 'value' => 'abc']],
|
||||
'files' => [['name' => 'a.pem', 'content' => 'pem-data']],
|
||||
]]);
|
||||
$owner = Customer::find($service->customer_id);
|
||||
$path = "/v1/client/client-service/{$service->uuid}/ssl-archive";
|
||||
|
||||
$response = $this->actingAs($owner, 'client')->withHeaders(['x-access-token' => $this->buildClientAccessToken()])->get($path);
|
||||
$response->assertOk();
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($response->baseResponse->getFile()->getPathname());
|
||||
$this->assertSame('pem-data', $zip->getFromName('a.pem'));
|
||||
$this->assertStringContainsString('CNAME: abc', $zip->getFromName('thong-tin.txt'));
|
||||
|
||||
$this->actingAs(Customer::factory()->create(), 'client')->withHeaders(['x-access-token' => $this->buildClientAccessToken()])->get($path)->assertNotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\Client;
|
||||
|
||||
use App\Mail\Admin\ServiceCancellationRequestMail;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Notification;
|
||||
use App\Models\Service;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ServiceCancellationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Customer $owner;
|
||||
private Service $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Mail::fake();
|
||||
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
|
||||
User::factory()->create()->assignRole('Full Administrator');
|
||||
Setting::updateOrCreate(['key' => 'setting'], ['value' => json_encode(['admin_email' => 'admin@example.com'])]);
|
||||
|
||||
$this->owner = Customer::factory()->create();
|
||||
$this->service = Service::create(['customer_id' => $this->owner->id, 'type' => 'ssl', 'status' => 'active', 'server_name' => 'example.com']);
|
||||
}
|
||||
|
||||
private function as(Customer $customer): static
|
||||
{
|
||||
return $this->actingAs($customer, 'client')->withHeaders(['x-access-token' => $this->buildClientAccessToken()]);
|
||||
}
|
||||
|
||||
public function test_request_notifies_admins_and_blocks_duplicates_until_withdrawn(): void
|
||||
{
|
||||
$url = "/v1/client/client-service/{$this->service->uuid}/cancel-request";
|
||||
|
||||
$this->as($this->owner)->postJson($url, ['reason_code' => 'other', 'reason' => 'Chuyển sang nhà cung cấp khác'])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.reason', 'Chuyển sang nhà cung cấp khác');
|
||||
|
||||
$this->assertSame(1, Notification::where('type', 'service_cancellation_request')->where('recipient_type', 'admin')->count());
|
||||
Mail::assertQueued(ServiceCancellationRequestMail::class, fn ($mail) => $mail->hasTo('admin@example.com'));
|
||||
|
||||
$this->as($this->owner)->postJson($url, ['reason' => 'Lặp lại'])->assertStatus(422);
|
||||
|
||||
$this->as($this->owner)->deleteJson($url)->assertOk();
|
||||
$this->as($this->owner)->postJson($url, ['reason' => 'Gửi lại sau khi rút'])->assertCreated();
|
||||
}
|
||||
|
||||
public function test_other_customer_cannot_request_and_reason_is_required(): void
|
||||
{
|
||||
$url = "/v1/client/client-service/{$this->service->uuid}/cancel-request";
|
||||
|
||||
$this->as(Customer::factory()->create())->postJson($url, ['reason' => 'Hủy giúp'])->assertNotFound();
|
||||
$this->as($this->owner)->postJson($url, [])->assertStatus(422);
|
||||
$this->assertSame(0, Notification::where('type', 'service_cancellation_request')->count());
|
||||
}
|
||||
|
||||
public function test_admin_approves_or_rejects_once_and_customer_is_notified(): void
|
||||
{
|
||||
$admin = User::first();
|
||||
$pending = $this->service->cancellationRequests()->create(['customer_id' => $this->owner->id, 'reason' => 'Không dùng nữa']);
|
||||
$base = "/api/v1/administrator/services/{$this->service->id}/cancellation-requests";
|
||||
|
||||
$this->actingAs($admin, 'admin')->postJson("{$base}/{$pending->id}/resolve", ['action' => 'reject'])->assertStatus(422);
|
||||
$this->actingAs($admin, 'admin')->postJson("{$base}/{$pending->id}/resolve", ['action' => 'reject', 'note' => 'Còn hợp đồng'])->assertOk();
|
||||
$this->assertSame('active', $this->service->fresh()->status);
|
||||
$this->actingAs($admin, 'admin')->postJson("{$base}/{$pending->id}/resolve", ['action' => 'approve'])->assertStatus(422);
|
||||
|
||||
$second = $this->service->cancellationRequests()->create(['customer_id' => $this->owner->id, 'reason' => 'Lần hai']);
|
||||
$this->actingAs($admin, 'admin')->postJson("{$base}/{$second->id}/resolve", ['action' => 'approve'])->assertOk();
|
||||
|
||||
$this->assertSame('cancelled', $this->service->fresh()->status);
|
||||
$this->assertSame(2, Notification::where('recipient_type', 'client')->where('type', 'service_cancellation_resolved')->count());
|
||||
$this->actingAs($admin, 'admin')->getJson($base)->assertJsonCount(2, 'data');
|
||||
}
|
||||
|
||||
public function test_approved_cancellation_keeps_service_until_expiry_then_cancels(): void
|
||||
{
|
||||
$this->service->update(['expires_at' => now()->addDays(5), 'auto_renew' => true]);
|
||||
$pending = $this->service->cancellationRequests()->create(['customer_id' => $this->owner->id, 'reason' => 'Không dùng nữa']);
|
||||
|
||||
$this->actingAs(User::first(), 'admin')
|
||||
->postJson("/api/v1/administrator/services/{$this->service->id}/cancellation-requests/{$pending->id}/resolve", ['action' => 'approve'])
|
||||
->assertOk();
|
||||
|
||||
$service = $this->service->fresh();
|
||||
$this->assertSame('active', $service->status);
|
||||
$this->assertTrue($service->cancel_at_expiry);
|
||||
$this->assertFalse($service->auto_renew);
|
||||
$this->assertNull($service->cancelled_at);
|
||||
|
||||
$this->as($this->owner)->postJson("/v1/client/client-service/{$service->uuid}/cancel-request", ['reason' => 'Gửi lại'])->assertStatus(422);
|
||||
|
||||
$this->artisan('service:expiry')->assertSuccessful();
|
||||
$this->assertSame('active', $service->fresh()->status);
|
||||
|
||||
$service->update(['expires_at' => now()->subHour()]);
|
||||
$this->artisan('service:expiry')->assertSuccessful();
|
||||
$this->assertSame('cancelled', $service->fresh()->status);
|
||||
$this->assertNotNull($service->fresh()->cancelled_at);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,13 @@ use App\Models\OrderItem;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductPricings;
|
||||
use App\Models\Service;
|
||||
use App\Mail\ServiceRenewedMail;
|
||||
use App\Models\Notification;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Tests\TestCase;
|
||||
|
||||
class RenewServiceTest extends TestCase
|
||||
@@ -108,4 +114,51 @@ class RenewServiceTest extends TestCase
|
||||
$this->renew(2)->assertStatus(422);
|
||||
$this->assertSame(0, Invoice::where('invoice_type', 2)->count());
|
||||
}
|
||||
|
||||
public function test_customer_renewal_notifies_admins_by_notification_and_email(): void
|
||||
{
|
||||
Mail::fake();
|
||||
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
|
||||
User::factory()->create()->assignRole('Full Administrator');
|
||||
Setting::updateOrCreate(['key' => 'setting'], ['value' => json_encode(['admin_email' => 'admin@example.com'])]);
|
||||
|
||||
$this->renew(3)->assertOk();
|
||||
|
||||
$notification = Notification::where('type', 'service_renewed_by_customer')->where('recipient_type', 'admin')->sole();
|
||||
$this->assertSame('/services/' . $this->service->id, $notification->link);
|
||||
Mail::assertQueued(ServiceRenewedMail::class, fn ($mail) => $mail->hasTo('admin@example.com'));
|
||||
}
|
||||
|
||||
public function test_customer_reads_own_renewal_history_only(): void
|
||||
{
|
||||
$this->renew(3)->assertOk();
|
||||
$path = "/v1/client/client-service/{$this->service->uuid}/renewals";
|
||||
$headers = ['x-access-token' => $this->buildClientAccessToken()];
|
||||
|
||||
$this->actingAs($this->customer, 'client')->withHeaders($headers)->getJson($path)
|
||||
->assertOk()
|
||||
->assertJsonCount(1, 'data')
|
||||
->assertJsonPath('data.0.months', 3)
|
||||
->assertJsonPath('data.0.by', 'Bạn')
|
||||
->assertJsonPath('data.0.amount', 40000);
|
||||
|
||||
$this->actingAs(Customer::factory()->create(), 'client')->withHeaders($headers)->getJson($path)->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_renewal_invoice_detail_reports_renewal_term_and_next_date(): void
|
||||
{
|
||||
$expiresAt = $this->service->expires_at->copy();
|
||||
$this->renew(3)->assertOk();
|
||||
$invoice = Invoice::where('invoice_type', 2)->sole();
|
||||
|
||||
$this->actingAs($this->customer, 'client')
|
||||
->withHeaders(['x-access-token' => $this->buildClientAccessToken()])
|
||||
->getJson("/v1/client/invoices/order/ORD-RENEW-TEST?invoice={$invoice->invoice_number}")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.service.months', 3)
|
||||
->assertJsonPath('data.items.0.months', 3)
|
||||
->assertJsonPath('data.items.0.price', 40000)
|
||||
->assertJsonPath('data.billing_period_end', $expiresAt->copy()->addMonths(3)->toDateString())
|
||||
->assertJsonPath('data.next_renewal_date', $expiresAt->copy()->addMonths(3)->toDateString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Support\ServiceGroup;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ServiceGroupTest extends TestCase
|
||||
{
|
||||
public function test_of_classifies_by_type_then_name(): void
|
||||
{
|
||||
$this->assertSame('email', ServiceGroup::of('email', 'Email Business'));
|
||||
$this->assertSame('ssl', ServiceGroup::of('ssl', 'Whatever'));
|
||||
$this->assertSame('ssl', ServiceGroup::of('other', 'SSL - Positive'));
|
||||
$this->assertSame('colo', ServiceGroup::of('other', 'COLO VNPT STAR'));
|
||||
$this->assertSame('colo', ServiceGroup::of('server_rental', 'Chỗ đặt 1U'));
|
||||
$this->assertSame('dedicated', ServiceGroup::of('other', 'DEDICATED GOLD'));
|
||||
$this->assertSame('dedicated', ServiceGroup::of('server_rental', 'Máy chủ X'));
|
||||
$this->assertNull(ServiceGroup::of('other', 'Khác'));
|
||||
$this->assertNull(ServiceGroup::of('vps', 'colo vps'));
|
||||
}
|
||||
|
||||
public function test_constrain_sql_matches_of(): void
|
||||
{
|
||||
foreach (ServiceGroup::ALL as $group) {
|
||||
$sql = ServiceGroup::constrain(Product::query(), $group)->toSql();
|
||||
$this->assertStringContainsString('product_type', $sql, $group);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class TemplateSeederTest extends TestCase
|
||||
public function test_service_lifecycle_templates_are_seeded_once_as_marketing(): void
|
||||
{
|
||||
$items = ServiceLifecycleTemplates::all();
|
||||
$this->assertCount(37, $items);
|
||||
$this->assertCount(39, $items);
|
||||
|
||||
$this->seed(TemplateSeeder::class);
|
||||
$this->seed(TemplateSeeder::class);
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Http\Middleware\SealResponse;
|
||||
use App\Support\ResponseCipher;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ResponseCipherTest extends TestCase
|
||||
{
|
||||
private function key(): string
|
||||
{
|
||||
return str_repeat('k', 32);
|
||||
}
|
||||
|
||||
private function request(array $body = [], bool $optIn = false, ?string $key = null): Request
|
||||
{
|
||||
$request = Request::create('/x', 'POST', [], [], [], ['CONTENT_TYPE' => 'application/json'], json_encode($body));
|
||||
$request->headers->set('x-seal-key', base64_encode($key ?? $this->key()));
|
||||
if ($optIn) {
|
||||
$request->headers->set('x-seal', '1');
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function test_always_mode_seals_with_session_key_and_is_reversible(): void
|
||||
{
|
||||
$response = (new SealResponse())->handle(
|
||||
$this->request(),
|
||||
fn () => new JsonResponse(['data' => ['password' => 'Cloud226*()']]),
|
||||
'always',
|
||||
);
|
||||
|
||||
$body = $response->getData(true);
|
||||
$this->assertArrayNotHasKey('data', $body);
|
||||
$this->assertStringNotContainsString('Cloud226', $response->getContent());
|
||||
$this->assertSame(
|
||||
['data' => ['password' => 'Cloud226*()']],
|
||||
json_decode(ResponseCipher::open($body['enc'], $this->key()), true),
|
||||
);
|
||||
$this->assertNull(ResponseCipher::open($body['enc'], str_repeat('x', 32)));
|
||||
}
|
||||
|
||||
public function test_passthrough_without_key_or_opt_in(): void
|
||||
{
|
||||
$noKey = Request::create('/x');
|
||||
$noKey->headers->set('x-seal', '1');
|
||||
$plain = (new SealResponse())->handle($noKey, fn () => new JsonResponse(['a' => 1]));
|
||||
$this->assertSame(['a' => 1], $plain->getData(true));
|
||||
|
||||
$noOptIn = (new SealResponse())->handle($this->request(), fn () => new JsonResponse(['a' => 1]));
|
||||
$this->assertSame(['a' => 1], $noOptIn->getData(true));
|
||||
}
|
||||
|
||||
public function test_opt_in_seals_errors_and_opens_request_body(): void
|
||||
{
|
||||
$sealed = ['enc' => ResponseCipher::seal(json_encode(['name' => 'An']), $this->key())];
|
||||
|
||||
$response = (new SealResponse())->handle(
|
||||
$this->request($sealed, optIn: true),
|
||||
fn (Request $r) => new JsonResponse(['got' => $r->input('name')], 422),
|
||||
);
|
||||
|
||||
$this->assertSame(422, $response->getStatusCode());
|
||||
$this->assertSame(
|
||||
['got' => 'An'],
|
||||
json_decode(ResponseCipher::open($response->getData(true)['enc'], $this->key()), true),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_request_sealed_with_stale_key_gets_428(): void
|
||||
{
|
||||
$sealed = ['enc' => ResponseCipher::seal('{"a":1}', str_repeat('o', 32))];
|
||||
|
||||
$response = (new SealResponse())->handle(
|
||||
$this->request($sealed, optIn: true),
|
||||
fn () => new JsonResponse(['ok' => true]),
|
||||
);
|
||||
|
||||
$this->assertSame(428, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user