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_resolve_to_all_suppliers(): 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' => '鸣飞家具'], ]); $access = new SupplierPortalAccess($factory, $suppliers); $this->assertSame( ['晨丰', '鸣飞家具'], array_column($access->resolveAll('ding-supplier', 'union-supplier'), 'supplier_name'), ); } 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')); } public function test_binding_multiple_suppliers_keeps_all_and_prunes_only_removed_ones(): void { $user = user_model_class()::query()->firstOrFail(); $companyId = (int) $user->companies()->firstOrFail()->id; $access = new SupplierPortalAccess( Mockery::mock(UpstreamReadOnlyQueryFactory::class), Mockery::mock(SupplierManagementMirrorRepository::class), ); $access->bindMany($user, $companyId, [ [ 'supplier_name' => '晨丰', 'supplier_code' => 'CF-001', 'dingtalk_group_id' => 10, 'dingtalk_group_name' => '晨丰', ], [ 'supplier_name' => '鸣飞家具', 'supplier_code' => 'MF-001', 'dingtalk_group_id' => 20, 'dingtalk_group_name' => '鸣飞家具', ], ]); $this->assertSame(2, SupplierPortalSupplier::query()->where('user_id', $user->id)->count()); $this->assertSame(1, SupplierPortalIdentity::query()->where('user_id', $user->id)->count()); $access->bindMany($user, $companyId, [[ 'supplier_name' => '鸣飞家具', 'supplier_code' => 'MF-001', 'dingtalk_group_id' => 20, 'dingtalk_group_name' => '鸣飞家具', ]]); $this->assertDatabaseMissing('supplier_portal_suppliers', [ 'company_id' => $companyId, 'user_id' => $user->id, 'supplier_name' => '晨丰', ]); $this->assertDatabaseHas('supplier_portal_suppliers', [ 'company_id' => $companyId, 'user_id' => $user->id, 'supplier_name' => '鸣飞家具', ]); } }