diff --git a/cps/admin.py b/cps/admin.py index 5f481fa1..5daad958 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -54,6 +54,7 @@ from .services.worker import WorkerThread from .usermanagement import user_login_required from .babel import get_available_translations, get_available_locale, get_user_locale_language from . import debug_info +from .string_helper import strip_whitespaces log = logger.create() @@ -463,9 +464,9 @@ def edit_list_user(param): if 'value[]' in vals: setattr(user, param, prepare_tags(user, vals['action'][0], param, vals['value[]'])) else: - setattr(user, param, vals['value'].strip()) + setattr(user, param, strip_whitespaces(vals['value'])) else: - vals['value'] = vals['value'].strip() + vals['value'] = strip_whitespaces(vals['value']) if param == 'name': if user.name == "Guest": raise Exception(_("Guest Name can't be changed")) @@ -690,7 +691,7 @@ def delete_domain(): def list_domain(allow): answer = ub.session.query(ub.Registration).filter(ub.Registration.allow == allow).all() json_dumps = json.dumps([{"domain": r.domain.replace('%', '*').replace('_', '?'), "id": r.id} for r in answer]) - js = json.dumps(json_dumps.replace('"', "'")).lstrip('"').strip('"') + js = json.dumps(json_dumps.replace('"', "'")).strip('"') response = make_response(js.replace("'", '"')) response.headers["Content-Type"] = "application/json; charset=utf-8" return response @@ -1100,7 +1101,7 @@ def _config_checkbox_int(to_save, x): def _config_string(to_save, x): - return config.set_from_dictionary(to_save, x, lambda y: y.strip().strip(u'\u200B\u200C\u200D\ufeff') if y else y) + return config.set_from_dictionary(to_save, x, lambda y: strip_whitespaces(y) if y else y) def _configuration_gdrive_helper(to_save): @@ -1311,9 +1312,9 @@ def update_mailsettings(): if to_save.get("mail_password_e", ""): _config_string(to_save, "mail_password_e") _config_int(to_save, "mail_size", lambda y: int(y) * 1024 * 1024) - config.mail_server = to_save.get('mail_server', "").strip() - config.mail_from = to_save.get('mail_from', "").strip() - config.mail_login = to_save.get('mail_login', "").strip() + config.mail_server = strip_whitespaces(to_save.get('mail_server', "")) + config.mail_from = strip_whitespaces(to_save.get('mail_from', "")) + config.mail_login = strip_whitespaces(to_save.get('mail_login', "")) try: config.save() except (OperationalError, InvalidRequestError) as e: @@ -1678,10 +1679,10 @@ def cancel_task(): def _db_simulate_change(): param = request.form.to_dict() to_save = dict() - to_save['config_calibre_dir'] = re.sub(r'[\\/]metadata\.db$', + to_save['config_calibre_dir'] = strip_whitespaces(re.sub(r'[\\/]metadata\.db$', '', param['config_calibre_dir'], - flags=re.IGNORECASE).strip() + flags=re.IGNORECASE)) db_valid, db_change = calibre_db.check_valid_db(to_save["config_calibre_dir"], ub.app_DB_path, config.config_calibre_uuid) @@ -1775,9 +1776,8 @@ def _configuration_update_helper(): if "config_upload_formats" in to_save: to_save["config_upload_formats"] = ','.join( - helper.uniq([x.lstrip().rstrip().lower() for x in to_save["config_upload_formats"].split(',')])) + helper.uniq([x.strip().lower() for x in to_save["config_upload_formats"].split(',')])) _config_string(to_save, "config_upload_formats") - # constants.EXTENSIONS_UPLOAD = config.config_upload_formats.split(',') _config_string(to_save, "config_calibre") _config_string(to_save, "config_binariesdir") diff --git a/cps/config_sql.py b/cps/config_sql.py index 044c12b5..6a840af5 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -35,7 +35,7 @@ except ImportError: from . import constants, logger from .subproc_wrapper import process_wait - +from .string_helper import strip_whitespaces log = logger.create() _Base = declarative_base() @@ -288,19 +288,19 @@ class ConfigSQL(object): def list_denied_tags(self): mct = self.config_denied_tags or "" - return [t.strip() for t in mct.split(",")] + return [strip_whitespaces(t) for t in mct.split(",")] def list_allowed_tags(self): mct = self.config_allowed_tags or "" - return [t.strip() for t in mct.split(",")] + return [strip_whitespaces(t) for t in mct.split(",")] def list_denied_column_values(self): mct = self.config_denied_column_value or "" - return [t.strip() for t in mct.split(",")] + return [strip_whitespaces(t) for t in mct.split(",")] def list_allowed_column_values(self): mct = self.config_allowed_column_value or "" - return [t.strip() for t in mct.split(",")] + return [strip_whitespaces(t) for t in mct.split(",")] def get_log_level(self): return logger.get_level_name(self.config_log_level) @@ -372,7 +372,7 @@ class ConfigSQL(object): db_file = os.path.join(self.config_calibre_dir, 'metadata.db') have_metadata_db = os.path.isfile(db_file) self.db_configured = have_metadata_db - # constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip().lower() for x in self.config_upload_formats.split(',')] + from . import cli_param if os.environ.get('FLASK_DEBUG'): logfile = logger.setup(logger.LOG_TO_STDOUT, logger.logging.DEBUG) diff --git a/cps/db.py b/cps/db.py index d0edb871..dc08a7de 100644 --- a/cps/db.py +++ b/cps/db.py @@ -48,7 +48,7 @@ from flask import flash from . import logger, ub, isoLanguages from .pagination import Pagination - +from .string_helper import strip_whitespaces log = logger.create() @@ -875,10 +875,11 @@ class CalibreDB: authors_ordered = list() # error = False for auth in sort_authors: - results = self.session.query(Authors).filter(Authors.sort == auth.lstrip().strip()).all() + auth = strip_whitespaces(auth) + results = self.session.query(Authors).filter(Authors.sort == auth).all() # ToDo: How to handle not found author name if not len(results): - log.error("Author {} not found to display name in right order".format(auth.strip())) + log.error("Author {} not found to display name in right order".format(auth)) # error = True break for r in results: @@ -918,7 +919,7 @@ class CalibreDB: .filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first() def search_query(self, term, config, *join): - term.strip().lower() + strip_whitespaces(term).lower() self.session.connection().connection.connection.create_function("lower", 1, lcase) q = list() author_terms = re.split("[, ]+", term) @@ -1026,7 +1027,7 @@ class CalibreDB: if match: prep = match.group(1) title = title[len(prep):] + ', ' + prep - return title.strip() + return strip_whitespaces(title) try: # sqlalchemy <1.4.24 and sqlalchemy 2.0 diff --git a/cps/editbooks.py b/cps/editbooks.py index f1943a79..122b1c2c 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -47,7 +47,7 @@ from .kobo_sync_status import change_archived_books from .redirect import get_redirect_location from .file_helper import validate_mime_type from .usermanagement import user_login_required, login_required_if_no_ano - +from .string_helper import strip_whitespaces editbook = Blueprint('edit-book', __name__) log = logger.create() @@ -979,7 +979,7 @@ def render_edit_book(book_id): def edit_book_ratings(to_save, book): changed = False - if to_save.get("rating", "").strip(): + if strip_whitespaces(to_save.get("rating", "")): old_rating = False if len(book.ratings) > 0: old_rating = book.ratings[0].rating @@ -1003,14 +1003,14 @@ def edit_book_ratings(to_save, book): def edit_book_tags(tags, book): input_tags = tags.split(',') - input_tags = list(map(lambda it: it.strip(), input_tags)) + input_tags = list(map(lambda it: strip_whitespaces(it), input_tags)) # Remove duplicates input_tags = helper.uniq(input_tags) return modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags') def edit_book_series(series, book): - input_series = [series.strip()] + input_series = [strip_whitespaces(series)] input_series = [x for x in input_series if x != ''] return modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series') @@ -1072,7 +1072,7 @@ def edit_book_languages(languages, book, upload_mode=False, invalid=None): def edit_book_publisher(publishers, book): changed = False if publishers: - publisher = publishers.rstrip().strip() + publisher = strip_whitespaces(publishers) if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name): changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session, 'publisher') @@ -1119,7 +1119,7 @@ def edit_cc_data_string(book, c, to_save, cc_db_value, cc_string): changed = False if c.datatype == 'rating': to_save[cc_string] = str(int(float(to_save[cc_string]) * 2)) - if to_save[cc_string].strip() != cc_db_value: + if strip_whitespaces(to_save[cc_string]) != cc_db_value: if cc_db_value is not None: # remove old cc_val del_cc = getattr(book, cc_string)[0] @@ -1129,15 +1129,15 @@ def edit_cc_data_string(book, c, to_save, cc_db_value, cc_string): changed = True cc_class = db.cc_classes[c.id] new_cc = calibre_db.session.query(cc_class).filter( - cc_class.value == to_save[cc_string].strip()).first() + cc_class.value == strip_whitespaces(to_save[cc_string])).first() # if no cc val is found add it if new_cc is None: - new_cc = cc_class(value=to_save[cc_string].strip()) + new_cc = cc_class(value=strip_whitespaces(to_save[cc_string])) calibre_db.session.add(new_cc) changed = True calibre_db.session.flush() new_cc = calibre_db.session.query(cc_class).filter( - cc_class.value == to_save[cc_string].strip()).first() + cc_class.value == strip_whitespaces(to_save[cc_string])).first() # add cc value to book getattr(book, cc_string).append(new_cc) return changed, to_save @@ -1165,7 +1165,7 @@ def edit_cc_data(book_id, book, to_save, cc): cc_db_value = getattr(book, cc_string)[0].value else: cc_db_value = None - if to_save[cc_string].strip(): + if strip_whitespaces(to_save[cc_string]): if c.datatype in ['int', 'bool', 'float', "datetime", "comments"]: change, to_save = edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string) else: @@ -1181,7 +1181,7 @@ def edit_cc_data(book_id, book, to_save, cc): changed = True else: input_tags = to_save[cc_string].split(',') - input_tags = list(map(lambda it: it.strip(), input_tags)) + input_tags = list(map(lambda it: strip_whitespaces(it), input_tags)) changed |= modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], @@ -1284,7 +1284,7 @@ def upload_cover(cover_request, book): def handle_title_on_edit(book, book_title): # handle book title - book_title = book_title.rstrip().strip() + book_title = strip_whitespaces(book_title) if book.title != book_title: if book_title == '': book_title = _(u'Unknown') diff --git a/cps/epub.py b/cps/epub.py index c802f61d..e84822f3 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -25,6 +25,7 @@ from . import config, logger from .helper import split_authors from .epub_helper import get_content_opf, default_ns from .constants import BookMeta +from .string_helper import strip_whitespaces log = logger.create() @@ -90,7 +91,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): elif s == 'date': epub_metadata[s] = tmp[0][:10] else: - epub_metadata[s] = tmp[0].strip() + epub_metadata[s] = strip_whitespaces(tmp[0]) else: epub_metadata[s] = 'Unknown' diff --git a/cps/helper.py b/cps/helper.py index bc95762c..39c9c384 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -52,6 +52,7 @@ except ImportError: UnacceptableAddressException = MissingSchema = BaseException from . import calibre_db, cli_param +from .string_helper import strip_whitespaces from .tasks.convert import TaskConvert from . import logger, config, db, ub, fs from . import gdriveutils as gd @@ -118,7 +119,7 @@ def convert_book_format(book_id, calibre_path, old_book_format, new_book_format, # Texts are not lazy translated as they are supposed to get send out as is def send_test_mail(ereader_mail, user_name): for email in ereader_mail.split(','): - email = email.strip() + email = strip_whitespaces(email) WorkerThread.add(user_name, TaskEmail(_('Calibre-Web Test Email'), None, None, config.get_mail_settings(), email, N_("Test Email"), _('This Email has been sent via Calibre-Web.'))) @@ -228,7 +229,7 @@ def send_mail(book_id, book_format, convert, ereader_mail, calibrepath, user_id) link = '{}'.format(url_for('web.show_book', book_id=book_id), escape(book.title)) email_text = N_("%(book)s send to eReader", book=link) for email in ereader_mail.split(','): - email = email.strip() + email = strip_whitespaces(email) WorkerThread.add(user_id, TaskEmail(_("Send to eReader"), book.path, converted_file_name, config.get_mail_settings(), email, email_text, _('This Email has been sent via Calibre-Web.'), book.id)) @@ -252,7 +253,7 @@ def get_valid_filename(value, replace_whitespace=True, chars=128): # pipe has to be replaced with comma value = re.sub(r'[|]+', ',', value, flags=re.U) - value = value.encode('utf-8')[:chars].decode('utf-8', errors='ignore').strip() + value = strip_whitespaces(value.encode('utf-8')[:chars].decode('utf-8', errors='ignore')) if not value: raise ValueError("Filename cannot be empty") @@ -267,11 +268,11 @@ def split_authors(values): commas = author.count(',') if commas == 1: author_split = author.split(',') - authors_list.append(author_split[1].strip() + ' ' + author_split[0].strip()) + authors_list.append(strip_whitespaces(author_split[1]) + ' ' + strip_whitespaces(author_split[0])) elif commas > 1: - authors_list.extend([x.strip() for x in author.split(',')]) + authors_list.extend([strip_whitespaces(x) for x in author.split(',')]) else: - authors_list.append(author.strip()) + authors_list.append(strip_whitespaces(author)) return authors_list @@ -661,7 +662,7 @@ def check_email(email): def check_username(username): - username = username.strip() + username = strip_whitespaces(username) if ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.lower()).scalar(): log.error("This username is already taken") raise Exception(_("This username is already taken")) @@ -670,14 +671,14 @@ def check_username(username): def valid_email(emails): for email in emails.split(','): - email = email.strip() - # if email is not deleted - if email: - # Regex according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#validation - if not re.search(r"^[\w.!#$%&'*+\\/=?^_`{|}~-]+@[\w](?:[\w-]{0,61}[\w])?(?:\.[\w](?:[\w-]{0,61}[\w])?)*$", - email): - log.error("Invalid Email address format") - raise Exception(_("Invalid Email address format")) + email = strip_whitespaces(email) + # if email is not deleted + if email: + # Regex according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#validation + if not re.search(r"^[\w.!#$%&'*+\\/=?^_`{|}~-]+@[\w](?:[\w-]{0,61}[\w])?(?:\.[\w](?:[\w-]{0,61}[\w])?)*$", + email): + log.error("Invalid Email address format") + raise Exception(_("Invalid Email address format")) return email diff --git a/cps/search.py b/cps/search.py index 6054ec9e..da74984b 100644 --- a/cps/search.py +++ b/cps/search.py @@ -24,9 +24,9 @@ from flask_babel import format_date from flask_babel import gettext as _ from sqlalchemy.sql.expression import func, not_, and_, or_, text, true from sqlalchemy.sql.functions import coalesce -from sqlalchemy import exists from . import logger, db, calibre_db, config, ub +from .string_helper import strip_whitespaces from .usermanagement import login_required_if_no_ano from .render_template import render_title_template from .pagination import Pagination @@ -267,11 +267,11 @@ def render_adv_search_results(term, offset=None, order=None, limit=None): description = term.get("comment") read_status = term.get("read_status") if author_name: - author_name = author_name.strip().lower().replace(',', '|') + author_name = strip_whitespaces(author_name).lower().replace(',', '|') if book_title: - book_title = book_title.strip().lower() + book_title = strip_whitespaces(book_title).lower() if publisher: - publisher = publisher.strip().lower() + publisher = strip_whitespaces(publisher).lower() search_term = [] cc_present = False diff --git a/cps/string_helper.py b/cps/string_helper.py new file mode 100644 index 00000000..b2d0cf81 --- /dev/null +++ b/cps/string_helper.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) +# Copyright (C) 2024 OzzieIsaacs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import re + + +def strip_whitespaces(text): + return re.sub("(^[\s\u200B-\u200D\ufeff]+)|([\s\u200B-\u200D\ufeff]+$)","", text) + diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index 3bef81a9..b163ecfe 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -39,6 +39,7 @@ from cps.file_helper import get_temp_dir from cps.tasks.mail import TaskEmail from cps import gdriveutils, helper from cps.constants import SUPPORTED_CALIBRE_BINARIES +from cps.string_helper import strip_whitespaces log = logger.create() @@ -107,7 +108,7 @@ class TaskConvert(CalibreTask): try: EmailText = N_(u"%(book)s send to E-Reader", book=escape(self.title)) for email in self.ereader_mail.split(','): - email = email.strip() + email = strip_whitespaces(email) worker_thread.add(self.user, TaskEmail(self.settings['subject'], self.results["path"], filename, diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 4f85eefa..f332e267 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -34,6 +34,7 @@ from cps.services import gmail from cps.embed_helper import do_calibre_export from cps import logger, config from cps import gdriveutils +from cps.string_helper import strip_whitespaces import uuid log = logger.create() @@ -127,9 +128,9 @@ class TaskEmail(CalibreTask): try: # Parse out the address from the From line, and then the domain from that from_email = parseaddr(self.settings["mail_from"])[1] - msgid_domain = from_email.partition('@')[2].strip() + msgid_domain = strip_whitespaces(from_email.partition('@')[2]) # This can sometimes sneak through parseaddr if the input is malformed - msgid_domain = msgid_domain.rstrip('>').strip() + msgid_domain = strip_whitespaces(msgid_domain.rstrip('>')) except Exception: msgid_domain = '' return msgid_domain or 'calibre-web.com' diff --git a/cps/ub.py b/cps/ub.py index e548cc12..5b889a72 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -54,7 +54,7 @@ from sqlalchemy.orm import backref, relationship, sessionmaker, Session, scoped_ from werkzeug.security import generate_password_hash from . import constants, logger - +from .string_helper import strip_whitespaces log = logger.create() @@ -196,19 +196,19 @@ class UserBase: def list_denied_tags(self): mct = self.denied_tags or "" - return [t.strip() for t in mct.split(",")] + return [strip_whitespaces(t) for t in mct.split(",")] def list_allowed_tags(self): mct = self.allowed_tags or "" - return [t.strip() for t in mct.split(",")] + return [strip_whitespaces(t) for t in mct.split(",")] def list_denied_column_values(self): mct = self.denied_column_value or "" - return [t.strip() for t in mct.split(",")] + return [strip_whitespaces(t) for t in mct.split(",")] def list_allowed_column_values(self): mct = self.allowed_column_value or "" - return [t.strip() for t in mct.split(",")] + return [strip_whitespaces(t) for t in mct.split(",")] def get_view_property(self, page, prop): if not self.view_settings.get(page): diff --git a/cps/uploader.py b/cps/uploader.py index 94a0f8c0..d59142c4 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -24,6 +24,7 @@ from . import logger, comic, isoLanguages from .constants import BookMeta from .helper import split_authors from .file_helper import get_temp_dir +from .string_helper import strip_whitespaces log = logger.create() @@ -97,9 +98,9 @@ def process(tmp_file_path, original_file_name, original_file_extension, rar_exec except Exception as ex: log.warning('cannot parse metadata, using default: %s', ex) - if not meta.title.strip(): + if not strip_whitespaces(meta.title): meta = meta._replace(title=original_file_name) - if not meta.author.strip() or meta.author.lower() == 'unknown': + if not strip_whitespaces(meta.author) or meta.author.lower() == 'unknown': meta = meta._replace(author=_('Unknown')) return meta diff --git a/cps/web.py b/cps/web.py index f43cdd87..7e66730e 100644 --- a/cps/web.py +++ b/cps/web.py @@ -60,6 +60,7 @@ from . import limiter from .services.worker import WorkerThread from .tasks_status import render_task_status from .usermanagement import user_login_required +from .string_helper import strip_whitespaces feature_support = { @@ -1286,7 +1287,7 @@ def register_post(): if not config.get_mail_server_configured(): flash(_("Oops! Email server is not configured, please contact your administrator."), category="error") return render_title_template('register.html', title=_("Register"), page="register") - nickname = to_save.get("email", "").strip() if config.config_register_email else to_save.get('name') + nickname = strip_whitespaces(to_save.get("email", "")) if config.config_register_email else to_save.get('name') if not nickname or not to_save.get("email"): flash(_("Oops! Please complete all fields."), category="error") return render_title_template('register.html', title=_("Register"), page="register") @@ -1311,7 +1312,7 @@ def register_post(): ub.session.commit() if feature_support['oauth']: register_user_with_oauth(content) - send_registration_mail(to_save.get("email", "").strip(), nickname, password) + send_registration_mail(strip_whitespaces(to_save.get("email", "")), nickname, password) except Exception: ub.session.rollback() flash(_("Oops! An unknown error occurred. Please try again later."), category="error") @@ -1370,11 +1371,11 @@ def login(): @web.route('/login', methods=['POST']) -@limiter.limit("40/day", key_func=lambda: request.form.get('username', "").strip().lower()) -@limiter.limit("3/minute", key_func=lambda: request.form.get('username', "").strip().lower()) +@limiter.limit("40/day", key_func=lambda: strip_whitespaces(request.form.get('username', "")).lower()) +@limiter.limit("3/minute", key_func=lambda: strip_whitespaces(request.form.get('username', "")).lower()) def login_post(): form = request.form.to_dict() - username = form.get('username', "").strip().lower().replace("\n","").replace("\r","") + username = strip_whitespaces(form.get('username', "")).lower().replace("\n","").replace("\r","") try: limiter.check() except RateLimitExceeded: diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 8b5335b9..44ef1d89 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2024-08-11 19:57:31

+

Start Time: 2024-08-12 18:42:30

-

Stop Time: 2024-08-12 03:10:54

+

Stop Time: 2024-08-13 01:57:45

-

Duration: 5h 59 min

+

Duration: 6h 1 min

@@ -4519,11 +4519,11 @@ ModuleNotFoundError: No module named 'build_release' - + TestThumbnails 8 - 6 - 1 + 7 + 0 0 1 @@ -4560,31 +4560,11 @@ ModuleNotFoundError: No module named 'build_release' - +
TestThumbnails - test_cover_change_on_upload_new_cover
- -
- FAIL -
- - - - + PASS @@ -4739,11 +4719,11 @@ AssertionError: 0.023989181595169266 not greater than or equal to 0.03 - + TestUploadAudio 12 - 11 - 1 + 12 + 0 0 0 @@ -4780,33 +4760,11 @@ AssertionError: 0.023989181595169266 not greater than or equal to 0.03 - +
TestUploadAudio - test_upload_flac
- -
- FAIL -
- - - - + PASS @@ -5849,8 +5807,8 @@ AssertionError: 'Album' != 'Flac Album' Total 519 - 506 - 2 + 508 + 0 2 9   @@ -6390,7 +6348,7 @@ AssertionError: 'Album' != 'Flac Album'