Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4048235
DatabaseBlockStoreTest.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
32 KB
Referenced Files
None
Subscribers
None
DatabaseBlockStoreTest.php
View Options
<?php
use
MediaWiki\Block\DatabaseBlock
;
use
MediaWiki\Block\DatabaseBlockStore
;
use
MediaWiki\Block\Restriction\NamespaceRestriction
;
use
MediaWiki\Block\Restriction\PageRestriction
;
use
MediaWiki\Config\ServiceOptions
;
use
MediaWiki\Deferred\DeferredUpdates
;
use
MediaWiki\HookContainer\HookContainer
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\Tests\Unit\DummyServicesTrait
;
use
MediaWiki\Tests\User\TempUser\TempUserTestTrait
;
use
MediaWiki\User\User
;
use
Psr\Log\NullLogger
;
use
Wikimedia\IPUtils
;
use
Wikimedia\ScopedCallback
;
/**
* Integration tests for DatabaseBlockStore.
*
* @author DannyS712
* @group Blocking
* @group Database
* @covers \MediaWiki\Block\DatabaseBlockStore
* @coversDefaultClass \MediaWiki\Block\DatabaseBlockStore
*/
class
DatabaseBlockStoreTest
extends
MediaWikiIntegrationTestCase
{
use
DummyServicesTrait
;
use
TempUserTestTrait
;
private
User
$sysop
;
private
int
$expiredBlockId
=
11111
;
private
int
$unexpiredBlockId
=
22222
;
private
int
$autoblockId
=
33333
;
/**
* @param array $options
* - config: Override the ServiceOptions config
* - constructorArgs: Override the constructor arguments
* @return DatabaseBlockStore
*/
private
function
getStore
(
array
$options
=
[]
):
DatabaseBlockStore
{
$overrideConfig
=
$options
[
'config'
]
??
[];
$overrideConstructorArgs
=
$options
[
'constructorArgs'
]
??
[];
$defaultConfig
=
[
MainConfigNames
::
AutoblockExpiry
=>
86400
,
MainConfigNames
::
BlockCIDRLimit
=>
[
'IPv4'
=>
16
,
'IPv6'
=>
19
],
MainConfigNames
::
BlockDisablesLogin
=>
false
,
MainConfigNames
::
PutIPinRC
=>
true
,
MainConfigNames
::
UpdateRowsPerQuery
=>
10
,
];
$config
=
array_merge
(
$defaultConfig
,
$overrideConfig
);
// This ensures continuation after hooks
$hookContainer
=
$this
->
createMock
(
HookContainer
::
class
);
$hookContainer
->
method
(
'run'
)
->
willReturn
(
true
);
// Most tests need read only to be false
$readOnlyMode
=
$this
->
getDummyReadOnlyMode
(
false
);
$services
=
$this
->
getServiceContainer
();
$defaultConstructorArgs
=
[
'serviceOptions'
=>
new
ServiceOptions
(
DatabaseBlockStore
::
CONSTRUCTOR_OPTIONS
,
$config
),
'logger'
=>
new
NullLogger
(),
'actorStoreFactory'
=>
$services
->
getActorStoreFactory
(),
'blockRestrictionStore'
=>
$services
->
getBlockRestrictionStore
(),
'commentStore'
=>
$services
->
getCommentStore
(),
'hookContainer'
=>
$hookContainer
,
'dbProvider'
=>
$services
->
getDBLoadBalancerFactory
(),
'readOnlyMode'
=>
$readOnlyMode
,
'userFactory'
=>
$services
->
getUserFactory
(),
'tempUserConfig'
=>
$services
->
getTempUserConfig
(),
'blockTargetFactory'
=>
$services
->
getBlockTargetFactory
(),
'autoblockExemptionList'
=>
$services
->
getAutoblockExemptionList
(),
];
$constructorArgs
=
array_merge
(
$defaultConstructorArgs
,
$overrideConstructorArgs
);
return
new
DatabaseBlockStore
(
...
array_values
(
$constructorArgs
)
);
}
/**
* @param array $options
* - target: The intended target, an unblocked user by default
* - autoblock: Whether this block is autoblocking
* @return DatabaseBlock
*/
private
function
getBlock
(
array
$options
=
[]
):
DatabaseBlock
{
$targetFactory
=
$this
->
getServiceContainer
()->
getBlockTargetFactory
();
if
(
isset
(
$options
[
'target'
]
)
)
{
if
(
$options
[
'target'
]
instanceof
User
)
{
$target
=
$targetFactory
->
newFromUser
(
$options
[
'target'
]
);
}
else
{
$target
=
$targetFactory
->
newFromString
(
$options
[
'target'
]
);
}
}
else
{
$target
=
$targetFactory
->
newUserBlockTarget
(
$this
->
getTestUser
()->
getUser
()
);
}
$autoblock
=
$options
[
'autoblock'
]
??
false
;
return
new
DatabaseBlock
(
[
'by'
=>
$this
->
sysop
,
'target'
=>
$target
,
'enableAutoblock'
=>
$autoblock
,
]
);
}
/**
* Check that an autoblock corresponds to a parent block. The following are not
* required to be equal, so are not tested:
* - target
* - type
* - expiry
* - autoblocking
*
* @param DatabaseBlock $block
* @param DatabaseBlock $autoblock
*/
private
function
assertAutoblockEqualsBlock
(
DatabaseBlock
$block
,
DatabaseBlock
$autoblock
)
{
$this
->
assertSame
(
$autoblock
->
getParentBlockId
(),
$block
->
getId
()
);
$this
->
assertSame
(
$autoblock
->
isHardblock
(),
$block
->
isHardblock
()
);
$this
->
assertSame
(
$autoblock
->
isCreateAccountBlocked
(),
$block
->
isCreateAccountBlocked
()
);
$this
->
assertSame
(
$autoblock
->
getHideName
(),
$block
->
getHideName
()
);
$this
->
assertSame
(
$autoblock
->
isEmailBlocked
(),
$block
->
isEmailBlocked
()
);
$this
->
assertSame
(
$autoblock
->
isUsertalkEditAllowed
(),
$block
->
isUsertalkEditAllowed
()
);
$this
->
assertSame
(
$autoblock
->
isSitewide
(),
$block
->
isSitewide
()
);
$this
->
assertSame
(
$autoblock
->
getReasonComment
()->
text
,
wfMessage
(
'autoblocker'
,
$block
->
getTargetName
(),
$block
->
getReasonComment
()->
text
)->
text
()
);
$restrictionStore
=
$this
->
getServiceContainer
()->
getBlockRestrictionStore
();
$this
->
assertTrue
(
$restrictionStore
->
equals
(
$autoblock
->
getRestrictions
(),
$block
->
getRestrictions
()
)
);
}
/**
* @covers ::newFromID
* @covers ::newListFromTarget
* @covers ::newFromRow
*/
public
function
testNewFromID_exists
()
{
$store
=
$this
->
getStore
();
$block
=
$store
->
insertBlockWithParams
(
[
'address'
=>
'1.2.3.4'
,
'by'
=>
$this
->
getTestSysop
()->
getUser
(),
]
);
$blockId
=
$block
->
getId
();
$newFromIdRes
=
$store
->
newFromID
(
$blockId
);
$this
->
assertInstanceOf
(
DatabaseBlock
::
class
,
$newFromIdRes
,
'Looking up an existing block by id'
);
$newListRes
=
$store
->
newListFromTarget
(
"#$blockId"
);
$this
->
assertCount
(
1
,
$newListRes
,
'newListFromTarget with a block id for an existing block'
);
$this
->
assertInstanceOf
(
DatabaseBlock
::
class
,
$newListRes
[
0
],
'DatabaseBlock returned'
);
$this
->
assertSame
(
$blockId
,
$newListRes
[
0
]->
getId
(),
'Block returned is the correct one'
);
}
/**
* @covers ::newFromID
* @covers ::newListFromTarget
*/
public
function
testNewFromID_missing
()
{
$store
=
$this
->
getStore
();
$missingBlockId
=
9998
;
$dbRow
=
$this
->
getDb
()->
newSelectQueryBuilder
()
->
select
(
'*'
)
->
from
(
'block'
)
->
where
(
[
'bl_id'
=>
$missingBlockId
]
)
->
caller
(
__METHOD__
)
->
fetchRow
();
$this
->
assertFalse
(
$dbRow
,
"Sanity check: make sure there is no block with id $missingBlockId"
);
$newFromIdRes
=
$store
->
newFromID
(
$missingBlockId
);
$this
->
assertNull
(
$newFromIdRes
,
'Looking up a missing block by id'
);
$newListRes
=
$store
->
newListFromTarget
(
"#$missingBlockId"
);
$this
->
assertCount
(
0
,
$newListRes
,
'newListFromTarget with a block id for a missing block'
);
}
/**
* @covers ::newFromRow
*/
public
function
testNewFromRow
()
{
$badActor
=
$this
->
getTestUser
()->
getUser
();
$sysop
=
$this
->
getTestSysop
()->
getUser
();
$blockStore
=
$this
->
getServiceContainer
()->
getDatabaseBlockStore
();
$block
=
$blockStore
->
insertBlockWithParams
(
[
'targetUser'
=>
$badActor
,
'by'
=>
$sysop
,
'expiry'
=>
'infinity'
,
]
);
$blockQuery
=
$blockStore
->
getQueryInfo
();
$db
=
$this
->
getDb
();
$row
=
$db
->
newSelectQueryBuilder
()
->
queryInfo
(
$blockQuery
)
->
where
(
[
'bl_id'
=>
$block
->
getId
(),
]
)
->
caller
(
__METHOD__
)
->
fetchRow
();
$block
=
$blockStore
->
newFromRow
(
$db
,
$row
);
$this
->
assertInstanceOf
(
DatabaseBlock
::
class
,
$block
);
$this
->
assertEquals
(
$block
->
getBy
(),
$sysop
->
getId
()
);
$this
->
assertEquals
(
$block
->
getTargetName
(),
$badActor
->
getName
()
);
$this
->
assertEquals
(
$block
->
getTargetName
(),
$badActor
->
getName
()
);
$this
->
assertTrue
(
$block
->
isBlocking
(
$badActor
),
'Is blocking expected user'
);
$this
->
assertEquals
(
$block
->
getTargetUserIdentity
()->
getId
(),
$badActor
->
getId
()
);
}
/**
* @covers ::getQueryInfo
*/
public
function
testGetQueryInfo
()
{
// We don't list all of the fields that should be included, because that just
// duplicates the function itself. Instead, check the structure and the field
// aliases. The fact that this query info is everything needed to create a block
// is validated by its uses within the service
$queryInfo
=
$this
->
getStore
()->
getQueryInfo
();
$this
->
assertArrayHasKey
(
'tables'
,
$queryInfo
);
$this
->
assertArrayHasKey
(
'fields'
,
$queryInfo
);
$this
->
assertArrayHasKey
(
'joins'
,
$queryInfo
);
$this
->
assertIsArray
(
$queryInfo
[
'fields'
]
);
$this
->
assertArrayHasKey
(
'bl_by'
,
$queryInfo
[
'fields'
]
);
$this
->
assertSame
(
'block_by_actor.actor_user'
,
$queryInfo
[
'fields'
][
'bl_by'
]
);
$this
->
assertArrayHasKey
(
'bl_by_text'
,
$queryInfo
[
'fields'
]
);
$this
->
assertSame
(
'block_by_actor.actor_name'
,
$queryInfo
[
'fields'
][
'bl_by_text'
]
);
}
/**
* @covers ::newListFromIPs
* @covers ::newFromRow
*/
public
function
testNewListFromIPs
()
{
$store
=
$this
->
getStore
();
$inserted
=
$store
->
insertBlockWithParams
(
[
'address'
=>
'1.2.3.4'
,
'by'
=>
$this
->
getTestSysop
()->
getUser
(),
]
);
// Early return of empty array if no ips in the list
$list
=
$store
->
newListFromIPs
(
[],
true
);
$this
->
assertCount
(
0
,
$list
,
'No matching blocks'
);
// Empty array for no match
$list
=
$store
->
newListFromIPs
(
[
'10.1.1.1'
,
'192.168.1.1'
],
true
);
$this
->
assertCount
(
0
,
$list
,
'No blocks retrieved if all ips are invalid or trusted proxies'
);
// Actually fetching, block was inserted above
$list
=
$store
->
newListFromIPs
(
[
'1.2.3.4'
],
true
);
$this
->
assertCount
(
1
,
$list
,
'Block retrieved for the blocked ip'
);
$this
->
assertInstanceOf
(
DatabaseBlock
::
class
,
$list
[
0
],
'Sanity check: DatabaseBlock returned'
);
$this
->
assertSame
(
$inserted
->
getId
(),
$list
[
0
]->
getId
(),
'Block returned is the correct one'
);
}
/**
* @covers ::newListFromTarget
* @covers ::newLoad
*/
public
function
testNewListFromTargetAuto
()
{
// Set up once and do multiple tests, faster than a provider
$store
=
$this
->
getStore
();
$ip
=
'1.2.3.4'
;
$inserted
=
$store
->
insertBlockWithParams
(
[
'address'
=>
$ip
,
'by'
=>
$this
->
getTestSysop
()->
getUser
(),
'auto'
=>
true
,
]
);
// ALL
$list
=
$store
->
newListFromTarget
(
$ip
,
null
,
false
,
DatabaseBlockStore
::
AUTO_ALL
);
$this
->
assertCount
(
1
,
$list
);
// Specified with IP
$list
=
$store
->
newListFromTarget
(
$ip
,
null
,
false
,
DatabaseBlockStore
::
AUTO_SPECIFIED
);
$this
->
assertCount
(
0
,
$list
);
// Specified autoblock ID
$list
=
$store
->
newListFromTarget
(
"#{$inserted->getId()}"
,
null
,
false
,
DatabaseBlockStore
::
AUTO_SPECIFIED
);
$this
->
assertCount
(
1
,
$list
);
// None with IP
$list
=
$store
->
newListFromTarget
(
$ip
,
null
,
false
,
DatabaseBlockStore
::
AUTO_NONE
);
$this
->
assertCount
(
0
,
$list
);
// None with autoblock ID
$list
=
$store
->
newListFromTarget
(
"#{$inserted->getId()}"
,
null
,
false
,
DatabaseBlockStore
::
AUTO_NONE
);
$this
->
assertCount
(
0
,
$list
);
}
public
static
function
provideGetConditionForRanges
()
{
$hex1
=
IPUtils
::
toHex
(
'1.2.3.4'
);
$hex2
=
IPUtils
::
toHex
(
'1.2.3.5'
);
$hex3
=
IPUtils
::
toHex
(
'1.2.3.6'
);
$hex4
=
IPUtils
::
toHex
(
'1.2.3.7'
);
yield
'Both same end'
=>
[
[
[
$hex1
,
null
],
[
$hex2
,
null
]
],
[
"(bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex1'"
.
" AND bt_range_end >= '$hex1')"
,
"(bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex2'"
.
" AND bt_range_end >= '$hex2')"
,
"(bt_ip_hex IN ('01020304','01020305') AND bt_range_start IS NULL)"
],
];
yield
'Both different end'
=>
[
[
[
$hex1
,
$hex2
],
[
$hex3
,
$hex4
]
],
[
"(bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex1'"
.
" AND bt_range_end >= '$hex2')"
,
"(bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex3'"
.
" AND bt_range_end >= '$hex4')"
,
],
];
yield
'First same end, second different end'
=>
[
[
[
$hex1
,
null
],
[
$hex3
,
$hex4
]
],
[
"(bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex1'"
.
" AND bt_range_end >= '$hex1')"
,
"(bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex3'"
.
" AND bt_range_end >= '$hex4')"
,
"(bt_ip_hex = '$hex1' AND bt_range_start IS NULL)"
,
],
];
yield
'First different end, second same end'
=>
[
[
[
$hex1
,
$hex2
],
[
$hex3
,
null
]
],
[
"(bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex1'"
.
" AND bt_range_end >= '$hex2')"
,
"(bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex3'"
.
" AND bt_range_end >= '$hex3')"
,
"(bt_ip_hex = '$hex3' AND bt_range_start IS NULL)"
,
],
];
}
/**
* @dataProvider provideGetConditionForRanges
* @covers ::getConditionForRanges
* @covers ::getIpFragment
*/
public
function
testGetConditionForRanges
(
array
$ranges
,
array
$expect
)
{
$this
->
assertSame
(
$expect
,
$this
->
getStore
()->
getConditionForRanges
(
$ranges
)
);
}
public
static
function
provideGetRangeCond
()
{
// $start, $end, $expect
$hex1
=
IPUtils
::
toHex
(
'1.2.3.4'
);
$hex2
=
IPUtils
::
toHex
(
'1.2.3.5'
);
yield
'IPv4 start, same end'
=>
[
$hex1
,
null
,
"((bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex1'"
.
" AND bt_range_end >= '$hex1'))"
.
" OR ((bt_ip_hex = '$hex1' AND bt_range_start IS NULL))"
];
yield
'IPv4 start, different end'
=>
[
$hex1
,
$hex2
,
"((bt_range_start LIKE '0102%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex1'"
.
" AND bt_range_end >= '$hex2'))"
];
$hex3
=
IPUtils
::
toHex
(
'2000:DEAD:BEEF:A:0:0:0:0'
);
$hex4
=
IPUtils
::
toHex
(
'2000:DEAD:BEEF:A:0:0:000A:000F'
);
yield
'IPv6 start, same end'
=>
[
$hex3
,
null
,
"((bt_range_start LIKE 'v6-2000%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex3'"
.
" AND bt_range_end >= '$hex3'))"
.
" OR ((bt_ip_hex = '$hex3' AND bt_range_start IS NULL))"
];
yield
'IPv6 start, different end'
=>
[
$hex3
,
$hex4
,
"((bt_range_start LIKE 'v6-2000%' ESCAPE '`'"
.
" AND bt_range_start <= '$hex3'"
.
" AND bt_range_end >= '$hex4'))"
];
}
/**
* @dataProvider provideGetRangeCond
* @covers ::getRangeCond
* @covers ::getIpFragment
*/
public
function
testGetRangeCond
(
$start
,
$end
,
$expect
)
{
$this
->
assertSame
(
$expect
,
$this
->
getStore
()->
getRangeCond
(
$start
,
$end
)
);
}
public
static
function
provideGetRangeCondIntegrated
()
{
return
[
'single IP block'
=>
[
'3.3.3.3'
,
'3.3.3.3'
,
true
],
'/32 range blocks single IP'
=>
[
'3.3.3.3/32'
,
'3.3.3.3'
,
true
],
'single IP block mismatch'
=>
[
'3.3.3.3'
,
'3.3.3.4'
,
false
],
'/32 range mismatch'
=>
[
'3.3.3.3/32'
,
'3.3.3.4'
,
false
],
'/24 match'
=>
[
'3.3.3.0/24'
,
'3.3.3.0'
,
true
],
'/24 mismatch'
=>
[
'3.3.3.0/24'
,
'3.3.4.0'
,
false
],
'range search exact match'
=>
[
'3.3.3.0/24'
,
'3.3.3.0/24'
,
true
],
'encompassing range match'
=>
[
'3.3.3.0/24'
,
'3.3.3.1/27'
,
true
],
'excessive range mismatch'
=>
[
'3.3.0.0/24'
,
'3.3.0.0/22'
,
false
],
];
}
/**
* Test getRangeCond() by inserting blocks and checking for matches
*
* @dataProvider provideGetRangeCondIntegrated
* @param string $blockTarget
* @param string $searchTarget
* @param bool $isBlocked
*/
public
function
testGetRangeCondIntegrated
(
$blockTarget
,
$searchTarget
,
$isBlocked
)
{
$store
=
$this
->
getStore
();
$store
->
insertBlock
(
$this
->
getBlock
(
[
'target'
=>
$blockTarget
]
)
);
[
$start
,
$end
]
=
IPUtils
::
parseRange
(
$searchTarget
);
$rows
=
$this
->
getDb
()->
newSelectQueryBuilder
()
->
queryInfo
(
$store
->
getQueryInfo
()
)
->
where
(
$store
->
getRangeCond
(
$start
,
$end
)
)
->
fetchResultSet
();
$this
->
assertSame
(
$isBlocked
?
1
:
0
,
$rows
->
numRows
()
);
}
/**
* @dataProvider provideInsertBlockSuccess
*/
public
function
testInsertBlockSuccess
(
$options
)
{
$block
=
$this
->
getBlock
(
$options
[
'block'
]
??
[]
);
$block
->
setRestrictions
(
[
new
NamespaceRestriction
(
0
,
NS_MAIN
),
]
);
$store
=
$this
->
getStore
(
$options
[
'store'
]
??
[]
);
$result
=
$store
->
insertBlock
(
$block
);
$this
->
assertIsArray
(
$result
);
$this
->
assertArrayHasKey
(
'id'
,
$result
);
$this
->
assertArrayHasKey
(
'autoIds'
,
$result
);
$this
->
assertCount
(
0
,
$result
[
'autoIds'
]
);
$retrievedBlock
=
$store
->
newFromID
(
$result
[
'id'
]
);
$this
->
assertTrue
(
$block
->
equals
(
$retrievedBlock
)
);
}
public
static
function
provideInsertBlockSuccess
()
{
return
[
'No conflicting block, not autoblocking'
=>
[
'block'
=>
[
'autoblock'
=>
false
,
],
],
'No conflicting block, autoblocking but IP not in recent changes'
=>
[
[
'block'
=>
[
'autoblock'
=>
true
,
],
'store'
=>
[
'constructorArgs'
=>
[
MainConfigNames
::
PutIPinRC
=>
false
],
],
],
],
'No conflicting block, autoblocking but no recent edits'
=>
[
'block'
=>
[
'autoblock'
=>
true
,
],
],
'Conflicting block, expired'
=>
[
'block'
=>
[
// Blocked with expired block in addDBData
'target'
=>
'1.1.1.1'
,
],
],
];
}
public
function
testInsertBlockConflict
()
{
$block
=
$this
->
getBlock
(
[
'target'
=>
$this
->
sysop
]
);
$store
=
$this
->
getStore
();
$result
=
$store
->
insertBlock
(
$block
);
$this
->
assertFalse
(
$result
);
$this
->
assertNull
(
$block
->
getId
()
);
}
/**
* @dataProvider provideInsertBlockLogout
*/
public
function
testInsertBlockLogout
(
$options
,
$expectTokenEqual
)
{
$block
=
$this
->
getBlock
();
$userFactory
=
$this
->
getServiceContainer
()->
getUserFactory
();
$targetToken
=
$userFactory
->
newFromUserIdentity
(
$block
->
getTargetUserIdentity
()
)->
getToken
();
$store
=
$this
->
getStore
(
$options
);
$store
->
insertBlock
(
$block
);
$this
->
assertSame
(
$expectTokenEqual
,
$targetToken
===
$userFactory
->
newFromUserIdentity
(
$block
->
getTargetUserIdentity
()
)->
getToken
()
);
}
public
static
function
provideInsertBlockLogout
()
{
return
[
'Blocked user can log in'
=>
[
[
'config'
=>
[
MainConfigNames
::
BlockDisablesLogin
=>
false
],
],
true
,
],
'Blocked user cannot log in'
=>
[
[
'config'
=>
[
MainConfigNames
::
BlockDisablesLogin
=>
true
],
],
false
,
],
];
}
public
function
testInsertBlockAutoblock
()
{
// This is quicker than adding a recent change for an unblocked user.
// See addDBDataOnce documentation for more details.
$target
=
$this
->
sysop
;
$store
=
$this
->
getStore
();
$store
->
deleteBlocksMatchingConds
(
[
'bt_user'
=>
$target
->
getId
()
]
);
$block
=
$this
->
getBlock
(
[
'autoblock'
=>
true
,
'target'
=>
$target
,
]
);
$result
=
$store
->
insertBlock
(
$block
);
$this
->
assertIsArray
(
$result
);
$this
->
assertArrayHasKey
(
'autoIds'
,
$result
);
$this
->
assertCount
(
1
,
$result
[
'autoIds'
]
);
$retrievedBlock
=
$store
->
newFromID
(
$result
[
'autoIds'
][
0
]
);
$this
->
assertSame
(
$block
->
getId
(),
$retrievedBlock
->
getParentBlockId
()
);
$this
->
assertAutoblockEqualsBlock
(
$block
,
$retrievedBlock
);
}
public
function
testInsertBlockError
()
{
$block
=
$this
->
createMock
(
DatabaseBlock
::
class
);
$this
->
expectException
(
InvalidArgumentException
::
class
);
$this
->
expectExceptionMessage
(
'insert'
);
$store
=
$this
->
getStore
();
$store
->
insertBlock
(
$block
);
}
public
function
testInsertExistingBlock
()
{
$blockStore
=
$this
->
getServiceContainer
()->
getDatabaseBlockStore
();
$badActor
=
$this
->
getTestUser
()->
getUser
();
$sysop
=
$this
->
getTestSysop
()->
getUser
();
$page
=
$this
->
getExistingTestPage
(
'Foo'
);
$restriction
=
new
PageRestriction
(
0
,
$page
->
getId
()
);
$block
=
$blockStore
->
insertBlockWithParams
(
[
'targetUser'
=>
$badActor
,
'by'
=>
$sysop
,
'expiry'
=>
'infinity'
,
'restrictions'
=>
[
$restriction
],
]
);
// Insert the block again, which should result in a failure
$result
=
$blockStore
->
insertBlock
(
$block
);
$this
->
assertFalse
(
$result
);
// Ensure that there are no restrictions where the blockId is 0.
$count
=
$this
->
getDb
()->
newSelectQueryBuilder
()
->
select
(
'*'
)
->
from
(
'ipblocks_restrictions'
)
->
where
(
[
'ir_ipb_id'
=>
0
]
)
->
caller
(
__METHOD__
)->
fetchRowCount
();
$this
->
assertSame
(
0
,
$count
);
}
public
function
testUpdateBlock
()
{
$store
=
$this
->
getStore
();
$existingBlock
=
$store
->
newFromTarget
(
$this
->
sysop
);
$existingBlockTimestamp
=
$existingBlock
->
getTimestamp
();
// Insert an autoblock for T351173 regression testing
$autoblockId
=
$store
->
doAutoblock
(
$existingBlock
,
'127.0.0.1'
);
// Modify a block option
$existingBlock
->
isUsertalkEditAllowed
(
true
);
$newExpiry
=
wfTimestamp
(
TS_MW
,
time
()
+
1000
);
$existingBlock
->
setExpiry
(
$newExpiry
);
$result
=
$store
->
updateBlock
(
$existingBlock
);
$updatedBlock
=
$store
->
newFromID
(
$result
[
'id'
]
);
$autoblock
=
$store
->
newFromID
(
$autoblockId
);
$this
->
assertTrue
(
$updatedBlock
->
equals
(
$existingBlock
)
);
// Check that the timestamp was updated
$this
->
assertGreaterThan
(
$existingBlockTimestamp
,
$updatedBlock
->
getTimestamp
()
);
$this
->
assertAutoblockEqualsBlock
(
$existingBlock
,
$autoblock
);
$this
->
assertLessThanOrEqual
(
$newExpiry
,
$autoblock
->
getExpiry
()
);
}
public
function
testUpdateBlockAddOrRemoveAutoblock
()
{
$store
=
$this
->
getStore
();
// Existing block is autoblocking to begin with
$existingBlock
=
$store
->
newFromTarget
(
$this
->
sysop
);
$existingBlock
->
isAutoblocking
(
false
);
$result
=
$store
->
updateBlock
(
$existingBlock
);
$updatedBlock
=
$store
->
newFromID
(
$result
[
'id'
]
);
$this
->
assertTrue
(
$updatedBlock
->
equals
(
$existingBlock
)
);
$this
->
assertCount
(
0
,
$result
[
'autoIds'
]
);
// Test adding an autoblock in the same test run, since we need the
// target to be the sysop (see addDBDataOnce documentation), and the
// sysop is blocked with an autoblock between test runs.
$existingBlock
->
isAutoblocking
(
true
);
$result
=
$store
->
updateBlock
(
$existingBlock
);
$updatedBlock
=
$store
->
newFromID
(
$result
[
'id'
]
);
$autoblock
=
$store
->
newFromID
(
$result
[
'autoIds'
][
0
]
);
$this
->
assertTrue
(
$updatedBlock
->
equals
(
$existingBlock
)
);
$this
->
assertAutoblockEqualsBlock
(
$existingBlock
,
$autoblock
);
}
/**
* @dataProvider provideUpdateBlockRestrictions
*/
public
function
testUpdateBlockRestrictions
(
$expectedCount
)
{
$store
=
$this
->
getStore
();
$existingBlock
=
$store
->
newFromTarget
(
$this
->
sysop
);
$restrictions
=
[];
for
(
$ns
=
0
;
$ns
<
$expectedCount
;
$ns
++
)
{
$restrictions
[]
=
new
NamespaceRestriction
(
$existingBlock
->
getId
(),
$ns
);
}
$existingBlock
->
setRestrictions
(
$restrictions
);
$result
=
$store
->
updateBlock
(
$existingBlock
);
$retrievedBlock
=
$store
->
newFromID
(
$result
[
'id'
]
);
$this
->
assertCount
(
$expectedCount
,
$retrievedBlock
->
getRestrictions
()
);
}
public
static
function
provideUpdateBlockRestrictions
()
{
return
[
'Restrictions deleted if removed'
=>
[
0
],
'Restrictions changed if updated'
=>
[
2
],
];
}
public
function
testDeleteBlockSuccess
()
{
$store
=
$this
->
getStore
();
$target
=
$this
->
sysop
;
$block
=
$store
->
newFromTarget
(
$target
);
$this
->
assertTrue
(
$store
->
deleteBlock
(
$block
)
);
$this
->
assertNull
(
$store
->
newFromTarget
(
$target
)
);
}
public
function
testDeleteBlockFailureReadOnly
()
{
$store
=
$this
->
getStore
(
[
'constructorArgs'
=>
[
'readOnlyMode'
=>
$this
->
getDummyReadOnlyMode
(
true
)
],
]
);
$target
=
$this
->
sysop
;
$block
=
$store
->
newFromTarget
(
$target
);
$this
->
assertFalse
(
$store
->
deleteBlock
(
$block
)
);
$this
->
assertTrue
(
(
bool
)
$store
->
newFromTarget
(
$target
)
);
}
public
function
testDeleteBlockFailureNoBlockId
()
{
$block
=
$this
->
createMock
(
DatabaseBlock
::
class
);
$block
->
method
(
'getId'
)
->
willReturn
(
null
);
$block
->
method
(
'getWikiId'
)
->
willReturn
(
DatabaseBlock
::
LOCAL
);
$this
->
expectException
(
InvalidArgumentException
::
class
);
$this
->
expectExceptionMessage
(
'delete'
);
$store
=
$this
->
getStore
();
$store
->
deleteBlock
(
$block
);
}
/**
* Check whether expired blocks and restrictions were removed from the database.
*
* @param int $blockId
* @param bool $expected Whether to expect to find any rows
*/
private
function
assertPurgeWorked
(
int
$blockId
,
bool
$expected
):
void
{
$blockRows
=
(
bool
)
$this
->
getDb
()->
newSelectQueryBuilder
()
->
select
(
'bl_id'
)
->
from
(
'block'
)
->
where
(
[
'bl_id'
=>
$blockId
]
)
->
fetchResultSet
()->
numRows
();
$blockRestrictionsRows
=
(
bool
)
$this
->
getDb
()->
newSelectQueryBuilder
()
->
select
(
'ir_ipb_id'
)
->
from
(
'ipblocks_restrictions'
)
->
where
(
[
'ir_ipb_id'
=>
$blockId
]
)
->
fetchResultSet
()->
numRows
();
$this
->
assertSame
(
$expected
,
$blockRows
);
$this
->
assertSame
(
$expected
,
$blockRestrictionsRows
);
}
public
function
testPurgeExpiredBlocksSuccess
()
{
$store
=
$this
->
getStore
();
$store
->
purgeExpiredBlocks
();
$this
->
assertPurgeWorked
(
$this
->
expiredBlockId
,
false
);
$this
->
assertPurgeWorked
(
$this
->
unexpiredBlockId
,
true
);
}
public
function
testPurgeExpiredBlocksFailureReadOnly
()
{
$store
=
$this
->
getStore
(
[
'constructorArgs'
=>
[
'readOnlyMode'
=>
$this
->
getDummyReadOnlyMode
(
true
),
],
]
);
$store
->
purgeExpiredBlocks
();
$this
->
assertPurgeWorked
(
$this
->
expiredBlockId
,
true
);
}
/**
* Regression test for T382881
*/
public
function
testPurgeExpiredBlocksMulti
()
{
// Make deferred updates be actually deferred
$scope
=
DeferredUpdates
::
preventOpportunisticUpdates
();
// Add a second block
$store
=
$this
->
getStore
();
$store
->
insertBlockWithParams
(
[
'address'
=>
'1.1.1.1'
,
'expiry'
=>
'2001-01-01T00:00:00'
,
'reason'
=>
'additional expired block'
,
'by'
=>
$this
->
sysop
,
'expectedTargetCount'
=>
1
,
]
);
// Check that there are really two blocks on that user now
$this
->
newSelectQueryBuilder
()
->
select
(
'bt_count'
)
->
from
(
'block_target'
)
->
where
(
[
'bt_address'
=>
'1.1.1.1'
]
)
->
caller
(
__METHOD__
)
->
assertFieldValue
(
'2'
);
// Run deferred updates, purging them both
ScopedCallback
::
consume
(
$scope
);
$this
->
runDeferredUpdates
();
// Now the block_target row should be gone
$this
->
newSelectQueryBuilder
()
->
select
(
'bt_count'
)
->
from
(
'block_target'
)
->
where
(
[
'bt_address'
=>
'1.1.1.1'
]
)
->
caller
(
__METHOD__
)
->
assertEmptyResult
();
}
/**
* In order to autoblock a user, they must have a recent change.
*
* Make a recent change for the test sysop. This user persists between test runs,
* so will always have this recent change.
*
* Regular test users don't persist between test runs, because the TestUserRegistry
* is cleared between runs. If we tested autoblocking on a regular test user, we
* would need to make a recent change for each test, which is slow.
*
* Instead we always test autoblocks on the test sysop.
*/
public
function
addDBDataOnce
()
{
$this
->
editPage
(
'DatabaseBlockStoreTest test page'
,
'an edit'
,
'a summary'
,
NS_MAIN
,
$this
->
getTestSysop
()->
getUser
()
);
}
/**
* Three blocks are added:
* - an expired block with restrictions, against an IP
* - a current block with restrictions, against a user with recent changes
* - a current autoblock from the current block above
*/
public
function
addDBData
()
{
$this
->
sysop
=
$this
->
getTestSysop
()->
getUser
();
// Get a comment ID. One was added in addDBDataOnce.
$commentId
=
$this
->
getDb
()->
newSelectQueryBuilder
()
->
select
(
'comment_id'
)
->
from
(
'comment'
)
->
caller
(
__METHOD__
)
->
fetchField
();
$commonBlockData
=
[
'bl_by_actor'
=>
$this
->
sysop
->
getActorId
(),
'bl_reason_id'
=>
$commentId
,
'bl_timestamp'
=>
$this
->
getDb
()->
timestamp
(
'20000101000000'
),
'bl_anon_only'
=>
0
,
'bl_create_account'
=>
0
,
'bl_deleted'
=>
0
,
'bl_block_email'
=>
0
,
'bl_allow_usertalk'
=>
0
,
'bl_sitewide'
=>
0
,
];
$targetRows
=
[
'1.1.1.1'
=>
[
'bt_address'
=>
'1.1.1.1'
,
'bt_ip_hex'
=>
IPUtils
::
toHex
(
'1.1.1.1'
),
'bt_auto'
=>
0
,
],
'sysop'
=>
[
'bt_user'
=>
$this
->
sysop
->
getId
(),
'bt_user_text'
=>
$this
->
sysop
->
getName
(),
'bt_auto'
=>
0
,
],
'2.2.2.2'
=>
[
'bt_address'
=>
'2.2.2.2'
,
'bt_ip_hex'
=>
IPUtils
::
toHex
(
'2.2.2.2'
),
'bt_auto'
=>
1
,
]
];
$targetIds
=
[];
foreach
(
$targetRows
as
$i
=>
$row
)
{
$this
->
getDb
()->
newInsertQueryBuilder
()
->
insertInto
(
'block_target'
)
->
row
(
$row
+
[
'bt_count'
=>
1
]
)
->
execute
();
$targetIds
[
$i
]
=
$this
->
getDb
()->
insertId
();
}
$blockData
=
[
[
'bl_id'
=>
$this
->
expiredBlockId
,
'bl_target'
=>
$targetIds
[
'1.1.1.1'
],
'bl_expiry'
=>
$this
->
getDb
()->
timestamp
(
'20010101000000'
),
'bl_enable_autoblock'
=>
0
,
'bl_parent_block_id'
=>
0
,
]
+
$commonBlockData
,
[
'bl_id'
=>
$this
->
unexpiredBlockId
,
'bl_target'
=>
$targetIds
[
'sysop'
],
'bl_expiry'
=>
$this
->
getDb
()->
getInfinity
(),
'bl_enable_autoblock'
=>
1
,
'bl_parent_block_id'
=>
0
,
]
+
$commonBlockData
,
[
'bl_id'
=>
$this
->
autoblockId
,
'bl_target'
=>
$targetIds
[
'2.2.2.2'
],
'bl_expiry'
=>
$this
->
getDb
()->
getInfinity
(),
'bl_enable_autoblock'
=>
0
,
'bl_parent_block_id'
=>
$this
->
unexpiredBlockId
,
]
+
$commonBlockData
,
];
$restrictionData
=
[
[
'ir_ipb_id'
=>
$this
->
expiredBlockId
,
'ir_type'
=>
1
,
'ir_value'
=>
1
,
],
[
'ir_ipb_id'
=>
$this
->
unexpiredBlockId
,
'ir_type'
=>
2
,
'ir_value'
=>
2
,
],
[
'ir_ipb_id'
=>
$this
->
autoblockId
,
'ir_type'
=>
2
,
'ir_value'
=>
2
,
],
];
$this
->
getDb
()->
newInsertQueryBuilder
()
->
insertInto
(
'block'
)
->
rows
(
$blockData
)
->
caller
(
__METHOD__
)
->
execute
();
$this
->
getDb
()->
newInsertQueryBuilder
()
->
insertInto
(
'ipblocks_restrictions'
)
->
rows
(
$restrictionData
)
->
caller
(
__METHOD__
)
->
execute
();
}
public
function
testHardBlocks
()
{
// Set up temp user config
$this
->
enableAutoCreateTempUser
();
$targetFactory
=
$this
->
getServiceContainer
()->
getBlockTargetFactory
();
$store
=
$this
->
getStore
();
$blocker
=
$this
->
getTestUser
()->
getUser
();
$block
=
new
DatabaseBlock
();
$block
->
setTarget
(
$targetFactory
->
newFromString
(
'1.2.3.4'
)
);
$block
->
setBlocker
(
$blocker
);
$block
->
setReason
(
'test'
);
$block
->
setExpiry
(
'infinity'
);
$block
->
isHardblock
(
false
);
$store
->
insertBlock
(
$block
);
$this
->
assertFalse
(
(
bool
)
$store
->
newFromTarget
(
'~1'
),
'Temporary user is not blocked directly'
);
$this
->
assertTrue
(
(
bool
)
$store
->
newFromTarget
(
'~1'
,
'1.2.3.4'
),
'Temporary user is blocked by soft block'
);
$this
->
assertFalse
(
(
bool
)
$store
->
newFromTarget
(
$blocker
,
'1.2.3.4'
),
'Logged-in user is not blocked by soft block'
);
}
/**
* Regression test for T31116 which relates to CheckUser asking for blocks with an
* empty string for a vague target.
*
* @dataProvider provideT31116Data
*/
public
function
testT31116NewFromTargetWithEmptyIp
(
$vagueTarget
)
{
$store
=
$this
->
getStore
();
$target
=
$this
->
getTestUser
()->
getUser
();
$initialBlock
=
$this
->
getBlock
(
[
'target'
=>
$target
]
);
$store
->
insertBlock
(
$initialBlock
);
$block
=
$this
->
getStore
()->
newFromTarget
(
$target
->
getName
(),
$vagueTarget
);
$this
->
assertTrue
(
$initialBlock
->
equals
(
$block
),
"newFromTarget() returns the same block as the one that was made when "
.
"given empty vagueTarget param "
.
var_export
(
$vagueTarget
,
true
)
);
}
public
static
function
provideT31116Data
()
{
return
[
[
null
],
[
''
],
[
false
]
];
}
public
static
function
provideNewFromTargetRangeBlocks
()
{
return
[
'Blocks to IPv4 ranges'
=>
[
[
'0.0.0.0/20'
,
'0.0.0.0/30'
,
'0.0.0.0/25'
],
'0.0.0.0'
,
'0.0.0.0/30'
],
'Blocks to IPv6 ranges'
=>
[
[
'0:0:0:0:0:0:0:0/20'
,
'0:0:0:0:0:0:0:0/30'
,
'0:0:0:0:0:0:0:0/25'
],
'0:0:0:0:0:0:0:0'
,
'0:0:0:0:0:0:0:0/30'
],
'Blocks to wide IPv4 range and IP'
=>
[
[
'0.0.0.0/16'
,
'0.0.0.0'
],
'0.0.0.0'
,
'0.0.0.0'
],
'Blocks to narrow IPv4 range and IP'
=>
[
[
'0.0.0.0/31'
,
'0.0.0.0'
],
'0.0.0.0'
,
'0.0.0.0'
],
'Blocks to wide IPv6 range and IP'
=>
[
[
'0:0:0:0:0:0:0:0/19'
,
'0:0:0:0:0:0:0:0'
],
'0:0:0:0:0:0:0:0'
,
'0:0:0:0:0:0:0:0'
],
'Blocks to narrow IPv6 range and IP'
=>
[
[
'0:0:0:0:0:0:0:0/127'
,
'0:0:0:0:0:0:0:0'
],
'0:0:0:0:0:0:0:0'
,
'0:0:0:0:0:0:0:0'
],
'Blocks to wide IPv6 range and IP, large numbers'
=>
[
[
'2000:DEAD:BEEF:A:0:0:0:0/19'
,
'2000:DEAD:BEEF:A:0:0:0:0'
],
'2000:DEAD:BEEF:A:0:0:0:0'
,
'2000:DEAD:BEEF:A:0:0:0:0'
],
'Blocks to narrow IPv6 range and IP, large numbers'
=>
[
[
'2000:DEAD:BEEF:A:0:0:0:0/127'
,
'2000:DEAD:BEEF:A:0:0:0:0'
],
'2000:DEAD:BEEF:A:0:0:0:0'
,
'2000:DEAD:BEEF:A:0:0:0:0'
],
];
}
/**
* @dataProvider provideNewFromTargetRangeBlocks
*/
public
function
testNewFromTargetRangeBlocks
(
$targets
,
$ip
,
$expectedTarget
)
{
$blockStore
=
$this
->
getStore
();
$targetFactory
=
$this
->
getServiceContainer
()->
getBlockTargetFactory
();
$blocker
=
$this
->
getTestSysop
()->
getUser
();
foreach
(
$targets
as
$target
)
{
$block
=
new
DatabaseBlock
();
$block
->
setTarget
(
$targetFactory
->
newFromString
(
$target
)
);
$block
->
setBlocker
(
$blocker
);
$blockStore
->
insertBlock
(
$block
);
}
// Should find the block with the narrowest range
$block
=
$blockStore
->
newFromTarget
(
$this
->
getTestUser
()->
getUserIdentity
(),
$ip
);
$this
->
assertSame
(
$expectedTarget
,
$block
->
getTargetName
()
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 15 2026, 13:48 (12 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
10/65/5b6a046c60ce6df132f15f49f546
Default Alt Text
DatabaseBlockStoreTest.php (32 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment