1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2025-04-22 21:35:50 +00:00

Tests for OCS

This commit is contained in:
J. King 2025-03-17 21:00:00 -04:00
parent 4afee3cd47
commit 7a890394d0
5 changed files with 130 additions and 10 deletions
lib/REST/NextcloudNews
tests

View file

@ -7,10 +7,8 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST\NextcloudNews;
use GuzzleHttp\Psr7\Response;
use MensBeam\Mime\MimeType;
use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\User\ExceptionConflict;
use Psr\Http\Message\ServerRequestInterface;
@ -108,6 +106,11 @@ class OCS extends \JKingWeb\Arsse\REST\AbstractHandler {
'Allow' => "GET,HEAD",
'Vary' => "Accept",
]));
} elseif ($req->getMethod() !== "GET") {
return HTTP::respEmpty(405, [
'Allow' => "GET,HEAD",
'Vary' => "Accept",
]);
}
// try to authenticate
if ($req->getAttribute("authenticated", false)) {
@ -152,23 +155,23 @@ class OCS extends \JKingWeb\Arsse\REST\AbstractHandler {
$body = [
'ocs' => [
'meta' => self::BASE_META[$code],
'data' => !$data && !$xml ? new \stdClass : $data, // we need a stdClass for the JSON encoder to return an empty object
'data' => !$data && !$xml ? new \stdClass : $data ?? [], // we need a stdClass for the JSON encoder to return an empty object
],
];
// the response formatting code was lifted from the Fever implementation, with changes
if ($xml) {
$d = new \DOMDocument("1.0", "utf-8");
$d->appendChild($this->makeXMLAssoc($data['ocs'], $d->createElement("ocs")));
return HTTP::respXml($d->saveXML($d->documentElement, \LIBXML_NOEMPTYTAG), $status);
$d->appendChild($this->makeXMLAssoc($body['ocs'], $d->createElement("ocs")));
return HTTP::respXml($d->saveXML($d->documentElement, \LIBXML_NOEMPTYTAG), $status, ['Content-Type' => "$type; charset=UTF-8"]);
} else {
return HTTP::respJson($body, $status, [], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
return HTTP::respJson($body, $status, ['Content-Type' => "$type"], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
}
}
protected function makeXMLAssoc(array $data, \DOMElement $p): \DOMElement {
$d = $p->ownerDocument;
foreach ($data as $k => $v) {
if (!is_array($v) || !$v) {
if (!is_array($v)) {
$p->appendChild($d->createElement($k, (string) $v));
} elseif (isset($v[0])) {
// this is a very simplistic check for an indexed array
@ -186,9 +189,9 @@ class OCS extends \JKingWeb\Arsse\REST\AbstractHandler {
protected function makeXMLIndexed(array $data, \DOMElement $p): \DOMElement {
$d = $p->ownerDocument;
foreach ($data as $v) {
if (!is_array($v) || !$v) {
if (!is_array($v)) {
$p->appendChild($d->createElement("element", (string) $v));
} elseif (isset($v[0])) {
} elseif (isset($v[0])) { // @codeCoverageIgnore
// this case is never encountered with Nextcloud's output
$p->appendChild($this->makeXMLIndexed($v, $d->createElement("element"))); // @codeCoverageIgnore
} else {

View file

@ -11,6 +11,7 @@ namespace JKingWeb\Arsse\TestCase\Misc;
use JKingWeb\Arsse\Misc\HTTP;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use JKingWeb\Arsse\Arsse;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Psr\Http\Message\ResponseInterface;
@ -54,4 +55,12 @@ class TestHTTP extends \JKingWeb\Arsse\Test\AbstractTest {
["respXml", ["<html/>", 451, ['Content-Type' => "text/plain", 'Vary' => "ETag"]], new Response(451, ['Content-Type' => "text/plain", 'Vary' => "ETag"], "<html/>")],
];
}
public function testSendAuthenticationChallenges(): void {
self::setConf();
$in = new Response();
$exp = $in->withHeader("WWW-Authenticate", 'Basic realm="'.Arsse::$conf->httpRealm.'", charset="UTF-8"');
$act = HTTP::challenge($in);
$this->assertMessage($exp, $act);
}
}

View file

@ -0,0 +1,108 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\REST\NextcloudNews;
use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\User;
use JKingWeb\Arsse\REST\NextcloudNews\OCS;
use JKingWeb\Arsse\User\ExceptionConflict;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Psr\Http\Message\ResponseInterface;
#[CoversClass(\JKingWeb\Arsse\REST\NextcloudNews\OCS::class)]
class TestOCS extends \JKingWeb\Arsse\Test\AbstractTest {
protected $h;
protected $userId;
protected $now;
protected function req(string $method, string $target, $data = "", array $headers = [], bool $authenticated = true, bool $body = true): ResponseInterface {
$prefix = "/ocs/v1.php/cloud/users/";
$url = $prefix.$target;
if ($body) {
$params = [];
} else {
$params = $data;
$data = [];
}
$req = $this->serverRequest($method, $url, $prefix, $headers, [], $data, "application/json", $params, $authenticated ? "john.doe@example.com" : "");
return $this->h->dispatch($req);
}
public function setUp(): void {
parent::setUp();
self::setConf();
// create a mock user manager
$this->userId = "john.doe@example.com";
Arsse::$user = \Phake::mock(User::class);
Arsse::$user->id = $this->userId;
\Phake::when(Arsse::$user)->auth->thenReturn(true);
\Phake::when(Arsse::$user)->propertiesGet($this->userId, $this->anything())->thenReturn(['admin' => true, 'lang' => "en_CA"]);
\Phake::when(Arsse::$user)->propertiesGet("jane.doe@example.com", $this->anything())->thenReturn(['admin' => false, 'lang' => null]);
// produce consistent timestamps
$this->now = new \DateTimeImmutable();
\Phake::when(Arsse::$obj)->get(\DateTimeImmutable::class)->thenReturn($this->now);
// initialize a handler
$this->h = new OCS();
}
protected static function v($value) {
return $value;
}
public function testSendAuthenticationChallenge(): void {
$exp = HTTP::respEmpty(401);
$this->assertMessage($exp, $this->req("GET", $this->userId, "", [], false));
}
public function testSendOptionsRequest(): void {
$exp = HTTP::challenge(HTTP::respEmpty(204, ['Allow' => "GET,HEAD", 'Vary' => "Accept"]));
$this->assertMessage($exp, $this->req("OPTIONS", $this->userId, "", [], false));
}
public function testSendBadRequest(): void {
$exp = HTTP::respEmpty(405, ['Allow' => "GET,HEAD", 'Vary' => "Accept"]);
$this->assertMessage($exp, $this->req("PUT", $this->userId, "", [], false));
}
public function testQuerySelf(): void {
$now = $this->now->getTimestamp();
$user = $this->userId;
$exp = HTTP::respXml("<ocs><meta><status>ok</status><statuscode>200</statuscode><message>OK</message></meta><data><enabled>1</enabled><storageLocation>/</storageLocation><id>$user</id><firstLoginTimestamp>-1</firstLoginTimestamp><lastLoginTimestamp>$now</lastLoginTimestamp><lastLogin>{$now}000</lastLogin><backend>Database</backend><subadmin/><quota><free>-3</free><used>0</used><total>-3</total><relative>0</relative><quota>-3</quota></quota><manager/><avatarScope>v2-federated</avatarScope><email/><emailScope>v2-federated</emailScope><additional_mail/><additional_mailScope/><displayname>$user</displayname><display-name>$user</display-name><displaynameScope/><phone/><phoneScope>v2-local</phoneScope><address/><addressScope>v2-local</addressScope><website/><websiteScope>v2-local</websiteScope><twitter/><twitterScope>v2-local</twitterScope><fediverse/><fediverseScope>v2-local</fediverseScope><organisation/><organisationScope>v2-local</organisationScope><role/><roleScope>v2-local</roleScope><headline/><headlineScope>v2-local</headlineScope><biography/><biographyScope>v2-local</biographyScope><profile_enabled>0</profile_enabled><profile_enabledScope>v2-local</profile_enabledScope><pronouns/><pronounsScope>v2-federated</pronounsScope><groups><element>admin</element></groups><language>en_CA</language><locale/><notify_email/><backendCapabilities><setDisplayName/><setPassword/></backendCapabilities></data></ocs>", 200);
$this->assertMessage($exp, $this->req("GET", $user));
$exp = HTTP::respJson(['ocs' => ['meta' => ['status' => "ok", 'statuscode' => 200, 'message' => "OK"], 'data' => ['enabled' => true,'storageLocation' => '/','id' => $user,'firstLoginTimestamp' => -1,'lastLoginTimestamp' => $now,'lastLogin' => $now * 1000,'backend' => 'Database','subadmin' => [],'quota' => ['free' => -3,'used' => 0,'total' => -3,'relative' => 0,'quota' => -3],'manager' => '','avatarScope' => 'v2-federated','email' => null,'emailScope' => 'v2-federated','additional_mail' => [],'additional_mailScope' => [],'displayname' => $user,'display-name' => $user,'displaynameScope' => null,'phone' => '','phoneScope' => 'v2-local','address' => '','addressScope' => 'v2-local','website' => '','websiteScope' => 'v2-local','twitter' => '','twitterScope' => 'v2-local','fediverse' => '','fediverseScope' => 'v2-local','organisation' => '','organisationScope' => 'v2-local','role' => '','roleScope' => 'v2-local','headline' => '','headlineScope' => 'v2-local','biography' => '','biographyScope' => 'v2-local','profile_enabled' => '0','profile_enabledScope' => 'v2-local','pronouns' => '','pronounsScope' => 'v2-federated','groups' => ['admin'],'language' => 'en_CA','locale' => '','notify_email' => null,'backendCapabilities' => ['setDisplayName' => false,'setPassword' => false]]]], 200);
$this->assertMessage($exp, $this->req("GET", $user, "", ['Accept' => "application/json"]));
}
public function testQueryAnotherUser(): void {
$now = $this->now->getTimestamp();
$user = "jane.doe@example.com";
$exp = HTTP::respXml("<ocs><meta><status>ok</status><statuscode>200</statuscode><message>OK</message></meta><data><enabled>1</enabled><storageLocation>/</storageLocation><id>$user</id><firstLoginTimestamp>-1</firstLoginTimestamp><lastLoginTimestamp>$now</lastLoginTimestamp><lastLogin>{$now}000</lastLogin><backend>Database</backend><subadmin/><quota><free>-3</free><used>0</used><total>-3</total><relative>0</relative><quota>-3</quota></quota><manager/><avatarScope>v2-federated</avatarScope><email/><emailScope>v2-federated</emailScope><additional_mail/><additional_mailScope/><displayname>$user</displayname><display-name>$user</display-name><displaynameScope/><phone/><phoneScope>v2-local</phoneScope><address/><addressScope>v2-local</addressScope><website/><websiteScope>v2-local</websiteScope><twitter/><twitterScope>v2-local</twitterScope><fediverse/><fediverseScope>v2-local</fediverseScope><organisation/><organisationScope>v2-local</organisationScope><role/><roleScope>v2-local</roleScope><headline/><headlineScope>v2-local</headlineScope><biography/><biographyScope>v2-local</biographyScope><profile_enabled>0</profile_enabled><profile_enabledScope>v2-local</profile_enabledScope><pronouns/><pronounsScope>v2-federated</pronounsScope><groups></groups><language>en</language><locale/><notify_email/><backendCapabilities><setDisplayName/><setPassword/></backendCapabilities></data></ocs>", 200);
$this->assertMessage($exp, $this->req("GET", $user));
$exp = HTTP::respJson(['ocs' => ['meta' => ['status' => "ok", 'statuscode' => 200, 'message' => "OK"], 'data' => ['enabled' => true,'storageLocation' => '/','id' => $user,'firstLoginTimestamp' => -1,'lastLoginTimestamp' => $now,'lastLogin' => $now * 1000,'backend' => 'Database','subadmin' => [],'quota' => ['free' => -3,'used' => 0,'total' => -3,'relative' => 0,'quota' => -3],'manager' => '','avatarScope' => 'v2-federated','email' => null,'emailScope' => 'v2-federated','additional_mail' => [],'additional_mailScope' => [],'displayname' => $user,'display-name' => $user,'displaynameScope' => null,'phone' => '','phoneScope' => 'v2-local','address' => '','addressScope' => 'v2-local','website' => '','websiteScope' => 'v2-local','twitter' => '','twitterScope' => 'v2-local','fediverse' => '','fediverseScope' => 'v2-local','organisation' => '','organisationScope' => 'v2-local','role' => '','roleScope' => 'v2-local','headline' => '','headlineScope' => 'v2-local','biography' => '','biographyScope' => 'v2-local','profile_enabled' => '0','profile_enabledScope' => 'v2-local','pronouns' => '','pronounsScope' => 'v2-federated','groups' => [],'language' => 'en','locale' => '','notify_email' => null,'backendCapabilities' => ['setDisplayName' => false,'setPassword' => false]]]], 200);
$this->assertMessage($exp, $this->req("GET", $user, "", ['Accept' => "application/json"]));
}
public function testQueryAnotherUserWithoutAuthority(): void {
\Phake::when(Arsse::$user)->propertiesGet($this->userId, $this->anything())->thenReturn(['admin' => false, 'lang' => "en_CA"]);
$user = "jane.doe@example.com";
$exp = HTTP::respXml("<ocs><meta><status>failure</status><statuscode>998</statuscode><message></message></meta><data></data></ocs>", 404);
$this->assertMessage($exp, $this->req("GET", $user));
$exp = HTTP::respJson(['ocs' => ['meta' => ['status' => "failure", 'statuscode' => 998, 'message' => ""], 'data' => []]], 404);
$this->assertMessage($exp, $this->req("GET", $user, "", ['Accept' => "application/json"]));
}
public function testQueryAMissingUser(): void {
\Phake::when(Arsse::$user)->propertiesGet("oops", $this->anything())->thenThrow(new ExceptionConflict());
$exp = HTTP::respXml("<ocs><meta><status>failure</status><statuscode>404</statuscode><message>User does not exist</message></meta><data></data></ocs>", 404);
$this->assertMessage($exp, $this->req("GET", "oops"));
$exp = HTTP::respJson(['ocs' => ['meta' => ['status' => "failure", 'statuscode' => 404, 'message' => "User does not exist"], 'data' => []]], 404);
$this->assertMessage($exp, $this->req("GET", "oops", "", ['Accept' => "application/json"]));
}
}

View file

@ -193,7 +193,6 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
$expBody = @json_decode((string) $exp->getBody(), true);
$actBody = @json_decode((string) $act->getBody(), true);
$this->assertSame(\JSON_ERROR_NONE, json_last_error(), "Response body is not valid JSON");
$this->assertEquals($expBody, $actBody, $text);
$this->assertSame($expBody, $actBody, $text);
} elseif ($exp instanceof ResponseInterface && HTTP::matchType($exp, ["application/xml", "text/xml"], false)) {
$this->assertXmlStringEqualsXmlString((string) $exp->getBody(), (string) $act->getBody(), $text);

View file

@ -121,6 +121,7 @@
<file>cases/REST/NextcloudNews/TestVersions.php</file>
<file>cases/REST/NextcloudNews/TestV1_2.php</file>
<file>cases/REST/NextcloudNews/TestV1_3.php</file>
<file>cases/REST/NextcloudNews/TestOCS.php</file>
<file>cases/REST/NextcloudNews/PDO/TestV1_2.php</file>
<file>cases/REST/NextcloudNews/PDO/TestV1_3.php</file>
</testsuite>