Passed
Pull Request — master (#42)
by
unknown
04:10
created

testContainsAllChangesForAllPages()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 7
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
7
use SilverStripe\VersionFeed\VersionFeed;
8
use SilverStripe\VersionFeed\Filters\CachedContentFilter;
9
use SilverStripe\VersionFeed\Filters\RateLimitFilter;
10
use SilverStripe\VersionFeed\VersionFeedController;
11
use SilverStripe\Core\Config\Config;
12
use SilverStripe\Core\Injector\Injector;
13
use SilverStripe\CMS\Model\SiteTree;
14
use SilverStripe\Versioned\Versioned;
15
use SilverStripe\SiteConfig\SiteConfig;
16
use SilverStripe\Dev\FunctionalTest;
17
use SilverStripe\Control\Director;
18
use Psr\SimpleCache\CacheInterface;
19
20
21
class VersionFeedFunctionalTest extends FunctionalTest {
22
	protected $usesDatabase = true;
23
	
24
	protected $baseURI = 'http://www.fakesite.test';	
25
26
	protected static $required_extensions = array(
27
		'Page' => array(VersionFeed::class),
28
		'PageController' => array(VersionFeedController::class),
29
	);
30
31
	protected $userIP;
32
33
	protected function setUp() {
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
	public function tearDown() {
58
		Director::config()->set('alternate_base_url', null);
59
		
60
		$_SERVER['REMOTE_ADDR'] = $this->userIP;
61
62
		parent::tearDown();
63
	}
64
65
	public function testPublicHistory() {
66
		$page = $this->createPageWithChanges(array('PublicHistory' => false));
67
68
		$response = $this->get($page->RelativeLink('changes'));
69
		$this->assertEquals(404, $response->getStatusCode());
70
71
		$response = $this->get($page->RelativeLink('allchanges'));
72
		$this->assertEquals(200, $response->getStatusCode());
73
		$xml = simplexml_load_string($response->getBody());
74
		$this->assertFalse((bool)$xml->channel->item);
75
76
		$page = $this->createPageWithChanges(array('PublicHistory' => true));
77
78
		$response = $this->get($page->RelativeLink('changes'));
79
		$this->assertEquals(200, $response->getStatusCode());
80
		$xml = simplexml_load_string($response->getBody());
81
		$this->assertTrue((bool)$xml->channel->item);
82
83
		$response = $this->get($page->RelativeLink('allchanges'));
84
		$this->assertEquals(200, $response->getStatusCode());
85
		$xml = simplexml_load_string($response->getBody());
86
		$this->assertTrue((bool)$xml->channel->item);
87
	}
88
89
	public function testRateLimiting() {
90
		// Re-enable locking just for this test
91
		Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 20);
92
		Config::modify()->set(CachedContentFilter::class, 'cache_enabled', true);
93
94
		$page1 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page1'));
95
		$page2 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page2'));
96
97
		// Artifically set cache lock
98
		Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false);
99
		$cache = Injector::inst()->get(
100
			CacheInterface::class . '.VersionFeedController'
101
		);
102
		$cache->set(RateLimitFilter::CACHE_PREFIX, time() + 10);
103
104
		// Test normal hit
105
		$response = $this->get($page1->RelativeLink('changes'));
106
		$this->assertEquals(429, $response->getStatusCode());
107
		$this->assertGreaterThan(0, $response->getHeader('Retry-After'));
108
		$response = $this->get($page2->RelativeLink('changes'));
109
		$this->assertEquals(429, $response->getStatusCode());
110
		$this->assertGreaterThan(0, $response->getHeader('Retry-After'));
111
112
		// Test page specific lock
113
		Config::modify()->set(RateLimitFilter::class, 'lock_bypage', true);
114
		$key = implode('_', array(
115
			'changes',
116
			$page1->ID,
117
			Versioned::get_versionnumber_by_stage(SiteTree::class, 'Live', $page1->ID, false)
118
		));
119
		$key = RateLimitFilter::CACHE_PREFIX . '_' . md5($key);
120
		$cache->set($key, time() + 10);
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(200, $response->getStatusCode());
126
		Config::modify()->set(RateLimitFilter::class, 'lock_bypage', false);
127
128
		// Test rate limit hit by IP
129
		Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', true);
130
		$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
131
		$cache->set(RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1'), time() + 10);
132
		$response = $this->get($page1->RelativeLink('changes'));
133
		$this->assertEquals(429, $response->getStatusCode());
134
		$this->assertGreaterThan(0, $response->getHeader('Retry-After'));
135
136
		// Test rate limit doesn't hit other IP
137
		$_SERVER['REMOTE_ADDR'] = '127.0.0.20';
138
		$cache->set(RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1'), time() + 10);
139
		$response = $this->get($page1->RelativeLink('changes'));
140
		$this->assertEquals(200, $response->getStatusCode());
141
142
		// Restore setting
143
		Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false);
144
		Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 0);
145
		Config::modify()->set(CachedContentFilter::class, 'cache_enabled', false);
146
	}
147
148
	public function testContainsChangesForPageOnly() {
149
		$page1 = $this->createPageWithChanges(array('Title' => 'Page1'));
150
		$page2 = $this->createPageWithChanges(array('Title' => 'Page2'));
151
152
		$response = $this->get($page1->RelativeLink('changes'));
153
		$xml = simplexml_load_string($response->getBody());
154
		$titles = array_map(function($item) {return (string)$item->title;}, $xml->xpath('//item'));
155
		// TODO Unclear if this should contain the original version
156
		$this->assertContains('Changed: Page1', $titles);
157
		$this->assertNotContains('Changed: Page2', $titles);
158
159
		$response = $this->get($page2->RelativeLink('changes'));
160
		$xml = simplexml_load_string($response->getBody());
161
		$titles = array_map(function($item) {return (string)$item->title;}, $xml->xpath('//item'));
162
		// TODO Unclear if this should contain the original version
163
		$this->assertNotContains('Changed: Page1', $titles);
164
		$this->assertContains('Changed: Page2', $titles);
165
	}
166
167
	public function testContainsAllChangesForAllPages() {
168
		$page1 = $this->createPageWithChanges(array('Title' => 'Page1'));
169
		$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...
170
171
		$response = $this->get($page1->RelativeLink('allchanges'));
172
		$xml = simplexml_load_string($response->getBody());
173
		$titles = array_map(function($item) {return (string)$item->title;}, $xml->xpath('//item'));
174
		$this->assertContains('Page1', $titles);
175
		$this->assertContains('Page2', $titles);
176
	}
177
178
	protected function createPageWithChanges($seed = null) {
179
		$page = new Page();
180
181
		$seed = array_merge(array(
182
			'Title' => 'My Title',
183
			'Content' => 'My Content'
184
		), $seed);
185
		$page->update($seed);
186
		$page->write();
187
		$page->publish('Stage', 'Live');
188
189
		$page->update(array(
190
			'Title' => 'Changed: ' . $seed['Title'],
191
			'Content' => 'Changed: ' . $seed['Content'],
192
		));
193
		$page->write();
194
		$page->publish('Stage', 'Live');
195
196
		$page->update(array(
197
			'Title' => 'Changed again: ' . $seed['Title'],
198
			'Content' => 'Changed again: ' . $seed['Content'],
199
		));
200
		$page->write();
201
		$page->publish('Stage', 'Live');
202
203
		$page->update(array(
204
			'Title' => 'Unpublished: ' . $seed['Title'],
205
			'Content' => 'Unpublished: ' . $seed['Content'],
206
		));
207
		$page->write();
208
209
		return $page;
210
	}
211
212
	/**
213
	 * Tests response code for globally disabled feedss
214
	 */
215
	public function testFeedViewability() {
216
217
		// Nested loop through each configuration
218
		foreach(array(true, false) as $publicHistory_Page) {
219
			$page = $this->createPageWithChanges(array('PublicHistory' => $publicHistory_Page, 'Title' => 'Page'));
220
221
			// Test requests to 'changes' action
222
			foreach(array(true, false) as $publicHistory_Config) {
223
				Config::modify()->set(VersionFeed::class, 'changes_enabled', $publicHistory_Config);
224
				$expectedResponse = $publicHistory_Page && $publicHistory_Config ? 200 : 404;
225
				$response = $this->get($page->RelativeLink('changes'));
226
				$this->assertEquals($expectedResponse, $response->getStatusCode());
227
			}
228
229
			// Test requests to 'allchanges' action on each page
230
			foreach(array(true, false) as $allChanges_Config) {
231
				foreach(array(true, false) as $allChanges_SiteConfig) {
232
					Config::modify()->set(VersionFeed::class, 'allchanges_enabled', $allChanges_Config);
233
					$siteConfig = SiteConfig::current_site_config();
234
					$siteConfig->AllChangesEnabled = $allChanges_SiteConfig;
235
					$siteConfig->write();
236
237
					$expectedResponse = $allChanges_Config && $allChanges_SiteConfig ? 200 : 404;
238
					$response = $this->get($page->RelativeLink('allchanges'));
239
					$this->assertEquals($expectedResponse, $response->getStatusCode());
240
				}
241
			}
242
		}
243
	}
244
245
}
246
247