user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); } public function test_page_renders_mirror_rows_second_page_and_filters(): void { $this->grantAdminPermission(); setting()->set('default.list_limit', 25); $states = [ NonJingxiRefundMirrorRepository::INFO_DATASET => ['status' => 'running', 'initial_completed_at' => '2026-08-15 00:00:00'], NonJingxiRefundMirrorRepository::ITEM_DATASET => ['status' => 'running', 'initial_completed_at' => '2026-08-15 00:00:00'], ]; $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('states')->once()->andReturn($states); $repository->shouldReceive('syncStatus')->once()->with($states)->andReturn([ 'code' => 'syncing', 'last_succeeded_at' => '2026-08-21 12:00:00', ]); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('paginate')->once()->withArgs( static fn (array $filters, int $page, int $limit): bool => $filters === [ 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', ] && $page === 2 && $limit === 25 )->andReturn([ 'data' => [(object) [ 'Id' => 102, 'refundDate' => '2026-08-20 10:00:00.123456', 'as_id' => 2002, 'outer_as_id' => 'AS-2002', 'waybillNo' => 'WB-2002', 'o_id' => 3002, 'so_id' => 'ORDER-1', 'type' => '退款', 'node' => '完成', 'isZC' => '是', 'shop_name' => '测试店', 'platform' => '天猫', 'responsibleDepartment' => '电商部', 'totalRefundMoney' => 99.5, 'originPostcode' => 'ZIP', 'shop_i_id' => 'ITEM-1', 'shop_sku_id' => 'SHOP-SKU-1', 'i_id' => 'STYLE-1', 'sku_id' => 'SKU-1', 'buyer_paid_amount' => 120, 'qty' => 1, 'unitCostGoods' => 50, 'detailRefundMoney' => 99.5, 'refundType' => '退货退款', 'refundQty' => 1, 'refundCostGoods' => 50, 'RefundMoneyIncome' => 99.5, 'AfSalesMoney' => 99.5, 'createTime' => '2026-08-20 09:00:00', 'status' => '完成', 'shop_status' => '完成', 'created' => '2026-08-20 09:00:00', 'confirm_date' => '2026-08-20 10:00:00', ]], 'total' => 26, ]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $url = route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'refunds', 'page' => 'non-jingxi-refund-data', ]) . '?' . http_build_query([ 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', 'page' => 2, ]); $response = $this->loginAs()->get($url); $response ->assertOk() ->assertSee('data-filter-contract="non-jingxi-refund-data.filters"', false) ->assertSee('data-sync-state="syncing"', false) ->assertSeeText('业务镜像同步中,当前显示最近成功数据') ->assertSeeText('AS-2002') ->assertSeeText('WB-2002') ->assertSeeText('2026-08-20 10:00:00') ->assertDontSee('2026-08-20 10:00:00.123456', false) ->assertSeeText('业务来源') ->assertSeeText('商品明细') ->assertSeeText('线上订单号') ->assertSeeText('SKU-1') ->assertSeeText('99.50') ->assertSeeText('共 26 条记录') ->assertSeeText('第 2 / 2 页') ->assertSeeText('导出筛选结果') ->assertSee('workspace/refunds/non-jingxi-refund-data/export', false) ->assertSee('data-finance-export-base=', false) ->assertSee('data-export-field="refundDate"', false) ->assertSee('data-date-picker', false) ->assertSee('data-date-calendar', false) ->assertSee('data-date-toggle', false) ->assertSee('CaiWuDatePicker.setup(form)', false) ->assertSee('value="2026-08-01"', false) ->assertSee('value="ORDER-1"', false) ->assertSee('name="waybill_no"', false) ->assertSee('value="WB-2002"', false); $this->assertSame(1, preg_match('/data-finance-export-query="([^"]+)"/', $response->getContent(), $exportQuery)); $this->assertSame([ 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', ], json_decode(html_entity_decode($exportQuery[1], ENT_QUOTES | ENT_HTML5, 'UTF-8'), true, 512, JSON_THROW_ON_ERROR)); } public function test_refund_pending_page_uses_fixed_confirmation_filter_and_online_status_label(): void { $this->grantAdminPermission(); setting()->set('default.list_limit', 25); $states = [ NonJingxiRefundMirrorRepository::INFO_DATASET => ['status' => 'succeeded', 'initial_completed_at' => '2026-08-15 00:00:00'], NonJingxiRefundMirrorRepository::ITEM_DATASET => ['status' => 'succeeded', 'initial_completed_at' => '2026-08-15 00:00:00'], ]; $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('states')->once()->andReturn($states); $repository->shouldReceive('syncStatus')->once()->with($states)->andReturn([ 'code' => 'healthy', 'last_succeeded_at' => '2026-08-21 12:00:00', ]); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('pendingConfirmationStatusReady')->once()->andReturnTrue(); $repository->shouldReceive('pendingConfirmationPlatformMonthlySummary') ->once() ->with('2026-10-01', '2026-11-04') ->andReturn([ 'months' => [ ['month' => '2026-10', 'total' => 5641], ['month' => '2026-11', 'total' => 3232], ], 'platforms' => [ ['platform' => '京东商城', 'total' => 8000, 'months' => ['2026-10' => 5000, '2026-11' => 3000]], ['platform' => '天猫', 'total' => 873, 'months' => ['2026-10' => 641, '2026-11' => 232]], ], ]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $response = $this->loginAs()->get(route('business.pages.show', [ 'section' => 'refunds', 'page' => 'refund-pending', 'from' => '2026-11-01', 'to' => '2026-11-04', 'order_no' => '743478803', 'shop_name' => '京东-米客米家旗舰店', ])); $response ->assertOk() ->assertSeeText('固定筛选:状态“待确认” · 线上状态“退款成功”') ->assertSeeText('按退款月份统计') ->assertSeeText('平台月度汇总') ->assertSeeText('京东商城') ->assertSeeText('天猫') ->assertSeeText('2026年10月') ->assertSeeText('2026年11月') ->assertSeeText('点击汇总数字查看对应待确认明细') ->assertSeeText('月度合计') ->assertSee('data-refund-detail-trigger', false) ->assertSee('data-refund-detail-month="2026-10"', false) ->assertSee('data-refund-detail-platform="京东商城"', false) ->assertSee('data-refund-summary-detail', false) ->assertDontSee('data-refund-month-toggle', false) ->assertDontSeeText('5,641 条待确认') ->assertSeeText('导出全部明细') ->assertSee('data-refund-all-export', false) ->assertSee('from=2026-10-01&to=2026-11-04', false) ->assertDontSee('data-refund-month-export', false) ->assertSee('data-refund-page="pending-confirmation"', false) ->assertSee('data-refund-month-summary', false) ->assertSee('workspace/refunds/refund-pending/month-details', false); } public function test_refund_pending_hides_export_and_blocks_export_url_without_operation_permission(): void { $this->grantAdminPermission(); $this->user->syncRoles([Role::where('name', 'user')->firstOrFail()->id]); $subject = 'user_' . $this->user->id; $pageKey = 'refunds/refund-pending'; setting()->set('system.sidebar_permissions', [ 'configured' => [$subject], 'subjects' => [$subject => [$pageKey]], ]); $states = [ NonJingxiRefundMirrorRepository::INFO_DATASET => ['status' => 'succeeded', 'initial_completed_at' => '2026-08-15 00:00:00'], NonJingxiRefundMirrorRepository::ITEM_DATASET => ['status' => 'succeeded', 'initial_completed_at' => '2026-08-15 00:00:00'], ]; $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('states')->once()->andReturn($states); $repository->shouldReceive('syncStatus')->once()->with($states)->andReturn([ 'code' => 'healthy', 'last_succeeded_at' => '2026-08-21 12:00:00', ]); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('pendingConfirmationStatusReady')->once()->andReturnTrue(); $repository->shouldReceive('pendingConfirmationPlatformMonthlySummary') ->once() ->andReturn(['months' => [], 'platforms' => []]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->assertFalse(BusinessOperationPermissions::can('refunds', 'refund-pending', 'export-details', $this->user)); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'refunds', 'page' => 'refund-pending', ])) ->assertOk() ->assertDontSeeText('导出全部明细') ->assertDontSee('data-refund-all-export', false) ->assertDontSee('workspace/refunds/refund-pending/export', false); $this->withExceptionHandling() ->get(route('business.refunds.refund-pending.export')) ->assertForbidden(); } public function test_refund_pending_month_details_load_selected_month(): void { $this->grantAdminPermission(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('pagePendingConfirmation')->once()->withArgs( static fn (array $filters, int $page, int $limit): bool => $filters === [ 'from' => '2026-10-01', 'to' => '2026-10-31', 'order_no' => '', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => '', 'platform' => '京东商城', ] && $page === 2 && $limit === 25 )->andReturn([ 'data' => [(object) [ 'refundDate' => '2026-10-29 12:51:37', 'as_id' => 743441809, 'outer_as_id' => '47467351078', 'so_id' => '3668475003538709', 'waybillNo' => 'WB-743441809', 'o_id' => 10001001, 'shop_name' => '京东-米客米家旗舰店', 'platform' => '京东商城', 'totalRefundMoney' => '199.00', 'AfSalesMoney' => '99.50', 'sku_id' => 'SKU-PENDING-1', 'qty' => 1, 'detailRefundMoney' => '99.50', 'status' => '待确认', 'shop_status' => '退款成功', ]], 'has_more' => true, ]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->loginAs()->getJson(route('business.refunds.refund-pending.month-details', [ 'month' => '2026-10', 'page' => 2, 'platform' => '京东商城', ])) ->assertOk() ->assertJsonPath('month', '2026-10') ->assertJsonPath('platform', '京东商城') ->assertJsonPath('page', 2) ->assertJsonPath('has_more', true) ->assertJsonPath('rows.0.after_sales_no', '743441809') ->assertJsonPath('rows.0.external_after_sales_no', '47467351078') ->assertJsonPath('rows.0.online_order_no', '3668475003538709') ->assertJsonPath('rows.0.internal_order_no', '10001001') ->assertJsonPath('rows.0.platform', '京东商城') ->assertJsonPath('rows.0.status', '待确认') ->assertJsonPath('rows.0.online_status', '退款成功') ->assertJsonPath('rows.0.sku_id', 'SKU-PENDING-1'); } public function test_refund_pending_all_details_endpoint_uses_october_to_today(): void { $this->grantAdminPermission(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('pagePendingConfirmation')->once()->withArgs( static fn (array $filters, int $page, int $limit): bool => $filters === [ 'from' => '2026-10-01', 'to' => '2026-11-04', 'order_no' => '', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => '', 'platform' => '', ] && $page === 1 && $limit === 25 )->andReturn(['data' => [], 'has_more' => false]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->loginAs()->getJson(route('business.refunds.refund-pending.month-details', [ 'month' => 'all', ])) ->assertOk() ->assertJsonPath('month', 'all') ->assertJsonPath('platform', '') ->assertJsonPath('page', 1) ->assertJsonPath('has_more', false) ->assertJsonCount(0, 'rows'); } public function test_refund_export_downloads_filtered_csv(): void { $this->grantAdminPermission(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('exportRows')->once()->with([ 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', ])->andReturn([(object) [ 'Id' => 102, 'refundDate' => '2026-08-20 10:00:00.123456', 'as_id' => 2002, 'outer_as_id' => 'AS-2002', 'waybillNo' => 'WB-2002', 'o_id' => 3002, 'so_id' => 'ORDER-1', 'type' => '退款', 'node' => '完成', 'isZC' => '是', 'shop_name' => '测试店', 'platform' => '天猫', 'responsibleDepartment' => '电商部', 'totalRefundMoney' => 99.5, 'originPostcode' => 'ZIP', 'shop_i_id' => 'ITEM-1', 'shop_sku_id' => 'SHOP-SKU-1', 'i_id' => 'STYLE-1', 'sku_id' => 'SKU-1', 'buyer_paid_amount' => 120, 'qty' => 1, 'unitCostGoods' => 50, 'detailRefundMoney' => 99.5, 'refundType' => '退货退款', 'refundQty' => 1, 'refundCostGoods' => 50, 'RefundMoneyIncome' => 99.5, 'AfSalesMoney' => 99.5, 'createTime' => '2026-08-20 09:00:00', 'status' => '完成', 'shop_status' => '完成', 'created' => '2026-08-20 09:00:00', 'confirm_date' => '2026-08-20 10:00:00', ]]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $response = $this->loginAs()->get(route('business.refunds.non-jingxi-refund-data.export', [ 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', ])); $response->assertOk()->assertDownload('after_sales_refunds_2026-08-01_2026-08-21.csv'); $content = $response->streamedContent(); $this->assertStringStartsWith("\xEF\xBB\xBF", $content); $this->assertStringContainsString('序号/主键ID', $content); $this->assertStringContainsString('线上订单号', $content); $this->assertStringContainsString('ORDER-1', $content); $this->assertStringNotContainsString('子订单ID', $content); $this->assertStringContainsString('AS-2002', $content); $this->assertStringContainsString('WB-2002', $content); $this->assertStringContainsString('2026-08-20 10:00:00', $content); $this->assertStringNotContainsString('.123456', $content); $this->assertStringContainsString('99.5', $content); } public function test_refund_pending_export_uses_fixed_confirmation_rows(): void { $this->grantAdminPermission(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('exportPendingConfirmationRows')->once()->with([ 'from' => '2026-11-01', 'to' => '2026-11-04', 'order_no' => '743478803', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => '', ])->andReturn([(object) [ 'Id' => 743478803, 'refundDate' => '2026-11-04 14:12:06', 'as_id' => 743478803, 'outer_as_id' => '743478803', 'o_id' => 9765753, 'shop_name' => '京东-米客米家旗舰店', 'status' => '待确认', 'shop_status' => '退款成功', ]]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $response = $this->loginAs()->get(route('business.refunds.refund-pending.export', [ 'from' => '2026-11-01', 'to' => '2026-11-04', 'order_no' => '743478803', ])); $response->assertOk()->assertDownload('refund_pending_2026-11-01_2026-11-04.csv'); $content = $response->streamedContent(); $this->assertStringContainsString('线上状态', $content); $this->assertStringContainsString('待确认', $content); $this->assertStringContainsString('退款成功', $content); $this->assertStringContainsString('743478803', $content); } public function test_refund_export_accepts_only_declared_columns_in_requested_order(): void { $this->grantAdminPermission(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('exportRows')->once()->andReturn([(object) [ 'shop_name' => '测试店', 'refundDate' => '2026-08-20', 'sku_id' => 'SKU-1', ]]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $response = $this->loginAs()->get(route('business.refunds.non-jingxi-refund-data.export', [ 'columns' => 'shop_name,refundDate,not_a_real_field', ])); $response->assertOk(); $content = $response->streamedContent(); $header = strtok($content, "\r\n"); $this->assertSame("\xEF\xBB\xBF店铺名称,退款日期", $header); $lines = preg_split('/\r?\n/', trim($content)); $this->assertSame(['测试店', '2026-08-20 00:00:00'], str_getcsv($lines[1])); $this->assertStringNotContainsString('SKU编码', $content); } public function test_page_removes_dates_before_2026_without_changing_other_filters(): void { $this->grantAdminPermission(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available', 'ready', 'queryReady')->andReturnTrue(); $repository->shouldReceive('states')->andReturn([]); $repository->shouldReceive('syncStatus')->andReturn(['code' => 'healthy', 'last_succeeded_at' => null]); $repository->shouldReceive('paginate')->once()->withArgs( static fn (array $filters): bool => $filters['from'] === '2026-01-01' && $filters['to'] === '2026-08-21' && $filters['shop_name'] === '测试店' )->andReturn(['data' => [], 'total' => 0]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'refunds', 'page' => 'non-jingxi-refund-data', 'from' => '2025-01-01', 'to' => '2026-08-21', 'shop_name' => '测试店', ]))->assertOk() ->assertSee('data-date-picker', false) ->assertSee('value="2026-01-01"', false); } public function test_export_clamps_the_date_and_sends_headers_before_consuming_all_rows(): void { $this->grantAdminPermission(); $finished = false; $rows = (static function () use (&$finished): \Generator { yield (object) ['refundDate' => '2026-01-01 00:00:00']; yield (object) ['refundDate' => '2026-01-02 00:00:00']; $finished = true; })(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available', 'ready', 'queryReady')->andReturnTrue(); $repository->shouldReceive('exportRows')->once()->withArgs( static fn (array $filters): bool => $filters['from'] === '2026-01-01' && $filters['to'] === '2026-01-02' )->andReturn($rows); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $response = $this->loginAs()->get(route('business.refunds.non-jingxi-refund-data.export', [ 'from' => '2025-12-01', 'to' => '2026-01-02', 'columns' => 'refundDate', ])); $response->assertOk()->assertDownload('after_sales_refunds_2026-01-01_2026-01-02.csv') ->assertHeader('X-Accel-Buffering', 'no'); $this->assertFalse($finished); $content = $response->streamedContent(); $this->assertTrue($finished); $this->assertStringContainsString('2026-01-01 00:00:00', $content); $this->assertStringContainsString('2026-01-02 00:00:00', $content); } public function test_export_with_only_old_dates_does_not_expand_into_the_retained_range(): void { $this->grantAdminPermission(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available', 'ready', 'queryReady')->andReturnTrue(); $repository->shouldReceive('exportRows')->once()->withArgs( static fn (array $filters): bool => $filters['from'] === '2026-01-01' && $filters['to'] === '2025-12-31' )->andReturn([]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $response = $this->loginAs()->get(route('business.refunds.non-jingxi-refund-data.export', [ 'from' => '2025-12-31', 'to' => '2025-01-01', 'columns' => 'refundDate', ])); $response->assertOk(); $this->assertSame("\xEF\xBB\xBF退款日期\n", $response->streamedContent()); } public function test_export_reports_first_batch_failure_before_starting_download(): void { $this->grantAdminPermission(); $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available', 'ready', 'queryReady')->andReturnTrue(); $repository->shouldReceive('exportRows')->andReturn((static function (): \Generator { throw new \RuntimeException('refund_export_test_failure'); yield; })()); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->loginAs()->get(route('business.refunds.non-jingxi-refund-data.export')) ->assertRedirect()->assertSessionHas('error', '售后(退货退款)导出暂时无法完成,请稍后重试。'); } public function test_page_does_not_report_empty_data_before_initial_sync_finishes(): void { $this->grantAdminPermission(); $states = [ NonJingxiRefundMirrorRepository::INFO_DATASET => ['status' => 'running'], NonJingxiRefundMirrorRepository::ITEM_DATASET => ['status' => 'running'], ]; $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('states')->once()->andReturn($states); $repository->shouldReceive('syncStatus')->once()->with($states)->andReturn([ 'code' => 'initializing', 'last_succeeded_at' => null, ]); $repository->shouldReceive('ready')->once()->andReturnFalse(); $repository->shouldNotReceive('queryReady'); $repository->shouldNotReceive('paginate'); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'refunds', 'page' => 'non-jingxi-refund-data', ])) ->assertOk() ->assertSeeText('业务镜像正在执行首次同步') ->assertSeeText('首次同步尚未完成,完成后将自动显示数据。') ->assertDontSeeText('当前筛选范围暂无数据。'); } public function test_page_shows_a_real_empty_filter_result(): void { $this->grantAdminPermission(); $states = [ NonJingxiRefundMirrorRepository::INFO_DATASET => ['status' => 'succeeded', 'initial_completed_at' => '2026-08-15 00:00:00'], NonJingxiRefundMirrorRepository::ITEM_DATASET => ['status' => 'succeeded', 'initial_completed_at' => '2026-08-15 00:00:00'], ]; $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('states')->once()->andReturn($states); $repository->shouldReceive('syncStatus')->once()->andReturn(['code' => 'healthy', 'last_succeeded_at' => '2026-08-21 12:00:00']); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('paginate')->once()->andReturn(['data' => [], 'total' => 0]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'refunds', 'page' => 'non-jingxi-refund-data', 'sku_id' => 'NOT-FOUND', ])) ->assertOk() ->assertSeeText('当前筛选范围暂无数据。') ->assertSeeText('共 0 条记录') ->assertSee('is-disabled', false) ->assertSeeText('导出筛选结果'); } public function test_page_does_not_run_the_query_before_required_indexes_exist(): void { $this->grantAdminPermission(); $states = [ NonJingxiRefundMirrorRepository::INFO_DATASET => ['status' => 'running', 'initial_completed_at' => '2026-08-15 00:00:00'], NonJingxiRefundMirrorRepository::ITEM_DATASET => ['status' => 'running', 'initial_completed_at' => '2026-08-15 00:00:00'], ]; $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('states')->once()->andReturn($states); $repository->shouldReceive('syncStatus')->once()->with($states)->andReturn([ 'code' => 'syncing', 'last_succeeded_at' => '2026-08-17 10:05:07', ]); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnFalse(); $repository->shouldNotReceive('paginate'); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->loginAs(); $url = route('business.pages.show', [ 'section' => 'refunds', 'page' => 'non-jingxi-refund-data', ]).'?page=2'; $this->get($url) ->assertOk() ->assertSee('upstream-data-alert--warning', false) ->assertSeeText('页面查询索引正在准备,生产同步轮转完成后将自动显示数据。') ->assertSeeText('页面查询索引尚未就绪,当前不会执行高压力查询。') ->assertSeeText('第 1 / 1 页') ->assertDontSeeText('第 2 / 1 页') ->assertDontSeeText('当前筛选范围暂无数据。'); } public function test_page_replaces_invalid_dates_with_the_default_range(): void { $this->grantAdminPermission(); $this->travelTo('2026-08-21 12:00:00'); $states = [ NonJingxiRefundMirrorRepository::INFO_DATASET => ['status' => 'succeeded', 'initial_completed_at' => '2026-08-15 00:00:00'], NonJingxiRefundMirrorRepository::ITEM_DATASET => ['status' => 'succeeded', 'initial_completed_at' => '2026-08-15 00:00:00'], ]; $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); $repository->shouldReceive('available')->once()->andReturnTrue(); $repository->shouldReceive('states')->once()->andReturn($states); $repository->shouldReceive('syncStatus')->once()->with($states)->andReturn([ 'code' => 'healthy', 'last_succeeded_at' => '2026-08-21 12:00:00', ]); $repository->shouldReceive('ready')->once()->andReturnTrue(); $repository->shouldReceive('queryReady')->once()->andReturnTrue(); $repository->shouldReceive('paginate')->once()->withArgs( static fn (array $filters, int $page, int $limit): bool => $filters['from'] === '2026-08-15' && $filters['to'] === '2026-08-21' && $page === 1 && $limit === 25 )->andReturn(['data' => [], 'total' => 0]); $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'refunds', 'page' => 'non-jingxi-refund-data', 'from' => '2026-02-31', 'to' => 'not-a-date', ])) ->assertOk() ->assertSee('value="2026-08-15"', false) ->assertSee('value="2026-08-21"', false) ->assertDontSee('非京喜退款数据暂时无法读取,请稍后重试。'); } public function test_page_rejects_users_without_sidebar_permission(): void { $subject = 'user_' . $this->user->id; $this->user->roles()->sync([\App\Models\Auth\Role::where('name', 'admin')->firstOrFail()->id]); setting()->set('system.sidebar_permissions', json_encode([ 'configured' => [$subject], 'subjects' => [ $subject => ['company-management/basic-information'], ], ], JSON_UNESCAPED_UNICODE)); $this->withExceptionHandling()->loginAs()->get(route('business.pages.show', [ 'section' => 'refunds', 'page' => 'non-jingxi-refund-data', ]))->assertForbidden(); } }