Completed
Push — master ( 921b7c...de70e5 )
by Robbie
13s
created

testChangesActionContainsChangesForCurrentPageOnly()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 22
rs 9.2
cc 1
eloc 16
nc 1
nop 0
1
<?php
2
3
namespace SilverStripe\VersionFeed\Tests;
4
5
use Page;
0 ignored issues
show
Bug introduced by
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Psr\SimpleCache\CacheInterface;
7
use SilverStripe\CMS\Model\SiteTree;
8
use SilverStripe\Control\Director;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\Dev\FunctionalTest;
12
use SilverStripe\SiteConfig\SiteConfig;
13
use SilverStripe\Versioned\Versioned;
14
use SilverStripe\VersionFeed\Filters\CachedContentFilter;
15
use SilverStripe\VersionFeed\Filters\RateLimitFilter;
16
use SilverStripe\VersionFeed\VersionFeed;
17
use SilverStripe\VersionFeed\VersionFeedController;
18
19
class VersionFeedFunctionalTest extends FunctionalTest
20
{
21
    protected $usesDatabase = true;
22
    
23
    protected $baseURI = 'http://www.fakesite.test';
24
25
    protected static $required_extensions = array(
26
        'Page' => array(VersionFeed::class),
27
        'PageController' => array(VersionFeedController::class),
28
    );
29
30
    protected $userIP;
31
32
    protected function setUp()
33
    {
34
        Director::config()->set('alternate_base_url', $this->baseURI);
35
        
36
        parent::setUp();
37
38
        $cache = Injector::inst()->get(
39
            CacheInterface::class . '.VersionFeedController'
40
        );
41
        $cache->clear();
42
43
        $this->userIP = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
44
45
        // Enable history by default
46
        Config::modify()->set(VersionFeed::class, 'changes_enabled', true);
47
        Config::modify()->set(VersionFeed::class, 'allchanges_enabled', true);
48
49
        // Disable caching and locking by default
50
        Config::modify()->set(CachedContentFilter::class, 'cache_enabled', false);
51
        Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 0);
52
        Config::modify()->set(RateLimitFilter::class, 'lock_bypage', false);
53
        Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false);
54
        Config::modify()->set(RateLimitFilter::class, 'lock_cooldown', false);
55
    }
56
57
    protected function tearDown()
58
    {
59
        Director::config()->set('alternate_base_url', null);
60
        
61
        $_SERVER['REMOTE_ADDR'] = $this->userIP;
62
63
        parent::tearDown();
64
    }
65
66
    public function testPublicHistory()
67
    {
68
        $page = $this->createPageWithChanges(array('PublicHistory' => false));
69
70
        $response = $this->get($page->RelativeLink('changes'));
71
        $this->assertEquals(
72
            404,
73
            $response->getStatusCode(),
74
            'With Page\'s "PublicHistory" disabled, `changes` action response code should be 404'
75
        );
76
77
        $response = $this->get($page->RelativeLink('allchanges'));
78
        $this->assertEquals(200, $response->getStatusCode());
79
        $xml = simplexml_load_string($response->getBody());
80
        $this->assertFalse(
81
            (bool)$xml->channel->item,
82
            'With Page\'s "PublicHistory" disabled, `allchanges` action should not have an item in the channel'
83
        );
84
85
        $page = $this->createPageWithChanges(array('PublicHistory' => true));
86
87
        $response = $this->get($page->RelativeLink('changes'));
88
        $this->assertEquals(200, $response->getStatusCode());
89
        $xml = simplexml_load_string($response->getBody());
90
        $this->assertTrue(
91
            (bool)$xml->channel->item,
92
            'With Page\'s "PublicHistory" enabled, `changes` action should have an item in the channel'
93
        );
94
95
        $response = $this->get($page->RelativeLink('allchanges'));
96
        $this->assertEquals(200, $response->getStatusCode());
97
        $xml = simplexml_load_string($response->getBody());
98
        $this->assertTrue(
99
            (bool)$xml->channel->item,
100
            'With "PublicHistory" enabled, `allchanges` action should have an item in the channel'
101
        );
102
    }
103
104
    public function testRateLimiting()
105
    {
106
        // Re-enable locking just for this test
107
        Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 20);
108
        Config::modify()->set(CachedContentFilter::class, 'cache_enabled', true);
109
110
        $page1 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page1'));
111
        $page2 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page2'));
112
113
        // Artifically set cache lock
114
        Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false);
115
        $cache = Injector::inst()->get(
116
            CacheInterface::class . '.VersionFeedController'
117
        );
118
        $cache->set(RateLimitFilter::CACHE_PREFIX, time() + 10);
119
120
        // Test normal hit
121
        $response = $this->get($page1->RelativeLink('changes'));
122
        $this->assertEquals(429, $response->getStatusCode());
123
        $this->assertGreaterThan(0, $response->getHeader('Retry-After'));
124
        $response = $this->get($page2->RelativeLink('changes'));
125
        $this->assertEquals(429, $response->getStatusCode());
126
        $this->assertGreaterThan(0, $response->getHeader('Retry-After'));
127
128
        // Test page specific lock
129
        Config::modify()->set(RateLimitFilter::class, 'lock_bypage', true);
130
        $key = implode('_', array(
131
            'changes',
132
            $page1->ID,
133
            Versioned::get_versionnumber_by_stage(SiteTree::class, 'Live', $page1->ID, false)
134
        ));
135
        $key = RateLimitFilter::CACHE_PREFIX . '_' . md5($key);
136
        $cache->set($key, time() + 10);
137
        $response = $this->get($page1->RelativeLink('changes'));
138
        $this->assertEquals(429, $response->getStatusCode());
139
        $this->assertGreaterThan(0, $response->getHeader('Retry-After'));
140
        $response = $this->get($page2->RelativeLink('changes'));
141
        $this->assertEquals(200, $response->getStatusCode());
142
        Config::modify()->set(RateLimitFilter::class, 'lock_bypage', false);
143
144
        // Test rate limit hit by IP
145
        Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', true);
146
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
147
        $cache->set(RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1'), time() + 10);
148
        $response = $this->get($page1->RelativeLink('changes'));
149
        $this->assertEquals(429, $response->getStatusCode());
150
        $this->assertGreaterThan(0, $response->getHeader('Retry-After'));
151
152
        // Test rate limit doesn't hit other IP
153
        $_SERVER['REMOTE_ADDR'] = '127.0.0.20';
154
        $cache->set(RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1'), time() + 10);
155
        $response = $this->get($page1->RelativeLink('changes'));
156
        $this->assertEquals(200, $response->getStatusCode());
157
158
        // Restore setting
159
        Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false);
160
        Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 0);
161
        Config::modify()->set(CachedContentFilter::class, 'cache_enabled', false);
162
    }
163
164
    public function testChangesActionContainsChangesForCurrentPageOnly()
165
    {
166
        $page1 = $this->createPageWithChanges(array('Title' => 'Page1'));
167
        $page2 = $this->createPageWithChanges(array('Title' => 'Page2'));
168
169
        $response = $this->get($page1->RelativeLink('changes'));
170
        $xml = simplexml_load_string($response->getBody());
171
        $titles = array_map(function ($item) {
172
            return (string)$item->title;
173
        }, $xml->xpath('//item'));
174
        // TODO Unclear if this should contain the original version
175
        $this->assertContains('Changed: Page1', $titles);
176
        $this->assertNotContains('Changed: Page2', $titles);
177
178
        $response = $this->get($page2->RelativeLink('changes'));
179
        $xml = simplexml_load_string($response->getBody());
180
        $titles = array_map(function ($item) {
181
            return (string)$item->title;
182
        }, $xml->xpath('//item'));
183
        // TODO Unclear if this should contain the original version
184
        $this->assertNotContains('Changed: Page1', $titles);
185
        $this->assertContains('Changed: Page2', $titles);
186
    }
187
188
    public function testAllChangesActionContainsAllChangesForAllPages()
189
    {
190
        $page1 = $this->createPageWithChanges(array('Title' => 'Page1'));
191
        $page2 = $this->createPageWithChanges(array('Title' => 'Page2'));
0 ignored issues
show
Unused Code introduced by
The assignment to $page2 is dead and can be removed.
Loading history...
192
193
        $response = $this->get($page1->RelativeLink('allchanges'));
194
        $xml = simplexml_load_string($response->getBody());
195
        $titles = array_map(function ($item) {
196
            return str_replace('Changed: ', '', (string) $item->title);
197
        }, $xml->xpath('//item'));
198
        $this->assertContains('Page1', $titles);
199
        $this->assertContains('Page2', $titles);
200
    }
201
202
    protected function createPageWithChanges($seed = null)
203
    {
204
        $page = new Page();
205
206
        $seed = array_merge(array(
207
            'Title' => 'My Title',
208
            'Content' => 'My Content'
209
        ), $seed);
210
        $page->update($seed);
211
        $page->write();
212
        $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
213
214
        $page->update(array(
215
            'Title' => 'Changed: ' . $seed['Title'],
216
            'Content' => 'Changed: ' . $seed['Content'],
217
        ));
218
        $page->write();
219
        $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
220
221
        $page->update(array(
222
            'Title' => 'Changed again: ' . $seed['Title'],
223
            'Content' => 'Changed again: ' . $seed['Content'],
224
        ));
225
        $page->write();
226
        $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
227
228
        $page->update(array(
229
            'Title' => 'Unpublished: ' . $seed['Title'],
230
            'Content' => 'Unpublished: ' . $seed['Content'],
231
        ));
232
        $page->write();
233
234
        return $page;
235
    }
236
237
    /**
238
     * Tests response code for globally disabled feeds
239
     */
240
    public function testFeedViewability()
241
    {
242
243
        // Nested loop through each configuration
244
        foreach (array(true, false) as $publicHistory_Page) {
245
            $page = $this->createPageWithChanges(array('PublicHistory' => $publicHistory_Page, 'Title' => 'Page'));
246
247
            // Test requests to 'changes' action
248
            foreach (array(true, false) as $publicHistory_Config) {
249
                Config::modify()->set(VersionFeed::class, 'changes_enabled', $publicHistory_Config);
250
                $expectedResponse = $publicHistory_Page && $publicHistory_Config ? 200 : 404;
251
                $response = $this->get($page->RelativeLink('changes'));
252
                $this->assertEquals($expectedResponse, $response->getStatusCode());
253
            }
254
255
            // Test requests to 'allchanges' action on each page
256
            foreach (array(true, false) as $allChanges_Config) {
257
                foreach (array(true, false) as $allChanges_SiteConfig) {
258
                    Config::modify()->set(VersionFeed::class, 'allchanges_enabled', $allChanges_Config);
259
                    $siteConfig = SiteConfig::current_site_config();
260
                    $siteConfig->AllChangesEnabled = $allChanges_SiteConfig;
261
                    $siteConfig->write();
262
263
                    $expectedResponse = $allChanges_Config && $allChanges_SiteConfig ? 200 : 404;
264
                    $response = $this->get($page->RelativeLink('allchanges'));
265
                    $this->assertEquals($expectedResponse, $response->getStatusCode());
266
                }
267
            }
268
        }
269
    }
270
}
271