shouldReceive('select')->twice()->andReturnUsing(function (string $sql, array $bindings): array { UpstreamReadOnlyQuery::assertReadOnlySql($sql); if (str_contains($sql, 'hr_employee')) { $this->assertSame(['ding-supplier'], $bindings); return [[ 'userid' => 'ding-supplier', 'unionid' => 'union-supplier', 'active' => 1, 'dept_id_list' => '55248182', ]]; } $this->assertContains('55248182', $bindings); return [ ['Id' => 55248182, 'parent_id' => 900, 'name' => '晨丰', 'depth' => 0], ['Id' => 900, 'parent_id' => 1, 'name' => '供应商', 'depth' => 1], ]; }); $factory = Mockery::mock(UpstreamReadOnlyQueryFactory::class); $factory->shouldReceive('forSource')->once()->with('operations_replica')->andReturn($query); $suppliers = Mockery::mock(SupplierManagementMirrorRepository::class); $suppliers->shouldReceive('suppliersByNames')->once() ->with(['晨丰', '供应商']) ->andReturn([(object) ['Id' => 9, 'scmcode' => 'CF-001', 'scmname' => '晨丰']]); $resolved = (new SupplierPortalAccess($factory, $suppliers))->resolve('ding-supplier', 'union-supplier'); $this->assertSame('晨丰', $resolved['supplier_name']); $this->assertSame('CF-001', $resolved['supplier_code']); $this->assertSame(55248182, $resolved['dingtalk_group_id']); } public function test_inactive_or_union_mismatched_employee_is_denied(): void { foreach ([ [['userid' => 'ding-supplier', 'unionid' => 'union-supplier', 'active' => 0, 'dept_id_list' => '1']], [['userid' => 'ding-supplier', 'unionid' => 'different-union', 'active' => 1, 'dept_id_list' => '1']], ] as $employees) { $query = Mockery::mock(UpstreamReadOnlyQuery::class); $query->shouldReceive('select')->once()->with(Mockery::on(function (string $sql): bool { UpstreamReadOnlyQuery::assertReadOnlySql($sql); return str_contains($sql, 'hr_employee'); }), ['ding-supplier'])->andReturn($employees); $factory = Mockery::mock(UpstreamReadOnlyQueryFactory::class); $factory->shouldReceive('forSource')->andReturn($query); $suppliers = Mockery::mock(SupplierManagementMirrorRepository::class); $suppliers->shouldNotReceive('suppliersByNames'); $this->assertNull((new SupplierPortalAccess($factory, $suppliers))->resolve('ding-supplier', 'union-supplier')); } } public function test_multiple_supplier_ancestors_are_ambiguous_and_denied(): void { $query = Mockery::mock(UpstreamReadOnlyQuery::class); $query->shouldReceive('select')->twice()->andReturnUsing(function (string $sql): array { UpstreamReadOnlyQuery::assertReadOnlySql($sql); if (str_contains($sql, 'hr_employee')) { return [['userid' => 'ding-supplier', 'unionid' => 'union-supplier', 'active' => 1, 'dept_id_list' => '10,20']]; } return [ ['Id' => 10, 'parent_id' => 1, 'name' => '晨丰', 'depth' => 0], ['Id' => 20, 'parent_id' => 1, 'name' => '鸣飞家具', 'depth' => 0], ]; }); $factory = Mockery::mock(UpstreamReadOnlyQueryFactory::class); $factory->shouldReceive('forSource')->once()->andReturn($query); $suppliers = Mockery::mock(SupplierManagementMirrorRepository::class); $suppliers->shouldReceive('suppliersByNames')->once()->andReturn([ (object) ['Id' => 1, 'scmcode' => 'CF', 'scmname' => '晨丰'], (object) ['Id' => 2, 'scmcode' => 'MF', 'scmname' => '鸣飞家具'], ]); $this->assertNull((new SupplierPortalAccess($factory, $suppliers))->resolve('ding-supplier', 'union-supplier')); } public function test_organization_cycle_is_rejected(): void { $query = Mockery::mock(UpstreamReadOnlyQuery::class); $query->shouldReceive('select')->twice()->andReturnUsing(function (string $sql): array { UpstreamReadOnlyQuery::assertReadOnlySql($sql); if (str_contains($sql, 'hr_employee')) { return [['userid' => 'ding-supplier', 'unionid' => 'union-supplier', 'active' => 1, 'dept_id_list' => '30']]; } return [['Id' => 30, 'parent_id' => 30, 'name' => '晨丰', 'depth' => 20]]; }); $factory = Mockery::mock(UpstreamReadOnlyQueryFactory::class); $factory->shouldReceive('forSource')->once()->andReturn($query); $suppliers = Mockery::mock(SupplierManagementMirrorRepository::class); $suppliers->shouldNotReceive('suppliersByNames'); $this->assertNull((new SupplierPortalAccess($factory, $suppliers))->resolve('ding-supplier', 'union-supplier')); } }