environmentSetUp(); Carbon::setTestNow('2026-11-12 10:00:00'); (require dirname(__DIR__, 2) . '/database/migrations/2026_11_12_120000_create_company_monthly_reports.php')->up(); Schema::create('admin', function (Blueprint $t) { $t->id(); $t->string('username'); }); DB::table('admin')->insert([['id' => 1, 'username' => 'root'], ['id' => 2, 'username' => 'finance']]); $this->root = sys_get_temp_dir() . '/company-report-' . bin2hex(random_bytes(4)); config(['filesystems.disks.local' => ['driver' => 'local', 'root' => $this->root]]); $this->app->instance('filesystem', new FilesystemManager($this->app)); } protected function tearDown(): void { Carbon::setTestNow(); foreach ($this->temp as $file) @unlink($file); if (is_dir($this->root)) { array_map('unlink', glob($this->root . '/company-reports/*') ?: []); @rmdir($this->root . '/company-reports'); @rmdir($this->root); } $this->environmentTearDown(); } private function excel(string $name, string $head = "PK\x03\x04"): UploadedFile { $path = tempnam(sys_get_temp_dir(), 'xls'); file_put_contents($path, $head . random_bytes(32)); $this->temp[] = $path; return new UploadedFile($path, $name, null, null, true); } private function admin(string $action, array $params = [], array $files = [], int $adminId = 1) { $request = Request::create('/admin/companyReport/' . $action, $files ? 'POST' : 'GET', $params, [], $files); $request->user = (object) ['id' => $adminId]; $this->app->instance('request', $request); $response = (new CompanyReport())->{$action === '' ? 'index' : $action}($request, new CompanyMonthlyReportService()); return $response instanceof BinaryFileResponse ? $response : $response->getData(true); } private function agentCall(Agent $agent, string $action, array $params = []) { $request = Request::create('/agent/company-reports', 'GET', $params); $request->attributes->set('agent', $agent); $this->app->instance('request', $request); $response = (new CompanyReportController())->{$action}($request, new CompanyMonthlyReportService()); return $response instanceof BinaryFileResponse ? $response : $response->getData(true); } public function test_admin_uploads_last_month_once_and_reuploads_through_edit(): void { $this->assertSame('2026-10', CompanyMonthlyReportService::allowedMonth()); $wrong = $this->admin('create', ['month' => '2026-11'], ['file' => $this->excel('11月.xlsx')]); $this->assertSame('只能上传已结束月份的报表(2026-10 及以前)', $wrong['msg']); $this->assertSame('请上传 Excel 文件(.xlsx 或 .xls)', $this->admin('create', ['month' => '2026-10'], ['file' => $this->excel('a.pdf')])['msg']); $this->assertSame('请上传 Excel 文件(.xlsx 或 .xls)', $this->admin('create', ['month' => '2026-10'], ['file' => $this->excel('fake.xlsx', 'abcd')])['msg'], 'content must be excel'); $encrypted = $this->admin('create', ['month' => '2026-08'], ['file' => $this->excel('8月-加密.xlsx', "\xD0\xCF\x11\xE0")]); $this->assertSame([0, '2026-08'], [$encrypted['code'], $encrypted['data']['month'] ?? null], 'a missed earlier month can be added; password-protected xlsx is OLE'); $created = $this->admin('create', ['month' => '2026-10'], ['file' => $this->excel('10月营业报表.xlsx')]); $this->assertSame(0, $created['code'], $created['msg']); $this->assertSame(['2026-10', '10月营业报表.xlsx'], [$created['data']['month'], $created['data']['file_name']]); $this->assertArrayNotHasKey('file_path', $created['data']); $this->assertSame('2026-10 的报表已存在,请在列表中编辑重新上传', $this->admin('create', ['month' => '2026-10'], ['file' => $this->excel('again.xlsx')])['msg']); $oldPath = DB::table('company_monthly_reports')->where('month', '2026-10')->value('file_path'); $this->assertFileExists($this->root . '/' . $oldPath); $updated = $this->admin('update', ['id' => $created['data']['id']], ['file' => $this->excel('10月营业报表-修正.xls', "\xD0\xCF\x11\xE0")], 2); $this->assertSame(0, $updated['code'], $updated['msg']); $this->assertSame(['2026-10', '10月营业报表-修正.xls'], [$updated['data']['month'], $updated['data']['file_name']]); $this->assertFileDoesNotExist($this->root . '/' . $oldPath, 'old file is removed after re-upload'); $list = $this->admin('', [])['data']; $this->assertSame([2, '2026-10'], [$list['total'], $list['allowed_month']]); $this->assertSame(['2026-10', '2026-08'], array_column($list['list'], 'month')); $this->assertSame(['root', 'finance'], [$list['list'][0]['created_by_name'], $list['list'][0]['updated_by_name']]); $file = $this->admin('download', ['id' => $created['data']['id']]); $this->assertInstanceOf(BinaryFileResponse::class, $file); $this->assertStringContainsString('attachment', (string) $file->headers->get('content-disposition')); $this->assertSame('数据不存在', $this->admin('download', ['id' => 999])['msg']); } public function test_only_equity_agents_can_list_and_download(): void { Carbon::setTestNow('2026-10-05 10:00:00'); $this->admin('create', ['month' => '2026-09'], ['file' => $this->excel('9月.xlsx')]); Carbon::setTestNow('2026-11-12 10:00:00'); $id = $this->admin('create', ['month' => '2026-10'], ['file' => $this->excel('10月.xlsx')])['data']['id']; $equity = $this->agent(Agent::TYPE_EQUITY); $list = $this->agentCall($equity, 'index'); $this->assertSame(0, $list['code'], $list['msg']); $this->assertSame(['2026-10', '2026-09'], array_column($list['data']['list'], 'month'), 'newest month first'); $this->assertArrayNotHasKey('created_by', $list['data']['list'][0], 'admin info stays in the admin'); $this->assertSame(['2026-09'], array_column($this->agentCall($equity, 'index', ['end_month' => '2026-09'])['data']['list'], 'month')); $this->assertInstanceOf(BinaryFileResponse::class, $this->agentCall($equity, 'download', ['id' => $id])); $joint = $this->agent(); $this->assertSame('仅入股代理可查看公司营业报表', $this->agentCall($joint, 'index')['msg']); $this->assertSame('仅入股代理可查看公司营业报表', $this->agentCall($joint, 'download', ['id' => $id])['msg']); } }