array(array('id' => 1, 'title' => 'Live', 'date' => '2020-01-01 10:00:00', 'story' => '

Old.

')), 'queue' => array(array('id' => 5, 'title' => 'Waiting', 'date' => '2020-03-01 10:00:00', 'story' => '', 'img' => 'QUEUED IMAGE', 'mime' => 'image/gif')), ); protected function setUp(): void { parent::setUp(); SiteData::install(self::TABLES); open_db('open'); } // Save as an editor, with $action and the POST fields given private function save(string $action, array $post, array $get = array()): string { $this->asEditor(); $GLOBALS['action'] = $action; $this->request($get, $post + array('title' => 'Title', 'author' => 'Ann', 'story' => '

Text.

')); return (string)save_data(); } private function pendingImage(): string { $name = bin2hex(random_bytes(16)); file_put_contents(upload_dir() . "/$name", self::png()); $_SESSION['pending_upload'] = $name; return upload_dir() . "/$name"; } public function testEditorSavesANewStoryToTheQueue(): void { $id = $this->save('save', array('email' => 'ann@example.org', 'comments' => 'Note', 'editor' => 'ed', 'hidden' => '1', 'date' => '2021-05-06 07:08:09')); $this->assertSame('6', $id); $row = SiteData::find('queue', 6); $this->assertSame(array('Title', 'Ann', 'ann@example.org', '

Text.

', 'Note', 'ed', 1, '2021-05-06 07:08:09'), array($row['title'], $row['author'], $row['email'], $row['story'], $row['comments'], $row['editor'], $row['hidden'], $row['date'])); $this->assertCount(1, SiteData::$tables['xxx']); } public function testANewStoryGetsTheDateNow(): void { $this->save('save', array()); $date = SiteData::find('queue', 6)['date']; $this->assertEqualsWithDelta(time(), strtotime($date . ' UTC'), 5); } public function testEditorUpdatesAQueuedStoryInPlace(): void { $id = $this->save('save', array('id' => '5', 'title' => 'Changed', 'date' => '2030-01-01 00:00:00')); $this->assertSame('5', $id); $this->assertSame('Changed', SiteData::find('queue', 5)['title']); // An update keeps the date $this->assertSame('2020-03-01 10:00:00', SiteData::find('queue', 5)['date']); $update = FakeDb::matching('/^UPDATE queue/')[0]; $this->assertStringEndsWith('WHERE `id` = ? LIMIT 1', $update['sql']); $this->assertSame(5, end($update['params'])); } public function testEditorUpdatesALiveStory(): void { $this->assertSame('1', $this->save('post', array('id' => '1', 'title' => 'Edited'))); $this->assertSame('Edited', SiteData::find('xxx', 1)['title']); $this->assertCount(1, SiteData::$tables['xxx']); } public function testPostingFromTheQueueMovesTheStory(): void { $id = $this->save('post', array('id' => '5', 'title' => 'Now live', 'date' => '2021-01-01 00:00:00'), array('queue' => '1')); $this->assertSame('6', $id); $row = SiteData::find('xxx', 6); $this->assertSame(array('Now live', '2021-01-01 00:00:00', '

Text.

', NULL), array($row['title'], $row['date'], $row['story'], $row['img'])); $this->assertNull(SiteData::find('queue', 5)); $this->assertFalse($GLOBALS['queue']); } public function testPostingFromTheQueueCanKeepTheQueuedImage(): void { $this->save('post', array('id' => '5', 'uploaded' => '5'), array('queue' => '1')); $row = SiteData::find('xxx', 6); $this->assertSame(array('QUEUED IMAGE', 'image/gif', ''), array($row['img'], $row['mime'], $row['story'])); } public function testAPendingUploadBecomesTheImage(): void { $file = $this->pendingImage(); $this->save('save', array('uploaded' => 'pending')); $row = SiteData::find('queue', 6); $this->assertSame(array(self::png(), 'image/png', ''), array($row['img'], $row['mime'], $row['story'])); $this->assertFileDoesNotExist($file); $this->assertArrayNotHasKey('pending_upload', $_SESSION); } public function testRemovingTheImageKeepsTheText(): void { $this->save('save', array('id' => '5', 'remove_img' => '1', 'story' => '

Words.

')); $row = SiteData::find('queue', 5); $this->assertSame(array(NULL, NULL, '

Words.

'), array($row['img'], $row['mime'], $row['story'])); } public function testTheStoryIsSanitised(): void { $this->save('save', array('story' => '

a -- b

')); $this->assertSame('

a — b

', SiteData::find('queue', 6)['story']); } public function testValuesAreBoundAsParameters(): void { $this->save('save', array('title' => "O'Brien\"; DROP TABLE xxx; --")); $insert = FakeDb::matching('/^INSERT INTO queue/')[0]; $this->assertStringNotContainsString('DROP', $insert['sql']); $this->assertContains("O'Brien\"; DROP TABLE xxx; --", $insert['params']); } // Visitors always add to the queue, and each story sends mail public function testVisitorsCanOnlyAddToTheQueue(): void { $state = $this->tmp . '/state'; file_put_contents($state, serialize(array( 'tables' => self::TABLES, 'post' => array('id' => '1', 'title' => 'Mine', 'author' => 'Vi', 'story' => '

Hi.

', 'comments' => 'sneaky', 'editor' => 'ed', 'hidden' => '1', 'date' => '1999-01-01 00:00:00'), 'session' => array('captcha_ok' => time()), ))); $file = var_export($state, TRUE); $code = << \$id, 'tables' => Xxx\Tests\Support\SiteData::\$tables, 'session' => \$_SESSION, 'action' => \$action))); PHP; $result = Mail::run($code, array('UPLOAD_DIR' => $this->tmp . '/uploads')); $this->assertSame(0, $result['status'], $result['stderr']); $this->assertSame('', $result['log']); $saved = unserialize(file_get_contents($state)); $this->assertSame('6', (string)$saved['id']); $this->assertSame('submit', $saved['action']); $this->assertSame(self::TABLES['xxx'][0]['title'], $saved['tables']['xxx'][0]['title']); $row = end($saved['tables']['queue']); $this->assertSame(array('6', 'Mine', 'original author', 0, '', '

Hi.

'), array($row['id'], $row['title'], $row['editor'], $row['hidden'], $row['comments'], $row['story'])); $this->assertNotSame('1999-01-01 00:00:00', $row['date']); $this->assertSame(array('allow_queue_image' => '6'), $saved['session']); $this->assertCount(1, $result['mails']); $this->assertSame('New submission: Mine', $result['mails'][0]['subject']); $this->assertStringContainsString('Vi has sent the following new submission', $result['mails'][0]['body']); } }