Issues (21)

tests/VersionFeedFunctionalTest.php (3 issues)

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