calls[] = ['sql' => $sql, 'bindings' => $bindings]; return 1; } public function select(string $sql, array $bindings = []): array { $this->calls[] = ['sql' => $sql, 'bindings' => $bindings]; return [[ 'id' => 9, 'shopName' => '测试店铺', 'tradeDay' => '2026-06-30 12:00:00', 'tradeTypeName' => '全站投放', 'tradeAMTIn' => 12.345, 'tradeAMTOut' => 67.891, ]]; } public function first(string $sql, array $bindings = []): ?array { $this->calls[] = ['sql' => $sql, 'bindings' => $bindings]; return ['income_total' => 12.345, 'expense_total' => 67.891, 'cnt' => 1]; } }; $factory = new class($query) extends UpstreamReadOnlyQueryFactory { public function __construct(private readonly UpstreamReadOnlyQuery $query) { } public function forSource(string $source): UpstreamReadOnlyQuery { TestCase::assertSame('jiansun', $source); return $this->query; } }; $repository = new AlibabaBillRepository($factory); $result = $repository->paginate('测试店铺', '全站投放', '2026-06-30', '2026-06-30', 2, 250); $summary = $repository->summary('测试店铺', '全站投放', '2026-06-30', '2026-06-30'); $this->assertSame(1, $result['total']); $this->assertSame('测试店铺', $result['rows'][0]['shop_name']); $this->assertSame(12.35, $result['rows'][0]['income_amount']); $this->assertSame(67.89, $result['rows'][0]['expense_amount']); $this->assertSame(-55.54, $summary['net']); $this->assertStringContainsString('tradeTypeName = ?', $query->calls[0]['sql']); $this->assertStringContainsString('CAST(src.tradeDay AS date)', $query->calls[0]['sql']); $this->assertSame(101, $query->calls[1]['bindings'][4]); $this->assertSame(200, $query->calls[1]['bindings'][5]); } }