10 my $git = `which git`;
12 plan(skip_all => 'git not available') unless -x $git;
14 plan(skip_all => "CGI not available")
20 plan(skip_all => "IPC::Run not available")
30 # We check for English error messages
36 my $installed = $ENV{INSTALLED_TESTS};
40 @command = qw(ikiwiki --plugin inline);
43 ok(! system("make -s ikiwiki.out"));
44 @command = ("perl", "-I".getcwd."/blib/lib", './ikiwiki.out',
45 '--underlaydir='.getcwd.'/underlays/basewiki',
46 '--set', 'underlaydirbase='.getcwd.'/underlays',
47 '--templatedir='.getcwd.'/templates');
54 writefile($name, $dir, $content);
55 ok(utime(333333333, 333333333, "$dir/$name"));
58 sub write_setup_file {
60 wikiname => 'this is the name of my wiki',
61 srcdir => getcwd.'/t/tmp/in/doc',
62 destdir => getcwd.'/t/tmp/out',
63 url => 'http://example.com',
64 cgiurl => 'http://example.com/cgi-bin/ikiwiki.cgi',
65 cgi_wrapper => getcwd.'/t/tmp/ikiwiki.cgi',
66 cgi_wrappermode => '0751',
67 add_plugins => [qw(anonok attachment lockedit recentchanges)],
68 disable_plugins => [qw(emailauth openid passwordauth)],
69 anonok_pagespec => 'writable/*',
70 locked_pages => '!writable/*',
72 git_wrapper => getcwd.'/t/tmp/in/.git/hooks/post-commit',
73 git_wrappermode => '0754',
74 gitorigin_branch => '',
77 $setup{ENV} = { 'PERL5LIB' => getcwd.'/blib/lib' };
79 writefile("test.setup", "t/tmp",
80 "# IkiWiki::Setup::Yaml - YAML formatted setup file\n" .
84 sub thoroughly_rebuild {
85 ok(unlink("t/tmp/ikiwiki.cgi") || $!{ENOENT});
86 ok(unlink("t/tmp/in/.git/hooks/post-commit") || $!{ENOENT});
87 ok(! system(@command, qw(--setup t/tmp/test.setup --rebuild --wrappers)));
90 sub check_cgi_mode_bits {
93 (undef, undef, $mode, undef, undef,
94 undef, undef, undef, undef, undef,
95 undef, undef, undef) = stat('t/tmp/ikiwiki.cgi');
96 is ($mode & 07777, 0751);
97 (undef, undef, $mode, undef, undef,
98 undef, undef, undef, undef, undef,
99 undef, undef, undef) = stat('t/tmp/in/.git/hooks/post-commit');
100 is ($mode & 07777, 0754);
106 my $method = $args{method} || 'GET';
107 my $environ = $args{environ} || {};
108 my $params = $args{params} || { do => 'prefs' };
111 SCRIPT_NAME => '/cgi-bin/ikiwiki.cgi',
112 HTTP_HOST => 'example.com',
115 my $cgi = CGI->new($args{params});
116 my $query_string = $cgi->query_string();
119 if ($method eq 'POST') {
120 $defaults{REQUEST_METHOD} = 'POST';
122 $defaults{CONTENT_LENGTH} = length $in;
124 $defaults{REQUEST_METHOD} = 'GET';
125 $defaults{QUERY_STRING} = $query_string;
132 run(["./t/tmp/ikiwiki.cgi"], \$in, \$out, init => sub {
134 $ENV{$_} = $envvars{$_}
138 return decode_utf8($out);
142 my (undef, $filename, $line) = caller;
144 my $desc = shift || join(' ', 'git', @$args);
146 ok(run(['git', @$args], \$in, \$out, init => sub {
147 chdir 't/tmp/in' or die $!;
148 my $name = 'The IkiWiki Tests';
149 my $email = 'nobody@ikiwiki-tests.invalid';
150 if ($args->[0] eq 'commit') {
151 $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
152 $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
154 }), "$desc at $filename:$line");
162 ok(! system(qw(rm -rf t/tmp)));
163 ok(! system(qw(mkdir t/tmp)));
165 write_old_file('.gitignore', 't/tmp/in', "/doc/.ikiwiki/\n");
166 write_old_file('doc/writable/one.mdwn', 't/tmp/in', 'This is the first test page');
167 write_old_file('doc/writable/two.mdwn', 't/tmp/in', 'This is the second test page');
168 write_old_file('doc/writable/three.mdwn', 't/tmp/in', 'This is the third test page');
169 write_old_file('doc/writable/three.bin', 't/tmp/in', 'An attachment');
170 write_old_file('doc/writable/blog.mdwn', 't/tmp/in',
171 '[[!inline pages="writable/blog/*" actions=yes rootpage=writable/blog postform=yes show=0]]');
172 write_old_file('doc/writable/__172__blog.mdwn', 't/tmp/in',
173 '[[!inline pages="writable/¬blog/*" actions=yes rootpage="writable/¬blog" postform=yes show=0]]');
174 write_old_file('doc/writable/中文.mdwn', 't/tmp/in',
175 '[[!inline pages="writable/中文/*" actions=yes rootpage="writable/中文" postform=yes show=0]]');
177 unless ($installed) {
178 ok(! system(qw(cp -pRL doc/wikiicons t/tmp/in/doc/)));
179 ok(! system(qw(cp -pRL doc/recentchanges.mdwn t/tmp/in/doc/)));
183 run_git(['add', '.']);
184 run_git(['commit', '-m', 'Initial commit']);
187 thoroughly_rebuild();
188 check_cgi_mode_bits();
190 ok(-e 't/tmp/out/writable/one/index.html');
191 $content = readfile('t/tmp/out/writable/one/index.html');
192 like($content, qr{This is the first test page});
193 my $orig_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
195 # We have to wait 1 second here so that new writes are guaranteed
196 # to have a strictly larger mtime.
199 # Test the git hook, which accepts git commits
200 writefile('doc/writable/one.mdwn', 't/tmp/in',
201 'This is new content for the first test page');
202 run_git(['add', '.']);
203 run_git(['commit', '-m', 'Git commit']);
204 my $first_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
205 isnt($orig_sha1, $first_revertable_sha1);
207 ok(-e 't/tmp/out/writable/one/index.html');
208 $content = readfile('t/tmp/out/writable/one/index.html');
209 like($content, qr{This is new content for the first test page});
212 $content = run_cgi(method => 'POST',
215 page => 'writable/two',
217 editmessage => 'Web commit',
218 editcontent => 'Here is new content for the second page',
219 _submit => 'Save Page',
223 like($content, qr{^Status:\s*302\s}m);
224 like($content, qr{^Location:\s*http://example\.com/writable/two/\?updated}m);
225 my $second_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
226 isnt($orig_sha1, $second_revertable_sha1);
227 isnt($first_revertable_sha1, $second_revertable_sha1);
229 ok(-e 't/tmp/out/writable/two/index.html');
230 $content = readfile('t/tmp/out/writable/two/index.html');
231 like($content, qr{Here is new content for the second page});
234 writefile('doc/writable/three.mdwn', 't/tmp/in',
235 'Also new content for the third page');
236 unlink('t/tmp/in/doc/writable/three.bin');
237 writefile('doc/writable/three.bin', 't/tmp/in',
238 'Changed attachment');
239 run_git(['add', '.']);
240 run_git(['commit', '-m', 'Git commit']);
241 ok(-e 't/tmp/out/writable/three/index.html');
242 $content = readfile('t/tmp/out/writable/three/index.html');
243 like($content, qr{Also new content for the third page});
244 $content = readfile('t/tmp/out/writable/three.bin');
245 like($content, qr{Changed attachment});
246 my $third_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
247 isnt($orig_sha1, $third_revertable_sha1);
248 isnt($second_revertable_sha1, $third_revertable_sha1);
250 run_git(['mv', 'doc/writable/one.mdwn', 'doc/one.mdwn']);
251 run_git(['mv', 'doc/writable/two.mdwn', 'two.mdwn']);
252 run_git(['commit', '-m', 'Rename files to test CVE-2016-10026']);
253 ok(! -e 't/tmp/out/writable/two/index.html');
254 ok(! -e 't/tmp/out/writable/one/index.html');
255 ok(-e 't/tmp/out/one/index.html');
256 my $sha1_before_revert = run_git(['rev-list', '--max-count=1', 'HEAD']);
257 isnt($sha1_before_revert, $third_revertable_sha1);
259 $content = run_cgi(method => 'post',
262 revertmessage => 'CVE-2016-10026',
263 rev => $first_revertable_sha1,
265 _submitted_revert => '1',
268 like($content, qr{is locked and cannot be edited});
269 # The tree is left clean
270 run_git(['diff', '--exit-code']);
271 run_git(['diff', '--cached', '--exit-code']);
272 my $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
273 is($sha1, $sha1_before_revert);
275 ok(-e 't/tmp/out/one/index.html');
276 ok(! -e 't/tmp/in/doc/writable/one.mdwn');
277 ok(-e 't/tmp/in/doc/one.mdwn');
278 $content = readfile('t/tmp/out/one/index.html');
279 like($content, qr{This is new content for the first test page});
281 $content = run_cgi(method => 'post',
284 revertmessage => 'CVE-2016-10026',
285 rev => $second_revertable_sha1,
287 _submitted_revert => '1',
290 like($content, qr{you are not allowed to change two\.mdwn});
291 run_git(['diff', '--exit-code']);
292 run_git(['diff', '--cached', '--exit-code']);
293 $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
294 is($sha1, $sha1_before_revert);
296 ok(! -e 't/tmp/out/writable/two/index.html');
297 ok(! -e 't/tmp/out/two/index.html');
298 ok(! -e 't/tmp/in/doc/writable/two.mdwn');
299 ok(-e 't/tmp/in/two.mdwn');
300 $content = readfile('t/tmp/in/two.mdwn');
301 like($content, qr{Here is new content for the second page});
303 # We have to wait 1 second here so that new writes are guaranteed
304 # to have a strictly larger mtime.
307 # This one can legitimately be reverted
308 $content = run_cgi(method => 'post',
311 revertmessage => 'not CVE-2016-10026',
312 rev => $third_revertable_sha1,
314 _submitted_revert => '1',
317 like($content, qr{^Status:\s*302\s}m);
318 like($content, qr{^Location:\s*http://example\.com/recentchanges/}m);
319 run_git(['diff', '--exit-code']);
320 run_git(['diff', '--cached', '--exit-code']);
321 ok(-e 't/tmp/out/writable/three/index.html');
322 $content = readfile('t/tmp/out/writable/three/index.html');
323 like($content, qr{This is the third test page});
324 $content = readfile('t/tmp/out/writable/three.bin');
325 like($content, qr{An attachment});
327 $content = readfile('t/tmp/out/writable/blog/index.html');
328 like($content, qr{<input type="hidden" name="from" value="writable/blog"});
329 $content = run_cgi(method => 'get',
332 from => 'writable/blog',
337 like($content, qr{<option selected="selected" value="writable/blog/hello">writable/blog/hello</option>});
339 # Regression test for a bug in which we couldn't use an
340 # alphanumeric, but non-ASCII, root page.
341 $content = readfile('t/tmp/out/writable/中文/index.html');
342 like($content, qr{<input type="hidden" name="from" value="writable/中文"});
343 $content = run_cgi(method => 'get',
346 from => 'writable/中文',
351 like($content, qr{<option selected="selected" value="writable/中文/hello">writable/中文/hello</option>});
352 unlike($content, qr{Error: bad page name});