1<?php
2/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
3
4namespace Tests\Icinga\Web;
5
6use Mockery;
7use Icinga\Web\Form;
8use Icinga\Web\Request;
9use Icinga\Test\BaseTestCase;
10
11class PostRequest extends Request
12{
13    public function getMethod()
14    {
15        return 'POST';
16    }
17}
18
19class SuccessfulForm extends Form
20{
21    public function onSuccess()
22    {
23        return true;
24    }
25}
26
27class FormTest extends BaseTestCase
28{
29    public function tearDown(): void
30    {
31        Mockery::close(); // Necessary as some tests are running isolated
32    }
33
34    public function testWhetherASubmitButtonIsAddedWithASubmitLabelBeingSet()
35    {
36        $form = new Form();
37        $form->setTokenDisabled();
38        $form->setSubmitLabel('test');
39        $form->create();
40
41        $this->assertInstanceOf(
42            '\Zend_Form_Element',
43            $form->getElement('btn_submit'),
44            'Form::create() does not add a submit button in case a submit label is set'
45        );
46    }
47
48    public function testWhetherNoSubmitButtonIsAddedWithoutASubmitLabelBeingSet()
49    {
50        $form = new Form();
51        $form->setTokenDisabled();
52        $form->create();
53
54        $this->assertNull(
55            $form->getElement('btn_submit'),
56            'Form::create() adds a submit button in case no submit label is set'
57        );
58    }
59
60    /**
61     * @depends testWhetherASubmitButtonIsAddedWithASubmitLabelBeingSet
62     */
63    public function testWhetherIsSubmittedReturnsTrueWithASubmitLabelBeingSet()
64    {
65        $form = new Form();
66        $form->setTokenDisabled();
67        $form->setSubmitLabel('test');
68        $form->populate(array('btn_submit' => true));
69        $form->setRequest(new PostRequest());
70
71        $this->assertTrue(
72            $form->isSubmitted(),
73            'Form::isSubmitted() does not return true in case a submit label is set'
74        );
75    }
76
77    /**
78     * @depends testWhetherNoSubmitButtonIsAddedWithoutASubmitLabelBeingSet
79     */
80    public function testWhetherIsSubmittedReturnsFalseWithoutASubmitLabelBeingSet()
81    {
82        $form = new Form();
83
84        $this->assertFalse(
85            $form->isSubmitted(),
86            'Form::isSubmitted() does not return false in case no submit label is set'
87        );
88    }
89
90    public function testWhetherTheCurrentLocationIsUsedAsDefaultRedirection()
91    {
92        $this->getRequestMock()->shouldReceive('getPathInfo')->andReturn('default/route');
93        $this->getResponseMock()->shouldReceive('redirectAndExit')->atLeast()->once()
94            ->with(Mockery::on(function ($url) {
95                return $url->getRelativeUrl() === 'default/route';
96            }));
97
98        $form = new SuccessfulForm();
99        $form->setTokenDisabled();
100        $form->setUidDisabled();
101        $form->handleRequest();
102    }
103
104    public function testWhetherAnExplicitlySetRedirectUrlIsUsedForRedirection()
105    {
106        $this->getResponseMock()->shouldReceive('redirectAndExit')->atLeast()->once()
107            ->with(Mockery::on(function ($url) {
108                return $url->getRelativeUrl() === 'special/route';
109            }));
110
111        $form = new SuccessfulForm();
112        $form->setTokenDisabled();
113        $form->setUidDisabled();
114        $form->setRedirectUrl('special/route');
115        $form->handleRequest();
116    }
117
118    /**
119     * @runInSeparateProcess
120     */
121    public function testWhetherACsrfCounterMeasureIsBeingAdded()
122    {
123        Mockery::mock('alias:Icinga\Web\Session')->shouldReceive('getSession->getId')->andReturn('1234567890');
124
125        $form = new Form();
126        $form->create();
127
128        $this->assertInstanceOf(
129            '\Zend_Form_Element',
130            $form->getElement($form->getTokenElementName()),
131            'Form::create() does not add a csrf counter measure element'
132        );
133    }
134
135    public function testWhetherACsrfCounterMeasureIsNotBeingAdded()
136    {
137        $form = new Form();
138        $form->setTokenDisabled();
139        $form->create();
140
141        $this->assertNull(
142            $form->getElement($form->getTokenElementName()),
143            'Form::create() adds a csrf counter measure element in case it\'s disabled'
144        );
145    }
146
147    public function testWhetherAUniqueFormIdIsBeingAdded()
148    {
149        $form = new Form();
150        $form->setTokenDisabled();
151        $form->create();
152
153        $this->assertInstanceOf(
154            '\Zend_Form_Element',
155            $form->getElement($form->getUidElementName()),
156            'Form::create() does not add a form identification element'
157        );
158    }
159
160    public function testWhetherAUniqueFormIdIsNotBeingAdded()
161    {
162        $form = new Form();
163        $form->setTokenDisabled();
164        $form->setUidDisabled();
165        $form->create();
166
167        $this->assertNull(
168            $form->getElement($form->getUidElementName()),
169            'Form::create() adds a form identification element in case it\'s disabled'
170        );
171    }
172
173    /**
174     * @depends testWhetherAUniqueFormIdIsBeingAdded
175     */
176    public function testWhetherAFormIsSentWithAUniqueFormIdBeingAdded()
177    {
178        $form = new Form();
179        $form->setTokenDisabled();
180        $form->create();
181
182        $this->assertTrue(
183            $form->wasSent(
184                array(
185                    $form->getUidElementName() => $form->getElement($form->getUidElementName())->getValue()
186                )
187            ),
188            'Form::wasSent() does not return true in case a the form identification value is being sent'
189        );
190    }
191
192    /**
193     * @depends testWhetherAUniqueFormIdIsNotBeingAdded
194     */
195    public function testWhetherAFormIsNotSentWithoutAUniqueFormIdBeingAdded()
196    {
197        $form = new Form();
198        $form->setTokenDisabled();
199        $form->setUidDisabled();
200        $form->create();
201
202        $this->assertFalse(
203            $form->wasSent(array()),
204            'Form::wasSent() does not return false in case no form identification element was added'
205        );
206    }
207
208    public function testWhetherADefaultActionIsBeingSetOnFormCreation()
209    {
210        $this->getRequestMock()->shouldReceive('getPathInfo')->andReturn('some/route');
211
212        $form = new Form();
213        $form->setTokenDisabled();
214        $form->create();
215
216        $this->assertEquals(
217            '/some/route',
218            $form->getAction(),
219            'Form::create() does not set a default action if none was set explicitly'
220        );
221    }
222
223    /**
224     * @depends testWhetherAUniqueFormIdIsBeingAdded
225     * @depends testWhetherASubmitButtonIsAddedWithASubmitLabelBeingSet
226     */
227    public function testWhetherItIsPossibleToRecreateAForm()
228    {
229        $form = new Form();
230        $form->setTokenDisabled();
231        $form->setSubmitLabel('test');
232        $form->create(); // sets the flag $this->created to true
233        $form->clearElements(); // should reset the flag..
234        $form->create(); // ..so that we can recreate the form
235
236        $this->assertCount(
237            2,
238            $form->getElements(),
239            'Form::clearElements() does not fully reset the form'
240        );
241    }
242
243    public function testWhetherGetNameReturnsTheEscapedClassNameByDefault()
244    {
245        $form = new Form();
246
247        $this->assertEquals(
248            $form->filterName(get_class($form)),
249            $form->getName(),
250            'Form::getName() does not return the escaped class name in case no name was explicitly set'
251        );
252    }
253
254    public function testWhetherTheOnSuccessOptionMustBeCallable()
255    {
256        $this->expectException(\Icinga\Exception\ProgrammingError::class);
257
258        new Form(array('onSuccess' => '_invalid_'));
259    }
260
261    /**
262     * @depends testWhetherACsrfCounterMeasureIsNotBeingAdded
263     * @depends testWhetherAUniqueFormIdIsNotBeingAdded
264     * @depends testWhetherNoSubmitButtonIsAddedWithoutASubmitLabelBeingSet
265     */
266    public function testWhetherAClosureCanBePassedAsOnSuccessCallback()
267    {
268        $request = new Request();
269        $form = new Form(array(
270            'onSuccess' => function ($form) {
271                $form->getRequest()->setParam('test', 'tset');
272                return false;
273            }
274        ));
275        $form->setTokenDisabled();
276        $form->setUidDisabled();
277        $form->handleRequest($request);
278
279        $this->assertEquals(
280            'tset',
281            $request->getParam('test'),
282            'Form does not utilize the onSuccess callback set with form options on instantiation'
283        );
284    }
285}
286