Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.29% covered (success)
90.29%
93 / 103
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
GrowthConfigValidation
90.29% covered (success)
90.29%
93 / 103
40.00% covered (danger)
40.00%
2 / 5
11.11
0.00% covered (danger)
0.00%
0 / 1
 getConfigDescriptors
100.00% covered (success)
100.00%
76 / 76
100.00% covered (success)
100.00%
1 / 1
1
 validateField
68.75% covered (warning)
68.75%
11 / 16
0.00% covered (danger)
0.00%
0 / 1
4.49
 validate
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 validateVariable
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getDefaultContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments\Config\Validation;
4
5use GrowthExperiments\Config\GrowthExperimentsMultiConfig;
6use InvalidArgumentException;
7use MediaWiki\Message\Message;
8use StatusValue;
9
10/**
11 * Validation class for MediaWiki:GrowthExperimentsConfig.json
12 */
13class GrowthConfigValidation implements IConfigValidator {
14    use DatatypeValidationTrait;
15
16    /**
17     * Copy of TemplateCollectionFeature::MAX_TEMPLATES_IN_COLLECTION. We avoid a direct reference
18     * to keep CirrusSearch an optional dependency.
19     */
20    public const MAX_TEMPLATES_IN_COLLECTION = 800;
21
22    private function getConfigDescriptors(): array {
23        return [
24            'GEHelpPanelReadingModeNamespaces' => [
25                'type' => 'int[]',
26            ],
27            'GEHelpPanelExcludedNamespaces' => [
28                'type' => 'int[]',
29            ],
30            'GEHelpPanelHelpDeskTitle' => [
31                'type' => '?string',
32            ],
33            'GEHelpPanelHelpDeskPostOnTop' => [
34                'type' => 'bool',
35            ],
36            'GEHelpPanelViewMoreTitle' => [
37                'type' => 'string',
38            ],
39            'GEHelpPanelSearchNamespaces' => [
40                'type' => 'int[]',
41            ],
42            'GEHelpPanelAskMentor' => [
43                'type' => 'bool',
44            ],
45            'GEMentorshipEnabled' => [
46                'type' => 'bool',
47            ],
48            'GEHelpPanelLinks' => [
49                'type' => 'array<int,array<string,string>>',
50            ],
51            'GEHomepageSuggestedEditsIntroLinks' => [
52                'type' => 'array<string,string>',
53            ],
54            'GEInfoboxTemplates' => [
55                'type' => 'array',
56                'maxSize' => self::MAX_TEMPLATES_IN_COLLECTION,
57            ],
58            'GEInfoboxTemplatesTest' => [
59                'type' => 'array',
60                'maxSize' => self::MAX_TEMPLATES_IN_COLLECTION,
61            ],
62            'GECampaigns' => [
63                'type' => 'array',
64            ],
65            'GECampaignTopics' => [
66                'type' => 'array',
67            ],
68            'GEMentorshipAutomaticEligibility' => [
69                'type' => 'bool',
70            ],
71            'GEMentorshipMinimumAge' => [
72                'type' => 'int',
73            ],
74            'GEMentorshipMinimumEditcount' => [
75                'type' => 'int',
76            ],
77            'GEPersonalizedPraiseDefaultNotificationsFrequency' => [
78                'type' => 'int',
79            ],
80            'GEPersonalizedPraiseDays' => [
81                'type' => 'int',
82            ],
83            'GEPersonalizedPraiseMinEdits' => [
84                'type' => 'int',
85            ],
86            'GEPersonalizedPraiseMaxEdits' => [
87                'type' => 'int',
88            ],
89            'GEPersonalizedPraiseMaxReverts' => [
90                'type' => '?int',
91            ],
92            'GELevelingUpGetStartedMaxTotalEdits' => [
93                'type' => 'int',
94            ],
95            'GELevelingUpKeepGoingNotificationThresholds' => [
96                'type' => 'int[]',
97            ],
98        ];
99    }
100
101    /**
102     * Validate a given field
103     *
104     * @param string $fieldName Name of the field to be validated
105     * @param array $descriptor Descriptor of the field (
106     * @param array $data
107     * @return StatusValue
108     */
109    private function validateField(
110        string $fieldName,
111        array $descriptor,
112        array $data
113    ): StatusValue {
114        // validate is supposed to make sure $data has $field as a key,
115        // so this should not throw key errors.
116        $value = $data[$fieldName];
117
118        $expectedType = $descriptor['type'];
119        if ( !$this->validateFieldDatatype( $expectedType, $value ) ) {
120            return StatusValue::newFatal(
121                'growthexperiments-config-validator-datatype-mismatch',
122                $fieldName,
123                $expectedType,
124                gettype( $value )
125            );
126        }
127
128        if ( isset( $descriptor['maxSize'] ) && count( $value ) > $descriptor['maxSize'] ) {
129            return StatusValue::newFatal(
130                'growthexperiments-config-validator-array-toobig',
131                $fieldName,
132                Message::numParam( $descriptor['maxSize'] )
133            );
134        }
135
136        return StatusValue::newGood();
137    }
138
139    /**
140     * @inheritDoc
141     */
142    public function validate( array $data ): StatusValue {
143        $status = StatusValue::newGood();
144        foreach ( $this->getConfigDescriptors() as $field => $descriptor ) {
145            if ( !array_key_exists( $field, $data ) ) {
146                // No need to validate something we're not setting
147                continue;
148            }
149
150            $status->merge( $this->validateField( $field, $descriptor, $data ) );
151        }
152
153        return $status;
154    }
155
156    /**
157     * @inheritDoc
158     */
159    public function validateVariable( string $variable, $value ): void {
160        if ( !in_array( $variable, GrowthExperimentsMultiConfig::ALLOW_LIST ) ) {
161            throw new InvalidArgumentException(
162                'Invalid attempt to set a variable via WikiPageConfigWriter'
163            );
164        }
165    }
166
167    /**
168     * @inheritDoc
169     */
170    public function getDefaultContent(): array {
171        return [];
172    }
173}