summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--app/classes/CSRF.php4
-rw-r--r--app/classes/Opml.php11
-rw-r--r--app/classes/OpmlManager.php65
-rw-r--r--phpunit.xml27
-rw-r--r--tests/CSRFTest.php36
-rw-r--r--tests/HelpersTest.php11
-rw-r--r--tests/OpmlManagerTest.php53
-rw-r--r--tests/OpmlTest.php53
-rw-r--r--tests/PlanetConfigTest.php7
-rw-r--r--tests/PlanetTest.php11
-rw-r--r--tests/opml/test-empty.opml0
-rw-r--r--tests/opml/test-valid.opml16
13 files changed, 251 insertions, 46 deletions
diff --git a/Makefile b/Makefile
index 17647f0..00506fa 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,11 @@
VENDOR=./vendor/bin/
+PHPUNIT=php -dxdebug.enabled=1 -dxdebug.mode=coverage ./vendor/bin/phpunit --coverage-text
test:
{ php -S 127.0.0.1:8081 >& /dev/null & }; \
PID=$$!; \
- $(VENDOR)phpunit; \
+ $(PHPUNIT); \
RES=$$?; \
kill $$PID; \
exit $$RES
diff --git a/app/classes/CSRF.php b/app/classes/CSRF.php
index 9a700cf..cf9fc1e 100644
--- a/app/classes/CSRF.php
+++ b/app/classes/CSRF.php
@@ -3,7 +3,7 @@
class CSRF
{
/** @var string */
- const HMAC_ALGORITHM = 'sha1';
+ const HMAC_ALGORITHM = 'sha256';
/** @var string */
const SESSION_KEY_NAME = '_csrf_key';
@@ -48,7 +48,7 @@ class CSRF
public static function getKey()
{
if (empty($_SESSION[self::SESSION_KEY_NAME])) {
- $_SESSION[self::SESSION_KEY_NAME] = random_bytes(16);
+ $_SESSION[self::SESSION_KEY_NAME] = bin2hex(random_bytes(16));
}
return $_SESSION[self::SESSION_KEY_NAME];
}
diff --git a/app/classes/Opml.php b/app/classes/Opml.php
index c5f185f..b91b43e 100644
--- a/app/classes/Opml.php
+++ b/app/classes/Opml.php
@@ -9,6 +9,9 @@ class Opml
public string $ownerEmail = '';
public string $ownerId = '';
+ public string $dateCreated = '';
+ public string $dateModified = '';
+
public string $title = '';
/** @var array<int, string> */
@@ -21,7 +24,7 @@ class Opml
'TITLE' => 'name',
'XMLURL' => 'feed',
'DESCRIPTION' => 'description',
- 'ISDOWN' => 'isDown'
+ 'ISDOWN' => 'isDown',
);
@@ -81,6 +84,12 @@ class Opml
case 'OWNERID':
$this->ownerId = $cdata;
break;
+ case 'DATECREATED':
+ $this->dateCreated = $cdata;
+ break;
+ case 'DATEMODIFIED':
+ $this->dateModified = $cdata;
+ break;
}
}
diff --git a/app/classes/OpmlManager.php b/app/classes/OpmlManager.php
index cd3d685..679d1c4 100644
--- a/app/classes/OpmlManager.php
+++ b/app/classes/OpmlManager.php
@@ -3,7 +3,7 @@
class OpmlManager
{
- public static function load($file)
+ public static function load(string $file) : Opml
{
if (!file_exists($file)) {
throw new Exception('OPML file not found!');
@@ -22,44 +22,63 @@ class OpmlManager
return $opml;
}
- /**
- * @param Opml $opml
- * @param string $file
- */
- public static function save($opml, $file)
+ public static function format(Opml $opml, $freezeDateModified = false) : string
{
- $out = '<?xml version="1.0"?>'."\n";
- $out.= '<opml version="2.0">'."\n";
- $out.= '<head>'."\n";
- $out.= '<title>'.htmlspecialchars($opml->getTitle()).'</title>'."\n";
- $out.= '<dateCreated>'.gmdate('c').'</dateCreated>'."\n";
- $out.= '<dateModified>'.gmdate('c').'</dateModified>'."\n";
+ $owner = '';
if ($opml->ownerName != '') {
- $out.= '<ownerName>'.htmlspecialchars($opml->ownerName).'</ownerName>'."\n";
+ $owner .= '<ownerName>'.htmlspecialchars($opml->ownerName).'</ownerName>'."\n";
}
if ($opml->ownerEmail != '') {
- $out.= '<ownerEmail>'.htmlspecialchars($opml->ownerEmail).'</ownerEmail>'."\n";
+ $owner .= '<ownerEmail>'.htmlspecialchars($opml->ownerEmail).'</ownerEmail>'."\n";
}
if ($opml->ownerId != '') {
- $out.= '<ownerId>'.htmlspecialchars($opml->ownerId).'</ownerId>'."\n";
+ $owner .= '<ownerId>'.htmlspecialchars($opml->ownerId).'</ownerId>'."\n";
}
- $out.= '<docs>http://opml.org/spec2.opml</docs>'."\n";
- $out.= '</head>'."\n";
- $out.= '<body>'."\n";
+ $entries = '';
foreach ($opml->entries as $person) {
- $out .= sprintf(
- '<outline text="%s" htmlUrl="%s" xmlUrl="%s" isDown="%s" />',
+ $entries .= sprintf(
+ "\t" . '<outline text="%s" htmlUrl="%s" xmlUrl="%s" isDown="%s" />',
htmlspecialchars($person['name'], ENT_QUOTES),
htmlspecialchars($person['website'], ENT_QUOTES),
htmlspecialchars($person['feed'], ENT_QUOTES),
htmlspecialchars($person['isDown'] ?? '', ENT_QUOTES)
) . "\n";
}
- $out.= '</body>'."\n";
- $out.= '</opml>';
- file_put_contents($file, $out);
+ $template = <<<XML
+<?xml version="1.0"?>
+<opml version="2.0">
+<head>
+ <title>%s</title>
+ <dateCreated>%s</dateCreated>
+ <dateModified>%s</dateModified>
+ %s
+ <docs>http://opml.org/spec2.opml</docs>
+</head>
+<body>
+%s
+</body>
+</opml>
+XML;
+
+ return sprintf(
+ $template,
+ htmlspecialchars($opml->getTitle()),
+ $opml->dateCreated,
+ $freezeDateModified ? $opml->dateModified : date_format(date_create('now', new DateTimeZone('UTC')), DateTimeInterface::ATOM),
+ $owner,
+ $entries
+ );
+ }
+
+ /**
+ * @param Opml $opml
+ * @param string $file
+ */
+ public static function save(Opml $opml, string $file) : int|bool
+ {
+ return file_put_contents($file, self::format($opml));
}
public static function backup($file)
diff --git a/phpunit.xml b/phpunit.xml
index 334431c..8d463d4 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -7,13 +7,32 @@
beStrictAboutOutputDuringTests="true"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutTodoAnnotatedTests="true"
+ cacheResultFile="tmp/.phpunit.cache/test-results"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
verbose="true">
- <coverage processUncoveredFiles="true">
+
+
+ <coverage processUncoveredFiles="false">
<include>
<directory suffix=".php">.</directory>
</include>
+ <exclude>
+ <directory>vendor</directory>
+ </exclude>
+ <report>
+ <html outputDirectory="tmp/html-coverage" lowUpperBound="50" highLowerBound="90"/>
+ <php outputFile="tmp/coverage.php"/>
+ <text outputFile="tmp/coverage.txt" showUncoveredFiles="true" showOnlySummary="true"/>
+ <xml outputDirectory="tmp/xml-coverage"/>
+ </report>
</coverage>
- <testsuite name="moonmoon tests">
- <directory suffix="Test.php">tests</directory>
- </testsuite>
+
+ <testsuites>
+ <testsuite name="moonmoon tests">
+ <directory suffix="Test.php">tests</directory>
+ </testsuite>
+ </testsuites>
</phpunit>
diff --git a/tests/CSRFTest.php b/tests/CSRFTest.php
new file mode 100644
index 0000000..39fda58
--- /dev/null
+++ b/tests/CSRFTest.php
@@ -0,0 +1,36 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+class CSRFTest extends TestCase
+{
+ public function testGetKey()
+ {
+ $this->temp_key = CSRF::getKey();
+ $this->assertIsString($this->temp_key);
+ $this->assertEquals(32, strlen($this->temp_key));
+ }
+
+ public function testGenerate()
+ {
+ $token = CSRF::generate("some-action");
+ $this->assertIsString($token);
+ $this->assertEquals(64, strlen($token));
+
+ $this->expectException(InvalidArgumentException::class);
+ CSRF::generate();
+ CSRF::generate(12);
+ CSRF::generate(null);
+ }
+
+ public function testVerify()
+ {
+ $token = CSRF::generate("some-action");
+ $this->assertEquals(CSRF::verify($token, "some-action"), true);
+ $this->assertEquals(CSRF::verify($token, "other-action"), false);
+ $this->assertEquals(CSRF::verify("anything-else", "some-action"), false);
+ $this->assertEquals(CSRF::verify(1, "string"), false);
+ $this->assertEquals(CSRF::verify("string", 2), false);
+ $this->assertEquals(CSRF::verify(null, null), false);
+ }
+}
diff --git a/tests/HelpersTest.php b/tests/HelpersTest.php
deleted file mode 100644
index a21ef1b..0000000
--- a/tests/HelpersTest.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-use PHPUnit\Framework\TestCase;
-
-class HelpersTest extends TestCase
-{
- public function test_a()
- {
- $this->assertTrue(true);
- }
-}
diff --git a/tests/OpmlManagerTest.php b/tests/OpmlManagerTest.php
new file mode 100644
index 0000000..718e2f0
--- /dev/null
+++ b/tests/OpmlManagerTest.php
@@ -0,0 +1,53 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+class OpmlTest extends TestCase
+{
+ public function setUp() : void
+ {
+ $this->fixtures = [
+ [file_get_contents('./tests/opml/test-empty.opml'), [], '', '', '', ''],
+ [
+ file_get_contents('./tests/opml/test-valid.opml'),
+ [
+ [
+ 'website' => 'https://blog.example.com/',
+ 'name' => 'text 1',
+ 'feed' => 'https://some.other.example.com/feed/path',
+ 'isDown' => '',
+ ],
+ [
+ 'website' => 'https://blog2.example.com',
+ 'name' => 'text 2',
+ 'feed' => 'https://blog2.example.com/rss.xml',
+ 'isDown' => '',
+ ]
+ ],
+ 'Test OPML',
+ 'user name',
+ 'user@example.com',
+ 'http://user.example.com/'
+ ]
+ ];
+ }
+
+ public function testLoadValidFile()
+ {
+ $mngr = OpmlManager::load('tests/opml/test-valid.opml');
+ $this->assertInstanceOf('Opml', $mngr);
+ }
+
+ public function testLoadAbsentFile()
+ {
+ $this->expectException('Exception');
+ OpmlManager::load('/some/where');
+ }
+
+ public function testFormat()
+ {
+ $file = 'tests/opml/test-valid.opml';
+ $opml = OpmlManager::load($file);
+ $this->assertXmlStringEqualsXmlFile($file, OpmlManager::format($opml, true));
+ }
+}
diff --git a/tests/OpmlTest.php b/tests/OpmlTest.php
new file mode 100644
index 0000000..571fdaf
--- /dev/null
+++ b/tests/OpmlTest.php
@@ -0,0 +1,53 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+class OpmlManagerTest extends TestCase
+{
+ public function setUp() : void
+ {
+ $this->fixtures = [
+ [file_get_contents('./tests/opml/test-empty.opml'), [], '', '', '', ''],
+ [
+ file_get_contents('./tests/opml/test-valid.opml'),
+ [
+ [
+ 'website' => 'https://blog.example.com/',
+ 'name' => 'text 1',
+ 'feed' => 'https://some.other.example.com/feed/path',
+ 'isDown' => '',
+ ],
+ [
+ 'website' => 'https://blog2.example.com',
+ 'name' => 'text 2',
+ 'feed' => 'https://blog2.example.com/rss.xml',
+ 'isDown' => '',
+ ]
+ ],
+ 'Test OPML',
+ 'user name',
+ 'user@example.com',
+ 'http://user.example.com/'
+ ]
+ ];
+ }
+
+ public function testParse()
+ {
+ foreach ($this->fixtures as $data) {
+ $given = $data[0];
+ $entries = $data[1];
+
+ $opml = new Opml();
+ $entries = $opml->parse($given);
+
+ $this->assertEquals($data[1], $entries);
+ $this->assertEquals($data[1], $opml->entries);
+ $this->assertEquals($data[1], $opml->getPeople());
+ $this->assertEquals($data[2], $opml->getTitle());
+ $this->assertEquals($data[3], $opml->ownerName);
+ $this->assertEquals($data[4], $opml->ownerEmail);
+ $this->assertEquals($data[5], $opml->ownerId);
+ }
+ }
+}
diff --git a/tests/PlanetConfigTest.php b/tests/PlanetConfigTest.php
index 4db6e90..ddf7373 100644
--- a/tests/PlanetConfigTest.php
+++ b/tests/PlanetConfigTest.php
@@ -70,4 +70,11 @@ class PlanetConfigTest extends TestCase
$conf = new PlanetConfig(['foo' => 'bar'], false);
$this->assertEquals("---\nfoo: bar\n", $conf->toYaml());
}
+
+ public function testConfigLoad()
+ {
+ $conf = PlanetConfig::load(".");
+ $default = new PlanetConfig();
+ $this->assertEquals($default, $conf);
+ }
}
diff --git a/tests/PlanetTest.php b/tests/PlanetTest.php
index d4b87b8..1bc699b 100644
--- a/tests/PlanetTest.php
+++ b/tests/PlanetTest.php
@@ -22,10 +22,12 @@ class FoolItem
{
protected $categories;
- public function __construct($categories)
+ public function __construct($categories = null)
{
- foreach ($categories as $c) {
- $this->categories[] = new FoolCategory($c);
+ if (is_array($categories)) {
+ foreach ($categories as $c) {
+ $this->categories[] = new FoolCategory($c);
+ }
}
}
@@ -49,7 +51,8 @@ class PlanetTest extends TestCase
new FoolItem(array('catA', 'catB', 'catC')),
new FoolItem(array('catB')),
new FoolItem(array('catA')),
- new FoolItem(array('catC'))
+ new FoolItem(array('catC')),
+ new FoolItem(),
);
}
diff --git a/tests/opml/test-empty.opml b/tests/opml/test-empty.opml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/opml/test-empty.opml
diff --git a/tests/opml/test-valid.opml b/tests/opml/test-valid.opml
new file mode 100644
index 0000000..686d19e
--- /dev/null
+++ b/tests/opml/test-valid.opml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<opml version="2.0">
+<head>
+<title>Test OPML</title>
+<dateCreated>2022-01-14T17:15:05+00:00</dateCreated>
+<dateModified>2022-01-14T17:15:05+00:00</dateModified>
+<ownerName>user name</ownerName>
+<ownerEmail>user@example.com</ownerEmail>
+<ownerId>http://user.example.com/</ownerId>
+<docs>http://opml.org/spec2.opml</docs>
+</head>
+<body>
+<outline text="text 1" htmlUrl="https://blog.example.com/" xmlUrl="https://some.other.example.com/feed/path" isDown="" />
+<outline text="text 2" htmlUrl="https://blog2.example.com" xmlUrl="https://blog2.example.com/rss.xml" isDown="" />
+</body>
+</opml> \ No newline at end of file