Validate the value of VectorWebABTestEnrollment

The generation of JavaScript will throw a RuntimeException
making it obvious when an invalid A/B test has been setup.

Bug: T297662
Change-Id: I75b0e923463bf52f8fc5b5c6b7f9baf586053154
This commit is contained in:
jdlrobson 2021-12-14 11:24:32 -08:00 committed by Jdlrobson
parent 0cbf9c61d9
commit 64ee622c73
2 changed files with 184 additions and 3 deletions

View file

@ -7,6 +7,7 @@ use HTMLForm;
use MediaWiki\MediaWikiServices;
use OutputPage;
use ResourceLoaderContext;
use RuntimeException;
use Skin;
use SkinTemplate;
use SkinVector;
@ -23,6 +24,41 @@ use Vector\HTMLForm\Fields\HTMLLegacySkinVersionField;
* @internal
*/
class Hooks {
/**
* @param Config $config
* @return array
*/
private static function getActiveABTest( $config ) {
$ab = $config->get(
Constants::CONFIG_STICKY_HEADER_TREATMENT_AB_TEST_ENROLLMENT
);
if ( count( $ab ) === 0 ) {
// If array is empty then no experiment and need to validate.
return $ab;
}
if ( !array_key_exists( 'buckets', $ab ) ) {
throw new RuntimeException( 'Invalid VectorWebABTestEnrollment value: Must contain buckets key.' );
}
if ( !array_key_exists( 'unsampled', $ab['buckets'] ) ) {
throw new RuntimeException( 'Invalid VectorWebABTestEnrollment value: Must define an `unsampled` bucket.' );
} else {
// check bucket values.
foreach ( $ab['buckets'] as $bucketName => $bucketDefinition ) {
if ( !is_array( $bucketDefinition ) ) {
throw new RuntimeException( 'Invalid VectorWebABTestEnrollment value: Buckets should be arrays' );
}
$samplingRate = $bucketDefinition['samplingRate'];
if ( is_string( $samplingRate ) ) {
throw new RuntimeException(
'Invalid VectorWebABTestEnrollment value: Sampling rate should be number between 0 and 1.'
);
}
}
}
return $ab;
}
/**
* Passes config variables to Vector (modern) ResourceLoader module.
* @param ResourceLoaderContext $context
@ -35,9 +71,7 @@ class Hooks {
) {
return [
'wgVectorSearchHost' => $config->get( 'VectorSearchHost' ),
'wgVectorWebABTestEnrollment' => $config->get(
Constants::CONFIG_STICKY_HEADER_TREATMENT_AB_TEST_ENROLLMENT
),
'wgVectorWebABTestEnrollment' => self::getActiveABTest( $config ),
];
}

View file

@ -43,6 +43,123 @@ class VectorHooksTest extends MediaWikiIntegrationTestCase {
];
}
public function provideGetVectorResourceLoaderConfig() {
return [
[
[
'VectorWebABTestEnrollment' => [],
'VectorSearchHost' => 'en.wikipedia.org'
],
[
'wgVectorSearchHost' => 'en.wikipedia.org',
'wgVectorWebABTestEnrollment' => [],
]
],
[
[
'VectorWebABTestEnrollment' => [
'name' => 'vector.sticky_header',
'enabled' => true,
'buckets' => [
'unsampled' => [
'samplingRate' => 1,
],
'control' => [
'samplingRate' => 0
],
'stickyHeaderEnabled' => [
'samplingRate' => 0
],
'stickyHeaderDisabled' => [
'samplingRate' => 0
],
],
],
'VectorSearchHost' => 'en.wikipedia.org'
],
[
'wgVectorSearchHost' => 'en.wikipedia.org',
'wgVectorWebABTestEnrollment' => [
'name' => 'vector.sticky_header',
'enabled' => true,
'buckets' => [
'unsampled' => [
'samplingRate' => 1,
],
'control' => [
'samplingRate' => 0
],
'stickyHeaderEnabled' => [
'samplingRate' => 0
],
'stickyHeaderDisabled' => [
'samplingRate' => 0
],
],
],
]
],
];
}
public function provideGetVectorResourceLoaderConfigWithExceptions() {
return [
# Bad experiment (no buckets)
[
[
'VectorSearchHost' => 'en.wikipedia.org',
'VectorWebABTestEnrollment' => [
'name' => 'vector.sticky_header',
'enabled' => true,
],
]
],
# Bad experiment (no unsampled bucket)
[
[
'VectorSearchHost' => 'en.wikipedia.org',
'VectorWebABTestEnrollment' => [
'name' => 'vector.sticky_header',
'enabled' => true,
'buckets' => [
'a' => [
'samplingRate' => 0
],
]
],
]
],
# Bad experiment (wrong format)
[
[
'VectorSearchHost' => 'en.wikipedia.org',
'VectorWebABTestEnrollment' => [
'name' => 'vector.sticky_header',
'enabled' => true,
'buckets' => [
'unsampled' => 1,
]
],
]
],
# Bad experiment (samplingRate defined as string)
[
[
'VectorSearchHost' => 'en.wikipedia.org',
'VectorWebABTestEnrollment' => [
'name' => 'vector.sticky_header',
'enabled' => true,
'buckets' => [
'unsampled' => [
'samplingRate' => '1'
],
]
],
]
],
];
}
/**
* @covers ::shouldDisableMaxWidth
*/
@ -220,6 +337,36 @@ class VectorHooksTest extends MediaWikiIntegrationTestCase {
);
}
/**
* @covers ::getVectorResourceLoaderConfig
* @dataProvider provideGetVectorResourceLoaderConfig
*/
public function testGetVectorResourceLoaderConfig( $configData, $expected ) {
$config = new HashConfig( $configData );
$vectorConfig = Hooks::getVectorResourceLoaderConfig(
$this->createMock( ResourceLoaderContext::class ),
$config
);
$this->assertSame(
$vectorConfig,
$expected
);
}
/**
* @covers ::getVectorResourceLoaderConfig
* @dataProvider provideGetVectorResourceLoaderConfigWithExceptions
*/
public function testGetVectorResourceLoaderConfigWithExceptions( $configData ) {
$config = new HashConfig( $configData );
$this->expectException( RuntimeException::class );
$vectorConfig = Hooks::getVectorResourceLoaderConfig(
$this->createMock( ResourceLoaderContext::class ),
$config
);
}
/**
* @covers ::onGetPreferences
*/