mirror of
https://code.castopod.org/adaures/castopod
synced 2025-05-29 05:22:02 +00:00
parent
ea20206ee6
commit
e64001d006
@ -41,3 +41,8 @@ cache.handler="file"
|
|||||||
# cache.redis.password=null
|
# cache.redis.password=null
|
||||||
# cache.redis.port=6379
|
# cache.redis.port=6379
|
||||||
# cache.redis.database=0
|
# cache.redis.database=0
|
||||||
|
|
||||||
|
#REST API configuration
|
||||||
|
#--------------------------------------------------------------------
|
||||||
|
# 0/1 Disabled/Enabled
|
||||||
|
REST_API_ENABLED=1
|
@ -65,7 +65,19 @@ lint-js:
|
|||||||
|
|
||||||
tests:
|
tests:
|
||||||
stage: quality
|
stage: quality
|
||||||
|
services:
|
||||||
|
- mariadb
|
||||||
|
variables:
|
||||||
|
MYSQL_DATABASE: "tests"
|
||||||
|
MYSQL_ROOT_PASSWORD: "R00Tp4ssW0RD"
|
||||||
|
MYSQL_USER: "tests_user"
|
||||||
|
MYSQL_PASSWORD: "password"
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- apt-get install -y mariadb-client libmariadb-dev
|
||||||
|
|
||||||
|
- echo "SHOW DATABASES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mariadb "$MYSQL_DATABASE"
|
||||||
|
|
||||||
# run phpunit without code coverage
|
# run phpunit without code coverage
|
||||||
# TODO: add code coverage
|
# TODO: add code coverage
|
||||||
- vendor/bin/phpunit --no-coverage
|
- vendor/bin/phpunit --no-coverage
|
||||||
|
@ -50,6 +50,7 @@ class Autoload extends AutoloadConfig
|
|||||||
'Modules\Install' => ROOTPATH . 'modules/Install/',
|
'Modules\Install' => ROOTPATH . 'modules/Install/',
|
||||||
'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/',
|
'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/',
|
||||||
'Modules\WebSub' => ROOTPATH . 'modules/WebSub/',
|
'Modules\WebSub' => ROOTPATH . 'modules/WebSub/',
|
||||||
|
'Modules\Api\Rest\V1' => ROOTPATH . 'modules/Api/Rest/V1',
|
||||||
'Config' => APPPATH . 'Config/',
|
'Config' => APPPATH . 'Config/',
|
||||||
'ViewComponents' => APPPATH . 'Libraries/ViewComponents/',
|
'ViewComponents' => APPPATH . 'Libraries/ViewComponents/',
|
||||||
'ViewThemes' => APPPATH . 'Libraries/ViewThemes/',
|
'ViewThemes' => APPPATH . 'Libraries/ViewThemes/',
|
||||||
|
@ -10,6 +10,7 @@ use CodeIgniter\Filters\DebugToolbar;
|
|||||||
use CodeIgniter\Filters\Honeypot;
|
use CodeIgniter\Filters\Honeypot;
|
||||||
use CodeIgniter\Filters\InvalidChars;
|
use CodeIgniter\Filters\InvalidChars;
|
||||||
use CodeIgniter\Filters\SecureHeaders;
|
use CodeIgniter\Filters\SecureHeaders;
|
||||||
|
use Modules\Api\Rest\V1\Filters\ApiFilter;
|
||||||
use Modules\Auth\Filters\PermissionFilter;
|
use Modules\Auth\Filters\PermissionFilter;
|
||||||
use Modules\Fediverse\Filters\AllowCorsFilter;
|
use Modules\Fediverse\Filters\AllowCorsFilter;
|
||||||
use Modules\Fediverse\Filters\FediverseFilter;
|
use Modules\Fediverse\Filters\FediverseFilter;
|
||||||
@ -34,6 +35,7 @@ class Filters extends BaseConfig
|
|||||||
'permission' => PermissionFilter::class,
|
'permission' => PermissionFilter::class,
|
||||||
'fediverse' => FediverseFilter::class,
|
'fediverse' => FediverseFilter::class,
|
||||||
'allow-cors' => AllowCorsFilter::class,
|
'allow-cors' => AllowCorsFilter::class,
|
||||||
|
'rest-api' => ApiFilter::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
141
app/Database/Seeds/FakeSinglePodcastApiSeeder.php
Normal file
141
app/Database/Seeds/FakeSinglePodcastApiSeeder.php
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Database\Seeds;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Seeder;
|
||||||
|
|
||||||
|
class FakeSinglePodcastApiSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<mixed>
|
||||||
|
*/
|
||||||
|
public static function cover(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => 1,
|
||||||
|
'file_path' => 'podcasts/Handle/cover.jpg',
|
||||||
|
'file_size' => 400000,
|
||||||
|
'file_mimetype' => 'image/jpeg',
|
||||||
|
'file_metadata' => '{"FILE":{"FileName":"cover.jpg","FileDateTime":1654861723,"FileSize":468541,"FileType":2,"MimeType":"image\/jpeg","SectionsFound":"COMMENT"},"COMPUTED":{"html":"width=\"1400\" height=\"1400\"","Height":1400,"Width":1400,"IsColor":1},"COMMENT":["CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90\n"],"sizes":{"tiny":{"width":40,"height":40,"mimetype":"image\/webp","extension":"webp"},"thumbnail":{"width":150,"height":150,"mimetype":"image\/webp","extension":"webp"},"medium":{"width":320,"height":320,"mimetype":"image\/webp","extension":"webp"},"large":{"width":1024,"height":1024,"mimetype":"image\/webp","extension":"webp"},"feed":{"width":1400,"height":1400},"id3":{"width":500,"height":500},"og":{"width":1200,"height":1200},"federation":{"width":400,"height":400},"webmanifest192":{"width":192,"height":192,"mimetype":"image\/png","extension":"png"},"webmanifest512":{"width":512,"height":512,"mimetype":"image\/png","extension":"png"}}}',
|
||||||
|
'type' => 'image',
|
||||||
|
'description' => null,
|
||||||
|
'language_code' => null,
|
||||||
|
'uploaded_by' => 1,
|
||||||
|
'updated_by' => 1,
|
||||||
|
'uploaded_at' => '2022-06-13 8:00:00',
|
||||||
|
'updated_at' => '2022-06-13 8:00:00',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<mixed>
|
||||||
|
*/
|
||||||
|
public static function banner(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => 2,
|
||||||
|
'file_path' => 'podcasts/Handle/banner.jpg',
|
||||||
|
'file_size' => 400000,
|
||||||
|
'file_mimetype' => 'image/jpeg',
|
||||||
|
'file_metadata' => '{"FILE":{"FileName":"banner.jpg","FileDateTime":1654861724,"FileSize":98209,"FileType":2,"MimeType":"image\/jpeg","SectionsFound":""},"COMPUTED":{"html":"width=\"1500\" height=\"500\"","Height":500,"Width":1500,"IsColor":1},"sizes":{"small":{"width":320,"height":128,"mimetype":"image\/webp","extension":"webp"},"medium":{"width":960,"height":320,"mimetype":"image\/webp","extension":"webp"},"federation":{"width":1500,"height":500}}}',
|
||||||
|
'type' => 'image',
|
||||||
|
'description' => null,
|
||||||
|
'language_code' => null,
|
||||||
|
'uploaded_by' => 1,
|
||||||
|
'updated_by' => 1,
|
||||||
|
'uploaded_at' => '2022-06-13 8:00:00',
|
||||||
|
'updated_at' => '2022-06-13 8:00:00',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<mixed>
|
||||||
|
*/
|
||||||
|
public static function actor(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => 1,
|
||||||
|
'uri' => getenv('app_baseURL') . '@Handle',
|
||||||
|
'username' => 'Handle',
|
||||||
|
'domain' => getenv('app_baseURL'),
|
||||||
|
'private_key' => 'private_key',
|
||||||
|
'public_key' => 'public_key',
|
||||||
|
'display_name' => 'Title',
|
||||||
|
'summary' => '<p>description</p>',
|
||||||
|
'avatar_image_url' => getenv('app_baseURL') . 'media/podcasts/Handle',
|
||||||
|
'avatar_image_mimetype' => 'image/webp',
|
||||||
|
'cover_image_url' => null,
|
||||||
|
'cover_image_mimetype' => null,
|
||||||
|
'inbox_url' => getenv('app_baseURL') . '@Handle/inbox',
|
||||||
|
'outbox_url' => getenv('app_baseURL') . '@Handle/outbox',
|
||||||
|
'followers_url' => getenv('app_baseURL') . '@Handle/followers',
|
||||||
|
'followers_count' => 0,
|
||||||
|
'posts_count' => 0,
|
||||||
|
'is_blocked' => 0,
|
||||||
|
'created_at' => '2022-06-13 8:00:00',
|
||||||
|
'updated_at' => '2022-06-13 8:00:00',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<mixed>
|
||||||
|
*/
|
||||||
|
public static function podcast(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => 1,
|
||||||
|
'guid' => '0d341200-0234-5de7-99a6-a7d02bea4ce2',
|
||||||
|
'actor_id' => 1,
|
||||||
|
'handle' => 'Handle',
|
||||||
|
'title' => 'Title',
|
||||||
|
'description_markdown' => 'description',
|
||||||
|
'description_html' => '<p>description</p>',
|
||||||
|
'cover_id' => 1,
|
||||||
|
'banner_id' => 2,
|
||||||
|
'language_code' => 'en',
|
||||||
|
'category_id' => 1,
|
||||||
|
'parental_advisory' => null,
|
||||||
|
'owner_name' => 'Owner',
|
||||||
|
'owner_email' => 'Owner@gmail.com',
|
||||||
|
'publisher' => '',
|
||||||
|
'type' => 'episodic',
|
||||||
|
'copyright' => '',
|
||||||
|
'episode_description_footer_markdown' => null,
|
||||||
|
'episode_description_footer_html' => null,
|
||||||
|
'is_blocked' => 0,
|
||||||
|
'is_completed' => 0,
|
||||||
|
'is_locked' => 1,
|
||||||
|
'imported_feed_url' => null,
|
||||||
|
'new_feed_url' => null,
|
||||||
|
'payment_pointer' => null,
|
||||||
|
'location_name' => null,
|
||||||
|
'location_geo' => null,
|
||||||
|
'location_osm' => null,
|
||||||
|
'custom_rss' => null,
|
||||||
|
'is_published_on_hubs' => 0,
|
||||||
|
'partner_id' => null,
|
||||||
|
'partner_link_url' => null,
|
||||||
|
'partner_image_url' => null,
|
||||||
|
'created_by' => 1,
|
||||||
|
'updated_by' => 1,
|
||||||
|
'created_at' => '2022-06-13 8:00:00',
|
||||||
|
'updated_at' => '2022-06-13 8:00:00',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$this->call(AppSeeder::class);
|
||||||
|
$this->call(TestSeeder::class);
|
||||||
|
$this->db->table('media')
|
||||||
|
->insert(self::cover());
|
||||||
|
$this->db->table('media')
|
||||||
|
->insert(self::banner());
|
||||||
|
$this->db->table('fediverse_actors')
|
||||||
|
->insert(self::actor());
|
||||||
|
$this->db->table('podcasts')
|
||||||
|
->insert(self::podcast());
|
||||||
|
}
|
||||||
|
}
|
19
modules/Api/Rest/V1/Config/Api.php
Normal file
19
modules/Api/Rest/V1/Config/Api.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Modules\Api\Rest\V1\Config;
|
||||||
|
|
||||||
|
use CodeIgniter\Config\BaseConfig;
|
||||||
|
|
||||||
|
class Api extends BaseConfig
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Rest API gateway
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Defines a base route for all API pages
|
||||||
|
*/
|
||||||
|
public string $gateway = 'api/rest/v1/';
|
||||||
|
}
|
21
modules/Api/Rest/V1/Config/Routes.php
Normal file
21
modules/Api/Rest/V1/Config/Routes.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Modules\Api\Rest\V1\Config;
|
||||||
|
|
||||||
|
$routes = service('routes');
|
||||||
|
|
||||||
|
$routes->group(
|
||||||
|
config('Api')
|
||||||
|
->gateway . 'podcasts',
|
||||||
|
[
|
||||||
|
'namespace' => 'Modules\Api\Rest\V1\Controllers',
|
||||||
|
'filter' => 'rest-api',
|
||||||
|
],
|
||||||
|
function ($routes): void {
|
||||||
|
$routes->get('/', 'PodcastController::list');
|
||||||
|
$routes->get('(:num)', 'PodcastController::view/$1');
|
||||||
|
$routes->get('(:any)', 'ExceptionController::notFound');
|
||||||
|
}
|
||||||
|
);
|
20
modules/Api/Rest/V1/Config/Services.php
Normal file
20
modules/Api/Rest/V1/Config/Services.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Modules\Api\Rest\V1\Config;
|
||||||
|
|
||||||
|
use CodeIgniter\Config\BaseService;
|
||||||
|
use Modules\Api\Rest\V1\Core\Exceptions;
|
||||||
|
|
||||||
|
class Services extends BaseService
|
||||||
|
{
|
||||||
|
public static function restApiExceptions(bool $getShared = true)
|
||||||
|
{
|
||||||
|
if ($getShared) {
|
||||||
|
return static::getSharedInstance('restApiExceptions');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Exceptions(config('Exceptions'), static::request(), static::response());
|
||||||
|
}
|
||||||
|
}
|
19
modules/Api/Rest/V1/Controllers/ExceptionController.php
Normal file
19
modules/Api/Rest/V1/Controllers/ExceptionController.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Modules\Api\Rest\V1\Controllers;
|
||||||
|
|
||||||
|
use CodeIgniter\API\ResponseTrait;
|
||||||
|
use CodeIgniter\Controller;
|
||||||
|
use CodeIgniter\HTTP\Response;
|
||||||
|
|
||||||
|
class ExceptionController extends Controller
|
||||||
|
{
|
||||||
|
use ResponseTrait;
|
||||||
|
|
||||||
|
public function notFound(): Response
|
||||||
|
{
|
||||||
|
return $this->failNotFound('Podcast not found');
|
||||||
|
}
|
||||||
|
}
|
42
modules/Api/Rest/V1/Controllers/PodcastController.php
Normal file
42
modules/Api/Rest/V1/Controllers/PodcastController.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Modules\Api\Rest\V1\Controllers;
|
||||||
|
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use CodeIgniter\API\ResponseTrait;
|
||||||
|
use CodeIgniter\Controller;
|
||||||
|
use CodeIgniter\HTTP\Response;
|
||||||
|
use Modules\Api\Rest\V1\Config\Services;
|
||||||
|
|
||||||
|
class PodcastController extends Controller
|
||||||
|
{
|
||||||
|
use ResponseTrait;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
Services::restApiExceptions()->initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(): Response
|
||||||
|
{
|
||||||
|
$data = (new PodcastModel())->findAll();
|
||||||
|
array_map(function ($podcast): void {
|
||||||
|
$podcast->feed_url = $podcast->getFeedUrl();
|
||||||
|
}, $data);
|
||||||
|
return $this->respond($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(int $id): Response
|
||||||
|
{
|
||||||
|
$data = (new PodcastModel())->getPodcastById($id);
|
||||||
|
if (! $data instanceof Podcast) {
|
||||||
|
return $this->failNotFound('Podcast not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data->feed_url = $data->getFeedUrl();
|
||||||
|
return $this->respond($data);
|
||||||
|
}
|
||||||
|
}
|
32
modules/Api/Rest/V1/Core/Exceptions.php
Normal file
32
modules/Api/Rest/V1/Core/Exceptions.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Modules\Api\Rest\V1\Core;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class Exceptions extends \CodeIgniter\Debug\Exceptions
|
||||||
|
{
|
||||||
|
protected function render(Throwable $exception, int $statusCode): void
|
||||||
|
{
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$data = [
|
||||||
|
'status' => $statusCode,
|
||||||
|
'error' => $statusCode,
|
||||||
|
'messages' => [
|
||||||
|
'error' => 'Unexpected error',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
if (ENVIRONMENT === 'development') {
|
||||||
|
$data['messages'] = array_merge($data['messages'], [
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
'trace' => $exception->getTrace(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
}
|
||||||
|
}
|
25
modules/Api/Rest/V1/Filters/ApiFilter.php
Normal file
25
modules/Api/Rest/V1/Filters/ApiFilter.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Modules\Api\Rest\V1\Filters;
|
||||||
|
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\Filters\FilterInterface;
|
||||||
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
|
||||||
|
class ApiFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
public function before(RequestInterface $request, $arguments = null): void
|
||||||
|
{
|
||||||
|
if (! getenv('REST_API_ENABLED')) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
|
||||||
|
{
|
||||||
|
// Do something here
|
||||||
|
}
|
||||||
|
}
|
328
modules/Api/Rest/V1/podcast.json
Normal file
328
modules/Api/Rest/V1/podcast.json
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"title": "Castopod podcasts"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/api/rest/v1/podcasts": {
|
||||||
|
"get": {
|
||||||
|
"summary": "List all podcasts",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Object of podcasts",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Podcasts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "unexpected error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/rest/v1/podcasts/{id}": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "The id of the podcast to retrieve",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1,
|
||||||
|
"maxLength": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"get": {
|
||||||
|
"summary": "Info for a specific podcast",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Expected response to a valid request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Podcast"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "unexpected error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Podcast": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"guid",
|
||||||
|
"actor_id",
|
||||||
|
"handle",
|
||||||
|
"title",
|
||||||
|
"description_markdown",
|
||||||
|
"description_html",
|
||||||
|
"cover_id",
|
||||||
|
"language_code",
|
||||||
|
"category_id",
|
||||||
|
"owner_name",
|
||||||
|
"owner_email",
|
||||||
|
"type",
|
||||||
|
"is_blocked",
|
||||||
|
"is_completed",
|
||||||
|
"is_locked",
|
||||||
|
"is_published_on_hubs",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"feed_url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1,
|
||||||
|
"maxLength": 10
|
||||||
|
},
|
||||||
|
"guid": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 36
|
||||||
|
},
|
||||||
|
"actor_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1,
|
||||||
|
"maxLength": 10
|
||||||
|
},
|
||||||
|
"handle": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 32
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
|
"description_markdown": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description_html": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"cover_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1,
|
||||||
|
"maxLength": 10
|
||||||
|
},
|
||||||
|
"banner_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1,
|
||||||
|
"maxLength": 10
|
||||||
|
},
|
||||||
|
"language_code": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 2
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"parental_advisory": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["clean", "explicit"]
|
||||||
|
},
|
||||||
|
"owner_name": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
|
"owner_email": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 255
|
||||||
|
},
|
||||||
|
"publisher": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["episodic", "serial"]
|
||||||
|
},
|
||||||
|
"copyright": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
|
"episode_description_footer_markdown": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"episode_description_footer_html": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"is_blocked": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"enum": [0, 1],
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"is_completed": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"enum": [0, 1],
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"is_locked": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"enum": [0, 1],
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"imported_feed_url": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 512
|
||||||
|
},
|
||||||
|
"new_feed_url": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 512
|
||||||
|
},
|
||||||
|
"payment_pointer": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
|
"location_name": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
|
"location_geo": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 32
|
||||||
|
},
|
||||||
|
"location_osm": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 12
|
||||||
|
},
|
||||||
|
"custom_rss": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"is_published_on_hubs": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"enum": [0, 1],
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"partner_id": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 32
|
||||||
|
},
|
||||||
|
"partner_link_url": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 512
|
||||||
|
},
|
||||||
|
"partner_image_url": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 512
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1,
|
||||||
|
"maxLength": 10
|
||||||
|
},
|
||||||
|
"updated_by": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1,
|
||||||
|
"maxLength": 10
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"timezone_type": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"timezone": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"timezone_type": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"timezone": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feed_url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Podcasts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Podcast"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Error": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,13 +45,12 @@
|
|||||||
<!-- Directory containing the front controller (index.php) -->
|
<!-- Directory containing the front controller (index.php) -->
|
||||||
<const name="PUBLICPATH" value="./public/"/>
|
<const name="PUBLICPATH" value="./public/"/>
|
||||||
<!-- Database configuration -->
|
<!-- Database configuration -->
|
||||||
<!-- Uncomment to provide your own database for testing
|
<env name="database.tests.hostname" value="mariadb"/>
|
||||||
<env name="database.tests.hostname" value="localhost"/>
|
|
||||||
<env name="database.tests.database" value="tests"/>
|
<env name="database.tests.database" value="tests"/>
|
||||||
<env name="database.tests.username" value="tests_user"/>
|
<env name="database.tests.username" value="tests_user"/>
|
||||||
<env name="database.tests.password" value=""/>
|
<env name="database.tests.password" value="password"/>
|
||||||
<env name="database.tests.DBDriver" value="MySQLi"/>
|
<env name="database.tests.DBDriver" value="MySQLi"/>
|
||||||
<env name="database.tests.DBPrefix" value="tests_"/>
|
<env name="database.tests.DBPrefix" value="tests_"/>
|
||||||
-->
|
<env name="REST_API_ENABLED" value="1"/>
|
||||||
</php>
|
</php>
|
||||||
</phpunit>
|
</phpunit>
|
109
tests/modules/Api/Rest/V1/PodcastTest.php
Normal file
109
tests/modules/Api/Rest/V1/PodcastTest.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace modules\Api\Rest\V1;
|
||||||
|
|
||||||
|
use App\Database\Seeds\FakeSinglePodcastApiSeeder;
|
||||||
|
use CodeIgniter\Test\CIUnitTestCase;
|
||||||
|
use CodeIgniter\Test\DatabaseTestTrait;
|
||||||
|
use CodeIgniter\Test\FeatureTestTrait;
|
||||||
|
|
||||||
|
class PodcastTest extends CIUnitTestCase
|
||||||
|
{
|
||||||
|
use FeatureTestTrait;
|
||||||
|
use DatabaseTestTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $migrate = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $migrateOnce = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $seed = 'FakeSinglePodcastApiSeeder';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $basePath = 'app/Database';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<mixed>
|
||||||
|
*/
|
||||||
|
private array $podcast = [];
|
||||||
|
|
||||||
|
private string $podcastApiUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<mixed> $data
|
||||||
|
*/
|
||||||
|
public function __construct(?string $name = null, array $data = [], $dataName = '')
|
||||||
|
{
|
||||||
|
parent::__construct($name, $data, $dataName);
|
||||||
|
$this->podcast = FakeSinglePodcastApiSeeder::podcast();
|
||||||
|
$this->podcast['created_at'] = [];
|
||||||
|
$this->podcast['updated_at'] = [];
|
||||||
|
$this->podcastApiUrl = config('Api')
|
||||||
|
->gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testList(): void
|
||||||
|
{
|
||||||
|
$result = $this->call('get', $this->podcastApiUrl . 'podcasts');
|
||||||
|
$result->assertStatus(200);
|
||||||
|
$result->assertHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
|
$result->assertJSONFragment([
|
||||||
|
0 => $this->podcast,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testView(): void
|
||||||
|
{
|
||||||
|
$result = $this->call('get', $this->podcastApiUrl . 'podcasts/1');
|
||||||
|
$result->assertStatus(200);
|
||||||
|
$result->assertHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
|
$result->assertJSONFragment($this->podcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testViewNotFound(): void
|
||||||
|
{
|
||||||
|
$result = $this->call('get', $this->podcastApiUrl . 'podcasts/2');
|
||||||
|
$result->assertStatus(404);
|
||||||
|
$result->assertJSONExact(
|
||||||
|
[
|
||||||
|
'status' => 404,
|
||||||
|
'error' => 404,
|
||||||
|
'messages' => [
|
||||||
|
'error' => 'Podcast not found',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$result->assertHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Refreshing database to fetch empty array of podcasts
|
||||||
|
*/
|
||||||
|
public function testListEmpty(): void
|
||||||
|
{
|
||||||
|
$this->regressDatabase();
|
||||||
|
$this->migrateDatabase();
|
||||||
|
$result = $this->call('get', $this->podcastApiUrl . 'podcasts');
|
||||||
|
$result->assertStatus(200);
|
||||||
|
$result->assertHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
|
$result->assertJSONExact([]);
|
||||||
|
$this->seed($this->seed);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user