mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 08:56:17 +00:00
Implement podcast: create+delete+get channel(s); delete episode
This commit is contained in:
parent
b438bb0121
commit
dd4614d735
54
docs/api.md
54
docs/api.md
@ -78,13 +78,13 @@ or with version 1.8.0.
|
||||
| [`createShare`](#createshare) | | ❌ |
|
||||
| [`updateShare`](#updateshare) | | ❌ |
|
||||
| [`deleteShare`](#deleteshare) | | ❌ |
|
||||
| [`getPodcasts`](#getpodcasts) | | ❔ |
|
||||
| [`getPodcasts`](#getpodcasts) | | ✔️ |
|
||||
| [`getNewestPodcasts`](#getnewestpodcasts) | 1.14.0 | ❔ |
|
||||
| [`refreshPodcasts`](#refreshpodcasts) | 1.9.0 | ❔ |
|
||||
| [`createPodcastChannel`](#createpodcastchannel) | 1.9.0 | ❔ |
|
||||
| [`deletePodcastChannel`](#deletepodcastchannel) | 1.9.0 | ❔ |
|
||||
| [`deletePodcastEpisode`](#deletepodcastepisode) | 1.9.0 | ❔ |
|
||||
| [`downloadPodcastEpisode`](#downloadpodcastepisode) | 1.9.0 | ❔ |
|
||||
| [`refreshPodcasts`](#refreshpodcasts) | 1.9.0 | 📅 |
|
||||
| [`createPodcastChannel`](#createpodcastchannel) | 1.9.0 | ✔️ |
|
||||
| [`deletePodcastChannel`](#deletepodcastchannel) | 1.9.0 | ✔️ |
|
||||
| [`deletePodcastEpisode`](#deletepodcastepisode) | 1.9.0 | ✔️ |
|
||||
| [`downloadPodcastEpisode`](#downloadpodcastepisode) | 1.9.0 | 📅 |
|
||||
| [`jukeboxControl`](#jukeboxcontrol) | | ✔️ |
|
||||
| [`getInternetRadioStations`](#getinternetradiostations) | 1.9.0 | ✔️ |
|
||||
| [`createInternetRadioStation`](#createinternetradiostation) | 1.16.0 | ✔️ |
|
||||
@ -555,12 +555,12 @@ No parameter
|
||||
### Podcast
|
||||
|
||||
#### `getPodcasts`
|
||||
❔
|
||||
✔️
|
||||
|
||||
| Parameter | Vers. | |
|
||||
|-------------------|-------|---|
|
||||
| `includeEpisodes` | 1.9.0 | ❔ |
|
||||
| `id` | 1.9.0 | ❔ |
|
||||
| `includeEpisodes` | 1.9.0 | ✔️ |
|
||||
| `id` | 1.9.0 | ✔️ |
|
||||
|
||||
#### `getNewestPodcasts`
|
||||
❔ 1.14.0
|
||||
@ -575,25 +575,25 @@ No parameter
|
||||
No parameter
|
||||
|
||||
#### `createPodcastChannel`
|
||||
❔ 1.9.0
|
||||
✔️ 1.9.0
|
||||
|
||||
| Parameter | Vers. | |
|
||||
|-----------|-------|---|
|
||||
| `url` | 1.9.0 | ❔ |
|
||||
| `url` | 1.9.0 | ✔️ |
|
||||
|
||||
#### `deletePodcastChannel`
|
||||
❔ 1.9.0
|
||||
✔️ 1.9.0
|
||||
|
||||
| Parameter | Vers. | |
|
||||
|-----------|-------|---|
|
||||
| `id` | 1.9.0 | ❔ |
|
||||
| `id` | 1.9.0 | ✔️ |
|
||||
|
||||
#### `deletePodcastEpisode`
|
||||
❔ 1.9.0
|
||||
✔️ 1.9.0
|
||||
|
||||
| Parameter | Vers. | |
|
||||
|-----------|-------|---|
|
||||
| `id` | 1.9.0 | ❔ |
|
||||
| `id` | 1.9.0 | ✔️ |
|
||||
|
||||
|
||||
#### `downloadPodcastEpisode`
|
||||
@ -619,35 +619,35 @@ No parameter
|
||||
### Internet radio
|
||||
|
||||
#### `getInternetRadioStations`
|
||||
❔ 1.9.0
|
||||
✔️ 1.9.0
|
||||
|
||||
No parameter
|
||||
|
||||
#### `createInternetRadioStation`
|
||||
❔ 1.16.0
|
||||
✔️ 1.16.0
|
||||
|
||||
| Parameter | Vers. | |
|
||||
|---------------|--------|---|
|
||||
| `streamUrl` | 1.16.0 | ❔ |
|
||||
| `name` | 1.16.0 | ❔ |
|
||||
| `homepageUrl` | 1.16.0 | ❔ |
|
||||
| `streamUrl` | 1.16.0 | ✔️ |
|
||||
| `name` | 1.16.0 | ✔️ |
|
||||
| `homepageUrl` | 1.16.0 | ✔️ |
|
||||
|
||||
#### `updateInternetRadioStation`
|
||||
❔ 1.16.0
|
||||
✔️ 1.16.0
|
||||
|
||||
| Parameter | Vers. | |
|
||||
|---------------|--------|---|
|
||||
| `id` | 1.16.0 | ❔ |
|
||||
| `streamUrl` | 1.16.0 | ❔ |
|
||||
| `name` | 1.16.0 | ❔ |
|
||||
| `homepageUrl` | 1.16.0 | ❔ |
|
||||
| `id` | 1.16.0 | ✔️ |
|
||||
| `streamUrl` | 1.16.0 | ✔️ |
|
||||
| `name` | 1.16.0 | ✔️ |
|
||||
| `homepageUrl` | 1.16.0 | ✔️ |
|
||||
|
||||
#### `deleteInternetRadioStation`
|
||||
❔ 1.16.0
|
||||
✔️ 1.16.0
|
||||
|
||||
| Parameter | Vers. | |
|
||||
|-----------|--------|---|
|
||||
| `id` | 1.16.0 | ❔ |
|
||||
| `id` | 1.16.0 | ✔️ |
|
||||
|
||||
### Chat
|
||||
|
||||
|
@ -14,12 +14,13 @@ import uuid
|
||||
|
||||
from flask import request
|
||||
from flask import Blueprint
|
||||
from functools import wraps
|
||||
from pony.orm import ObjectNotFound
|
||||
from pony.orm import commit
|
||||
|
||||
from ..managers.user import UserManager
|
||||
|
||||
from .exceptions import Unauthorized
|
||||
from .exceptions import Unauthorized, Forbidden
|
||||
from .formatters import JSONFormatter, JSONPFormatter, XMLFormatter
|
||||
|
||||
api = Blueprint("api", __name__)
|
||||
@ -104,6 +105,21 @@ def get_entity_id(cls, eid):
|
||||
raise GenericError("Invalid ID")
|
||||
|
||||
|
||||
def require_podcast(f):
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
is_admin = request.user and request.user.admin
|
||||
is_podcast = request.user and request.user.podcast
|
||||
|
||||
if not is_admin and not is_podcast:
|
||||
raise Forbidden()
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
|
||||
from .errors import *
|
||||
|
||||
from .system import *
|
||||
@ -115,6 +131,7 @@ from .annotation import *
|
||||
from .chat import *
|
||||
from .search import *
|
||||
from .playlists import *
|
||||
from .podcast import *
|
||||
from .jukebox import *
|
||||
from .radio import *
|
||||
from .unsupported import *
|
||||
|
60
supysonic/api/podcast.py
Normal file
60
supysonic/api/podcast.py
Normal file
@ -0,0 +1,60 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
# This file is part of Supysonic.
|
||||
# Supysonic is a Python implementation of the Subsonic server API.
|
||||
#
|
||||
# Copyright (C) 2020 Alban 'spl0k' Féron
|
||||
#
|
||||
# Distributed under terms of the GNU AGPLv3 license.
|
||||
|
||||
from flask import request
|
||||
|
||||
from ..db import PodcastChannel, PodcastEpisode
|
||||
|
||||
from . import api, get_entity, require_podcast
|
||||
from .exceptions import Forbidden, MissingParameter, NotFound
|
||||
|
||||
|
||||
@api.route("/getPodcasts.view", methods=["GET", "POST"])
|
||||
def get_podcasts():
|
||||
include_episodes, channel_id = map(request.values.get, ["includeEpisodes", "id"])
|
||||
|
||||
if channel_id:
|
||||
channels = (get_entity(PodcastChannel),)
|
||||
else:
|
||||
channels = PodcastChannel.select().sort_by(PodcastChannel.url)
|
||||
|
||||
return request.formatter(
|
||||
"podcasts",
|
||||
dict(channel=[ch.as_subsonic_channel(include_episodes) for ch in channels]),
|
||||
)
|
||||
|
||||
|
||||
@api.route("/createPodcastChannel.view", methods=["GET", "POST"])
|
||||
@require_podcast
|
||||
def create_podcast_channel():
|
||||
url = request.values["url"]
|
||||
|
||||
PodcastChannel(url=url)
|
||||
|
||||
return request.formatter.empty
|
||||
|
||||
|
||||
@api.route("/deletePodcastChannel.view", methods=["GET", "POST"])
|
||||
@require_podcast
|
||||
def delete_podcast_channel():
|
||||
res = get_entity(PodcastChannel)
|
||||
res.delete()
|
||||
|
||||
return request.formatter.empty
|
||||
|
||||
|
||||
@api.route("/deletePodcastEpisode.view", methods=["GET", "POST"])
|
||||
@require_podcast
|
||||
def delete_podcast_episode():
|
||||
res = get_entity(PodcastEpisode)
|
||||
res.delete()
|
||||
|
||||
return request.formatter.empty
|
||||
|
||||
|
@ -357,6 +357,7 @@ class User(db.Entity):
|
||||
|
||||
admin = Required(bool, default=False)
|
||||
jukebox = Required(bool, default=False)
|
||||
podcast = Required(bool, default=False)
|
||||
|
||||
lastfm_session = Optional(str, 32, nullable=True)
|
||||
lastfm_status = Required(
|
||||
@ -389,7 +390,7 @@ class User(db.Entity):
|
||||
playlistRole=True,
|
||||
coverArtRole=False,
|
||||
commentRole=False,
|
||||
podcastRole=False,
|
||||
podcastRole=self.admin or self.podcast,
|
||||
streamRole=True,
|
||||
jukeboxRole=self.admin or self.jukebox,
|
||||
shareRole=False,
|
||||
@ -572,6 +573,63 @@ class RadioStation(db.Entity):
|
||||
return info
|
||||
|
||||
|
||||
class PodcastChannel(db.Entity):
|
||||
_table_ = "podcast_channel"
|
||||
|
||||
id = PrimaryKey(UUID, default=uuid4)
|
||||
url = Required(str)
|
||||
title = Optional(str)
|
||||
description = Optional(str)
|
||||
cover_art = Optional(str)
|
||||
original_image_url = Optional(str)
|
||||
status = Required(str, default="new")
|
||||
error_message = Optional(str)
|
||||
created = Required(datetime, precision=0, default=now)
|
||||
last_fetched = Optional(datetime, precision=0)
|
||||
episodes = Set(lambda: PodcastEpisode, lazy=True)
|
||||
|
||||
def as_subsonic_channel(self, include_episodes=False):
|
||||
info = dict(
|
||||
id=self.id,
|
||||
url=self.url,
|
||||
title=self.title,
|
||||
description=self.description,
|
||||
status=self.status,
|
||||
errorMessage=self.error_message,
|
||||
)
|
||||
if include_episodes:
|
||||
self.episodes.load()
|
||||
info["episode"] = [ep.as_subsonic_episode() for ep in self.episodes]
|
||||
return info
|
||||
|
||||
|
||||
class PodcastEpisode(db.Entity):
|
||||
_table_ = "podcast_episode"
|
||||
|
||||
id = PrimaryKey(UUID, default=uuid4)
|
||||
channel = Required(PodcastChannel, column="channel_id")
|
||||
stream_url = Optional(str)
|
||||
file_path = Optional(str)
|
||||
title = Optional(str)
|
||||
description = Optional(str)
|
||||
duration = Optional(str)
|
||||
status = Required(str, default="new")
|
||||
publish_date = Optional(datetime, precision=0, default=now)
|
||||
created = Required(datetime, precision=0, default=now)
|
||||
|
||||
def as_subsonic_episode(self):
|
||||
info = dict(
|
||||
id=self.id,
|
||||
isDir=False,
|
||||
streamId="podcast:{}".format(self.id),
|
||||
title=self.title,
|
||||
description=self.description,
|
||||
status=self.status,
|
||||
publishDate=self.publish_date.isoformat(),
|
||||
)
|
||||
return info
|
||||
|
||||
|
||||
def parse_uri(database_uri):
|
||||
if not isinstance(database_uri, str):
|
||||
raise TypeError("Expecting a string")
|
||||
|
29
supysonic/schema/migration/mysql/20200620.sql
Normal file
29
supysonic/schema/migration/mysql/20200620.sql
Normal file
@ -0,0 +1,29 @@
|
||||
ALTER TABLE user ADD podcast BOOLEAN DEFAULT false NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_channel (
|
||||
id BINARY(16) PRIMARY KEY,
|
||||
url VARCHAR(256) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
cover_art VARCHAR(256),
|
||||
original_image_url VARCHAR(256),
|
||||
status VARCHAR(16),
|
||||
error_message VARCHAR(256),
|
||||
created TIMESTAMP NOT NULL,
|
||||
last_fetched TIMESTAMP
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_episode (
|
||||
id BINARY(16) PRIMARY KEY,
|
||||
stream_url VARCHAR(256),
|
||||
file_path VARCHAR(256),
|
||||
channel_id CHAR(36) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
duration VARCHAR(8),
|
||||
status VARCHAR(16) NOT NULL,
|
||||
publish_date DATETIME,
|
||||
created DATETIME NOT NULL
|
||||
FOREIGN KEY(channel_id) REFERENCES podcast_channel(id),
|
||||
INDEX index_episode_channel_id_fk (channel_id)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
28
supysonic/schema/migration/postgres/20200620.sql
Normal file
28
supysonic/schema/migration/postgres/20200620.sql
Normal file
@ -0,0 +1,28 @@
|
||||
ALTER TABLE user ADD podcast BOOLEAN DEFAULT false NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_channel (
|
||||
id UUID PRIMARY KEY,
|
||||
url VARCHAR(256) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
cover_art VARCHAR(256),
|
||||
original_image_url VARCHAR(256),
|
||||
status VARCHAR(16),
|
||||
error_message VARCHAR(256),
|
||||
created TIMESTAMP NOT NULL,
|
||||
last_fetched TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_episode (
|
||||
id UUID PRIMARY KEY,
|
||||
stream_url VARCHAR(256),
|
||||
file_path VARCHAR(256),
|
||||
channel_id CHAR(36) NOT NULL REFERENCES podcast_channel(id),
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
duration VARCHAR(8),
|
||||
status VARCHAR(16) NOT NULL,
|
||||
publish_date TIMESTAMP,
|
||||
created TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS index_episode_channel_id_fk ON podcast_channel(id);
|
28
supysonic/schema/migration/sqlite/20200620.sql
Normal file
28
supysonic/schema/migration/sqlite/20200620.sql
Normal file
@ -0,0 +1,28 @@
|
||||
ALTER TABLE user ADD podcast BOOLEAN DEFAULT false NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_channel (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
url VARCHAR(256) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
cover_art VARCHAR(256),
|
||||
original_image_url VARCHAR(256),
|
||||
status VARCHAR(16),
|
||||
error_message VARCHAR(256),
|
||||
created DATETIME NOT NULL,
|
||||
last_fetched DATETIME
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_episode (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
stream_url VARCHAR(256),
|
||||
file_path VARCHAR(256),
|
||||
channel_id CHAR(36) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
duration VARCHAR(8),
|
||||
status VARCHAR(16) NOT NULL,
|
||||
publish_date DATETIME,
|
||||
created DATETIME NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS index_episode_channel_id_fk ON podcast_channel(id);
|
@ -57,6 +57,7 @@ CREATE TABLE IF NOT EXISTS user (
|
||||
salt CHAR(6) NOT NULL,
|
||||
admin BOOLEAN NOT NULL,
|
||||
jukebox BOOLEAN NOT NULL,
|
||||
podcast BOOLEAN NOT NULL,
|
||||
lastfm_session CHAR(32),
|
||||
lastfm_status BOOLEAN NOT NULL,
|
||||
last_play_id BINARY(16) REFERENCES track(id),
|
||||
@ -158,3 +159,30 @@ CREATE TABLE IF NOT EXISTS radio_station (
|
||||
created DATETIME NOT NULL
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_channel (
|
||||
id BINARY(16) PRIMARY KEY,
|
||||
url VARCHAR(256) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
cover_art VARCHAR(256),
|
||||
original_image_url VARCHAR(256),
|
||||
status VARCHAR(16),
|
||||
error_message VARCHAR(256),
|
||||
created TIMESTAMP NOT NULL,
|
||||
last_fetched TIMESTAMP
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_episode (
|
||||
id BINARY(16) PRIMARY KEY,
|
||||
stream_url VARCHAR(256),
|
||||
file_path VARCHAR(256),
|
||||
channel_id CHAR(36) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
duration VARCHAR(8),
|
||||
status VARCHAR(16) NOT NULL,
|
||||
publish_date DATETIME,
|
||||
created DATETIME NOT NULL,
|
||||
FOREIGN KEY(channel_id) REFERENCES podcast_channel(id),
|
||||
INDEX index_episode_channel_id_fk (channel_id)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
@ -57,6 +57,7 @@ CREATE TABLE IF NOT EXISTS "user" (
|
||||
salt CHAR(6) NOT NULL,
|
||||
admin BOOLEAN NOT NULL,
|
||||
jukebox BOOLEAN NOT NULL,
|
||||
podcast BOOLEAN NOT NULL,
|
||||
lastfm_session CHAR(32),
|
||||
lastfm_status BOOLEAN NOT NULL,
|
||||
last_play_id UUID REFERENCES track,
|
||||
@ -158,3 +159,29 @@ CREATE TABLE IF NOT EXISTS radio_station (
|
||||
created TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_channel (
|
||||
id UUID PRIMARY KEY,
|
||||
url VARCHAR(256) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
cover_art VARCHAR(256),
|
||||
original_image_url VARCHAR(256),
|
||||
status VARCHAR(16),
|
||||
error_message VARCHAR(256),
|
||||
created TIMESTAMP NOT NULL,
|
||||
last_fetched TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_episode (
|
||||
id UUID PRIMARY KEY,
|
||||
stream_url VARCHAR(256),
|
||||
file_path VARCHAR(256),
|
||||
channel_id CHAR(36) NOT NULL REFERENCES podcast_channel(id),
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
duration VARCHAR(8),
|
||||
status VARCHAR(16) NOT NULL,
|
||||
publish_date TIMESTAMP,
|
||||
created TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS index_episode_channel_id_fk ON podcast_channel(id);
|
||||
|
@ -59,6 +59,7 @@ CREATE TABLE IF NOT EXISTS user (
|
||||
salt CHAR(6) NOT NULL,
|
||||
admin BOOLEAN NOT NULL,
|
||||
jukebox BOOLEAN NOT NULL,
|
||||
podcast BOOLEAN NOT NULL,
|
||||
lastfm_session CHAR(32),
|
||||
lastfm_status BOOLEAN NOT NULL,
|
||||
last_play_id CHAR(36) REFERENCES track,
|
||||
@ -160,3 +161,29 @@ CREATE TABLE IF NOT EXISTS radio_station (
|
||||
created DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_channel (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
url VARCHAR(256) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
cover_art VARCHAR(256),
|
||||
original_image_url VARCHAR(256),
|
||||
status VARCHAR(16),
|
||||
error_message VARCHAR(256),
|
||||
created DATETIME NOT NULL,
|
||||
last_fetched DATETIME
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS podcast_episode (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
stream_url VARCHAR(256),
|
||||
file_path VARCHAR(256),
|
||||
channel_id CHAR(36) NOT NULL,
|
||||
title VARCHAR(256),
|
||||
description VARCHAR(256),
|
||||
duration VARCHAR(8),
|
||||
status VARCHAR(16) NOT NULL,
|
||||
publish_date DATETIME,
|
||||
created DATETIME NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS index_episode_channel_id_fk ON podcast_channel(id);
|
||||
|
@ -22,6 +22,7 @@ from .test_annotation import AnnotationTestCase
|
||||
from .test_media import MediaTestCase
|
||||
from .test_transcoding import TranscodingTestCase
|
||||
from .test_radio import RadioStationTestCase
|
||||
from .test_podcast import PodcastTestCase
|
||||
|
||||
|
||||
def suite():
|
||||
@ -40,5 +41,6 @@ def suite():
|
||||
suite.addTest(unittest.makeSuite(MediaTestCase))
|
||||
suite.addTest(unittest.makeSuite(TranscodingTestCase))
|
||||
suite.addTest(unittest.makeSuite(RadioStationTestCase))
|
||||
suite.addTest(unittest.makeSuite(PodcastTestCase))
|
||||
|
||||
return suite
|
||||
|
170
tests/api/test_podcast.py
Normal file
170
tests/api/test_podcast.py
Normal file
@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
#
|
||||
# This file is part of Supysonic.
|
||||
# Supysonic is a Python implementation of the Subsonic server API.
|
||||
#
|
||||
# Copyright (C) 2020 Alban 'spl0k' Féron
|
||||
#
|
||||
# Distributed under terms of the GNU AGPLv3 license.
|
||||
|
||||
import uuid
|
||||
|
||||
from pony.orm import db_session
|
||||
|
||||
from supysonic.db import PodcastChannel, PodcastEpisode
|
||||
|
||||
from unittest import skip
|
||||
|
||||
from .apitestbase import ApiTestBase
|
||||
|
||||
|
||||
class PodcastTestCase(ApiTestBase):
|
||||
_non_admin_user_ = {"u": "bob", "p": "B0b", "username": "alice"}
|
||||
|
||||
def setUp(self):
|
||||
super(PodcastTestCase, self).setUp()
|
||||
|
||||
@db_session
|
||||
def assertDbCountEqual(self, entity, count):
|
||||
self.assertEqual(entity.select().count(), count)
|
||||
|
||||
def assertPodcastChannelEquals(self, channel, url, status, title='', description='', error_message=''):
|
||||
self.assertEqual(channel.url, url)
|
||||
self.assertEqual(channel.title, title)
|
||||
self.assertEqual(channel.description, description)
|
||||
self.assertEqual(channel.status, status)
|
||||
self.assertEqual(channel.error_message, error_message)
|
||||
|
||||
def test_create_podcast_channel(self):
|
||||
# test for non-admin access
|
||||
self._make_request(
|
||||
"createPodcastChannel",
|
||||
self._non_admin_user_,
|
||||
error=50
|
||||
)
|
||||
|
||||
# check params
|
||||
self._make_request("createPodcastChannel", error=10)
|
||||
|
||||
# create w/ required fields
|
||||
url = "https://example.local/podcast_channel/create"
|
||||
|
||||
self._make_request("createPodcastChannel", {"url": url})
|
||||
|
||||
# the correct value is 2 because _make_request uses GET then POST
|
||||
self.assertDbCountEqual(PodcastChannel, 2)
|
||||
|
||||
with db_session:
|
||||
for channel in PodcastChannel.select():
|
||||
self.assertPodcastChannelEquals(channel, url, "new")
|
||||
|
||||
|
||||
def test_delete_podcast_channel(self):
|
||||
# test for non-admin access
|
||||
self._make_request(
|
||||
"deletePodcastChannel",
|
||||
self._non_admin_user_,
|
||||
error=50
|
||||
)
|
||||
|
||||
# check params
|
||||
self._make_request("deletePodcastChannel", error=10)
|
||||
self._make_request("deletePodcastChannel", {"id": 1}, error=0)
|
||||
self._make_request("deletePodcastChannel", {"id": str(uuid.uuid4())}, error=70)
|
||||
|
||||
# delete
|
||||
with db_session:
|
||||
channel = PodcastChannel(
|
||||
url="https://example.local/podcast/delete",
|
||||
status="new",
|
||||
)
|
||||
|
||||
self._make_request("deletePodcastChannel", {"id": channel.id}, skip_post=True)
|
||||
|
||||
self.assertDbCountEqual(PodcastChannel, 0)
|
||||
|
||||
def test_delete_podcast_episode(self):
|
||||
# test for non-admin access
|
||||
self._make_request(
|
||||
"deletePodcastEpisode",
|
||||
self._non_admin_user_,
|
||||
error=50
|
||||
)
|
||||
|
||||
# check params
|
||||
self._make_request("deletePodcastEpisode", error=10)
|
||||
self._make_request("deletePodcastEpisode", {"id": 1}, error=0)
|
||||
self._make_request("deletePodcastEpisode", {"id": str(uuid.uuid4())}, error=70)
|
||||
|
||||
# delete
|
||||
with db_session:
|
||||
channel = PodcastChannel(
|
||||
url="https://example.local/episode/delete",
|
||||
status="new",
|
||||
)
|
||||
episode = channel.episodes.create(
|
||||
description="Test Episode 1",
|
||||
status="new",
|
||||
)
|
||||
channel.episodes.create(
|
||||
description="Test Episode 2",
|
||||
status="new",
|
||||
)
|
||||
|
||||
# validate starting condition
|
||||
self.assertDbCountEqual(PodcastEpisode, 2)
|
||||
|
||||
# validate delete of an episode
|
||||
self._make_request("deletePodcastEpisode", {"id": episode.id}, skip_post=True)
|
||||
self.assertDbCountEqual(PodcastEpisode, 1)
|
||||
|
||||
# test for cascading delete on PodcastChannel
|
||||
self._make_request("deletePodcastChannel", {"id": channel.id}, skip_post=True)
|
||||
self.assertDbCountEqual(PodcastChannel, 0)
|
||||
self.assertDbCountEqual(PodcastEpisode, 0)
|
||||
|
||||
def test_get_podcasts(self):
|
||||
test_range = 3
|
||||
with db_session:
|
||||
for x in range(test_range):
|
||||
ch = PodcastChannel(
|
||||
url="https://example.local/podcast-{}".format(x),
|
||||
status="new",
|
||||
)
|
||||
for y in range(x + 1):
|
||||
ch.episodes.create(description="episode {} for channel {}".format(y, x))
|
||||
|
||||
# verify data is stored
|
||||
self.assertDbCountEqual(PodcastChannel, test_range)
|
||||
|
||||
# compare api response to inventory
|
||||
rv, channels = self._make_request("getPodcasts", tag="podcasts")
|
||||
self.assertEqual(len(channels), test_range)
|
||||
|
||||
# This order is guaranteed to work because the api returns in order by name.
|
||||
# Test data is sequential by design.
|
||||
for x in range(test_range):
|
||||
channel = channels[x]
|
||||
self.assertTrue(channel.get("url").endswith("podcast-{}".format(x)))
|
||||
self.assertTrue(channel.get("status").endswith("new"))
|
||||
|
||||
# test for non-admin access
|
||||
rv, channels = self._make_request(
|
||||
"getPodcasts",
|
||||
self._non_admin_user_,
|
||||
tag="podcasts",
|
||||
)
|
||||
self.assertEqual(len(channels), test_range)
|
||||
|
||||
# test retrieving a podcast by id
|
||||
for channel in channels:
|
||||
rv, test_channels = self._make_request("getPodcasts", {"id": channel.get("id"), "includeEpisodes": True}, tag="podcasts", skip_post=True)
|
||||
# expect to work with only 1
|
||||
self.assertEqual(len(test_channels), 1)
|
||||
test_channel = test_channels[0]
|
||||
self.assertEqual(test_channel.get("id"), channel.get("id"))
|
||||
|
||||
# should have as many episodes as noted in the url
|
||||
count = int(channel.get("url")[-1]) + 1
|
||||
self.assertEqual(len(test_channel), count)
|
Loading…
Reference in New Issue
Block a user