Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.29% |
93 / 103 |
|
40.00% |
2 / 5 |
CRAP | |
0.00% |
0 / 1 |
GrowthConfigValidation | |
90.29% |
93 / 103 |
|
40.00% |
2 / 5 |
11.11 | |
0.00% |
0 / 1 |
getConfigDescriptors | |
100.00% |
76 / 76 |
|
100.00% |
1 / 1 |
1 | |||
validateField | |
68.75% |
11 / 16 |
|
0.00% |
0 / 1 |
4.49 | |||
validate | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
validateVariable | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getDefaultContent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\Config\Validation; |
4 | |
5 | use GrowthExperiments\Config\GrowthExperimentsMultiConfig; |
6 | use InvalidArgumentException; |
7 | use MediaWiki\Message\Message; |
8 | use StatusValue; |
9 | |
10 | /** |
11 | * Validation class for MediaWiki:GrowthExperimentsConfig.json |
12 | */ |
13 | class 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 | } |