diff --git a/acoustid-test.conf b/acoustid-test.conf
index 98633de60043a118d4dcf8b4ba97a97ae1f98b4a..08f4e5a3f0126b651ec95f7787c0bc094d276eb4 100644
--- a/acoustid-test.conf
+++ b/acoustid-test.conf
@@ -1,19 +1,33 @@
[database]
+two_phase_commit=yes
+
+[database:default]
host=127.0.0.1
-port=5432
+port=15432
user=acoustid
name=acoustid_test
password=acoustid
+[database:slow]
+host=127.0.0.1
+port=15432
+user=acoustid
+name=acoustid_slow_test
+password=acoustid
+
[logging]
level=WARNING
level.sqlalchemy=WARNING
syslog=yes
syslog_facility=local1
+[index]
+host=127.0.0.1
+port=16080
+
[redis]
host=127.0.0.1
-port=6379
+port=16379
[website]
base_url=http://acoustid.org/
diff --git a/acoustid.conf.dist b/acoustid.conf.dist
index caa52cca71998971c2a3f9ea14d0f8ccb959921c..2bb3d7eedd9e27e7d52ddda715397ea6b18356be 100644
--- a/acoustid.conf.dist
+++ b/acoustid.conf.dist
@@ -1,8 +1,14 @@
[database]
+name=acoustid
user=acoustid
password=XXX
-superuser=postgres
+host=localhost
+port=5432
+
+[database_slow]
name=acoustid
+user=acoustid
+password=XXX
host=localhost
port=5432
diff --git a/acoustid/api/v2/__init__.py b/acoustid/api/v2/__init__.py
index fc6a760703f5428ac8fc20b98daeea64b81e49f2..42ce8c72486e383d1e886758246908aaba5dc692 100644
--- a/acoustid/api/v2/__init__.py
+++ b/acoustid/api/v2/__init__.py
@@ -8,18 +8,15 @@ import time
import operator
from typing import Type
from acoustid import const
+from acoustid.db import DatabaseContext
from acoustid.const import MAX_REQUESTS_PER_SECOND
from acoustid.handler import Handler
+from acoustid.models import Application, Account, Submission, SubmissionResult, Track
from acoustid.data.track import lookup_mbids, resolve_track_gid, lookup_meta_ids
from acoustid.data.musicbrainz import lookup_metadata
-from acoustid.data.submission import insert_submission, lookup_submission_status
from acoustid.data.fingerprint import decode_fingerprint, FingerprintSearcher
-from acoustid.data.format import find_or_insert_format
from acoustid.data.application import lookup_application_id_by_apikey
-from acoustid.data.account import lookup_account_id_by_apikey
-from acoustid.data.source import find_or_insert_source
-from acoustid.data.meta import insert_meta, lookup_meta
-from acoustid.data.foreignid import find_or_insert_foreignid
+from acoustid.data.meta import lookup_meta
from acoustid.data.stats import update_lookup_counter, update_user_agent_counter, update_lookup_avg_time
from acoustid.ratelimiter import RateLimiter
from werkzeug.utils import cached_property
@@ -103,6 +100,7 @@ class APIHandler(Handler):
else:
connect = server.engine.connect
handler = cls(connect=connect)
+ handler.server = server
handler.index = server.index
handler.redis = server.redis
handler.config = server.config
@@ -564,6 +562,7 @@ class LookupHandler(APIHandler):
matches = [(0, track_id, p['track_gid'], 1.0)]
else:
matches = searcher.search(p['fingerprint'], p['duration'])
+ print(repr(matches))
all_matches.append(matches)
response = {}
if params.batch:
@@ -589,37 +588,121 @@ class LookupHandler(APIHandler):
return response
+class APIHandlerWithORM(APIHandler):
+
+ params_class = None # type: Type[APIHandlerParams]
+
+ def __init__(self, server):
+ self.server = server
+
+ @property
+ def index(self):
+ return self.server.index
+
+ @property
+ def redis(self):
+ return self.server.redis
+
+ @property
+ def config(self):
+ return self.server.config
+
+ @classmethod
+ def create_from_server(cls, server):
+ return cls(server=server)
+
+ def _rate_limit(self, user_ip, application_id):
+ ip_rate_limit = self.config.rate_limiter.ips.get(user_ip, MAX_REQUESTS_PER_SECOND)
+ if self.rate_limiter.limit('ip', user_ip, ip_rate_limit):
+ if application_id == DEMO_APPLICATION_ID:
+ raise errors.TooManyRequests(ip_rate_limit)
+ if application_id is not None:
+ application_rate_limit = self.config.rate_limiter.applications.get(application_id)
+ if application_rate_limit is not None:
+ if self.rate_limiter.limit('app', application_id, application_rate_limit):
+ if application_id == DEMO_APPLICATION_ID:
+ raise errors.TooManyRequests(application_rate_limit)
+
+ def handle(self, req):
+ params = self.params_class(self.config)
+ if req.access_route:
+ self.user_ip = req.access_route[0]
+ else:
+ self.user_ip = req.remote_addr
+ self.is_secure = req.is_secure
+ self.user_agent = req.user_agent
+ self.rate_limiter = RateLimiter(self.redis, 'rl')
+ try:
+ with DatabaseContext(self.server) as db:
+ self.db = db
+ try:
+ try:
+ params.parse(req.values, db)
+ self._rate_limit(self.user_ip, getattr(params, 'application_id', None))
+ return self._ok(self._handle_internal(params), params.format)
+ except errors.WebServiceError:
+ raise
+ except Exception:
+ logger.exception('Error while handling API request')
+ raise errors.InternalError()
+ finally:
+ self.db = None
+ except errors.WebServiceError as e:
+ logger.warning("WS error: %s", e.message)
+ return self._error(e.code, e.message, params.format, status=e.status)
+
+
+def get_submission_status(db, submission_ids):
+ submissions = (
+ db.session.query(SubmissionResult.submission_id, SubmissionResult.track_id)
+ .filter(SubmissionResult.submission_id.in_(submission_ids))
+ )
+ track_ids = {submission_id: track_id for (submission_id, track_id) in submissions}
+
+ tracks = (
+ db.session.query(Track.id, Track.gid)
+ .filter(Track.id.in_(track_ids.values()))
+ )
+ track_gids = {track_id: track_gid for (track_id, track_gid) in tracks}
+
+ response = {'submissions': []}
+ for submission_id in submission_ids:
+ submission_response = {'id': submission_id, 'status': 'pending'}
+ track_id = track_ids.get(submission_id)
+ if track_id is not None:
+ track_gid = track_gids.get(track_id)
+ if track_gid is not None:
+ submission_response.update({
+ 'status': 'imported',
+ 'response': {'id': track_gid},
+ })
+ response['submissions'].append(submission_response)
+ return response
+
+
class SubmissionStatusHandlerParams(APIHandlerParams):
- def parse(self, values, conn):
- super(SubmissionStatusHandlerParams, self).parse(values, conn)
- self._parse_client(values, conn)
+ def parse(self, values, db):
+ super(SubmissionStatusHandlerParams, self).parse(values, db)
+ self._parse_client(values, db.session.connection(mapper=Application))
self.ids = values.getlist('id', type=int)
-class SubmissionStatusHandler(APIHandler):
+class SubmissionStatusHandler(APIHandlerWithORM):
params_class = SubmissionStatusHandlerParams
def _handle_internal(self, params):
- response = {'submissions': [{'id': id, 'status': 'pending'} for id in params.ids]}
- tracks = lookup_submission_status(self.conn, params.ids)
- for submission in response['submissions']:
- id = submission['id']
- track_gid = tracks.get(id)
- if track_gid is not None:
- submission['status'] = 'imported'
- submission['result'] = {'id': track_gid}
- return response
+ return get_submission_status(self.db, params.ids)
class SubmitHandlerParams(APIHandlerParams):
- def _parse_user(self, values, conn):
+ def _parse_user(self, values, db):
account_apikey = values.get('user')
if not account_apikey:
raise errors.MissingParameterError('user')
- self.account_id = lookup_account_id_by_apikey(conn, account_apikey)
+ self.account_id = db.session.query(Account.id).filter(Account.apikey == account_apikey).scalar()
if not self.account_id:
raise errors.InvalidUserAPIKeyError()
@@ -640,8 +723,8 @@ class SubmitHandlerParams(APIHandlerParams):
p['foreignid'] = values.get('foreignid' + suffix)
if p['foreignid'] and not is_foreignid(p['foreignid']):
raise errors.InvalidForeignIDError('foreignid' + suffix)
- p['mbids'] = values.getlist('mbid' + suffix)
- if p['mbids'] and not all(map(is_uuid, p['mbids'])):
+ p['mbid'] = values.get('mbid' + suffix)
+ if p['mbid'] and not is_uuid(p['mbid']):
raise errors.InvalidUUIDError('mbid' + suffix)
self._parse_duration_and_format(p, values, suffix)
fingerprint_string = values.get('fingerprint' + suffix)
@@ -662,10 +745,10 @@ class SubmitHandlerParams(APIHandlerParams):
p['year'] = values.get('year' + suffix, type=int)
self.submissions.append(p)
- def parse(self, values, conn):
- super(SubmitHandlerParams, self).parse(values, conn)
- self._parse_client(values, conn)
- self._parse_user(values, conn)
+ def parse(self, values, db):
+ super(SubmitHandlerParams, self).parse(values, db)
+ self._parse_client(values, db.session.connection(mapper=Application))
+ self._parse_user(values, db)
self.wait = values.get('wait', type=int, default=0)
self.submissions = []
suffixes = list(iter_args_suffixes(values, 'fingerprint'))
@@ -679,73 +762,45 @@ class SubmitHandlerParams(APIHandlerParams):
raise
-class SubmitHandler(APIHandler):
+class SubmitHandler(APIHandlerWithORM):
params_class = SubmitHandlerParams
meta_fields = ('track', 'artist', 'album', 'album_artist', 'track_no',
'disc_no', 'year')
def _handle_internal(self, params):
- response = {'submissions': []}
- ids = set()
- with self.conn.begin():
- source_id = find_or_insert_source(self.conn, params.application_id, params.account_id, params.application_version)
- format_ids = {}
- for p in params.submissions:
- if p['format']:
- if p['format'] not in format_ids:
- format_ids[p['format']] = find_or_insert_format(self.conn, p['format'])
- p['format_id'] = format_ids[p['format']]
- for p in params.submissions:
- mbids = p['mbids'] or [None]
- for mbid in mbids:
- values = {
- 'mbid': mbid or None,
- 'puid': p['puid'] or None,
- 'bitrate': p['bitrate'] or None,
- 'fingerprint': p['fingerprint'],
- 'length': p['duration'],
- 'format_id': p.get('format_id'),
- 'source_id': source_id
- }
- meta_values = dict((n, p[n] or None) for n in self.meta_fields)
- if any(meta_values.itervalues()):
- values['meta_id'] = insert_meta(self.conn, meta_values)
- if p['foreignid']:
- values['foreignid_id'] = find_or_insert_foreignid(self.conn, p['foreignid'])
- id = insert_submission(self.conn, values)
- ids.add(id)
- submission = {'id': id, 'status': 'pending'}
- if p['index']:
- submission['index'] = p['index']
- response['submissions'].append(submission)
-
- if self.redis is not None:
- self.redis.publish('channel.submissions', json.dumps(list(ids)))
-
- clients_waiting_key = 'submission.waiting'
- clients_waiting = self.redis.incr(clients_waiting_key) - 1
- try:
- max_wait = 10
- self.redis.expire(clients_waiting_key, max_wait)
- tracks = {}
- remaining = min(max(0, max_wait - 2 ** clients_waiting), params.wait)
- logger.debug('starting to wait at %f %d', remaining, clients_waiting)
- while remaining > 0 and ids:
- logger.debug('waiting %f seconds', remaining)
- time.sleep(0.5) # XXX replace with LISTEN/NOTIFY
- remaining -= 0.5
- tracks = lookup_submission_status(self.conn, ids)
- if not tracks:
- continue
- for submission in response['submissions']:
- id = submission['id']
- track_gid = tracks.get(id)
- if track_gid is not None:
- submission['status'] = 'imported'
- submission['result'] = {'id': track_gid}
- ids.remove(id)
- finally:
- self.redis.decr(clients_waiting_key)
-
+ ids = {}
+ for p in params.submissions:
+ submission = Submission()
+ submission.account_id = params.account_id
+ submission.application_id = params.application_id
+ submission.application_version = params.application_version
+ submission.fingerprint = p['fingerprint']
+ submission.duration = p['duration']
+ submission.mbid = p['mbid'] or None
+ submission.puid = p['puid'] or None
+ submission.foreignid = p['foreignid'] or None
+ submission.bitrate = p['bitrate'] or None
+ submission.format = p['format'] or None
+ submission.track = p['track'] or None
+ submission.artist = p['artist'] or None
+ submission.album = p['album'] or None
+ submission.album_artist = p['album_artist'] or None
+ submission.track_no = p['track_no'] or None
+ submission.disc_no = p['disc_no'] or None
+ submission.year = p['year'] or None
+ self.db.session.add(submission)
+ self.db.session.flush()
+ ids[submission.id] = p['index']
+
+ self.db.session.commit()
+
+ self.redis.publish('channel.submissions', json.dumps(list(ids.keys())))
+
+ response = get_submission_status(self.db, list(ids.keys()))
+ for submission_response in response['submissions']:
+ submission_id = submission_response['id']
+ index = ids[submission_id]
+ if index:
+ submission_response['index'] = index
return response
diff --git a/acoustid/config.py b/acoustid/config.py
index fc75fb43990c8b51ad9860d33973456cde4d7204..a00316f41b88f93783fb18a887bc0db8c4ae946b 100644
--- a/acoustid/config.py
+++ b/acoustid/config.py
@@ -18,8 +18,10 @@ def read_env_item(obj, key, name, convert=None):
value = None
if name in os.environ:
value = os.environ[name]
- if name + '_FILE' in os.environ:
+ logger.info('Reading config value from environment variable %s', name)
+ elif name + '_FILE' in os.environ:
value = open(os.environ[name + '_FILE']).read().strip()
+ logger.info('Reading config value from environment variable %s', name + '_FILE')
if value is not None:
if convert is not None:
value = convert(value)
@@ -39,6 +41,35 @@ class BaseConfig(object):
pass
+class DatabasesConfig(BaseConfig):
+
+ def __init__(self):
+ self.databases = {
+ 'default': DatabaseConfig(),
+ 'slow': DatabaseConfig(),
+ }
+ self.use_two_phase_commit = False
+
+ def create_engines(self, **kwargs):
+ engines = {}
+ for name, db_config in self.databases.items():
+ engines[name] = db_config.create_engine(**kwargs)
+ return engines
+
+ def read_section(self, parser, section):
+ if parser.has_option(section, 'two_phase_commit'):
+ self.use_two_phase_commit = parser.getboolean(section, 'two_phase_commit')
+ for name, sub_config in self.databases.items():
+ sub_section = '{}:{}'.format(section, name)
+ sub_config.read_section(parser, sub_section)
+
+ def read_env(self, prefix):
+ read_env_item(self, 'use_two_phase_commit', prefix + 'TWO_PHASE_COMMIT', convert=str_to_bool)
+ for name, sub_config in self.databases.items():
+ sub_prefix = prefix + name.upper() + '_'
+ sub_config.read_env(sub_prefix)
+
+
class DatabaseConfig(BaseConfig):
def __init__(self):
@@ -103,21 +134,21 @@ class DatabaseConfig(BaseConfig):
if parser.has_option(section, 'password'):
self.password = parser.get(section, 'password')
if parser.has_option(section, 'pool_size'):
- self.password = parser.getint(section, 'pool_size')
+ self.pool_size = parser.getint(section, 'pool_size')
if parser.has_option(section, 'pool_recycle'):
- self.password = parser.getint(section, 'pool_recycle')
+ self.pool_recycle = parser.getint(section, 'pool_recycle')
if parser.has_option(section, 'pool_pre_ping'):
- self.password = parser.getboolean(section, 'pool_pre_ping')
+ self.pool_pre_ping = parser.getboolean(section, 'pool_pre_ping')
def read_env(self, prefix):
- read_env_item(self, 'name', prefix + 'POSTGRES_DB')
- read_env_item(self, 'host', prefix + 'POSTGRES_HOST')
- read_env_item(self, 'port', prefix + 'POSTGRES_PORT', convert=int)
- read_env_item(self, 'user', prefix + 'POSTGRES_USER')
- read_env_item(self, 'password', prefix + 'POSTGRES_PASSWORD')
- read_env_item(self, 'pool_size', prefix + 'POSTGRES_POOL_SIZE', convert=int)
- read_env_item(self, 'pool_recycle', prefix + 'POSTGRES_POOL_RECYCLE', convert=int)
- read_env_item(self, 'pool_pre_ping', prefix + 'POSTGRES_POOL_PRE_PING', convert=str_to_bool)
+ read_env_item(self, 'name', prefix + 'NAME')
+ read_env_item(self, 'host', prefix + 'HOST')
+ read_env_item(self, 'port', prefix + 'PORT', convert=int)
+ read_env_item(self, 'user', prefix + 'USER')
+ read_env_item(self, 'password', prefix + 'PASSWORD')
+ read_env_item(self, 'pool_size', prefix + 'POOL_SIZE', convert=int)
+ read_env_item(self, 'pool_recycle', prefix + 'POOL_RECYCLE', convert=int)
+ read_env_item(self, 'pool_pre_ping', prefix + 'POOL_PRE_PING', convert=str_to_bool)
class IndexConfig(BaseConfig):
@@ -342,7 +373,7 @@ class RateLimiterConfig(BaseConfig):
class Config(object):
def __init__(self):
- self.database = DatabaseConfig()
+ self.databases = DatabasesConfig()
self.logging = LoggingConfig()
self.website = WebSiteConfig()
self.index = IndexConfig()
@@ -357,7 +388,7 @@ class Config(object):
logger.info("Loading configuration file %s", path)
parser = ConfigParser.RawConfigParser()
parser.read(path)
- self.database.read(parser, 'database')
+ self.databases.read(parser, 'database')
self.logging.read(parser, 'logging')
self.website.read(parser, 'website')
self.index.read(parser, 'index')
@@ -373,7 +404,7 @@ class Config(object):
prefix = 'ACOUSTID_TEST_'
else:
prefix = 'ACOUSTID_'
- self.database.read_env(prefix)
+ self.databases.read_env(prefix)
self.logging.read_env(prefix)
self.website.read_env(prefix)
self.index.read_env(prefix)
diff --git a/acoustid/const.py b/acoustid/const.py
index c4d6a15a6a10acd1b4582af77610ce122ac4315c..f96a679d78bbc2ab2dc2e7dcb04838a9b960c1c4 100644
--- a/acoustid/const.py
+++ b/acoustid/const.py
@@ -21,3 +21,6 @@ FINGERPRINT_MAX_LENGTH_DIFF = 7
FINGERPRINT_MAX_ALLOWED_LENGTH_DIFF = 30
MAX_REQUESTS_PER_SECOND = 3
+
+MAX_FOREIGNID_NAMESPACE_LENGTH = 10
+MAX_FOREIGNID_VALUE_LENGTH = 64
diff --git a/acoustid/data/submission.py b/acoustid/data/submission.py
index 2605624f3e89f645b4de87ea50573aa646988f13..7bfde1c93882a274e105435fab3bfc4564291e2e 100644
--- a/acoustid/data/submission.py
+++ b/acoustid/data/submission.py
@@ -28,6 +28,7 @@ def insert_submission(conn, data):
'source_id': data.get('source_id'),
'format_id': data.get('format_id'),
'meta_id': data.get('meta_id'),
+ 'foreignid': data.get('foreignid'),
'foreignid_id': data.get('foreignid_id'),
})
id = conn.execute(insert_stmt).inserted_primary_key[0]
diff --git a/acoustid/db.py b/acoustid/db.py
index 8765092db874fb9967fb3f719383dbe4a2f17711..6d43da09f6f179c80cd3b78209904b488535d322 100644
--- a/acoustid/db.py
+++ b/acoustid/db.py
@@ -1,13 +1,29 @@
from sqlalchemy.orm import sessionmaker
+from acoustid.tables import metadata
Session = sessionmaker()
+def get_bind_args(engines):
+ binds = {}
+ for table in metadata.sorted_tables:
+ bind_key = table.info.get('bind_key', 'default')
+ if bind_key != 'default':
+ binds[table] = engines[bind_key]
+ return {'bind': engines['default'], 'binds': binds}
+
+
+def get_session_args(script):
+ kwargs = {'twophase': script.config.databases.use_two_phase_commit}
+ kwargs.update(get_bind_args(script.db_engines))
+ return kwargs
+
+
class DatabaseContext(object):
- def __init__(self, bind):
- self.session = Session(bind=bind)
+ def __init__(self, script):
+ self.session = Session(**get_session_args(script))
def __enter__(self):
return self
diff --git a/acoustid/models.py b/acoustid/models.py
index d190c624c24a800abc663405d5d9f1185aae8a6e..d95a07d64ea44b9e6194b17b826f9af2b07a625c 100644
--- a/acoustid/models.py
+++ b/acoustid/models.py
@@ -79,3 +79,11 @@ class StatsLookups(Base):
__table__ = tables.stats_lookups
application = relationship('Application')
+
+
+class Submission(Base):
+ __table__ = tables.submission
+
+
+class SubmissionResult(Base):
+ __table__ = tables.submission_result
diff --git a/acoustid/script.py b/acoustid/script.py
index 501d7d18c915d3439463fe3aa647eac4eedfeefd..d0272cffea373c875e059350215b1c4e5df9f643 100644
--- a/acoustid/script.py
+++ b/acoustid/script.py
@@ -11,11 +11,26 @@ from optparse import OptionParser
from acoustid.config import Config
from acoustid.indexclient import IndexClientPool
from acoustid.utils import LocalSysLogHandler
+from acoustid.db import DatabaseContext
from acoustid._release import GIT_RELEASE
logger = logging.getLogger(__name__)
+class ScriptContext(object):
+
+ def __init__(self, db, redis, index):
+ self.db = db
+ self.redis = redis
+ self.index = index
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.db.close()
+
+
class Script(object):
def __init__(self, config_path, tests=False):
@@ -23,25 +38,30 @@ class Script(object):
if config_path:
self.config.read(config_path)
self.config.read_env(tests=tests)
- if tests:
- self.engine = sqlalchemy.create_engine(self.config.database.create_url(),
- poolclass=sqlalchemy.pool.AssertionPool)
- else:
- self.engine = sqlalchemy.create_engine(self.config.database.create_url())
+
+ create_engine_kwargs = {'poolclass': sqlalchemy.pool.AssertionPool} if tests else {}
+ self.db_engines = self.config.databases.create_engines(**create_engine_kwargs)
+
if not self.config.index.host:
self.index = None
else:
self.index = IndexClientPool(host=self.config.index.host,
port=self.config.index.port,
recycle=60)
+
if not self.config.redis.host:
self.redis = None
else:
self.redis = Redis(host=self.config.redis.host,
port=self.config.redis.port)
+
self._console_logging_configured = False
self.setup_logging()
+ @property
+ def engine(self):
+ return self.db_engines['default']
+
def setup_logging(self):
for logger_name, level in sorted(self.config.logging.levels.items()):
logging.getLogger(logger_name).setLevel(level)
@@ -66,6 +86,10 @@ class Script(object):
def setup_sentry(self):
sentry_sdk.init(self.config.sentry.script_dsn, release=GIT_RELEASE)
+ def context(self):
+ db = DatabaseContext(self).session
+ return ScriptContext(db=db, redis=self.redis, index=self.index)
+
def run_script(func, option_cb=None, master_only=False):
parser = OptionParser()
diff --git a/acoustid/server.py b/acoustid/server.py
index 41aab52b2d984337b598b1e22d007a083efcccf0..b2b8a34f736520bffc02c3f404a20531a5b687a8 100644
--- a/acoustid/server.py
+++ b/acoustid/server.py
@@ -9,7 +9,7 @@ from cStringIO import StringIO
from werkzeug.exceptions import HTTPException
from werkzeug.routing import Map, Rule, Submount
from werkzeug.wrappers import Request
-from werkzeug.contrib.fixers import ProxyFix
+from werkzeug.middleware.proxy_fix import ProxyFix
from acoustid.script import Script
from acoustid._release import GIT_RELEASE
import acoustid.api.v1
@@ -53,12 +53,15 @@ admin_url_rules = [
class Server(Script):
- def __init__(self, config_path):
- super(Server, self).__init__(config_path)
+ def __init__(self, config_path, **kwargs):
+ super(Server, self).__init__(config_path, **kwargs)
url_rules = api_url_rules + admin_url_rules
self.url_map = Map(url_rules, strict_slashes=False)
def __call__(self, environ, start_response):
+ return self.wsgi_app(environ, start_response)
+
+ def wsgi_app(self, environ, start_response):
urls = self.url_map.bind_to_environ(environ)
handler = None
try:
@@ -112,16 +115,16 @@ def add_cors_headers(app):
return wrapped_app
-def make_application(config_path):
+def make_application(config_path, **kwargs):
"""Construct a WSGI application for the AcoustID server
:param config_path: path to the server configuration file
"""
- server = Server(config_path)
+ server = Server(config_path, **kwargs)
server.setup_sentry()
- app = GzipRequestMiddleware(server)
- app = ProxyFix(app)
- app = SentryWsgiMiddleware(app)
- app = replace_double_slashes(app)
- app = add_cors_headers(app)
- return server, app
+ server.wsgi_app = GzipRequestMiddleware(server.wsgi_app)
+ server.wsgi_app = ProxyFix(server.wsgi_app)
+ server.wsgi_app = SentryWsgiMiddleware(server.wsgi_app)
+ server.wsgi_app = replace_double_slashes(server.wsgi_app)
+ server.wsgi_app = add_cors_headers(server.wsgi_app)
+ return server
diff --git a/acoustid/tables.py b/acoustid/tables.py
index abe1b329a1cefa7bd16a0166d956236e8dfc5887..d70b1c853b81160c42a1484a56d4b5a6f7988575 100644
--- a/acoustid/tables.py
+++ b/acoustid/tables.py
@@ -1,6 +1,7 @@
+import sqlalchemy
import sqlalchemy.event
from sqlalchemy import (
- MetaData, Table, Column, Index,
+ MetaData, Table, Column, Index, Sequence,
ForeignKey, CheckConstraint,
Integer, String, DateTime, Boolean, Date, Text, SmallInteger, BigInteger, CHAR,
DDL, sql,
@@ -103,7 +104,7 @@ source = Table('source', metadata,
Index('source_idx_uniq', 'application_id', 'account_id', 'version', unique=True),
)
-submission = Table('submission', metadata,
+submission_old = Table('submission_old', metadata,
Column('id', Integer, primary_key=True),
Column('fingerprint', ARRAY(Integer), nullable=False),
Column('length', SmallInteger, CheckConstraint('length>0'), nullable=False),
@@ -118,7 +119,55 @@ submission = Table('submission', metadata,
Column('foreignid_id', Integer, ForeignKey('foreignid.id')),
)
-Index('submission_idx_handled', submission.c.id, postgresql_where=submission.c.handled == False) # noqa: E712
+Index('submission_idx_handled', submission_old.c.id, postgresql_where=submission_old.c.handled == False) # noqa: E712
+
+submission_id_seq = Sequence('submission_id_seq', metadata=metadata)
+
+submission = Table('submission', metadata,
+ Column('id', Integer, submission_id_seq, server_default=submission_id_seq.next_value(), primary_key=True),
+ Column('created', DateTime(timezone=True), server_default=sql.func.current_timestamp(), nullable=False),
+ Column('handled', Boolean, default=False, server_default=sql.false()),
+
+ Column('account_id', Integer, nullable=False), # ForeignKey('account.id')
+ Column('application_id', Integer, nullable=False), # ForeignKey('application.id')
+ Column('application_version', String),
+
+ Column('fingerprint', ARRAY(Integer), nullable=False),
+ Column('duration', Integer, CheckConstraint('duration>0'), nullable=False),
+ Column('bitrate', Integer, CheckConstraint('bitrate>0')),
+ Column('format', String),
+ Column('mbid', UUID),
+ Column('puid', UUID),
+ Column('foreignid', String),
+
+ Column('track', String),
+ Column('artist', String),
+ Column('album', String),
+ Column('album_artist', String),
+ Column('track_no', Integer),
+ Column('disc_no', Integer),
+ Column('year', Integer),
+
+ info={'bind_key': 'slow'},
+)
+
+submission_result = Table('submission_result', metadata,
+ Column('submission_id', Integer, primary_key=True, autoincrement=False),
+ Column('created', DateTime(timezone=True), server_default=sql.func.current_timestamp(), nullable=False),
+
+ Column('account_id', Integer, nullable=False), # ForeignKey('account.id')
+ Column('application_id', Integer, nullable=False), # ForeignKey('application.id')
+ Column('application_version', String),
+
+ Column('fingerprint_id', Integer, nullable=False), # ForeignKey('fingerprint.id')
+ Column('track_id', Integer, nullable=False), # ForeignKey('track.id')
+ Column('meta_id', Integer), # ForeignKey('meta.id')
+ Column('mbid', UUID),
+ Column('puid', UUID),
+ Column('foreignid', String),
+
+ info={'bind_key': 'slow'},
+)
stats = Table('stats', metadata,
Column('id', Integer, primary_key=True),
@@ -193,6 +242,8 @@ fingerprint = Table('fingerprint', metadata,
Index('fingerprint_idx_track_id', 'track_id'),
)
+fingerprint.add_is_dependent_on(track)
+
fingerprint_source = Table('fingerprint_source', metadata,
Column('id', Integer, primary_key=True),
Column('fingerprint_id', Integer, ForeignKey('fingerprint.id'), nullable=False),
diff --git a/acoustid/utils.py b/acoustid/utils.py
index ba6a5dfb4e5ee0e27023d1adb0c4f95421849aad..2c31323a7e7d32d54485d542fed686bf957470e1 100644
--- a/acoustid/utils.py
+++ b/acoustid/utils.py
@@ -9,6 +9,7 @@ import datetime
import hmac
import base64
import six
+from acoustid.const import MAX_FOREIGNID_NAMESPACE_LENGTH, MAX_FOREIGNID_VALUE_LENGTH
from six.moves.urllib.request import urlopen
from six.moves.urllib.parse import urlencode
from logging import Handler
@@ -54,7 +55,15 @@ def is_int(s):
def is_foreignid(s):
- return bool(re.match(r'^[0-9a-z]+:.+$', s))
+ match = re.match(r'^([0-9a-z]+):(.+)$', s)
+ if match is None:
+ return False
+ namespace, value = match.groups()
+ if len(namespace) > MAX_FOREIGNID_NAMESPACE_LENGTH:
+ return False
+ if len(value) > MAX_FOREIGNID_VALUE_LENGTH:
+ return False
+ return True
def singular(plural):
diff --git a/acoustid/web/app.py b/acoustid/web/app.py
index 8d72bb8588cb061c136c51af06d0b211d662cb34..8287961257b8563344c355c651e541f1b3fd9cb5 100644
--- a/acoustid/web/app.py
+++ b/acoustid/web/app.py
@@ -5,9 +5,10 @@ import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
from flask import Flask, request, session
from flask.sessions import SecureCookieSessionInterface
-from werkzeug.contrib.fixers import ProxyFix
+from werkzeug.middleware.proxy_fix import ProxyFix
from sqlalchemy.orm import scoped_session
from acoustid.script import Script
+from acoustid.db import get_session_args
from acoustid.web import db
from acoustid.web.views.general import general_page
from acoustid.web.views.user import user_page
@@ -36,6 +37,8 @@ def make_application(config_filename=None, tests=False):
GOOGLE_OAUTH_CLIENT_ID=config.website.google_oauth_client_id,
GOOGLE_OAUTH_CLIENT_SECRET=config.website.google_oauth_client_secret,
)
+ if tests:
+ app.config['TESTING'] = True
app.acoustid_script = script
app.acoustid_config = config
@@ -88,7 +91,7 @@ def make_application(config_filename=None, tests=False):
from acoustid.api import get_health_response
return get_health_response(script, request)
- db.session_factory.configure(bind=config.database.create_engine())
+ db.session_factory.configure(**get_session_args(script))
db.session = scoped_session(db.session_factory, scopefunc=get_flask_request_scope)
app.register_blueprint(general_page)
diff --git a/acoustid/web/views/general.py b/acoustid/web/views/general.py
index 5119e61261e53a18fd9d7ba0f59796312d4c000b..5b8c1e561130e84e785e57c2074e9706dbb18970 100644
--- a/acoustid/web/views/general.py
+++ b/acoustid/web/views/general.py
@@ -33,7 +33,7 @@ def render_page(name, **context):
text = file.read().decode('utf8')
text = render_template_string(text, **context)
md = Markdown(extensions=['meta'])
- md.treeprocessors["flask_links"] = MarkdownFlaskUrlProcessor(md)
+ md.treeprocessors.register(MarkdownFlaskUrlProcessor(md), 'flask_links', 50)
html = md.convert(text)
title = ' '.join(md.Meta.get('title', []))
return render_template('page.html', content=html, title=title)
diff --git a/admin/ci/create_db.sql b/admin/ci/create_db.sql
index bf0c6a00e3a4e0d0b253f57eb0906758aa1c2c66..f95388d6509ff4f8056578b167985fab7a9d289f 100644
--- a/admin/ci/create_db.sql
+++ b/admin/ci/create_db.sql
@@ -1,6 +1,9 @@
CREATE DATABASE "acoustid";
CREATE DATABASE "acoustid_test";
+CREATE DATABASE "acoustid_slow";
+CREATE DATABASE "acoustid_slow_test";
+
\c acoustid
create extension intarray;
create extension pgcrypto;
@@ -12,3 +15,15 @@ create extension intarray;
create extension pgcrypto;
create extension acoustid;
create extension cube;
+
+\c acoustid_slow
+create extension intarray;
+create extension pgcrypto;
+create extension acoustid;
+create extension cube;
+
+\c acoustid_slow_test
+create extension intarray;
+create extension pgcrypto;
+create extension acoustid;
+create extension cube;
diff --git a/alembic.ini b/alembic.ini
index 69c69c57c197ee46efe335bb750e497c1c9e7c70..2a13ffafa65cc69cae294906e0df333abc50e517 100644
--- a/alembic.ini
+++ b/alembic.ini
@@ -29,6 +29,8 @@ script_location = alembic
# are written from script.py.mako
# output_encoding = utf-8
+databases = default, slow
+
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
diff --git a/alembic/README b/alembic/README
index 98e4f9c44effe479ed38c66ba922e7bcc672916f..bcf10863a43339af6fc52d2822bf1bc6a6e679c9 100644
--- a/alembic/README
+++ b/alembic/README
@@ -1 +1 @@
-Generic single-database configuration.
\ No newline at end of file
+AcoustID's multi-database configuration.
diff --git a/alembic/env.py b/alembic/env.py
index 3c9d9c4c0741792e4a03c23612bd094c8b19c476..f23e465d8dcaf04a663bc838d69a856dab2c94a7 100644
--- a/alembic/env.py
+++ b/alembic/env.py
@@ -1,11 +1,13 @@
from __future__ import with_statement
import os
+import logging
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
config = context.config
fileConfig(config.config_file_name)
+logger = logging.getLogger("alembic.env")
import acoustid.tables
target_metadata = acoustid.tables.metadata
@@ -17,12 +19,25 @@ acoustid_config = acoustid.config.Config()
acoustid_config.read(acoustid_config_filename)
acoustid_config.read_env()
-def include_object(obj, name, type, reflected, compare_to):
- if type == "table" and obj.schema == "musicbrainz":
- return False
- if type == "column" and not obj.table.schema == "musicbrainz":
- return False
- return True
+use_two_phase_commit = acoustid_config.databases.use_two_phase_commit
+
+
+def include_object(db_name):
+ def inner(obj, name, obj_type, reflected, compare_to):
+ if obj_type == "table":
+ if obj.schema == "musicbrainz":
+ return False
+ bind_key = obj.info.get('bind_key', 'default')
+ if bind_key != db_name:
+ return False
+ if obj_type == "column":
+ if obj.table.schema == "musicbrainz":
+ return False
+ bind_key = obj.table.info.get('bind_key', 'default')
+ if bind_key != db_name:
+ return False
+ return True
+ return inner
def run_migrations_offline():
@@ -37,13 +52,18 @@ def run_migrations_offline():
script output.
"""
- url = acoustid_config.database.create_url()
- context.configure(
- url=url, target_metadata=target_metadata, literal_binds=True,
- include_object=include_object)
+ for name, db_config in acoustid_config.databases.databases.items():
+ logger.info("Migrating database %s" % name)
+
+ context.configure(
+ url=db_config.create_url(),
+ target_metadata=target_metadata,
+ literal_binds=True,
+ include_object=include_object(name),
+ )
- with context.begin_transaction():
- context.run_migrations()
+ with context.begin_transaction():
+ context.run_migrations()
def run_migrations_online():
@@ -53,17 +73,45 @@ def run_migrations_online():
and associate a connection with the context.
"""
- connectable = acoustid_config.database.create_engine(poolclass=pool.NullPool)
-
- with connectable.connect() as connection:
- context.configure(
- connection=connection,
- target_metadata=target_metadata,
- include_object=include_object,
- )
-
- with context.begin_transaction():
- context.run_migrations()
+ engines = {}
+ for name, db_config in acoustid_config.databases.databases.items():
+ engines[name] = rec = {}
+ rec["engine"] = db_config.create_engine(poolclass=pool.NullPool)
+
+ for name, rec in engines.items():
+ engine = rec["engine"]
+ rec["connection"] = conn = engine.connect()
+
+ if use_two_phase_commit:
+ rec["transaction"] = conn.begin_twophase()
+ else:
+ rec["transaction"] = conn.begin()
+
+ try:
+ for name, rec in engines.items():
+ logger.info("Migrating database %s" % name)
+ context.configure(
+ connection=rec["connection"],
+ upgrade_token="%s_upgrades" % name,
+ downgrade_token="%s_downgrades" % name,
+ target_metadata=target_metadata,
+ include_object=include_object(name),
+ )
+ context.run_migrations(engine_name=name)
+
+ if use_two_phase_commit:
+ for rec in engines.values():
+ rec["transaction"].prepare()
+
+ for rec in engines.values():
+ rec["transaction"].commit()
+ except:
+ for rec in engines.values():
+ rec["transaction"].rollback()
+ raise
+ finally:
+ for rec in engines.values():
+ rec["connection"].close()
if context.is_offline_mode():
diff --git a/alembic/script.py.mako b/alembic/script.py.mako
index 43c09401bc839adc93c2bbb41fb1407b09ce7718..5d43608eb4fca3dcb06d120028309256f7dbcb81 100644
--- a/alembic/script.py.mako
+++ b/alembic/script.py.mako
@@ -1,10 +1,16 @@
-"""${message}
+<%!
+import re
+
+%>"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
@@ -12,13 +18,20 @@ down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
-from alembic import op
-import sqlalchemy as sa
-${imports if imports else ""}
-def upgrade():
- ${upgrades if upgrades else "pass"}
+def upgrade(engine_name):
+ globals()["upgrade_%s" % engine_name]()
+
+
+def downgrade(engine_name):
+ globals()["downgrade_%s" % engine_name]()
+% for db_name in re.split(r',\s*', config.get_main_option("databases")):
+
+
+def upgrade_${db_name}():
+ ${context.get("%s_upgrades" % db_name, "pass")}
-def downgrade():
- ${downgrades if downgrades else "pass"}
+def downgrade_${db_name}():
+ ${context.get("%s_downgrades" % db_name, "pass")}
+% endfor
diff --git a/alembic/versions/3b48d5f44110_fixes.py b/alembic/versions/3b48d5f44110_fixes.py
index dbe3d082f7740962fef63099647e945d8ef18c46..a1a46b0ad30f82f55880f8d917f155da0c8aa59b 100644
--- a/alembic/versions/3b48d5f44110_fixes.py
+++ b/alembic/versions/3b48d5f44110_fixes.py
@@ -16,7 +16,15 @@ from alembic import op
import sqlalchemy as sa
-def upgrade():
+def upgrade(engine_name):
+ globals()["upgrade_%s" % engine_name]()
+
+
+def downgrade(engine_name):
+ globals()["downgrade_%s" % engine_name]()
+
+
+def upgrade_default():
op.create_foreign_key(op.f('account_google_fk_account_id'), 'account_google', 'account', ['account_id'], ['id'])
op.create_foreign_key(op.f('application_fk_account_id'), 'application', 'account', ['account_id'], ['id'])
op.create_foreign_key(op.f('stats_lookups_fk_application_id'), 'stats_lookups', 'application', ['application_id'], ['id'])
@@ -46,7 +54,7 @@ def upgrade():
op.alter_column('recording_acoustid', 'created', nullable=False)
-def downgrade():
+def downgrade_default():
op.alter_column('recording_acoustid', 'created', nullable=True)
op.alter_column('track_foreignid_source', 'created', nullable=True)
op.alter_column('track_foreignid', 'created', nullable=True)
@@ -74,3 +82,11 @@ def downgrade():
op.drop_constraint(op.f('stats_lookups_fk_application_id'), 'stats_lookups', type_='foreignkey')
op.drop_constraint(op.f('application_fk_account_id'), 'application', type_='foreignkey')
op.drop_constraint(op.f('account_google_fk_account_id'), 'account_google', type_='foreignkey')
+
+
+def upgrade_slow():
+ pass
+
+
+def downgrade_slow():
+ pass
diff --git a/alembic/versions/57c4d22c87b8_initial.py b/alembic/versions/57c4d22c87b8_initial.py
index d1e9615c2498662b28fa8efbcccf9a5ab193a6a2..1f110445ac87d48e7f417606a7819cecebb6d63a 100644
--- a/alembic/versions/57c4d22c87b8_initial.py
+++ b/alembic/versions/57c4d22c87b8_initial.py
@@ -16,7 +16,16 @@ from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
-def upgrade():
+
+def upgrade(engine_name):
+ globals()["upgrade_%s" % engine_name]()
+
+
+def downgrade(engine_name):
+ globals()["downgrade_%s" % engine_name]()
+
+
+def upgrade_default():
op.create_table('account_google',
sa.Column('google_user_id', sa.String(), nullable=False),
sa.Column('account_id', sa.Integer(), nullable=False),
@@ -356,7 +365,7 @@ def upgrade():
)
-def downgrade():
+def downgrade_default():
op.drop_table('track_puid_source')
op.drop_table('track_meta_source')
op.drop_index(op.f('track_mbid_source_idx_track_mbid_id'), table_name='track_mbid_source')
@@ -422,3 +431,11 @@ def downgrade():
op.drop_table('account_stats_control')
op.drop_index('account_google_idx_account_id', table_name='account_google')
op.drop_table('account_google')
+
+
+def upgrade_slow():
+ pass
+
+
+def downgrade_slow():
+ pass
diff --git a/alembic/versions/ae7e1e5763ef_remove_submission_id_constraints.py b/alembic/versions/ae7e1e5763ef_remove_submission_id_constraints.py
index 7560895742284497e428b49134abe93e76dee1ce..7abfe32a928688da575cc9398b4f27ab80adaac8 100644
--- a/alembic/versions/ae7e1e5763ef_remove_submission_id_constraints.py
+++ b/alembic/versions/ae7e1e5763ef_remove_submission_id_constraints.py
@@ -16,7 +16,15 @@ from alembic import op
import sqlalchemy as sa
-def upgrade():
+def upgrade(engine_name):
+ globals()["upgrade_%s" % engine_name]()
+
+
+def downgrade(engine_name):
+ globals()["downgrade_%s" % engine_name]()
+
+
+def upgrade_default():
op.drop_constraint(u'fingerprint_source_fk_submission_id', 'fingerprint_source', type_='foreignkey')
op.drop_constraint(u'track_foreignid_source_fk_submission_id', 'track_foreignid_source', type_='foreignkey')
op.drop_constraint(u'track_mbid_source_fk_submission_id', 'track_mbid_source', type_='foreignkey')
@@ -24,9 +32,17 @@ def upgrade():
op.drop_constraint(u'track_puid_source_fk_submission_id', 'track_puid_source', type_='foreignkey')
-def downgrade():
+def downgrade_default():
op.create_foreign_key(u'track_puid_source_fk_submission_id', 'track_puid_source', 'submission', ['submission_id'], ['id'])
op.create_foreign_key(u'track_meta_source_fk_submission_id', 'track_meta_source', 'submission', ['submission_id'], ['id'])
op.create_foreign_key(u'track_mbid_source_fk_submission_id', 'track_mbid_source', 'submission', ['submission_id'], ['id'])
op.create_foreign_key(u'track_foreignid_source_fk_submission_id', 'track_foreignid_source', 'submission', ['submission_id'], ['id'])
op.create_foreign_key(u'fingerprint_source_fk_submission_id', 'fingerprint_source', 'submission', ['submission_id'], ['id'])
+
+
+def upgrade_slow():
+ pass
+
+
+def downgrade_slow():
+ pass
diff --git a/alembic/versions/d52d50968cf7_move_submissions.py b/alembic/versions/d52d50968cf7_move_submissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..35905833fb5308df8d78f2ef212a6c5a62bf3e45
--- /dev/null
+++ b/alembic/versions/d52d50968cf7_move_submissions.py
@@ -0,0 +1,89 @@
+"""move submissions
+
+Revision ID: d52d50968cf7
+Revises: ae7e1e5763ef
+Create Date: 2019-06-08 11:04:53.563059
+
+"""
+import datetime
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import postgresql
+from sqlalchemy.schema import Sequence, CreateSequence, DropSequence
+from dateutil.relativedelta import relativedelta
+
+
+# revision identifiers, used by Alembic.
+revision = 'd52d50968cf7'
+down_revision = 'ae7e1e5763ef'
+branch_labels = None
+depends_on = None
+
+
+def upgrade(engine_name):
+ globals()["upgrade_%s" % engine_name]()
+
+
+def downgrade(engine_name):
+ globals()["downgrade_%s" % engine_name]()
+
+
+def upgrade_default():
+ op.rename_table('submission', 'submission_old')
+
+
+def downgrade_default():
+ op.rename_table('submission_old', 'submission')
+
+
+def upgrade_slow():
+ op.execute(CreateSequence(Sequence('submission_id_seq')))
+ op.create_table('submission',
+ sa.Column('id', sa.Integer(), server_default=sa.text("nextval('submission_id_seq')"), nullable=False),
+ sa.Column('created', sa.DateTime(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
+ sa.Column('handled', sa.Boolean(), server_default=sa.text('false'), nullable=True),
+ sa.Column('account_id', sa.Integer(), nullable=False),
+ sa.Column('application_id', sa.Integer(), nullable=False),
+ sa.Column('application_version', sa.String(), nullable=True),
+ sa.Column('fingerprint', postgresql.ARRAY(sa.Integer()), nullable=False),
+ sa.Column('duration', sa.Integer(), nullable=False),
+ sa.Column('bitrate', sa.Integer(), nullable=True),
+ sa.Column('format', sa.String(), nullable=True),
+ sa.Column('mbid', postgresql.UUID(), nullable=True),
+ sa.Column('puid', postgresql.UUID(), nullable=True),
+ sa.Column('foreignid', sa.String(), nullable=True),
+ sa.Column('track', sa.String(), nullable=True),
+ sa.Column('artist', sa.String(), nullable=True),
+ sa.Column('album', sa.String(), nullable=True),
+ sa.Column('album_artist', sa.String(), nullable=True),
+ sa.Column('track_no', sa.Integer(), nullable=True),
+ sa.Column('disc_no', sa.Integer(), nullable=True),
+ sa.Column('year', sa.Integer(), nullable=True),
+ postgresql_partition_by='RANGE (created)',
+ )
+
+ one_month = relativedelta(months=1)
+ partitions_from = datetime.date(2010, 1, 1)
+ partitions_to = datetime.date.today().replace(month=1, day=1) + relativedelta(months=12)
+ range_from = partitions_from
+ while range_from < partitions_to:
+ range_to = range_from + one_month
+ op.execute("""
+
+ CREATE TABLE IF NOT EXISTS submission_y{range_from.year:04d}m{range_to.month:02d}
+ PARTITION OF submission
+ FOR VALUES FROM ('{range_from}') TO ('{range_to}');
+
+ ALTER TABLE submission_y{range_from.year:04d}m{range_to.month:02d}
+ ADD PRIMARY KEY (id);
+
+ CREATE INDEX submission_y{range_from.year:04d}m{range_to.month:02d}_idx_handled
+ ON submission_y{range_from.year:04d}m{range_to.month:02d} (handled);
+
+ """.format(range_from=range_from, range_to=range_to))
+ range_from = range_to
+
+
+def downgrade_slow():
+ op.drop_table('submission')
+ op.execute(DropSequence(Sequence('submission_id_seq')))
diff --git a/alembic/versions/d5c0520500a6_account_is_admin.py b/alembic/versions/d5c0520500a6_account_is_admin.py
index 81e8cb23cd89dbdf175234cafd66a6f78db5b812..06df9efae49351dab1c257d18fe39f7904c60cf9 100644
--- a/alembic/versions/d5c0520500a6_account_is_admin.py
+++ b/alembic/versions/d5c0520500a6_account_is_admin.py
@@ -16,11 +16,27 @@ from alembic import op
import sqlalchemy as sa
-def upgrade():
+def upgrade(engine_name):
+ globals()["upgrade_%s" % engine_name]()
+
+
+def downgrade(engine_name):
+ globals()["downgrade_%s" % engine_name]()
+
+
+def upgrade_default():
op.add_column('account',
sa.Column('is_admin', sa.Boolean(), server_default=sa.text('false'), nullable=False)
)
-def downgrade():
+def downgrade_default():
op.drop_column('account', 'is_admin')
+
+
+def upgrade_slow():
+ pass
+
+
+def downgrade_slow():
+ pass
diff --git a/docker-compose.yml b/docker-compose.yml
index 985bb699139373ccbe4b99ef757ed78abca4e8bc..5143558f62fef640a595975d20ef09c4f32b5c51 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,12 +4,18 @@ services:
redis:
image: redis
ports:
- - "127.0.0.1:6379:6379"
+ - "127.0.0.1:16379:6379"
+
+ index:
+ image: quay.io/acoustid/acoustid-index:master
+ ports:
+ - "127.0.0.1:16080:6080"
postgres:
image: quay.io/acoustid/postgresql:master
+ command: ["-c", "max_connections=100", "-c", "max_prepared_transactions=110"]
ports:
- - "127.0.0.1:5432:5432"
+ - "127.0.0.1:15432:5432"
volumes:
- ./admin/ci/create_db.sql:/docker-entrypoint-initdb.d/10_create_db.sql
environment:
diff --git a/old_tests/__init__.py b/old_tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a32f92b201cd4dbb25b4c090ff7a0a67e0b8f494
--- /dev/null
+++ b/old_tests/__init__.py
@@ -0,0 +1,208 @@
+# Copyright (C) 2011 Lukas Lalinsky
+# Distributed under the MIT license, see the LICENSE file for details.
+
+import os
+import json
+import pprint
+import difflib
+from contextlib import closing
+from nose.tools import make_decorator
+from acoustid.script import Script
+from acoustid.tables import metadata
+from acoustid.db import DatabaseContext
+
+TEST_2_LENGTH = 320
+TEST_2_FP = 'AQABVtuUZFGShAqO-h9OHD96SvhwBVNCKQnOIYmiIc-ENwF7TDe8Hr0W_AjhvRCP2sfT4DTS7zjyOYeqaI-RSxee5RmaWzhOHnlcaB6HnPgpdE-DkWIH2ysYG_Eh9zJCyfCXGOdw-EGoD2p69IavWOhzMD-a9tBx9FgPVz2qNDvQH3744ISIXRKeHto5_MhyeMtxc-COnYJ_lHLwRAgPvShz_Hga4zd8HD9UKXWOPP3xRLmGnlbQHKfxGPeRvAt6UngMvcF-gkpRi0bUZjGaH6FUHb_xGDt6aHmM__ghfkmH70B4fWiuCj8y8uj3oImZY8d3NFWWHuGF-3hCPEd_uEOyE_nw4w8ueXi24znCHOHxSWtw9BnSBzrSHF2Y4S0e_EioZoh9XMGfo2dqNMeP80aQPM5xGT9efMeTYL-KIqmHdDraHs-P8IcYjoj0I7_Q43iJ9BF64nSKKth2SjG-cvCHH-2OL8txHsUt9HhF4LiK5j16lAf1FkjvQiN55FSOkkOPkmj4GK-OH80eIeyh98HhE_qhPwjzKAV-HJ2OZkd4Q_vhp0d_6Id-_IeWW9CKoP3RKM-Bo3mOfvhxND_6HMgZ6EfXHB-8Q8-iok1znOi-ozmx54P2Dg5V_PCgLxy8KiH6C0cbHU3Ebtiho9Rxw8er47tw7jgRNxl84ziPJ-B1_DiNNClzaGSCvMGPGxePMD5qZYEuAwdTXYSYcIkmodc2nMqg_WgqBk_yBdVx0vCjQD8uhNRxXTgvVFSOSOmx61C1KMaNsFwM93h-PBdmFm8o45nxDabx48cTbGl4hHuhasjSwPtxPvAV1A7yQMukREERR-nxL8j-EbWYQ8sj4joABQQmjQhkjLFCKSAo4QoxYiQwQhgmkGjCKGAIMMA4BIwQwjhAFMBUCCUAYEIxpYxUCDlEjJYOScSMgsIIAgADwjKEFBAUCkMEMYAagoARzAAHCDCIISKANkgYYBiQwgDDjHEMIGWZFUBQLhgohBGkhECOMEAMIYghogTgQghgiSLCYUegAsJApIQjxABNDFWCa6AIAQ4Q4KgAgIABgGDCMNGIMgQJRQAQTACpgBNIJkUcBMkpoKAgXCjAgAAGKIcYIVAYbZgwggkEmKLEiYGYAYQQShFAAAQBFEEAEuEIgwYRQoARnBkAmAGMEAGFGIgQBigCwAkABEIA'
+TEST_2_FP_RAW = [-772063964, -772066012, -1007079116, -1011011276, -1027788492, -1029889740, -1031261916, -1031269084, -1031270620, -1031021744, -1031079600, -1031078425, -1031032346, -1018580506, -1018646290, -1022775046, -1056337446, -1056261686, -1073039030, -1073039013, -1068976005, -1001867175, -733365175, -733302711, -737435575, -603332504, -603365272, -737516311, -188076117, -175490390, -745993558, -758642022, -767030630, -1034347878, -1038412133, -1039584631, -1039654264, -1034345784, -1030086056, -1011141092, -1045873092, -1045939676, -1045947803, -2132276505, -2132259914, -2140415082, -2140472938, -2140481130, -2140546634, -2140603929, -2144802523, -2144536267, -2123540203, -2115147515, -2081855203, -2098598611, -2098606801, -2098606737, -2106995329, -2090217989, -2123638309, -2140477173, -2140477173, -2123634405, -2106992325, -2107061957, -2107061991, -2107007715, -1033140963, -1023769329, -1025864433, -1026913002, -1010133962, -1017409482, -1017540482, -1022848902, -1056337830, -1056271030, -1056261302, -1073038501, -1068975271, -1060587447, -993477559, -1001672631, -737435575, -737566615, -737550231, -737419031, 1422585259, 1955228586, 1367940010, 1388841914, 1380453258, 1376328586, 1376458634, 1107956618, 1107899017, 1113072281, 1121657497, 1119494811, 1135224479, 1135226615, 1139489511, 1130891874, 1126713938, 1109977859, 1114237187, 1122691331, 1122695431, 1122687255, 1114233125, 1130944869, 1126746469, 1097554247, 1105885511, 1105885511, 1135270230, 1122523494, 1114135910, 1109939695, 1093236223, 1076520335, 1080714635, 1089107851, 11092923, 11010986, 15209450, 15492074, 7103274, 2913082, 2905882, 2940681, 2947848, 7138056, 32303368, 61716744, 44932552, 1118668232, 1118406137, 1122600424, 1110167912, 1110167848, 1110106424, 1122689305, 1118495003, 1118478714, 1118540010, 1122599146, 1110016234, 1110147562, 1110094153, 1076535560, 1076538376, -1058363384, -794183656, -794249176, -790063064, -261519320, -261519319, -529562582, -529628886, -530153430, -530280406, -534465494, -534459350, -517027794, -517027778, -517056387, 1630428508, 1634606924, 1643060940, -508616995, -508740929, -475252054, -487834709, -496223301, -496231493, -496092485, -488752486, -489735542, -494125366, -494125542, 1641889598, 1627335998, 1617898782, 1613703454, 1614756622, 537664270, 541854222, 541854238, 541874782, 558651982, 558168910, 558168910, 558168398, 566360398, 1636039038, 1669593454, 1938028670, 1942087766, 1942087766, 1665394807, 1631779173, 1640192868, 1640221300, 1640483428, 1640487796, 1631902020, 1627682884, 553932868, 554654068, 554589029, 567179879, 562985575, 562846279, 562879301, 558684487, 554678646, 554678646, 558873462, 567262070, 563067366, 562936022, 567064775, 558692565, 1628436725, 1661925605, 1661893095, 1666087909, 592329573, 567032663, 567032133, 567032132, 1640840020, 1909340900, 1909340900, -238142748, -775079212, -775152956, -1043580220, -1047774524, -2121450764, -2138162460, -2138162460, -2138232091, -2121520409, -2117330313, -2124670345, -2124604585, -2092205227, -2083848891, -2083787451, -2117489195, -2117550619, -2124902943, -2139517453, -2139383405, -2122597997, -2122598221, -2093090639, -2095162991, -2107737727, -2107754111, -2108805231, -2099495199, -2099499291, -2097402137, -2098451465, -2098709513, -2098717737, -2081859113, -2123773481, -2140434985, -2140496425, -2140428906, -2132171338, -2132236874, -2065124170, -2023181130, -2039964482, -2044162882, -2044098306, -2111137761, -2111117043, -2111165684, -2144720372, -2140460532, -2132132340, -2136328643, -2136407507, -2136471761, -2136471761, -2132277457, -2114187977, -2091268651, -2083809851, -2083872379, -2117361257, -2117552729, -2141681241, -2139584009, -2143708937, -2143618857, -2126874411, -2126894891, -2093339196, -2105923644, -2099628348, -2103836012, -2103966047, -2099751259, -2097639449, -2031579161, -2039763466, -2031375914, -2014728234, -2056675370, -2056548654, -2073127278, -2077251950, -2077233262, -2077266510, -2077332302, -2073154382, -2014434126, -2031143722, -2039794617, -2039792379, -2039792868, -2107033028, -2081936836, -2136462820, -2136265204, -2136263155, -2136456385, -2136456401, -2132228626, -2122791506, -2091070041, -2107647561, -2108769915, -2100384379]
+
+# MP3, 320kbps
+TEST_1A_LENGTH = 260
+TEST_1A_FP = 'AQABVkmYaEmkJEsiaGvhH33xXNDyol2Pz-jhW2h1_Bb6w4d2dIbPo8dzVA_Es0E1C9_RCGH1DDmP3hX8DD2-Bdq0ojmPa9CeS_CPdi0-9EFz4bqE02iOQzs6w-fR7-gP9R0ct2ipoEeqD60u5EyFPoSPfjF0NNKOXsMP7TkcMxyq3bjQ_GiP05TQw4d24ejQfDl-VD7EN8JdtFTQHWH8DN8RPhXsED_6BdoOv0df_IJ2FuZ69MaF5hba47dQ-OihC42MvjmeQPthvSRqaR3-HfUFH33YoC-UHddRWSL6Gr6gHd0DO-i0Hj1-WMJjXAdeqoN5_MfhrbgOVzyeGRfOCpbIauh7IY8evEGYo0_woIePyxDtFTgJt8d96BM64UZ1mLfQFw96-EW77Oh__Eb64WLwz_DxZXgHU6HRfbjQozlxhOfgH_rRHz0D60eF6-gPvw6Ow0-G_tBzNKxy9BZyoWOI5smR78HDQtMP-0X1Pfjwo9GPfslRiHksoVWIo1zRrMtxo-dhkviPwzo0PniDXsdOWGYLHfZ0_Ef_oGcK0aHxB7uIT7BwGeqP1_CmA_9hnDizGSd-WIfXo8c7VDZ8_Hgo_McP_3ha_Cl8utCD4xS8B8LhzUc9tAcabT1-aBXM4xBx2DrywyS-Z_A5XMVhK0f7ICf6wxb048R15BfOI3eHE0F-4AzM5dCNH4cPO-iOD7_QPELf4egDpzp6dM0g2jh-CSdEEj_yHt6O_ijSG8cDmSSMG3cAn-hxaNE0BbeO_tPQH_5RizGeAwWAQgg4hIwwgBkmgGKKAieMAgBYI4ACBAmHqDECSyYAIQIZDY0CQggFnAIIOCSYABg5AZxCCFBjkHJEGACAUgIoh5QwmCkAFBIEAKQsEEgRYYAU2CHrAABEAMuQcBwAwgBDgmIsjUBEKSKEIgAIAQwACgBrLHEYGKEEBMQAg5wBFAAGlEBGKAYEQcwQBACkigABjBNAKEEAtYIIYhwBgBAjABCSGMEUMIIJZ6QwnFgGJUDCACKYIEBRRcRAACBIrqECOCGtcEwRZpgEijiGjDKCSsYcIApAwQwimDlhhRHGMaGceFZwBxwhDAjCCA'
+TEST_1A_FP_RAW = [-1124735545, -1124600491, -1107954315, -1108017819, -1082858139, -1084889769, -1101531321, -1105799353, -1105832105, -1084795017, -1082696345, -1107993113, -1124772361, -1124739625, -1124608571, -1124600491, -1107962507, -1108028059, -1074469531, -1099635403, -1105857257, -1105758393, -1089054905, -1089054729, -1116316186, -1107866137, -1128970761, -1129024041, -1124829753, -1124735033, -1124600491, -1107962523, -1108017819, -1084955273, -1101666985, -1105725625, -1105799353, -1105766569, -1084795033, -1107862169, -1107995161, -1124772393, -1124739625, -1124608571, -1124600491, -1107962507, -1074465435, -1082858123, -1101667017, -1105726121, -1105832121, -1089054889, -1118414858, -1107927578, -1128968729, -1129032201, -1129024041, -1124735545, -1124735035, -1107954315, -1108026011, -1074463387, -1082858121, -1101531881, -1105791161, -1105832121, -1088989321, -1084795545, -1107993113, -1107995145, -1124772393, -1124608571, -1124600379, -1124600491, -1108028059, -1074465435, -1082858187, -1105861353, -1105726121, -1105832121, -1089054761, -1116315674, -1107931674, -1128968713, -1129032233, -1124829737, -1124735545, -1124604587, -1107954315, -1108017819, -1082854043, -1084889737, -1101531305, -1105791161, -1105832121, -1084795017, -1082698393, -1107995161, -1107995145, -1124772393, -1124641337, -1124600379, -1124608651, -1108026011, -1108023963, -1116445321, -1621793321, -1659424313, -1793715833, -1806297705, -1797909065, -1799942745, -1800069705, -1800135279, -1795934831, -1796905535, -1805158959, -1805257231, -1805255197, -1780089485, -1780095661, -1746405437, -1754794029, -1771579437, -1771580045, -1755196045, -1759414925, -1759414925, -1788609229, -1788674285, -1780220397, -1780351469, -1780418765, -1746884813, -1763593453, -1771842797, -1774988525, -1775151277, -1619823117, -1787658781, -1796006430, -1795879454, -1795945005, -1795916349, -1795719741, -1804108333, -1787331085, -1779040781, -1746405935, -1746405935, -1746544687, -1754967055, -1788585999, -1788774413, -1788775069, -1788641949, -1788609229, -1746796781, -1746669821, -1746667773, -1746675949, -1746811085, -1755253469, -1619991245, -1624119501, -1624148206, -1623761134, -1619624110, -1611235470, -1611182734, -1649062542, -1644931246, -1644341310, -1644300350, -1652688942, -1652758558, -1652759070, -1653213709, -1619528237, -1611205167, -1619601983, -1619567151, -1888133775, -1888133855, -1921647327, -1653213903, -1661475567, -1661475503, -1661471407, -1661631119, -1644919455, -1653295773, -1619527853, -1640564925, -1640605886, -1640542254, -1636352030, -1611190813, -1615520285, -1649140303, -1779165807, -1804257855, -1804110399, -2072611391, -2064157327, -2047548063, -2047548047, -1887967981, -1888026877, -1888035069, -1887971821, -1921714397, -1921718429, -1653213325, -1653090989, -1644767789, -1644702253, -1611147917, -1611344799, -1611352735, -1619729039, -1619659403, -1757948459, -1757981225, -1791615497, -1787413065, -1795809881, -1800073801, -1795943019, -1795918379, -1795857967, -1797135919, -1797136015, -1797200783, -1780358093, -1788611533, -1755063021, -1755063021, -1771905709, -1771905677, -1771844237, -1754835469, -1754958349, -1788512269, -1788479661, -1780099469, -1746545053, -1746602141, -1746868429, -1755257069, -1621035245, -1624148205, -1657571501, -1657637549, -1653459597, -1644821133, -1649080845, -1779165741, -1779141167, -1779075631, -1778909743, -1779303183, -1779303181, -1787691789, -1754008109, -1754017341, -1755065389, -1755065389, -1788348573, -1788479709, -1788478159, -1805254895, -1788477679, -1788477645, -1788346573, -1779968237, -1746544878, -1746537166, -1637484686, -1636313262, -1640671406, -1640540334, -1636352142, -1611186846, -1611326109, -1644946061, -1644939949, -1661700783, -1661639343, -1644698287, -1655179952, -581577392, -552084143, -552075439, -552075440, -547885231, -547885231, -547883055, -1621637647, -1613380109, -1646930445, -1646922253, -1646926381, -1646920205, -1646985885, -1647004317, -1653295837, -1653222125, -1623730941, -1623730429, -1623828717, -1619624077, -1611235485, -1611182093, -1611315247, -1611311151, -1655343151, -1653114927, -1653123597, -1653254797, -1644858254, -1611301550, -1644724926, -1644724910, -1653244557, -1653252749, -1661641357, -1661086223, -1661086255, -1661602351, -1653082671, -1653082671, -1653221903, -1619667487, -1619659295, -1619828397, -1888276205, -986509053, -1003417325, -999214797, -990763741, -994962141]
+
+# WMA, 160kbps
+TEST_1B_LENGTH = TEST_1A_LENGTH
+TEST_1B_FP = 'AQABVkmYaEmkJEsiaDv8Hr0GXRd6FuZ69DP6wBdaHb-FHj60CzeaH33wHNUD8WxQPTiF7ghDPcN5hHcFP0OPb4HWFY3M4xr0XGjeoV2LD33QPMKP_2iOQzs6w-fRa-hz4R_EmClaKjjCCK2eCDkt9GHgo18MoVF79At-aD-cd6g2prjQHw2P05TQw4d2AZ3h8-i1o2oO_1BdBi0V9Ej1DK1y5KnQh_CPfoF2WO3Ra_gF7TnMMUdvXGhuoT1OW-jho4cudEbj5ngCbT_8kri0DtV31Bd8oc_RM1B2XEdliegNf0Ih80HjoNOP_vhh4j-uEXipDuZx_IG34jpc8XhmXMVZwSSPjvWQ5xnOoLkQXviCHj4uQ7SP4xl8GT_0ohOO6oJ5C33xoIdftOPR58dvpB8uBv8MH1_wdLCkEP2GCz18tEf4w8-hox965rB-VLiO_vDr4Dj8DD2P59By5WhvIRc6hmie49ODPCw0_bBfVA8eBUf3F_6SExDzS0GrMDjKFV55VObREyaJ_zisQ-ODN-h17IT1Frpg9_hzoT96BnYI9cEu4hMs_FDp4zW86cCPwyfObMaJH9bh9dAOvUN1-MePh8J_HGZJPMefwqfxHDpOwXsgXPBm1EN7wHKP-tA04YchHoetIz98_Bl8Dj9gK0f7ICf644L448R15BfOIy8uHUF-4AzM5dCNH4cPO-iOD7_QPELf4egDpzp6dM0g2jh-CSdEEj_yHt6O_ihyw8cDmYePG3cAH_1xaJsGx1qOfkXPD_7R17hoAAMAMEYAgqgRBmAkBFJMUWAMNQwAYA1QBiGHpHHCCCyZAAAgAMlRQAhAnBLIOCWYABgJAAhCQjskgSCCAeAMIEA5pATATAElBEIAKQmMQIowoIQQyCFhgANAFMGQcBwARhgSDmMpuDEIKkWEUAQAAYABQAFgDVUOAyAAEp4AYJAzgALAhBDMEAEYIIYoQxB1lAAADHBCCUGAKYIIYowiAAhiBBCSCMEUMCQIZ6QwHCiAGJQACcqMIMAxqIhIABiIraFCACokEI4poohhEijiGDVKGUElYw4QBaBgBhHMnLDCCOOYoEIM46xgwhlLgCACAQ'
+TEST_1B_FP_RAW = [-1124735545, -1124604587, -1107954315, -1108026011, -1082854041, -1084889737, -1101531305, -1105799353, -1105766569, -1084795017, -1082696345, -1107993113, -1107995145, -1124772393, -1124608571, -1124600507, -1107831435, -1108028059, -1074469531, -1099635403, -1105857257, -1105791673, -1105832121, -1089054761, -1116316186, -1107866138, -1128970761, -1129024041, -1124829753, -1124735033, -1124600363, -1107962523, -1108017819, -1082858137, -1101666985, -1105725625, -1105799353, -1105766569, -1082697881, -1074309785, -1107995161, -1124772393, -1124739625, -1124608571, -1124600491, -1107962507, -1108017819, -1082858123, -1101732555, -1105726121, -1105832121, -1089054905, -1089054730, -1107927578, -1128837657, -1129032201, -1129024041, -1124735545, -1124735547, -1107823243, -1108028059, -1074465435, -1082858121, -1101667049, -1105791161, -1105832121, -1088989353, -1084795545, -1107993113, -1107995145, -1124772393, -1124608571, -1124608571, -1124600491, -1107962507, -1074463387, -1082858187, -1099569899, -1105726121, -1105832121, -1089054889, -1116317706, -1107931674, -1128968729, -1129032201, -1124829737, -1124735545, -1124604459, -1107954315, -1108026011, -1082851995, -1084955273, -1101531305, -1105791161, -1105832121, -1084795017, -1082698393, -1107993113, -1107995145, -1124772393, -1124641337, -1124600379, -1124608683, -1108026011, -1108023963, -1116445193, -1621793321, -1659424297, -1793715833, -1806297705, -1795811913, -1800008281, -1800069705, -1800135279, -1795934831, -1796905535, -1805163055, -1805257231, -1805257247, -1780089501, -1780095661, -1746409533, -1754794029, -1771579437, -1771579917, -1754933901, -1759414925, -1759414925, -1788609229, -1788674285, -1780285677, -1780351469, -1780418765, -1746884813, -1763593453, -1771842797, -1640770797, -1640933549, -1624017549, -1653441053, -1796071966, -1796141598, -1795945006, -1795924541, -1795719741, -1804108333, -1787331085, -1779040781, -1745357359, -1746405935, -1746414639, -1754967055, -1788520463, -1788774413, -1788775069, -1788773021, -1788609229, -1746796781, -1746669821, -1746667773, -1746675949, -1746811085, -1755261661, -1619991245, -1624119501, -1624148206, -1623761134, -1619624110, -1611235470, -1611248270, -1649062542, -1649125422, -1644341310, -1644300350, -1652688942, -1652693006, -1652759070, -1653279245, -1619528237, -1611139631, -1611213375, -1619567151, -1888133775, -1888133855, -1921647327, -1653213903, -1661475567, -1661475503, -1661471407, -1661627023, -1644919455, -1653295775, -1619528365, -1640564925, -1640605886, -1640542254, -1636335646, -1611190814, -1615520285, -1649140303, -1779165807, -1804331583, -1804110399, -1804175935, -2064157359, -1779112591, -1779112591, -1888099053, -1888026877, -1888035069, -1887971821, -1921648845, -1653282973, -1653213325, -1653090989, -1644767789, -1611213357, -1611147917, -1611344799, -1611352735, -1619729039, -1619659403, -1757940267, -1757981225, -1791599113, -1804190281, -1795809881, -1795879497, -1795943017, -1795918379, -1795857967, -1797135919, -1797136015, -1797201807, -1780358093, -1788611533, -1755063021, -1755063021, -1755128493, -1771905709, -1771844237, -1754835469, -1754958349, -1788512269, -1788479661, -1780099469, -1746545055, -1746602141, -1746864333, -1755257069, -1621035245, -1624148205, -1657571501, -1657637037, -1653197453, -1644821133, -1649080845, -1644948013, -1779141165, -1779075631, -1778909743, -1779303183, -1779303181, -1754137357, -1754008109, -1754017341, -1755065389, -1755065389, -1788349581, -1788479645, -1788478159, -1805254863, -1805254895, -1788477645, -1788346573, -1779968237, -1746544878, -1746536654, -1637484686, -1636313262, -1640540334, -1640540334, -1636352142, -1611186846, -1611326109, -1644946061, -1644939917, -1661700783, -1661639343, -1644698287, -1655179952, -581577392, -585640623, -552075439, -552075440, -547885231, -547885231, -547883055, -1621637647, -1613380109, -1646930445, -1646922253, -1646926381, -1646920205, -1646985885, -1647004317, -1653295837, -1653222125, -1623730941, -1623730429, -1623828717, -1619624077, -1611235485, -1611182093, -1611315247, -1611311151, -1655343151, -1653114927, -1653123597, -1653254797, -1644858254, -1611301550, -1644724926, -1644724910, -1653244589, -1653252749, -1661641357, -1661118991, -1661086255, -1661602351, -1653082671, -1653082671, -1653090831, -1619667487, -1619659295, -1619697293, -1888276205, -852283133, -1003417325, -999214797, -990699229, -994953949]
+
+# Vorbis, 64kbps
+TEST_1C_LENGTH = TEST_1A_LENGTH
+TEST_1C_FP = 'AQABYpESSUkiJYmSCD30C81RbeXwG7zQvBFa4bfQg_rRQ5vh8-jxHJoe-AyD6sF5NEJYPUPOo3cFP0N7fCm0F40k8fgOPUfzo12LDz0a3sIv4TSa44KGDv7Ra-hz4e8gxi1aBz8aHWF1CXlooWfgg_mO9hDVg6nwQ3sOnx2YvriC_mioBxc6Gj566ASzwyf6Hf0h5h3uFqeCw5I-9Bf6VLBD_OgXaD0srRz64tB-wdmYozp6IX2EVsgtdISPHjoaHX3woPshviQurUP1HfUFH30b9LCLH5oscXgx5YJ2dIedo12P7vhh3MJ9nHipDuZx_IG34jpcEc8cXMVZwRJZoe_xsMGZo3ExXbgWFNsf6Bvs43gG3_ihr-g0vCl6mLfQFw_RD36PdjzRLid848PF4J_h48uQv4OpEP2GS-gNn2gRnsN_iPvRC_1hyUL1D5eOwn9w3PCJXof2w1eO9hZyoWPRPEe-Bw8LTT_so_oefMePRj_6JUcPMW0stCOOckWzLsd99IRJCh9x-OgDPeh17IQos_jhtMdzoT-0M7BD_MFF7BNh4Tp0HTe86Tj-4_CJM5txwjxuCcp2lMe-Q5MN8_g-PMZ_HOaJJ8efwqeh5zhOwTt0XPBm1EN7oNHW4zu04zAMHbYuHOmP34F_XMUJ_2j14CHCh4cPXcdxHXmp48iPXDqOHDgDc4FuFcdh2OguPLgvNI_wdyj6wKmOHl0ziDZ-_NApWCKB_IKHcsYR3vDxBzoJ48YdwOdRHFq04l-Ofuj5Df5RizGeC-FD-AkeTLrxJsePUP1x7dCxc0Z8-DvCdcEvAIUQAsYgYYAhTADFFAXEIKMAAIwJ4IBhSDgElBFYKgAAAsYhaJAQAgBFCUBAOCWMyEhRQYhABhiSBFGOCKHMIEASSTFzVDEhEAJIWcAcYQZQJKRDRiAMgCiCIccBII4ZoYyQzholDQFICEUAAAAxBYA1wCjHgCBECAAUIMZAgCwQRhBDBGBCEMQMQQBAQxUBAoAngBTUJKUEMVIRJ4gRACAGJFFWOWCEA8JJAQki1ggJkKCEEUGAYlQRKhIAAhhprHAACSAdEYoBIphgEijgqCAELCGZAwoQYQQimDEnJDOmCSGNs0I4IIwlQBAGAGJGEEkAYEAJIASgAA'
+TEST_1C_FP_RAW = [-1107954315, -1108019867, -1082858137, -1082792617, -1101531385, -1105791161, -1105832113, -1082697857, -1082696337, -1107993105, -1107995137, -1124739625, -1124608569, -1124600491, -1107962507, -1108028059, -1074469531, -1099635401, -1105726185, -1105824441, -1089054905, -1089054729, -1116316186, -1107866137, -1128970761, -1129024041, -1124829241, -1126832185, -1126697003, -1110057627, -1118503579, -1084955289, -1101666985, -1105725625, -1105799353, -1105832105, -1084795545, -1107862169, -1107995161, -1124772393, -1124608553, -1124608571, -1124608683, -1107962507, -1108017819, -1082858123, -1101667019, -1105726121, -1105823929, -1089054905, -1084860425, -1107927578, -1128839705, -1129032201, -1129024041, -1124801073, -1124735009, -1107953795, -1107952283, -1074463387, -1082858121, -1101662889, -1105725617, -1105832113, -1088989345, -1082696337, -1107862161, -1107995137, -1124772385, -1124739633, -1124600371, -1107823275, -1107962507, -1074465435, -1082858123, -1101667049, -1105726121, -1105832105, -1089054889, -1118413321, -1107931673, -1128968713, -1129032233, -1124829737, -1124735545, -1124603947, -1110051467, -1108019867, -1074465435, -1084955273, -1101531369, -1105725625, -1105766569, -1082697866, -1082696346, -1107993241, -1107995145, -1124772393, -1124641337, -1124608571, -1107831435, -1108028059, -1108023963, -1116445195, -1621793321, -1659424297, -1793715833, -1806297705, -1797909065, -1799938649, -1800069705, -1800137321, -1795934825, -1805294139, -1805158971, -1805257231, -1805255197, -1788478093, -1780095661, -1746405565, -1754794029, -1771579437, -1771579405, -1771743757, -1754958477, -1759414925, -1788609229, -1788674285, -1780285677, -1780351469, -1780418765, -1746884813, -1746816237, -1771842797, -1640770797, -1640933549, -1619823117, -1653441053, -1661592093, -1795879453, -1795947053, -1795926585, -1795719737, -1795719721, -1787298317, -1780089359, -1746537007, -1746405935, -1746414639, -1754934287, -1788521487, -1788774413, -1788779165, -1788642013, -1788609229, -1746796781, -1746669821, -1746733309, -1746675949, -1763588301, -1637821149, -1619990733, -1624119533, -1624148205, -1623761133, -1619624109, -1611235469, -1611182733, -1649062414, -1649125422, -1644341310, -1644169278, -1652688942, -1652758542, -1652759070, -1653213709, -1619528461, -1611205167, -1611211327, -1619534383, -1619697807, -1921655007, -1653213919, -1653213903, -1661606639, -1661475567, -1661471407, -1661610639, -1644915359, -1653295757, -1619528365, -1640564925, -1640605886, -1640538158, -1636352030, -1611190813, -1615520285, -1649140303, -1644948079, -1670040127, -1804110399, -2072611391, -2064157327, -2047548063, -1913330319, -1887965933, -1888026877, -1888035069, -1887971821, -1921714397, -1653282973, -1653213325, -1652566701, -1644767917, -1611213485, -1611147917, -1611344541, -1611352735, -1619729039, -1619659403, -1623730729, -1757981225, -1791599177, -1804190281, -1795809881, -1795879499, -1795943019, -1795918379, -1795857963, -1780358703, -1780358799, -1797136335, -1780353997, -1788611533, -1755063021, -1755054829, -1771897517, -1771905709, -1771844237, -1754835469, -1754958349, -1788512301, -1788356781, -1779968395, -1780099227, -1746602143, -1746868429, -1612651245, -1619986669, -1624148205, -1657571501, -1657637037, -1653181069, -1644821133, -1649080845, -1644948013, -1779141167, -1779075631, -1778909743, -1779041039, -1779303181, -1754137357, -1754008109, -1754017341, -1755065389, -1755065389, -1788348573, -1788480221, -1788478159, -1788477647, -1788477679, -1788477647, -1788346573, -1746413805, -1746544877, -1612318926, -1637353614, -1636313262, -1640671406, -1640532142, -1636354190, -1611186846, -1611330206, -1644948109, -1644939949, -1661635247, -1661639343, -1644698287, -581569200, -581577392, -547891887, -552075440, -547881136, -547885231, -547885231, -547883055, -547895823, -1646934541, -1646930445, -1646922253, -1646922285, -1646920205, -1647002269, -1647008413, -1653295837, -1653222125, -1623730413, -1623730429, -1623828717, -1619624077, -1611235485, -1611182093, -1611315247, -1644865583, -1646954543, -1653114925, -1653254797, -1653254797, -1611303566, -1611303854, -1611170494, -1644724926, -1653244589, -1653252749, -1661641229, -1661086223, -1661086255, -1661602351, -1653082671, -1653082671, -1619667471, -1619667487, -1619659295, -1619693229, -1888276205, -1926024957, -1003417325, -999214797, -990763741, -996010717, -995947214, -995881710, -995869422, -991609530, -999993914, -983183930, -949893737, -966552169, -966543979, -958271087, -958279216, -941559312, -941530783, -983473887]
+
+# WMA, 32kbps
+TEST_1D_LENGTH = TEST_1A_LENGTH
+TEST_1D_FP = 'AQABVkmYaEkkZUkE7bDao4e-C9VhHtWLHtQFphJ-CT3sQ7vQGT76Bc_RB-LZ4Bb6HR3CUB9yVugr2MnQH_qITieah8eL_oIYs8O3FkUvNK6Fv0L3wz4O7egMn0e_oz_UD45pnBJ-pPLRShdyFn1awZ9xfIQorUdPfIf2C455tC-OXhcaqngldId99IKODs2X40d_OG8E1UXrox9SSR_6C3kq9CH8o1-gHVaPfsUvaD-cjTmq40JjXQjLIxc6Gj56XNAi9MvxBtotwSe-FtVP9BZ89HnQB3aIHz2Jn0Vz9DyOxjmm3sKPHy189MdR7R1s_PBRrofWHmL94Bl-4azQyDzySR725MGZojn65MiJ32g-6Me2_fjxDL7xHM_moWunQzVaUYePkniPcjr8oucR_vCP7_jxJsR_mAv-dIIdyWiPoz_RPPiQH_2h5ceLe0dzVDe-oRfSuA5w-BnC83gOLVc6hP1xoWPRPMf3DHlYNDEPPUf_4Ffwo_mDrkugwXmMVuFxlOvhdUGPmzu0koePH97RB3M__OglwzLxQ_SO6rPwoyfsMHhxSjiPY1Nw6sFhGzmQ74aD91Cm48R9eLfQ4x2gaTv8o9qN6-hhHk-OzylOuMuh4xSMH_phH6VGoEWjFyd2QouFAzquw3yOHM6-BdeL4_AZon_wZTm60sgvGLqE_0beEaeL_BtO_Phx4xrhBz9eEuKD5xDRszNOqEcfHeaL8OhVOKbRH-G0TDC1FdczHDd-I5WOw84u_MKRGz70bDhTIe_hGzcOoz9yaFsVw_mFfuh5pD96xrhyAAaIAgYiRIVjDGHCFAaGCKQYAkIAQIkBxAAjmGQCECIQNFQQo4CQBhBHBBECEEANQ0oApwQyhCBBJBBEUEWcEQRIRwTIzBEgEIBAKQuIckQAAQAUSjDljCKEDAIVkcAZBAAhhiEhFCFAIQWcMko6I4RlgAIBCACGKSoUEEQQ4AQwBgsFlFACKAkCE0IwQ4BwQhikiDHEAEEdAoY4IoBgFiKkEENCEgScEQ5QIwEipAkljBGKMIKAAFIoxohgAipClHEMICyVEYAJBywUChjBCDICAIeEs4gZJQAFTCglBCVEICAQA846IZywglHhhTLCkibQEJwAQQQiAA'
+TEST_1D_FP_RAW = [-1126832697, -1126701611, -1110051467, -1110059675, -1084951193, -1084885705, -1101662441, -1105725625, -1105758377, -1084786817, -1084793497, -1107862041, -1107993097, -1124772393, -1124608571, -1124600491, -1107823243, -1108028059, -1074469531, -1099635339, -1105857193, -1105758377, -1089054905, -1089054761, -1118413338, -1107866137, -1128968713, -1129024041, -1124796985, -1124735035, -1126701739, -1110059675, -1108028059, -1082854027, -1101667049, -1105725609, -1105725625, -1105766569, -1084858521, -1074307737, -1107993097, -1124772393, -1124739625, -1124608571, -1124600491, -1107962507, -1074465435, -1082858123, -1101732553, -1105857193, -1105766569, -1089054889, -1084852234, -1110024730, -1107866137, -1128966665, -1124829737, -1124796969, -1124735017, -1107823243, -1107962523, -1074465435, -1084955273, -1101662953, -1105725689, -1105725625, -1084795049, -1084859033, -1107862169, -1107993097, -1124772393, -1124608569, -1124600379, -1124600491, -1108028059, -1074465435, -1082858123, -1105861355, -1105726121, -1088981177, -1089054889, -1118413322, -1107931674, -1128968729, -1129032201, -1124829737, -1124735545, -1126832683, -1110051467, -1108028059, -1082854043, -1084955273, -1101531369, -1105725625, -1105766585, -1084784777, -1082696346, -1107862169, -1107995145, -1124772393, -1124641337, -1124600377, -1124600459, -1108028059, -1108023835, -1084987913, -1084918313, -1223215657, -1793649273, -1806297705, -1797909065, -1800008281, -1800073801, -1800137321, -1795934825, -1796921977, -1805556281, -1805552137, -731775513, -731775641, -715004585, -672929981, -689703101, -698099901, -698100397, -698231437, -702417565, -685640349, -714868941, -714866925, -706478573, -706609613, -673122525, -1746881231, -1771982573, -1771843309, -1774988525, -1775151277, -1758234653, -1787681310, -1796071962, -1800335898, -1800401450, -1796178490, -1796117049, -1804374841, -1804337929, -1787568907, -705440303, -671877679, -671886383, -680406031, -1754246159, -1791920655, -714906271, -714902237, -681443533, -673054957, -673059325, -689701373, -1763453165, -1763588301, -1772038365, -1770981582, -1775180014, -1640990958, -1624148206, -1619624174, -1611251886, -1611264654, -1783296526, -1779149358, -1779083326, -1779075134, -1786943534, -1786976286, -1786972702, -1653279261, -1644825101, -1611139647, -1611203135, -1619534527, -1619665551, -1892295391, -1653220063, -1653213903, -1661475504, -1661475504, -1661471408, -1661627024, -1644919455, -1653295775, -1623722669, -1640564926, -1640605886, -1640538158, -1636352030, -1745408542, -1749738013, -1783362157, -1779165807, -1804323455, -1804110399, -730434111, -705202703, -705370783, -705370765, -713562861, -680067325, -680075517, -713566701, -713754829, -713758941, -578947229, -1652566671, -1644178095, -1610640047, -1611164559, -1611361183, -1762347675, -1770740379, -1774848651, -1774725675, -1757981227, -1791549961, -1804190297, -1795809881, -1795748425, -1795943017, -1796196969, -1796185641, -723426857, -723394089, -723394445, -715000781, -715000525, -714867437, -681313006, -681313006, -1771905773, -1771905677, -1755064973, -1755195947, -1755187243, -1788749995, -1746676107, -1746807195, -1746864283, -1746864283, -1755257545, -1754204905, -1758333113, -1791789241, -1791854761, -1787660937, -1779301001, -1783561737, -1783623177, -1779403305, -1779337771, -1779171883, -1779171851, -1779303179, -1787699977, -1754147625, -1754009145, -1754009145, -1754008617, -1788611737, -1788610777, -1788750045, -1788739823, -1788477679, -1788477679, -1788346605, -1779968237, -1746807501, -1746799310, -1770784942, -1640802478, -1640933550, -1640949934, -1636354190, -1611253918, -1644886686, -1649142286, -1661717005, -1661717037, -1661639215, -570988591, -570427568, -847390896, -818553007, -818413743, -550109359, -545919151, -545790127, -545851951, -545864207, -537410063, -573188623, -573196845, -589976109, -589976079, -1644904991, -1644907167, -1653295837, -1619667661, -1623730413, -1623763198, -1624015086, -1619890318, -1611497630, -1611182605, -1615525933, -1646962733, -1646954541, -1655212077, -1787340845, -1787472526, -1787464334, -1745490606, -1644696238, -1644692142, -1653211821, -1653219981, -722084495, -721435151, -587344400, -587860528, -596118064, -579340848, -579340816, -545917472, -680135199, -697077389, -684510957, -986500861, -1003417325, -999214798, -990764766, -994962142]
+
+TEST_1_LENGTH = TEST_1A_LENGTH
+TEST_1_FP = TEST_1A_FP
+TEST_1_FP_RAW = TEST_1A_FP_RAW
+
+script = None
+
+SEQUENCES = [
+ ('account', 'id'),
+ ('application', 'id'),
+ ('format', 'id'),
+ ('foreignid', 'id'),
+ ('foreignid_vendor', 'id'),
+ ('source', 'id'),
+ ('submission', 'id'),
+ ('track', 'id'),
+ ('track_mbid', 'id'),
+ ('track_mbid_source', 'id'),
+ ('track_mbid_change', 'id'),
+ ('track_mbid_flag', 'id'),
+ ('track_puid', 'id'),
+ ('track_puid_source', 'id'),
+ ('track_meta', 'id'),
+ ('track_meta_source', 'id'),
+ ('track_foreignid', 'id'),
+ ('track_foreignid_source', 'id'),
+ ('fingerprint', 'id'),
+ ('fingerprint_source', 'id'),
+ ('meta', 'id'),
+]
+
+TABLES = [
+ 'account',
+ 'application',
+ 'format',
+ 'foreignid',
+ 'foreignid_vendor',
+ 'source',
+ 'stats',
+ 'track',
+ 'track_mbid',
+ 'track_mbid_source',
+ 'track_mbid_change',
+ 'track_mbid_flag',
+ 'track_puid',
+ 'track_puid_source',
+ 'track_meta',
+ 'track_meta_source',
+ 'track_foreignid',
+ 'track_foreignid_source',
+ 'meta',
+ 'musicbrainz.artist_credit_name',
+ 'musicbrainz.artist_credit',
+ 'musicbrainz.artist_name',
+ 'musicbrainz.artist',
+ 'musicbrainz.track_name',
+ 'musicbrainz.track',
+ 'musicbrainz.release',
+ 'musicbrainz.medium',
+ 'musicbrainz.medium_format',
+ 'musicbrainz.recording',
+ 'musicbrainz.release_name',
+ 'musicbrainz.release_group',
+ 'musicbrainz.clientversion',
+ 'musicbrainz.tracklist',
+]
+
+BASE_SQL = '''
+INSERT INTO account (name, apikey, lastlogin) VALUES ('User 1', 'user1key', now() - INTERVAL '2 day');
+INSERT INTO account (name, apikey, lastlogin) VALUES ('User 2', 'user2key', now() - INTERVAL '5 day');
+INSERT INTO application (name, apikey, version, account_id) VALUES ('App 1', 'app1key', '0.1', 1);
+INSERT INTO application (name, apikey, version, account_id) VALUES ('App 2', 'app2key', '0.1', 2);
+INSERT INTO format (name) VALUES ('FLAC');
+INSERT INTO source (account_id, application_id) VALUES (1, 1);
+INSERT INTO source (account_id, application_id) VALUES (2, 2);
+INSERT INTO track (id, gid) VALUES
+ (1, 'eb31d1c3-950e-468b-9e36-e46fa75b1291'),
+ (2, '92732e4b-97c6-4250-b237-1636384d466f'),
+ (3, '30e66c45-f761-490a-b1bd-55763e8b59be'),
+ (4, '014e973b-368e-42bf-b619-84cab14c4af6');
+INSERT INTO track_mbid (track_id, mbid, submission_count) VALUES (1, 'b81f83ee-4da4-11e0-9ed8-0025225356f3', 1);
+INSERT INTO meta (id, track, artist) VALUES (1, 'Custom Track', 'Custom Artist');
+INSERT INTO meta (id, track, artist) VALUES (2, 'Custom Track', 'Custom Artist');
+INSERT INTO track_meta (track_id, meta_id, submission_count) VALUES (1, 1, 1);
+INSERT INTO track_meta (track_id, meta_id, submission_count) VALUES (1, 2, 10);
+'''
+
+
+def prepare_sequences(conn):
+ with conn.begin():
+ for table, column in SEQUENCES:
+ conn.execute("""
+ SELECT setval('%(table)s_%(column)s_seq',
+ coalesce((SELECT max(%(column)s) FROM %(table)s), 0) + 1, false)
+ """ % {'table': table, 'column': column})
+
+
+def prepare_database(conn, sql, params=None):
+ with conn.begin():
+ prepare_sequences(conn)
+ conn.execute(sql, params)
+ prepare_sequences(conn)
+
+
+def with_database(func):
+ def wrapper(*args, **kwargs):
+ with closing(script.engine.connect()) as conn:
+ prepare_sequences(conn)
+ trans = conn.begin()
+ try:
+ func(conn, *args, **kwargs)
+ finally:
+ trans.rollback()
+ wrapper = make_decorator(func)(wrapper)
+ return wrapper
+
+
+def with_database_context(cleanup=False):
+ def inner(func):
+ def wrapper(*args, **kwargs):
+ with DatabaseContext(script) as db:
+ prepare_sequences(conn)
+ try:
+ func(db, *args, **kwargs)
+ finally:
+ if cleanup:
+ pass
+ wrapper = make_decorator(func)(wrapper)
+ return wrapper
+ return inner
+
+
+def cleanup_database():
+ create_all(script.db)
+
+
+def setup():
+ global script
+ config_path = os.path.dirname(os.path.abspath(__file__)) + '/../acoustid-test.conf'
+ script = Script(config_path, tests=True)
+ if not os.environ.get('SKIP_DB_SETUP'):
+ with DatabaseContext(script) as db:
+ conn = db.session.connection()
+ if os.environ.get('ACOUSTID_TEST_FULL_DB_SETUP'):
+ conn.execute('CREATE EXTENSION intarray')
+ conn.execute('CREATE EXTENSION pgcrypto')
+ conn.execute('CREATE EXTENSION cube')
+ conn.execute('CREATE EXTENSION acoustid')
+ metadata.create_all(conn)
+ for table in reversed(metadata.sorted_tables):
+ conn.execute(table.delete())
+ prepare_database(conn, BASE_SQL)
+ db.session.commit()
+
+
+def make_web_application():
+ from acoustid.web.app import make_application
+ config_path = os.path.dirname(os.path.abspath(__file__)) + '/../acoustid-test.conf'
+ return make_application(config_path, tests=True)
+
+
+def assert_dict_equals(d1, d2, msg=None):
+ if d1 != d2:
+ standardMsg = '%s != %s' % (repr(d1), repr(d2))
+ diff = ('\n' + '\n'.join(difflib.ndiff(
+ pprint.pformat(d1).splitlines(),
+ pprint.pformat(d2).splitlines())))
+ assert d1 == d2, standardMsg + '\n' + diff
+
+
+def assert_json_equals(expected, actual):
+ assert_dict_equals(expected, json.loads(actual))
diff --git a/old_tests/test_api.py b/old_tests/test_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3e2bd0d5840626b3ae1d82a7dc10d826ee39f17
--- /dev/null
+++ b/old_tests/test_api.py
@@ -0,0 +1,40 @@
+# Copyright (C) 2011 Lukas Lalinsky
+# Distributed under the MIT license, see the LICENSE file for details.
+
+from nose.tools import assert_equals
+from acoustid.api import serialize_response
+
+
+def test_serialize_json():
+ data = {'status': 'ok', 'artists': [{'name': 'Jean Michel Jarre', 'year': 1948, 'cities': ['Paris', 'Lyon']}]}
+ resp = serialize_response(data, 'json')
+ assert_equals('application/json; charset=UTF-8', resp.content_type)
+ expected = b'''{"artists": [{"cities": ["Paris", "Lyon"], "name": "Jean Michel Jarre", "year": 1948}], "status": "ok"}'''
+ assert_equals(expected, resp.data)
+
+
+def test_serialize_jsonp():
+ data = {'status': 'ok', 'artists': [{'name': 'Jean Michel Jarre', 'year': 1948, 'cities': ['Paris', 'Lyon']}]}
+ resp = serialize_response(data, 'jsonp:getData')
+ assert_equals('application/javascript; charset=UTF-8', resp.content_type)
+ expected = b'''getData({"artists": [{"cities": ["Paris", "Lyon"], "name": "Jean Michel Jarre", "year": 1948}], "status": "ok"})'''
+ assert_equals(expected, resp.data)
+
+
+def test_serialize_xml():
+ data = {'status': 'ok', 'artists': [{'name': 'Jean Michel Jarre', 'year': 1948, 'cities': ['Paris', 'Lyon']}]}
+ resp = serialize_response(data, 'xml')
+ assert_equals('text/xml; charset=UTF-8', resp.content_type)
+ expected = (
+ '''\nokParisLyon'''
+ '''Jean Michel Jarre1948'''
+ )
+ assert_equals(expected, resp.data)
+
+
+def test_serialize_xml_attribute():
+ data = {'@status': 'ok'}
+ resp = serialize_response(data, 'xml')
+ assert_equals('text/xml; charset=UTF-8', resp.content_type)
+ expected = '''\n'''
+ assert_equals(expected, resp.data)
diff --git a/tests/test_api_v1.py b/old_tests/test_api_v1.py
similarity index 100%
rename from tests/test_api_v1.py
rename to old_tests/test_api_v1.py
diff --git a/tests/test_api_v2.py b/old_tests/test_api_v2.py
similarity index 100%
rename from tests/test_api_v2.py
rename to old_tests/test_api_v2.py
diff --git a/tests/test_data_account.py b/old_tests/test_data_account.py
similarity index 100%
rename from tests/test_data_account.py
rename to old_tests/test_data_account.py
diff --git a/tests/test_data_application.py b/old_tests/test_data_application.py
similarity index 100%
rename from tests/test_data_application.py
rename to old_tests/test_data_application.py
diff --git a/tests/test_data_fingerprint.py b/old_tests/test_data_fingerprint.py
similarity index 100%
rename from tests/test_data_fingerprint.py
rename to old_tests/test_data_fingerprint.py
diff --git a/tests/test_data_format.py b/old_tests/test_data_format.py
similarity index 100%
rename from tests/test_data_format.py
rename to old_tests/test_data_format.py
diff --git a/tests/test_data_meta.py b/old_tests/test_data_meta.py
similarity index 100%
rename from tests/test_data_meta.py
rename to old_tests/test_data_meta.py
diff --git a/tests/test_data_source.py b/old_tests/test_data_source.py
similarity index 100%
rename from tests/test_data_source.py
rename to old_tests/test_data_source.py
diff --git a/tests/test_data_stats.py b/old_tests/test_data_stats.py
similarity index 100%
rename from tests/test_data_stats.py
rename to old_tests/test_data_stats.py
diff --git a/tests/test_data_submission.py b/old_tests/test_data_submission.py
similarity index 100%
rename from tests/test_data_submission.py
rename to old_tests/test_data_submission.py
diff --git a/tests/test_data_track.py b/old_tests/test_data_track.py
similarity index 100%
rename from tests/test_data_track.py
rename to old_tests/test_data_track.py
diff --git a/tests/test_pg.py b/old_tests/test_pg.py
similarity index 100%
rename from tests/test_pg.py
rename to old_tests/test_pg.py
diff --git a/tests/test_server.py b/old_tests/test_server.py
similarity index 100%
rename from tests/test_server.py
rename to old_tests/test_server.py
diff --git a/tests/test_utils.py b/old_tests/test_utils.py
similarity index 100%
rename from tests/test_utils.py
rename to old_tests/test_utils.py
diff --git a/old_tests/test_web.py b/old_tests/test_web.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd0a2913636a0e801e3e19a8f9d4deb42d6cfdb8
--- /dev/null
+++ b/old_tests/test_web.py
@@ -0,0 +1,21 @@
+from nose.tools import assert_equal
+from acoustid.web import db
+from tests import make_web_application
+
+app = None
+
+
+def setup():
+ global app
+ app = make_web_application()
+ app.config['TESTING'] = True
+
+
+def test_track_page():
+ client = app.test_client()
+
+ rv = client.get('/track/eb31d1c3-950e-468b-9e36-e46fa75b1291')
+ assert_equal(rv.status_code, 200)
+ assert_equal(rv.data.count('Custom Track'), 1)
+ assert_equal(rv.data.count('Custom Artist'), 1)
+ assert not db.session.registry.has()
diff --git a/requirements_dev.in b/requirements_dev.in
index f3c7e8e6ffb905f7de8b597eb22213a7dc20bfb3..53d3adf976b5dc89d0f3b4731b89f9010602d149 100644
--- a/requirements_dev.in
+++ b/requirements_dev.in
@@ -1 +1,2 @@
-nose
+pytest
+WebTest
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 2e0f2dbf1490238ceb5fdc5bf5b27442136aa047..f55fa4c7b4ac672bb0c1209741c965728104251d 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -4,4 +4,26 @@
#
# pip-compile --output-file requirements_dev.txt requirements_dev.in
#
-nose==1.3.7
+atomicwrites==1.3.0 # via pytest
+attrs==19.1.0 # via pytest
+backports.functools-lru-cache==1.5 # via soupsieve
+beautifulsoup4==4.7.1 # via webtest
+configparser==3.7.4 # via importlib-metadata
+contextlib2==0.5.5 # via importlib-metadata
+funcsigs==1.0.2 # via pytest
+importlib-metadata==0.17 # via pluggy, pytest
+more-itertools==5.0.0 # via pytest
+packaging==19.0 # via pytest
+pathlib2==2.3.3 # via importlib-metadata, pytest
+pluggy==0.12.0 # via pytest
+py==1.8.0 # via pytest
+pyparsing==2.4.0 # via packaging
+pytest==4.6.2
+scandir==1.10.0 # via pathlib2
+six==1.12.0 # via more-itertools, packaging, pathlib2, pytest, webtest
+soupsieve==1.9.1 # via beautifulsoup4
+waitress==1.3.0 # via webtest
+wcwidth==0.1.7 # via pytest
+webob==1.8.5 # via webtest
+webtest==2.0.33
+zipp==0.5.1 # via importlib-metadata
diff --git a/scripts/disable_bad_submissions.py b/scripts/disable_bad_submissions.py
index 5a524b36faafcdfe490d5220b1d0250bcb551c6a..11e4d33b06c0138c5324d2bbb113232bf33843f6 100755
--- a/scripts/disable_bad_submissions.py
+++ b/scripts/disable_bad_submissions.py
@@ -160,7 +160,7 @@ def disable_track_mbid(db, track_mbid, note):
def main(script, opts, args):
- db = DatabaseContext(script.engine)
+ db = DatabaseContext(script.db)
min_track_id = db.session.query(sql.func.min(Track.id)).scalar()
max_track_id = db.session.query(sql.func.max(Track.id)).scalar()
diff --git a/scripts/run_psql.py b/scripts/run_psql.py
index 0e578f9cad3bd74f2abc1893b39d5ffc4e4f002d..6d764dd56566cb224bbc1b6d5ce54f3bd22df42a 100755
--- a/scripts/run_psql.py
+++ b/scripts/run_psql.py
@@ -8,7 +8,7 @@ import os
def main(script, opts, args):
- os.execlp('psql', 'psql', *(script.config.database.create_psql_args() + args))
+ os.execlp('psql', 'psql', *(script.config.databases.databases['default'].create_psql_args() + args))
run_script(main)
diff --git a/tests/__init__.py b/tests/__init__.py
index fa615ec77713edb2601dbf3def605bd98143fd40..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,187 +0,0 @@
-# Copyright (C) 2011 Lukas Lalinsky
-# Distributed under the MIT license, see the LICENSE file for details.
-
-import os
-import json
-import pprint
-import difflib
-from contextlib import closing
-from nose.tools import make_decorator
-from acoustid.script import Script
-from acoustid.tables import metadata
-
-TEST_2_LENGTH = 320
-TEST_2_FP = 'AQABVtuUZFGShAqO-h9OHD96SvhwBVNCKQnOIYmiIc-ENwF7TDe8Hr0W_AjhvRCP2sfT4DTS7zjyOYeqaI-RSxee5RmaWzhOHnlcaB6HnPgpdE-DkWIH2ysYG_Eh9zJCyfCXGOdw-EGoD2p69IavWOhzMD-a9tBx9FgPVz2qNDvQH3744ISIXRKeHto5_MhyeMtxc-COnYJ_lHLwRAgPvShz_Hga4zd8HD9UKXWOPP3xRLmGnlbQHKfxGPeRvAt6UngMvcF-gkpRi0bUZjGaH6FUHb_xGDt6aHmM__ghfkmH70B4fWiuCj8y8uj3oImZY8d3NFWWHuGF-3hCPEd_uEOyE_nw4w8ueXi24znCHOHxSWtw9BnSBzrSHF2Y4S0e_EioZoh9XMGfo2dqNMeP80aQPM5xGT9efMeTYL-KIqmHdDraHs-P8IcYjoj0I7_Q43iJ9BF64nSKKth2SjG-cvCHH-2OL8txHsUt9HhF4LiK5j16lAf1FkjvQiN55FSOkkOPkmj4GK-OH80eIeyh98HhE_qhPwjzKAV-HJ2OZkd4Q_vhp0d_6Id-_IeWW9CKoP3RKM-Bo3mOfvhxND_6HMgZ6EfXHB-8Q8-iok1znOi-ozmx54P2Dg5V_PCgLxy8KiH6C0cbHU3Ebtiho9Rxw8er47tw7jgRNxl84ziPJ-B1_DiNNClzaGSCvMGPGxePMD5qZYEuAwdTXYSYcIkmodc2nMqg_WgqBk_yBdVx0vCjQD8uhNRxXTgvVFSOSOmx61C1KMaNsFwM93h-PBdmFm8o45nxDabx48cTbGl4hHuhasjSwPtxPvAV1A7yQMukREERR-nxL8j-EbWYQ8sj4joABQQmjQhkjLFCKSAo4QoxYiQwQhgmkGjCKGAIMMA4BIwQwjhAFMBUCCUAYEIxpYxUCDlEjJYOScSMgsIIAgADwjKEFBAUCkMEMYAagoARzAAHCDCIISKANkgYYBiQwgDDjHEMIGWZFUBQLhgohBGkhECOMEAMIYghogTgQghgiSLCYUegAsJApIQjxABNDFWCa6AIAQ4Q4KgAgIABgGDCMNGIMgQJRQAQTACpgBNIJkUcBMkpoKAgXCjAgAAGKIcYIVAYbZgwggkEmKLEiYGYAYQQShFAAAQBFEEAEuEIgwYRQoARnBkAmAGMEAGFGIgQBigCwAkABEIA'
-TEST_2_FP_RAW = [-772063964, -772066012, -1007079116, -1011011276, -1027788492, -1029889740, -1031261916, -1031269084, -1031270620, -1031021744, -1031079600, -1031078425, -1031032346, -1018580506, -1018646290, -1022775046, -1056337446, -1056261686, -1073039030, -1073039013, -1068976005, -1001867175, -733365175, -733302711, -737435575, -603332504, -603365272, -737516311, -188076117, -175490390, -745993558, -758642022, -767030630, -1034347878, -1038412133, -1039584631, -1039654264, -1034345784, -1030086056, -1011141092, -1045873092, -1045939676, -1045947803, -2132276505, -2132259914, -2140415082, -2140472938, -2140481130, -2140546634, -2140603929, -2144802523, -2144536267, -2123540203, -2115147515, -2081855203, -2098598611, -2098606801, -2098606737, -2106995329, -2090217989, -2123638309, -2140477173, -2140477173, -2123634405, -2106992325, -2107061957, -2107061991, -2107007715, -1033140963, -1023769329, -1025864433, -1026913002, -1010133962, -1017409482, -1017540482, -1022848902, -1056337830, -1056271030, -1056261302, -1073038501, -1068975271, -1060587447, -993477559, -1001672631, -737435575, -737566615, -737550231, -737419031, 1422585259, 1955228586, 1367940010, 1388841914, 1380453258, 1376328586, 1376458634, 1107956618, 1107899017, 1113072281, 1121657497, 1119494811, 1135224479, 1135226615, 1139489511, 1130891874, 1126713938, 1109977859, 1114237187, 1122691331, 1122695431, 1122687255, 1114233125, 1130944869, 1126746469, 1097554247, 1105885511, 1105885511, 1135270230, 1122523494, 1114135910, 1109939695, 1093236223, 1076520335, 1080714635, 1089107851, 11092923, 11010986, 15209450, 15492074, 7103274, 2913082, 2905882, 2940681, 2947848, 7138056, 32303368, 61716744, 44932552, 1118668232, 1118406137, 1122600424, 1110167912, 1110167848, 1110106424, 1122689305, 1118495003, 1118478714, 1118540010, 1122599146, 1110016234, 1110147562, 1110094153, 1076535560, 1076538376, -1058363384, -794183656, -794249176, -790063064, -261519320, -261519319, -529562582, -529628886, -530153430, -530280406, -534465494, -534459350, -517027794, -517027778, -517056387, 1630428508, 1634606924, 1643060940, -508616995, -508740929, -475252054, -487834709, -496223301, -496231493, -496092485, -488752486, -489735542, -494125366, -494125542, 1641889598, 1627335998, 1617898782, 1613703454, 1614756622, 537664270, 541854222, 541854238, 541874782, 558651982, 558168910, 558168910, 558168398, 566360398, 1636039038, 1669593454, 1938028670, 1942087766, 1942087766, 1665394807, 1631779173, 1640192868, 1640221300, 1640483428, 1640487796, 1631902020, 1627682884, 553932868, 554654068, 554589029, 567179879, 562985575, 562846279, 562879301, 558684487, 554678646, 554678646, 558873462, 567262070, 563067366, 562936022, 567064775, 558692565, 1628436725, 1661925605, 1661893095, 1666087909, 592329573, 567032663, 567032133, 567032132, 1640840020, 1909340900, 1909340900, -238142748, -775079212, -775152956, -1043580220, -1047774524, -2121450764, -2138162460, -2138162460, -2138232091, -2121520409, -2117330313, -2124670345, -2124604585, -2092205227, -2083848891, -2083787451, -2117489195, -2117550619, -2124902943, -2139517453, -2139383405, -2122597997, -2122598221, -2093090639, -2095162991, -2107737727, -2107754111, -2108805231, -2099495199, -2099499291, -2097402137, -2098451465, -2098709513, -2098717737, -2081859113, -2123773481, -2140434985, -2140496425, -2140428906, -2132171338, -2132236874, -2065124170, -2023181130, -2039964482, -2044162882, -2044098306, -2111137761, -2111117043, -2111165684, -2144720372, -2140460532, -2132132340, -2136328643, -2136407507, -2136471761, -2136471761, -2132277457, -2114187977, -2091268651, -2083809851, -2083872379, -2117361257, -2117552729, -2141681241, -2139584009, -2143708937, -2143618857, -2126874411, -2126894891, -2093339196, -2105923644, -2099628348, -2103836012, -2103966047, -2099751259, -2097639449, -2031579161, -2039763466, -2031375914, -2014728234, -2056675370, -2056548654, -2073127278, -2077251950, -2077233262, -2077266510, -2077332302, -2073154382, -2014434126, -2031143722, -2039794617, -2039792379, -2039792868, -2107033028, -2081936836, -2136462820, -2136265204, -2136263155, -2136456385, -2136456401, -2132228626, -2122791506, -2091070041, -2107647561, -2108769915, -2100384379]
-
-# MP3, 320kbps
-TEST_1A_LENGTH = 260
-TEST_1A_FP = 'AQABVkmYaEmkJEsiaGvhH33xXNDyol2Pz-jhW2h1_Bb6w4d2dIbPo8dzVA_Es0E1C9_RCGH1DDmP3hX8DD2-Bdq0ojmPa9CeS_CPdi0-9EFz4bqE02iOQzs6w-fR7-gP9R0ct2ipoEeqD60u5EyFPoSPfjF0NNKOXsMP7TkcMxyq3bjQ_GiP05TQw4d24ejQfDl-VD7EN8JdtFTQHWH8DN8RPhXsED_6BdoOv0df_IJ2FuZ69MaF5hba47dQ-OihC42MvjmeQPthvSRqaR3-HfUFH33YoC-UHddRWSL6Gr6gHd0DO-i0Hj1-WMJjXAdeqoN5_MfhrbgOVzyeGRfOCpbIauh7IY8evEGYo0_woIePyxDtFTgJt8d96BM64UZ1mLfQFw96-EW77Oh__Eb64WLwz_DxZXgHU6HRfbjQozlxhOfgH_rRHz0D60eF6-gPvw6Ow0-G_tBzNKxy9BZyoWOI5smR78HDQtMP-0X1Pfjwo9GPfslRiHksoVWIo1zRrMtxo-dhkviPwzo0PniDXsdOWGYLHfZ0_Ef_oGcK0aHxB7uIT7BwGeqP1_CmA_9hnDizGSd-WIfXo8c7VDZ8_Hgo_McP_3ha_Cl8utCD4xS8B8LhzUc9tAcabT1-aBXM4xBx2DrywyS-Z_A5XMVhK0f7ICf6wxb048R15BfOI3eHE0F-4AzM5dCNH4cPO-iOD7_QPELf4egDpzp6dM0g2jh-CSdEEj_yHt6O_ijSG8cDmSSMG3cAn-hxaNE0BbeO_tPQH_5RizGeAwWAQgg4hIwwgBkmgGKKAieMAgBYI4ACBAmHqDECSyYAIQIZDY0CQggFnAIIOCSYABg5AZxCCFBjkHJEGACAUgIoh5QwmCkAFBIEAKQsEEgRYYAU2CHrAABEAMuQcBwAwgBDgmIsjUBEKSKEIgAIAQwACgBrLHEYGKEEBMQAg5wBFAAGlEBGKAYEQcwQBACkigABjBNAKEEAtYIIYhwBgBAjABCSGMEUMIIJZ6QwnFgGJUDCACKYIEBRRcRAACBIrqECOCGtcEwRZpgEijiGjDKCSsYcIApAwQwimDlhhRHGMaGceFZwBxwhDAjCCA'
-TEST_1A_FP_RAW = [-1124735545, -1124600491, -1107954315, -1108017819, -1082858139, -1084889769, -1101531321, -1105799353, -1105832105, -1084795017, -1082696345, -1107993113, -1124772361, -1124739625, -1124608571, -1124600491, -1107962507, -1108028059, -1074469531, -1099635403, -1105857257, -1105758393, -1089054905, -1089054729, -1116316186, -1107866137, -1128970761, -1129024041, -1124829753, -1124735033, -1124600491, -1107962523, -1108017819, -1084955273, -1101666985, -1105725625, -1105799353, -1105766569, -1084795033, -1107862169, -1107995161, -1124772393, -1124739625, -1124608571, -1124600491, -1107962507, -1074465435, -1082858123, -1101667017, -1105726121, -1105832121, -1089054889, -1118414858, -1107927578, -1128968729, -1129032201, -1129024041, -1124735545, -1124735035, -1107954315, -1108026011, -1074463387, -1082858121, -1101531881, -1105791161, -1105832121, -1088989321, -1084795545, -1107993113, -1107995145, -1124772393, -1124608571, -1124600379, -1124600491, -1108028059, -1074465435, -1082858187, -1105861353, -1105726121, -1105832121, -1089054761, -1116315674, -1107931674, -1128968713, -1129032233, -1124829737, -1124735545, -1124604587, -1107954315, -1108017819, -1082854043, -1084889737, -1101531305, -1105791161, -1105832121, -1084795017, -1082698393, -1107995161, -1107995145, -1124772393, -1124641337, -1124600379, -1124608651, -1108026011, -1108023963, -1116445321, -1621793321, -1659424313, -1793715833, -1806297705, -1797909065, -1799942745, -1800069705, -1800135279, -1795934831, -1796905535, -1805158959, -1805257231, -1805255197, -1780089485, -1780095661, -1746405437, -1754794029, -1771579437, -1771580045, -1755196045, -1759414925, -1759414925, -1788609229, -1788674285, -1780220397, -1780351469, -1780418765, -1746884813, -1763593453, -1771842797, -1774988525, -1775151277, -1619823117, -1787658781, -1796006430, -1795879454, -1795945005, -1795916349, -1795719741, -1804108333, -1787331085, -1779040781, -1746405935, -1746405935, -1746544687, -1754967055, -1788585999, -1788774413, -1788775069, -1788641949, -1788609229, -1746796781, -1746669821, -1746667773, -1746675949, -1746811085, -1755253469, -1619991245, -1624119501, -1624148206, -1623761134, -1619624110, -1611235470, -1611182734, -1649062542, -1644931246, -1644341310, -1644300350, -1652688942, -1652758558, -1652759070, -1653213709, -1619528237, -1611205167, -1619601983, -1619567151, -1888133775, -1888133855, -1921647327, -1653213903, -1661475567, -1661475503, -1661471407, -1661631119, -1644919455, -1653295773, -1619527853, -1640564925, -1640605886, -1640542254, -1636352030, -1611190813, -1615520285, -1649140303, -1779165807, -1804257855, -1804110399, -2072611391, -2064157327, -2047548063, -2047548047, -1887967981, -1888026877, -1888035069, -1887971821, -1921714397, -1921718429, -1653213325, -1653090989, -1644767789, -1644702253, -1611147917, -1611344799, -1611352735, -1619729039, -1619659403, -1757948459, -1757981225, -1791615497, -1787413065, -1795809881, -1800073801, -1795943019, -1795918379, -1795857967, -1797135919, -1797136015, -1797200783, -1780358093, -1788611533, -1755063021, -1755063021, -1771905709, -1771905677, -1771844237, -1754835469, -1754958349, -1788512269, -1788479661, -1780099469, -1746545053, -1746602141, -1746868429, -1755257069, -1621035245, -1624148205, -1657571501, -1657637549, -1653459597, -1644821133, -1649080845, -1779165741, -1779141167, -1779075631, -1778909743, -1779303183, -1779303181, -1787691789, -1754008109, -1754017341, -1755065389, -1755065389, -1788348573, -1788479709, -1788478159, -1805254895, -1788477679, -1788477645, -1788346573, -1779968237, -1746544878, -1746537166, -1637484686, -1636313262, -1640671406, -1640540334, -1636352142, -1611186846, -1611326109, -1644946061, -1644939949, -1661700783, -1661639343, -1644698287, -1655179952, -581577392, -552084143, -552075439, -552075440, -547885231, -547885231, -547883055, -1621637647, -1613380109, -1646930445, -1646922253, -1646926381, -1646920205, -1646985885, -1647004317, -1653295837, -1653222125, -1623730941, -1623730429, -1623828717, -1619624077, -1611235485, -1611182093, -1611315247, -1611311151, -1655343151, -1653114927, -1653123597, -1653254797, -1644858254, -1611301550, -1644724926, -1644724910, -1653244557, -1653252749, -1661641357, -1661086223, -1661086255, -1661602351, -1653082671, -1653082671, -1653221903, -1619667487, -1619659295, -1619828397, -1888276205, -986509053, -1003417325, -999214797, -990763741, -994962141]
-
-# WMA, 160kbps
-TEST_1B_LENGTH = TEST_1A_LENGTH
-TEST_1B_FP = 'AQABVkmYaEmkJEsiaDv8Hr0GXRd6FuZ69DP6wBdaHb-FHj60CzeaH33wHNUD8WxQPTiF7ghDPcN5hHcFP0OPb4HWFY3M4xr0XGjeoV2LD33QPMKP_2iOQzs6w-fRa-hz4R_EmClaKjjCCK2eCDkt9GHgo18MoVF79At-aD-cd6g2prjQHw2P05TQw4d2AZ3h8-i1o2oO_1BdBi0V9Ej1DK1y5KnQh_CPfoF2WO3Ra_gF7TnMMUdvXGhuoT1OW-jho4cudEbj5ngCbT_8kri0DtV31Bd8oc_RM1B2XEdliegNf0Ih80HjoNOP_vhh4j-uEXipDuZx_IG34jpc8XhmXMVZwSSPjvWQ5xnOoLkQXviCHj4uQ7SP4xl8GT_0ohOO6oJ5C33xoIdftOPR58dvpB8uBv8MH1_wdLCkEP2GCz18tEf4w8-hox965rB-VLiO_vDr4Dj8DD2P59By5WhvIRc6hmie49ODPCw0_bBfVA8eBUf3F_6SExDzS0GrMDjKFV55VObREyaJ_zisQ-ODN-h17IT1Frpg9_hzoT96BnYI9cEu4hMs_FDp4zW86cCPwyfObMaJH9bh9dAOvUN1-MePh8J_HGZJPMefwqfxHDpOwXsgXPBm1EN7wHKP-tA04YchHoetIz98_Bl8Dj9gK0f7ICf644L448R15BfOIy8uHUF-4AzM5dCNH4cPO-iOD7_QPELf4egDpzp6dM0g2jh-CSdEEj_yHt6O_ihyw8cDmYePG3cAH_1xaJsGx1qOfkXPD_7R17hoAAMAMEYAgqgRBmAkBFJMUWAMNQwAYA1QBiGHpHHCCCyZAAAgAMlRQAhAnBLIOCWYABgJAAhCQjskgSCCAeAMIEA5pATATAElBEIAKQmMQIowoIQQyCFhgANAFMGQcBwARhgSDmMpuDEIKkWEUAQAAYABQAFgDVUOAyAAEp4AYJAzgALAhBDMEAEYIIYoQxB1lAAADHBCCUGAKYIIYowiAAhiBBCSCMEUMCQIZ6QwHCiAGJQACcqMIMAxqIhIABiIraFCACokEI4poohhEijiGDVKGUElYw4QBaBgBhHMnLDCCOOYoEIM46xgwhlLgCACAQ'
-TEST_1B_FP_RAW = [-1124735545, -1124604587, -1107954315, -1108026011, -1082854041, -1084889737, -1101531305, -1105799353, -1105766569, -1084795017, -1082696345, -1107993113, -1107995145, -1124772393, -1124608571, -1124600507, -1107831435, -1108028059, -1074469531, -1099635403, -1105857257, -1105791673, -1105832121, -1089054761, -1116316186, -1107866138, -1128970761, -1129024041, -1124829753, -1124735033, -1124600363, -1107962523, -1108017819, -1082858137, -1101666985, -1105725625, -1105799353, -1105766569, -1082697881, -1074309785, -1107995161, -1124772393, -1124739625, -1124608571, -1124600491, -1107962507, -1108017819, -1082858123, -1101732555, -1105726121, -1105832121, -1089054905, -1089054730, -1107927578, -1128837657, -1129032201, -1129024041, -1124735545, -1124735547, -1107823243, -1108028059, -1074465435, -1082858121, -1101667049, -1105791161, -1105832121, -1088989353, -1084795545, -1107993113, -1107995145, -1124772393, -1124608571, -1124608571, -1124600491, -1107962507, -1074463387, -1082858187, -1099569899, -1105726121, -1105832121, -1089054889, -1116317706, -1107931674, -1128968729, -1129032201, -1124829737, -1124735545, -1124604459, -1107954315, -1108026011, -1082851995, -1084955273, -1101531305, -1105791161, -1105832121, -1084795017, -1082698393, -1107993113, -1107995145, -1124772393, -1124641337, -1124600379, -1124608683, -1108026011, -1108023963, -1116445193, -1621793321, -1659424297, -1793715833, -1806297705, -1795811913, -1800008281, -1800069705, -1800135279, -1795934831, -1796905535, -1805163055, -1805257231, -1805257247, -1780089501, -1780095661, -1746409533, -1754794029, -1771579437, -1771579917, -1754933901, -1759414925, -1759414925, -1788609229, -1788674285, -1780285677, -1780351469, -1780418765, -1746884813, -1763593453, -1771842797, -1640770797, -1640933549, -1624017549, -1653441053, -1796071966, -1796141598, -1795945006, -1795924541, -1795719741, -1804108333, -1787331085, -1779040781, -1745357359, -1746405935, -1746414639, -1754967055, -1788520463, -1788774413, -1788775069, -1788773021, -1788609229, -1746796781, -1746669821, -1746667773, -1746675949, -1746811085, -1755261661, -1619991245, -1624119501, -1624148206, -1623761134, -1619624110, -1611235470, -1611248270, -1649062542, -1649125422, -1644341310, -1644300350, -1652688942, -1652693006, -1652759070, -1653279245, -1619528237, -1611139631, -1611213375, -1619567151, -1888133775, -1888133855, -1921647327, -1653213903, -1661475567, -1661475503, -1661471407, -1661627023, -1644919455, -1653295775, -1619528365, -1640564925, -1640605886, -1640542254, -1636335646, -1611190814, -1615520285, -1649140303, -1779165807, -1804331583, -1804110399, -1804175935, -2064157359, -1779112591, -1779112591, -1888099053, -1888026877, -1888035069, -1887971821, -1921648845, -1653282973, -1653213325, -1653090989, -1644767789, -1611213357, -1611147917, -1611344799, -1611352735, -1619729039, -1619659403, -1757940267, -1757981225, -1791599113, -1804190281, -1795809881, -1795879497, -1795943017, -1795918379, -1795857967, -1797135919, -1797136015, -1797201807, -1780358093, -1788611533, -1755063021, -1755063021, -1755128493, -1771905709, -1771844237, -1754835469, -1754958349, -1788512269, -1788479661, -1780099469, -1746545055, -1746602141, -1746864333, -1755257069, -1621035245, -1624148205, -1657571501, -1657637037, -1653197453, -1644821133, -1649080845, -1644948013, -1779141165, -1779075631, -1778909743, -1779303183, -1779303181, -1754137357, -1754008109, -1754017341, -1755065389, -1755065389, -1788349581, -1788479645, -1788478159, -1805254863, -1805254895, -1788477645, -1788346573, -1779968237, -1746544878, -1746536654, -1637484686, -1636313262, -1640540334, -1640540334, -1636352142, -1611186846, -1611326109, -1644946061, -1644939917, -1661700783, -1661639343, -1644698287, -1655179952, -581577392, -585640623, -552075439, -552075440, -547885231, -547885231, -547883055, -1621637647, -1613380109, -1646930445, -1646922253, -1646926381, -1646920205, -1646985885, -1647004317, -1653295837, -1653222125, -1623730941, -1623730429, -1623828717, -1619624077, -1611235485, -1611182093, -1611315247, -1611311151, -1655343151, -1653114927, -1653123597, -1653254797, -1644858254, -1611301550, -1644724926, -1644724910, -1653244589, -1653252749, -1661641357, -1661118991, -1661086255, -1661602351, -1653082671, -1653082671, -1653090831, -1619667487, -1619659295, -1619697293, -1888276205, -852283133, -1003417325, -999214797, -990699229, -994953949]
-
-# Vorbis, 64kbps
-TEST_1C_LENGTH = TEST_1A_LENGTH
-TEST_1C_FP = 'AQABYpESSUkiJYmSCD30C81RbeXwG7zQvBFa4bfQg_rRQ5vh8-jxHJoe-AyD6sF5NEJYPUPOo3cFP0N7fCm0F40k8fgOPUfzo12LDz0a3sIv4TSa44KGDv7Ra-hz4e8gxi1aBz8aHWF1CXlooWfgg_mO9hDVg6nwQ3sOnx2YvriC_mioBxc6Gj566ASzwyf6Hf0h5h3uFqeCw5I-9Bf6VLBD_OgXaD0srRz64tB-wdmYozp6IX2EVsgtdISPHjoaHX3woPshviQurUP1HfUFH30b9LCLH5oscXgx5YJ2dIedo12P7vhh3MJ9nHipDuZx_IG34jpcEc8cXMVZwRJZoe_xsMGZo3ExXbgWFNsf6Bvs43gG3_ihr-g0vCl6mLfQFw_RD36PdjzRLid848PF4J_h48uQv4OpEP2GS-gNn2gRnsN_iPvRC_1hyUL1D5eOwn9w3PCJXof2w1eO9hZyoWPRPEe-Bw8LTT_so_oefMePRj_6JUcPMW0stCOOckWzLsd99IRJCh9x-OgDPeh17IQos_jhtMdzoT-0M7BD_MFF7BNh4Tp0HTe86Tj-4_CJM5txwjxuCcp2lMe-Q5MN8_g-PMZ_HOaJJ8efwqeh5zhOwTt0XPBm1EN7oNHW4zu04zAMHbYuHOmP34F_XMUJ_2j14CHCh4cPXcdxHXmp48iPXDqOHDgDc4FuFcdh2OguPLgvNI_wdyj6wKmOHl0ziDZ-_NApWCKB_IKHcsYR3vDxBzoJ48YdwOdRHFq04l-Ofuj5Df5RizGeC-FD-AkeTLrxJsePUP1x7dCxc0Z8-DvCdcEvAIUQAsYgYYAhTADFFAXEIKMAAIwJ4IBhSDgElBFYKgAAAsYhaJAQAgBFCUBAOCWMyEhRQYhABhiSBFGOCKHMIEASSTFzVDEhEAJIWcAcYQZQJKRDRiAMgCiCIccBII4ZoYyQzholDQFICEUAAAAxBYA1wCjHgCBECAAUIMZAgCwQRhBDBGBCEMQMQQBAQxUBAoAngBTUJKUEMVIRJ4gRACAGJFFWOWCEA8JJAQki1ggJkKCEEUGAYlQRKhIAAhhprHAACSAdEYoBIphgEijgqCAELCGZAwoQYQQimDEnJDOmCSGNs0I4IIwlQBAGAGJGEEkAYEAJIASgAA'
-TEST_1C_FP_RAW = [-1107954315, -1108019867, -1082858137, -1082792617, -1101531385, -1105791161, -1105832113, -1082697857, -1082696337, -1107993105, -1107995137, -1124739625, -1124608569, -1124600491, -1107962507, -1108028059, -1074469531, -1099635401, -1105726185, -1105824441, -1089054905, -1089054729, -1116316186, -1107866137, -1128970761, -1129024041, -1124829241, -1126832185, -1126697003, -1110057627, -1118503579, -1084955289, -1101666985, -1105725625, -1105799353, -1105832105, -1084795545, -1107862169, -1107995161, -1124772393, -1124608553, -1124608571, -1124608683, -1107962507, -1108017819, -1082858123, -1101667019, -1105726121, -1105823929, -1089054905, -1084860425, -1107927578, -1128839705, -1129032201, -1129024041, -1124801073, -1124735009, -1107953795, -1107952283, -1074463387, -1082858121, -1101662889, -1105725617, -1105832113, -1088989345, -1082696337, -1107862161, -1107995137, -1124772385, -1124739633, -1124600371, -1107823275, -1107962507, -1074465435, -1082858123, -1101667049, -1105726121, -1105832105, -1089054889, -1118413321, -1107931673, -1128968713, -1129032233, -1124829737, -1124735545, -1124603947, -1110051467, -1108019867, -1074465435, -1084955273, -1101531369, -1105725625, -1105766569, -1082697866, -1082696346, -1107993241, -1107995145, -1124772393, -1124641337, -1124608571, -1107831435, -1108028059, -1108023963, -1116445195, -1621793321, -1659424297, -1793715833, -1806297705, -1797909065, -1799938649, -1800069705, -1800137321, -1795934825, -1805294139, -1805158971, -1805257231, -1805255197, -1788478093, -1780095661, -1746405565, -1754794029, -1771579437, -1771579405, -1771743757, -1754958477, -1759414925, -1788609229, -1788674285, -1780285677, -1780351469, -1780418765, -1746884813, -1746816237, -1771842797, -1640770797, -1640933549, -1619823117, -1653441053, -1661592093, -1795879453, -1795947053, -1795926585, -1795719737, -1795719721, -1787298317, -1780089359, -1746537007, -1746405935, -1746414639, -1754934287, -1788521487, -1788774413, -1788779165, -1788642013, -1788609229, -1746796781, -1746669821, -1746733309, -1746675949, -1763588301, -1637821149, -1619990733, -1624119533, -1624148205, -1623761133, -1619624109, -1611235469, -1611182733, -1649062414, -1649125422, -1644341310, -1644169278, -1652688942, -1652758542, -1652759070, -1653213709, -1619528461, -1611205167, -1611211327, -1619534383, -1619697807, -1921655007, -1653213919, -1653213903, -1661606639, -1661475567, -1661471407, -1661610639, -1644915359, -1653295757, -1619528365, -1640564925, -1640605886, -1640538158, -1636352030, -1611190813, -1615520285, -1649140303, -1644948079, -1670040127, -1804110399, -2072611391, -2064157327, -2047548063, -1913330319, -1887965933, -1888026877, -1888035069, -1887971821, -1921714397, -1653282973, -1653213325, -1652566701, -1644767917, -1611213485, -1611147917, -1611344541, -1611352735, -1619729039, -1619659403, -1623730729, -1757981225, -1791599177, -1804190281, -1795809881, -1795879499, -1795943019, -1795918379, -1795857963, -1780358703, -1780358799, -1797136335, -1780353997, -1788611533, -1755063021, -1755054829, -1771897517, -1771905709, -1771844237, -1754835469, -1754958349, -1788512301, -1788356781, -1779968395, -1780099227, -1746602143, -1746868429, -1612651245, -1619986669, -1624148205, -1657571501, -1657637037, -1653181069, -1644821133, -1649080845, -1644948013, -1779141167, -1779075631, -1778909743, -1779041039, -1779303181, -1754137357, -1754008109, -1754017341, -1755065389, -1755065389, -1788348573, -1788480221, -1788478159, -1788477647, -1788477679, -1788477647, -1788346573, -1746413805, -1746544877, -1612318926, -1637353614, -1636313262, -1640671406, -1640532142, -1636354190, -1611186846, -1611330206, -1644948109, -1644939949, -1661635247, -1661639343, -1644698287, -581569200, -581577392, -547891887, -552075440, -547881136, -547885231, -547885231, -547883055, -547895823, -1646934541, -1646930445, -1646922253, -1646922285, -1646920205, -1647002269, -1647008413, -1653295837, -1653222125, -1623730413, -1623730429, -1623828717, -1619624077, -1611235485, -1611182093, -1611315247, -1644865583, -1646954543, -1653114925, -1653254797, -1653254797, -1611303566, -1611303854, -1611170494, -1644724926, -1653244589, -1653252749, -1661641229, -1661086223, -1661086255, -1661602351, -1653082671, -1653082671, -1619667471, -1619667487, -1619659295, -1619693229, -1888276205, -1926024957, -1003417325, -999214797, -990763741, -996010717, -995947214, -995881710, -995869422, -991609530, -999993914, -983183930, -949893737, -966552169, -966543979, -958271087, -958279216, -941559312, -941530783, -983473887]
-
-# WMA, 32kbps
-TEST_1D_LENGTH = TEST_1A_LENGTH
-TEST_1D_FP = 'AQABVkmYaEkkZUkE7bDao4e-C9VhHtWLHtQFphJ-CT3sQ7vQGT76Bc_RB-LZ4Bb6HR3CUB9yVugr2MnQH_qITieah8eL_oIYs8O3FkUvNK6Fv0L3wz4O7egMn0e_oz_UD45pnBJ-pPLRShdyFn1awZ9xfIQorUdPfIf2C455tC-OXhcaqngldId99IKODs2X40d_OG8E1UXrox9SSR_6C3kq9CH8o1-gHVaPfsUvaD-cjTmq40JjXQjLIxc6Gj56XNAi9MvxBtotwSe-FtVP9BZ89HnQB3aIHz2Jn0Vz9DyOxjmm3sKPHy189MdR7R1s_PBRrofWHmL94Bl-4azQyDzySR725MGZojn65MiJ32g-6Me2_fjxDL7xHM_moWunQzVaUYePkniPcjr8oucR_vCP7_jxJsR_mAv-dIIdyWiPoz_RPPiQH_2h5ceLe0dzVDe-oRfSuA5w-BnC83gOLVc6hP1xoWPRPMf3DHlYNDEPPUf_4Ffwo_mDrkugwXmMVuFxlOvhdUGPmzu0koePH97RB3M__OglwzLxQ_SO6rPwoyfsMHhxSjiPY1Nw6sFhGzmQ74aD91Cm48R9eLfQ4x2gaTv8o9qN6-hhHk-OzylOuMuh4xSMH_phH6VGoEWjFyd2QouFAzquw3yOHM6-BdeL4_AZon_wZTm60sgvGLqE_0beEaeL_BtO_Phx4xrhBz9eEuKD5xDRszNOqEcfHeaL8OhVOKbRH-G0TDC1FdczHDd-I5WOw84u_MKRGz70bDhTIe_hGzcOoz9yaFsVw_mFfuh5pD96xrhyAAaIAgYiRIVjDGHCFAaGCKQYAkIAQIkBxAAjmGQCECIQNFQQo4CQBhBHBBECEEANQ0oApwQyhCBBJBBEUEWcEQRIRwTIzBEgEIBAKQuIckQAAQAUSjDljCKEDAIVkcAZBAAhhiEhFCFAIQWcMko6I4RlgAIBCACGKSoUEEQQ4AQwBgsFlFACKAkCE0IwQ4BwQhikiDHEAEEdAoY4IoBgFiKkEENCEgScEQ5QIwEipAkljBGKMIKAAFIoxohgAipClHEMICyVEYAJBywUChjBCDICAIeEs4gZJQAFTCglBCVEICAQA846IZywglHhhTLCkibQEJwAQQQiAA'
-TEST_1D_FP_RAW = [-1126832697, -1126701611, -1110051467, -1110059675, -1084951193, -1084885705, -1101662441, -1105725625, -1105758377, -1084786817, -1084793497, -1107862041, -1107993097, -1124772393, -1124608571, -1124600491, -1107823243, -1108028059, -1074469531, -1099635339, -1105857193, -1105758377, -1089054905, -1089054761, -1118413338, -1107866137, -1128968713, -1129024041, -1124796985, -1124735035, -1126701739, -1110059675, -1108028059, -1082854027, -1101667049, -1105725609, -1105725625, -1105766569, -1084858521, -1074307737, -1107993097, -1124772393, -1124739625, -1124608571, -1124600491, -1107962507, -1074465435, -1082858123, -1101732553, -1105857193, -1105766569, -1089054889, -1084852234, -1110024730, -1107866137, -1128966665, -1124829737, -1124796969, -1124735017, -1107823243, -1107962523, -1074465435, -1084955273, -1101662953, -1105725689, -1105725625, -1084795049, -1084859033, -1107862169, -1107993097, -1124772393, -1124608569, -1124600379, -1124600491, -1108028059, -1074465435, -1082858123, -1105861355, -1105726121, -1088981177, -1089054889, -1118413322, -1107931674, -1128968729, -1129032201, -1124829737, -1124735545, -1126832683, -1110051467, -1108028059, -1082854043, -1084955273, -1101531369, -1105725625, -1105766585, -1084784777, -1082696346, -1107862169, -1107995145, -1124772393, -1124641337, -1124600377, -1124600459, -1108028059, -1108023835, -1084987913, -1084918313, -1223215657, -1793649273, -1806297705, -1797909065, -1800008281, -1800073801, -1800137321, -1795934825, -1796921977, -1805556281, -1805552137, -731775513, -731775641, -715004585, -672929981, -689703101, -698099901, -698100397, -698231437, -702417565, -685640349, -714868941, -714866925, -706478573, -706609613, -673122525, -1746881231, -1771982573, -1771843309, -1774988525, -1775151277, -1758234653, -1787681310, -1796071962, -1800335898, -1800401450, -1796178490, -1796117049, -1804374841, -1804337929, -1787568907, -705440303, -671877679, -671886383, -680406031, -1754246159, -1791920655, -714906271, -714902237, -681443533, -673054957, -673059325, -689701373, -1763453165, -1763588301, -1772038365, -1770981582, -1775180014, -1640990958, -1624148206, -1619624174, -1611251886, -1611264654, -1783296526, -1779149358, -1779083326, -1779075134, -1786943534, -1786976286, -1786972702, -1653279261, -1644825101, -1611139647, -1611203135, -1619534527, -1619665551, -1892295391, -1653220063, -1653213903, -1661475504, -1661475504, -1661471408, -1661627024, -1644919455, -1653295775, -1623722669, -1640564926, -1640605886, -1640538158, -1636352030, -1745408542, -1749738013, -1783362157, -1779165807, -1804323455, -1804110399, -730434111, -705202703, -705370783, -705370765, -713562861, -680067325, -680075517, -713566701, -713754829, -713758941, -578947229, -1652566671, -1644178095, -1610640047, -1611164559, -1611361183, -1762347675, -1770740379, -1774848651, -1774725675, -1757981227, -1791549961, -1804190297, -1795809881, -1795748425, -1795943017, -1796196969, -1796185641, -723426857, -723394089, -723394445, -715000781, -715000525, -714867437, -681313006, -681313006, -1771905773, -1771905677, -1755064973, -1755195947, -1755187243, -1788749995, -1746676107, -1746807195, -1746864283, -1746864283, -1755257545, -1754204905, -1758333113, -1791789241, -1791854761, -1787660937, -1779301001, -1783561737, -1783623177, -1779403305, -1779337771, -1779171883, -1779171851, -1779303179, -1787699977, -1754147625, -1754009145, -1754009145, -1754008617, -1788611737, -1788610777, -1788750045, -1788739823, -1788477679, -1788477679, -1788346605, -1779968237, -1746807501, -1746799310, -1770784942, -1640802478, -1640933550, -1640949934, -1636354190, -1611253918, -1644886686, -1649142286, -1661717005, -1661717037, -1661639215, -570988591, -570427568, -847390896, -818553007, -818413743, -550109359, -545919151, -545790127, -545851951, -545864207, -537410063, -573188623, -573196845, -589976109, -589976079, -1644904991, -1644907167, -1653295837, -1619667661, -1623730413, -1623763198, -1624015086, -1619890318, -1611497630, -1611182605, -1615525933, -1646962733, -1646954541, -1655212077, -1787340845, -1787472526, -1787464334, -1745490606, -1644696238, -1644692142, -1653211821, -1653219981, -722084495, -721435151, -587344400, -587860528, -596118064, -579340848, -579340816, -545917472, -680135199, -697077389, -684510957, -986500861, -1003417325, -999214798, -990764766, -994962142]
-
-TEST_1_LENGTH = TEST_1A_LENGTH
-TEST_1_FP = TEST_1A_FP
-TEST_1_FP_RAW = TEST_1A_FP_RAW
-
-script = None
-
-SEQUENCES = [
- ('account', 'id'),
- ('application', 'id'),
- ('format', 'id'),
- ('foreignid', 'id'),
- ('foreignid_vendor', 'id'),
- ('source', 'id'),
- ('submission', 'id'),
- ('track', 'id'),
- ('track_mbid', 'id'),
- ('track_mbid_source', 'id'),
- ('track_mbid_change', 'id'),
- ('track_mbid_flag', 'id'),
- ('track_puid', 'id'),
- ('track_puid_source', 'id'),
- ('track_meta', 'id'),
- ('track_meta_source', 'id'),
- ('track_foreignid', 'id'),
- ('track_foreignid_source', 'id'),
- ('fingerprint', 'id'),
- ('fingerprint_source', 'id'),
- ('meta', 'id'),
-]
-
-TABLES = [
- 'account',
- 'application',
- 'format',
- 'foreignid',
- 'foreignid_vendor',
- 'source',
- 'stats',
- 'track',
- 'track_mbid',
- 'track_mbid_source',
- 'track_mbid_change',
- 'track_mbid_flag',
- 'track_puid',
- 'track_puid_source',
- 'track_meta',
- 'track_meta_source',
- 'track_foreignid',
- 'track_foreignid_source',
- 'meta',
- 'musicbrainz.artist_credit_name',
- 'musicbrainz.artist_credit',
- 'musicbrainz.artist_name',
- 'musicbrainz.artist',
- 'musicbrainz.track_name',
- 'musicbrainz.track',
- 'musicbrainz.release',
- 'musicbrainz.medium',
- 'musicbrainz.medium_format',
- 'musicbrainz.recording',
- 'musicbrainz.release_name',
- 'musicbrainz.release_group',
- 'musicbrainz.clientversion',
- 'musicbrainz.tracklist',
-]
-
-BASE_SQL = '''
-INSERT INTO account (name, apikey, lastlogin) VALUES ('User 1', 'user1key', now() - INTERVAL '2 day');
-INSERT INTO account (name, apikey, lastlogin) VALUES ('User 2', 'user2key', now() - INTERVAL '5 day');
-INSERT INTO application (name, apikey, version, account_id) VALUES ('App 1', 'app1key', '0.1', 1);
-INSERT INTO application (name, apikey, version, account_id) VALUES ('App 2', 'app2key', '0.1', 2);
-INSERT INTO format (name) VALUES ('FLAC');
-INSERT INTO source (account_id, application_id) VALUES (1, 1);
-INSERT INTO source (account_id, application_id) VALUES (2, 2);
-INSERT INTO track (id, gid) VALUES
- (1, 'eb31d1c3-950e-468b-9e36-e46fa75b1291'),
- (2, '92732e4b-97c6-4250-b237-1636384d466f'),
- (3, '30e66c45-f761-490a-b1bd-55763e8b59be'),
- (4, '014e973b-368e-42bf-b619-84cab14c4af6');
-INSERT INTO track_mbid (track_id, mbid, submission_count) VALUES (1, 'b81f83ee-4da4-11e0-9ed8-0025225356f3', 1);
-INSERT INTO meta (id, track, artist) VALUES (1, 'Custom Track', 'Custom Artist');
-INSERT INTO meta (id, track, artist) VALUES (2, 'Custom Track', 'Custom Artist');
-INSERT INTO track_meta (track_id, meta_id, submission_count) VALUES (1, 1, 1);
-INSERT INTO track_meta (track_id, meta_id, submission_count) VALUES (1, 2, 10);
-'''
-
-
-def prepare_sequences(conn):
- with conn.begin():
- for table, column in SEQUENCES:
- conn.execute("""
- SELECT setval('%(table)s_%(column)s_seq',
- coalesce((SELECT max(%(column)s) FROM %(table)s), 0) + 1, false)
- """ % {'table': table, 'column': column})
-
-
-def prepare_database(conn, sql, params=None):
- with conn.begin():
- prepare_sequences(conn)
- conn.execute(sql, params)
- prepare_sequences(conn)
-
-
-def with_database(func):
- def wrapper(*args, **kwargs):
- with closing(script.engine.connect()) as conn:
- prepare_sequences(conn)
- trans = conn.begin()
- try:
- func(conn, *args, **kwargs)
- finally:
- trans.rollback()
- wrapper = make_decorator(func)(wrapper)
- return wrapper
-
-
-def setup():
- global script
- config_path = os.path.dirname(os.path.abspath(__file__)) + '/../acoustid-test.conf'
- script = Script(config_path, tests=True)
- if not os.environ.get('SKIP_DB_SETUP'):
- with closing(script.engine.connect()) as conn:
- with conn.begin():
- if os.environ.get('ACOUSTID_TEST_FULL_DB_SETUP'):
- conn.execute('CREATE EXTENSION intarray')
- conn.execute('CREATE EXTENSION pgcrypto')
- conn.execute('CREATE EXTENSION cube')
- conn.execute('CREATE EXTENSION acoustid')
- metadata.create_all(conn)
- for table in reversed(metadata.sorted_tables):
- conn.execute(table.delete())
- prepare_database(conn, BASE_SQL)
-
-
-def make_web_application():
- from acoustid.web.app import make_application
- config_path = os.path.dirname(os.path.abspath(__file__)) + '/../acoustid-test.conf'
- return make_application(config_path, tests=True)
-
-
-def assert_dict_equals(d1, d2, msg=None):
- if d1 != d2:
- standardMsg = '%s != %s' % (repr(d1), repr(d2))
- diff = ('\n' + '\n'.join(difflib.ndiff(
- pprint.pformat(d1).splitlines(),
- pprint.pformat(d2).splitlines())))
- assert d1 == d2, standardMsg + '\n' + diff
-
-
-def assert_json_equals(expected, actual):
- assert_dict_equals(expected, json.loads(actual))
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d6593e34ab7a9098c8e41a485b762c785727f82
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,115 @@
+import os
+import pytest
+from acoustid.config import str_to_bool
+from acoustid.tables import metadata
+from acoustid.data.fingerprint import update_fingerprint_index
+from tests.data import create_sample_data
+
+
+RECREATE_DB = True
+
+
+def get_tables_for_bind(metadata, bind_key):
+ tables = []
+ for table in metadata.sorted_tables:
+ if table.info.get('bind_key', 'default') == bind_key:
+ tables.append(table)
+ return tables
+
+
+def create_all(metadata, engines):
+ for bind_key, engine in engines.items():
+ tables = get_tables_for_bind(metadata, bind_key)
+ with engine.connect() as conn:
+ with conn.begin():
+ metadata.create_all(bind=conn, tables=tables)
+
+
+def drop_all(metadata, engines):
+ for bind_key, engine in engines.items():
+ tables = get_tables_for_bind(metadata, bind_key)
+ with engine.connect() as conn:
+ with conn.begin():
+ metadata.drop_all(bind=conn, tables=tables)
+
+
+def delete_from_all_tables(metadata, engines):
+ for bind_key, engine in engines.items():
+ tables = get_tables_for_bind(metadata, bind_key)
+ with engine.connect() as conn:
+ with conn.begin():
+ for table in reversed(tables):
+ conn.execute(table.delete())
+
+
+def delete_shutdown_file(config):
+ if os.path.exists(config.website.shutdown_file_path):
+ os.remove(config.website.shutdown_file_path)
+
+
+def prepare_database(script):
+ delete_from_all_tables(metadata, script.db_engines)
+ with script.context() as ctx:
+ create_sample_data(ctx)
+ ctx.db.flush()
+ update_fingerprint_index(ctx.db.connection(), ctx.index)
+ ctx.db.commit()
+
+
+@pytest.fixture(scope='session')
+def config_path():
+ return os.path.dirname(os.path.abspath(__file__)) + '/../acoustid-test.conf'
+
+
+@pytest.fixture(scope='session')
+def script_global(config_path):
+ from acoustid.script import Script
+
+ script = Script(config_path, tests=True)
+
+ recreate_db = str_to_bool(os.environ.get('ACOUSTID_TEST_RECREATE', ''))
+ if recreate_db:
+ drop_all(metadata, script.db_engines)
+
+ create_all(metadata, script.db_engines)
+ prepare_database(script)
+
+ yield script
+
+
+@pytest.fixture(scope='session')
+def server_global(script_global, config_path):
+ from acoustid.server import make_application
+
+ server = make_application(config_path, tests=True)
+
+ delete_shutdown_file(server.config)
+
+ yield server
+
+
+@pytest.fixture(scope='session')
+def web_app_global(script_global, config_path):
+ from acoustid.web.app import make_application
+
+ server = make_application(config_path, tests=True)
+
+ delete_shutdown_file(server.acoustid_config)
+
+ yield server
+
+
+@pytest.fixture()
+def server(server_global):
+ yield server_global
+
+
+@pytest.fixture()
+def web_app(web_app_global):
+ yield web_app_global
+
+
+@pytest.fixture()
+def cleanup(script_global):
+ yield
+ prepare_database(script_global)
diff --git a/tests/data.py b/tests/data.py
new file mode 100644
index 0000000000000000000000000000000000000000..c13b817fad4a1f1189c3f36a8ebe031e2a1f6cdd
--- /dev/null
+++ b/tests/data.py
@@ -0,0 +1,24 @@
+from acoustid.models import Account, Application, Fingerprint, Meta, Track, TrackMeta
+
+TEST_2_LENGTH = 320
+TEST_2_FP = 'AQABVtuUZFGShAqO-h9OHD96SvhwBVNCKQnOIYmiIc-ENwF7TDe8Hr0W_AjhvRCP2sfT4DTS7zjyOYeqaI-RSxee5RmaWzhOHnlcaB6HnPgpdE-DkWIH2ysYG_Eh9zJCyfCXGOdw-EGoD2p69IavWOhzMD-a9tBx9FgPVz2qNDvQH3744ISIXRKeHto5_MhyeMtxc-COnYJ_lHLwRAgPvShz_Hga4zd8HD9UKXWOPP3xRLmGnlbQHKfxGPeRvAt6UngMvcF-gkpRi0bUZjGaH6FUHb_xGDt6aHmM__ghfkmH70B4fWiuCj8y8uj3oImZY8d3NFWWHuGF-3hCPEd_uEOyE_nw4w8ueXi24znCHOHxSWtw9BnSBzrSHF2Y4S0e_EioZoh9XMGfo2dqNMeP80aQPM5xGT9efMeTYL-KIqmHdDraHs-P8IcYjoj0I7_Q43iJ9BF64nSKKth2SjG-cvCHH-2OL8txHsUt9HhF4LiK5j16lAf1FkjvQiN55FSOkkOPkmj4GK-OH80eIeyh98HhE_qhPwjzKAV-HJ2OZkd4Q_vhp0d_6Id-_IeWW9CKoP3RKM-Bo3mOfvhxND_6HMgZ6EfXHB-8Q8-iok1znOi-ozmx54P2Dg5V_PCgLxy8KiH6C0cbHU3Ebtiho9Rxw8er47tw7jgRNxl84ziPJ-B1_DiNNClzaGSCvMGPGxePMD5qZYEuAwdTXYSYcIkmodc2nMqg_WgqBk_yBdVx0vCjQD8uhNRxXTgvVFSOSOmx61C1KMaNsFwM93h-PBdmFm8o45nxDabx48cTbGl4hHuhasjSwPtxPvAV1A7yQMukREERR-nxL8j-EbWYQ8sj4joABQQmjQhkjLFCKSAo4QoxYiQwQhgmkGjCKGAIMMA4BIwQwjhAFMBUCCUAYEIxpYxUCDlEjJYOScSMgsIIAgADwjKEFBAUCkMEMYAagoARzAAHCDCIISKANkgYYBiQwgDDjHEMIGWZFUBQLhgohBGkhECOMEAMIYghogTgQghgiSLCYUegAsJApIQjxABNDFWCa6AIAQ4Q4KgAgIABgGDCMNGIMgQJRQAQTACpgBNIJkUcBMkpoKAgXCjAgAAGKIcYIVAYbZgwggkEmKLEiYGYAYQQShFAAAQBFEEAEuEIgwYRQoARnBkAmAGMEAGFGIgQBigCwAkABEIA'
+TEST_2_FP_RAW = [-772063964, -772066012, -1007079116, -1011011276, -1027788492, -1029889740, -1031261916, -1031269084, -1031270620, -1031021744, -1031079600, -1031078425, -1031032346, -1018580506, -1018646290, -1022775046, -1056337446, -1056261686, -1073039030, -1073039013, -1068976005, -1001867175, -733365175, -733302711, -737435575, -603332504, -603365272, -737516311, -188076117, -175490390, -745993558, -758642022, -767030630, -1034347878, -1038412133, -1039584631, -1039654264, -1034345784, -1030086056, -1011141092, -1045873092, -1045939676, -1045947803, -2132276505, -2132259914, -2140415082, -2140472938, -2140481130, -2140546634, -2140603929, -2144802523, -2144536267, -2123540203, -2115147515, -2081855203, -2098598611, -2098606801, -2098606737, -2106995329, -2090217989, -2123638309, -2140477173, -2140477173, -2123634405, -2106992325, -2107061957, -2107061991, -2107007715, -1033140963, -1023769329, -1025864433, -1026913002, -1010133962, -1017409482, -1017540482, -1022848902, -1056337830, -1056271030, -1056261302, -1073038501, -1068975271, -1060587447, -993477559, -1001672631, -737435575, -737566615, -737550231, -737419031, 1422585259, 1955228586, 1367940010, 1388841914, 1380453258, 1376328586, 1376458634, 1107956618, 1107899017, 1113072281, 1121657497, 1119494811, 1135224479, 1135226615, 1139489511, 1130891874, 1126713938, 1109977859, 1114237187, 1122691331, 1122695431, 1122687255, 1114233125, 1130944869, 1126746469, 1097554247, 1105885511, 1105885511, 1135270230, 1122523494, 1114135910, 1109939695, 1093236223, 1076520335, 1080714635, 1089107851, 11092923, 11010986, 15209450, 15492074, 7103274, 2913082, 2905882, 2940681, 2947848, 7138056, 32303368, 61716744, 44932552, 1118668232, 1118406137, 1122600424, 1110167912, 1110167848, 1110106424, 1122689305, 1118495003, 1118478714, 1118540010, 1122599146, 1110016234, 1110147562, 1110094153, 1076535560, 1076538376, -1058363384, -794183656, -794249176, -790063064, -261519320, -261519319, -529562582, -529628886, -530153430, -530280406, -534465494, -534459350, -517027794, -517027778, -517056387, 1630428508, 1634606924, 1643060940, -508616995, -508740929, -475252054, -487834709, -496223301, -496231493, -496092485, -488752486, -489735542, -494125366, -494125542, 1641889598, 1627335998, 1617898782, 1613703454, 1614756622, 537664270, 541854222, 541854238, 541874782, 558651982, 558168910, 558168910, 558168398, 566360398, 1636039038, 1669593454, 1938028670, 1942087766, 1942087766, 1665394807, 1631779173, 1640192868, 1640221300, 1640483428, 1640487796, 1631902020, 1627682884, 553932868, 554654068, 554589029, 567179879, 562985575, 562846279, 562879301, 558684487, 554678646, 554678646, 558873462, 567262070, 563067366, 562936022, 567064775, 558692565, 1628436725, 1661925605, 1661893095, 1666087909, 592329573, 567032663, 567032133, 567032132, 1640840020, 1909340900, 1909340900, -238142748, -775079212, -775152956, -1043580220, -1047774524, -2121450764, -2138162460, -2138162460, -2138232091, -2121520409, -2117330313, -2124670345, -2124604585, -2092205227, -2083848891, -2083787451, -2117489195, -2117550619, -2124902943, -2139517453, -2139383405, -2122597997, -2122598221, -2093090639, -2095162991, -2107737727, -2107754111, -2108805231, -2099495199, -2099499291, -2097402137, -2098451465, -2098709513, -2098717737, -2081859113, -2123773481, -2140434985, -2140496425, -2140428906, -2132171338, -2132236874, -2065124170, -2023181130, -2039964482, -2044162882, -2044098306, -2111137761, -2111117043, -2111165684, -2144720372, -2140460532, -2132132340, -2136328643, -2136407507, -2136471761, -2136471761, -2132277457, -2114187977, -2091268651, -2083809851, -2083872379, -2117361257, -2117552729, -2141681241, -2139584009, -2143708937, -2143618857, -2126874411, -2126894891, -2093339196, -2105923644, -2099628348, -2103836012, -2103966047, -2099751259, -2097639449, -2031579161, -2039763466, -2031375914, -2014728234, -2056675370, -2056548654, -2073127278, -2077251950, -2077233262, -2077266510, -2077332302, -2073154382, -2014434126, -2031143722, -2039794617, -2039792379, -2039792868, -2107033028, -2081936836, -2136462820, -2136265204, -2136263155, -2136456385, -2136456401, -2132228626, -2122791506, -2091070041, -2107647561, -2108769915, -2100384379]
+
+
+def create_sample_data(ctx):
+ acct1 = Account(name='user1', apikey='user1_api_key')
+ ctx.db.add(acct1)
+
+ app1 = Application(name='app1', apikey='app1_api_key', version='1.0', account=acct1)
+ ctx.db.add(app1)
+
+ tr1 = Track(gid='eb31d1c3-950e-468b-9e36-e46fa75b1291')
+ ctx.db.add(tr1)
+
+ m1 = Meta(track='Custom Track', artist='Custom Artist')
+ ctx.db.add(m1)
+
+ ctx.db.add(TrackMeta(track=tr1, meta=m1, submission_count=1))
+
+ fp1 = Fingerprint(id=1, track=tr1, fingerprint=TEST_2_FP_RAW, length=TEST_2_LENGTH, submission_count=1)
+ ctx.db.add(fp1)
diff --git a/tests/test_api.py b/tests/test_api.py
index d3e2bd0d5840626b3ae1d82a7dc10d826ee39f17..68526db5d9600407c16169897d9026cdb82b7263 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,40 +1,8 @@
-# Copyright (C) 2011 Lukas Lalinsky
-# Distributed under the MIT license, see the LICENSE file for details.
+import pytest
+from webtest import TestApp, AppError
-from nose.tools import assert_equals
-from acoustid.api import serialize_response
-
-def test_serialize_json():
- data = {'status': 'ok', 'artists': [{'name': 'Jean Michel Jarre', 'year': 1948, 'cities': ['Paris', 'Lyon']}]}
- resp = serialize_response(data, 'json')
- assert_equals('application/json; charset=UTF-8', resp.content_type)
- expected = b'''{"artists": [{"cities": ["Paris", "Lyon"], "name": "Jean Michel Jarre", "year": 1948}], "status": "ok"}'''
- assert_equals(expected, resp.data)
-
-
-def test_serialize_jsonp():
- data = {'status': 'ok', 'artists': [{'name': 'Jean Michel Jarre', 'year': 1948, 'cities': ['Paris', 'Lyon']}]}
- resp = serialize_response(data, 'jsonp:getData')
- assert_equals('application/javascript; charset=UTF-8', resp.content_type)
- expected = b'''getData({"artists": [{"cities": ["Paris", "Lyon"], "name": "Jean Michel Jarre", "year": 1948}], "status": "ok"})'''
- assert_equals(expected, resp.data)
-
-
-def test_serialize_xml():
- data = {'status': 'ok', 'artists': [{'name': 'Jean Michel Jarre', 'year': 1948, 'cities': ['Paris', 'Lyon']}]}
- resp = serialize_response(data, 'xml')
- assert_equals('text/xml; charset=UTF-8', resp.content_type)
- expected = (
- '''\nokParisLyon'''
- '''Jean Michel Jarre1948'''
- )
- assert_equals(expected, resp.data)
-
-
-def test_serialize_xml_attribute():
- data = {'@status': 'ok'}
- resp = serialize_response(data, 'xml')
- assert_equals('text/xml; charset=UTF-8', resp.content_type)
- expected = '''\n'''
- assert_equals(expected, resp.data)
+def test_server(server):
+ client = TestApp(server)
+ with pytest.raises(AppError, match=r".* 404 NOT FOUND .*"):
+ client.get('/')
diff --git a/tests/test_api_health.py b/tests/test_api_health.py
new file mode 100644
index 0000000000000000000000000000000000000000..8037e7f9e1a458e62ca2d327de267486f989d753
--- /dev/null
+++ b/tests/test_api_health.py
@@ -0,0 +1,63 @@
+import os
+import pytest
+from webtest import TestApp, AppError
+
+
+def test_health(server):
+ client = TestApp(server)
+ resp = client.get('/_health')
+ assert resp.status_int == 200
+
+
+def test_health_on_slave(server):
+ server.config.cluster.role = 'slave'
+ try:
+ client = TestApp(server)
+ with pytest.raises(AppError, match=r".* 503 SERVICE UNAVAILABLE .*"):
+ client.get('/_health')
+ finally:
+ server.config.cluster.role = 'master'
+
+
+def test_health_ro(server):
+ client = TestApp(server)
+ resp = client.get('/_health_ro')
+ assert resp.status_int == 200
+
+
+def test_health_ro_on_slave(server):
+ server.config.cluster.role = 'slave'
+ try:
+ client = TestApp(server)
+ resp = client.get('/_health_ro')
+ assert resp.status_int == 200
+ finally:
+ server.config.cluster.role = 'master'
+
+
+def test_health_docker(server):
+ client = TestApp(server)
+ resp = client.get('/_health_docker')
+ assert resp.status_int == 200
+
+
+def test_health_docker_on_slave(server):
+ server.config.cluster.role = 'slave'
+ try:
+ client = TestApp(server)
+ resp = client.get('/_health_docker')
+ assert resp.status_int == 200
+ finally:
+ server.config.cluster.role = 'master'
+
+
+@pytest.mark.parametrize('endpoint', ['health', 'health_ro', 'health_docker'])
+def test_health_while_shutdown(server, endpoint):
+ with open(server.config.website.shutdown_file_path, 'wt') as shutdown_file:
+ shutdown_file.write('shutdown')
+ try:
+ client = TestApp(server)
+ with pytest.raises(AppError, match=r".* 503 SERVICE UNAVAILABLE .*"):
+ client.get('/_' + endpoint)
+ finally:
+ os.remove(server.config.website.shutdown_file_path)
diff --git a/tests/test_api_v2_lookup.py b/tests/test_api_v2_lookup.py
new file mode 100644
index 0000000000000000000000000000000000000000..7170d1e77d85e7b46821a605ba6e91cdcd7dc1d2
--- /dev/null
+++ b/tests/test_api_v2_lookup.py
@@ -0,0 +1,82 @@
+import pytest
+from webtest import TestApp, AppError
+from tests.data import TEST_2_FP, TEST_2_LENGTH
+
+
+def test_lookup_invalid_client(server):
+ client = TestApp(server)
+ with pytest.raises(AppError, match=r".* 400 BAD REQUEST .*") as ex:
+ client.post('/v2/lookup', {
+ 'client': 'xxx',
+ })
+ ex.match(r'.*"invalid API key\b.*')
+
+
+def test_lookup_missing_fingerprint(server):
+ client = TestApp(server)
+ with pytest.raises(AppError, match=r".* 400 BAD REQUEST .*") as ex:
+ client.post('/v2/lookup', {
+ 'client': 'app1_api_key',
+ })
+ ex.match(r'.*"missing required parameter.*fingerprint')
+
+
+def test_lookup_missing_duration(server):
+ client = TestApp(server)
+ with pytest.raises(AppError, match=r".* 400 BAD REQUEST .*") as ex:
+ client.post('/v2/lookup', {
+ 'client': 'app1_api_key',
+ 'fingerprint': TEST_2_FP,
+ })
+ ex.match(r'.*"missing required parameter.*duration')
+
+
+def test_lookup_by_fingerprint(server):
+ client = TestApp(server)
+ resp = client.post('/v2/lookup', {
+ 'client': 'app1_api_key',
+ 'fingerprint': TEST_2_FP,
+ 'duration': str(TEST_2_LENGTH),
+ 'meta': 'recordings usermeta',
+ })
+ assert resp.status_int == 200
+ assert resp.json == {
+ 'status': 'ok',
+ 'results': [
+ {
+ 'id': 'eb31d1c3-950e-468b-9e36-e46fa75b1291',
+ 'score': 1.0,
+ 'recordings': [
+ {
+ 'artists': ['Custom Artist'],
+ 'title': 'Custom Track',
+ }
+ ],
+ },
+ ],
+ }
+
+
+def test_lookup_by_track_id(server):
+ client = TestApp(server)
+ resp = client.post('/v2/lookup', {
+ 'client': 'app1_api_key',
+ 'trackid': 'eb31d1c3-950e-468b-9e36-e46fa75b1291',
+ 'meta': 'recordings usermeta',
+ })
+ assert resp.status_int == 200
+ assert resp.json == {
+ 'status': 'ok',
+ 'results': [
+ {
+ 'id': 'eb31d1c3-950e-468b-9e36-e46fa75b1291',
+ 'score': 1.0,
+ 'recordings': [
+ {
+ 'artists': ['Custom Artist'],
+ 'title': 'Custom Track',
+ }
+ ],
+ },
+ ],
+ }
diff --git a/tests/test_api_v2_submit.py b/tests/test_api_v2_submit.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6c8d51732d894ee7cd0bc4cfe94cc434aa689b8
--- /dev/null
+++ b/tests/test_api_v2_submit.py
@@ -0,0 +1,73 @@
+import pytest
+from webtest import TestApp, AppError
+from acoustid.models import Submission
+from tests.data import TEST_2_FP, TEST_2_FP_RAW, TEST_2_LENGTH
+
+
+def test_submit_invalid_client(server):
+ client = TestApp(server)
+ with pytest.raises(AppError, match=r".* 400 BAD REQUEST .*") as ex:
+ client.post('/v2/submit', {
+ 'client': 'xxx',
+ })
+ ex.match(r'.*"invalid API key\b.*')
+
+
+def test_submit_invalid_user(server):
+ client = TestApp(server)
+ with pytest.raises(AppError, match=r".* 400 BAD REQUEST .*") as ex:
+ client.post('/v2/submit', {
+ 'client': 'app1_api_key',
+ 'user': 'xxx',
+ })
+ ex.match(r'.*"invalid user API key\b.*')
+
+
+@pytest.mark.usefixtures("cleanup")
+def test_submit(server):
+ client = TestApp(server)
+ resp = client.post('/v2/submit', {
+ 'client': 'app1_api_key',
+ 'user': 'user1_api_key',
+ 'fingerprint': TEST_2_FP,
+ 'duration': str(TEST_2_LENGTH),
+ 'mbid': '7b924948-200e-4517-921f-5eaaa9300db2',
+ 'puid': 'b2325721-1584-4afb-ac84-f265d80650c4',
+ 'foreignid': 'youtube:dN44xpHjNxE',
+ })
+ assert resp.status_int == 200
+
+ with server.context() as ctx:
+ submissions = ctx.db.query(Submission).all()
+ assert len(submissions) == 1
+ s = submissions[0]
+ assert s.fingerprint == TEST_2_FP_RAW
+ assert s.duration == TEST_2_LENGTH
+ assert s.mbid == '7b924948-200e-4517-921f-5eaaa9300db2'
+ assert s.puid == 'b2325721-1584-4afb-ac84-f265d80650c4'
+ assert s.foreignid == 'youtube:dN44xpHjNxE'
+
+ assert resp.json == {
+ 'status': 'ok',
+ 'submissions': [
+ {
+ 'id': s.id,
+ 'status': 'pending',
+ },
+ ],
+ }
+
+ resp = client.post('/v2/submission_status', {
+ 'client': 'app1_api_key',
+ 'id': str(s.id),
+ })
+ assert resp.status_int == 200
+ assert resp.json == {
+ 'status': 'ok',
+ 'submissions': [
+ {
+ 'id': s.id,
+ 'status': 'pending',
+ },
+ ],
+ }
diff --git a/tests/test_web.py b/tests/test_web.py
index bd0a2913636a0e801e3e19a8f9d4deb42d6cfdb8..cc71007e40b8aa1feca4a844ebbdcc59b1cceb0a 100644
--- a/tests/test_web.py
+++ b/tests/test_web.py
@@ -1,21 +1,126 @@
-from nose.tools import assert_equal
-from acoustid.web import db
-from tests import make_web_application
+from webtest import TestApp
-app = None
+def test_index(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/')
+ resp.mustcontain(
+ 'Welcome to AcoustID!',
+ 'Applications',
+ 'Statistics',
+ 'Documentation',
+ 'Donate',
+ 'Sign in',
+ )
-def setup():
- global app
- app = make_web_application()
- app.config['TESTING'] = True
+def test_applications(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/applications')
+ resp.mustcontain('MusicBrainz Picard')
-def test_track_page():
- client = app.test_client()
- rv = client.get('/track/eb31d1c3-950e-468b-9e36-e46fa75b1291')
- assert_equal(rv.status_code, 200)
- assert_equal(rv.data.count('Custom Track'), 1)
- assert_equal(rv.data.count('Custom Artist'), 1)
- assert not db.session.registry.has()
+def test_stats(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/stats')
+ resp.mustcontain(
+ 'Basic statistics',
+ 'Daily additions',
+ 'Searches',
+ 'AcoustIDs per the number of linked recordings',
+ 'Recordings per the number of linked AcoustIDs',
+ )
+
+
+def test_docs(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/docs')
+ resp.mustcontain(
+ 'Documentation',
+ 'Frequently Asked Questions',
+ 'Web Service',
+ 'Server',
+ 'Chromaprint',
+ 'Fingerprinter',
+ )
+
+
+def test_faq(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/faq')
+ resp.mustcontain('Frequently Asked Questions')
+
+
+def test_webservice(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/webservice')
+ resp.mustcontain(
+ 'Web Service',
+ 'Usage Guidelines',
+ 'Overview',
+ 'Compression',
+ 'API Keys',
+ 'Methods',
+ 'Lookup by fingerprint',
+ 'Lookup by track ID',
+ 'Submit',
+ 'Get submission status',
+ 'List AcoustIDs by MBID',
+ )
+
+
+def test_server(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/server')
+ resp.mustcontain('Server')
+
+
+def test_chromaprint(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/chromaprint')
+ resp.mustcontain('Chromaprint', 'Download', 'Usage')
+
+
+def test_donate(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/donate')
+ resp.mustcontain('Donate Money')
+
+
+def test_login(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/login')
+ resp.mustcontain('Sign in')
+
+
+def test_contact(web_app):
+ client = TestApp(web_app)
+ resp = client.get('/contact')
+ resp.mustcontain(
+ 'Contact us',
+ 'acoustid@googlegroups.com',
+ 'info@acoustid.org',
+ '#acoustid',
+ )
+
+
+def test_track(web_app):
+ client = TestApp(web_app)
+
+ resp = client.get('/track/eb31d1c3-950e-468b-9e36-e46fa75b1291')
+ resp.mustcontain(
+ 'eb31d1c3-950e-468b-9e36-e46fa75b1291',
+ 'Track',
+ 'Custom Track',
+ 'Custom Artist',
+ )
+
+
+def test_fingerprint(web_app):
+ client = TestApp(web_app)
+
+ resp = client.get('/fingerprint/1')
+ resp.mustcontain(
+ 'Fingerprint #1',
+ 'eb31d1c3-950e-468b-9e36-e46fa75b1291',
+ )
diff --git a/tox.ini b/tox.ini
index f272f0c1eca9cab8e3d705e064c89e6aa6b56390..75ab1a67b69af35df4dc655e9c44bf0abfb96966 100644
--- a/tox.ini
+++ b/tox.ini
@@ -7,8 +7,10 @@ skipsdist = true
deps =
-rrequirements.txt
-rrequirements_dev.txt
-commands = nosetests -v
-setenv = PYTHONHASHSEED = 123
+commands = py.test -v tests/
+setenv =
+ PYTHONHASHSEED = 123
+ PYTHONPATH = {toxinidir}
passenv = ACOUSTID_TEST_*
[testenv:mypy-py2]
@@ -29,6 +31,9 @@ skip_install = true
deps = flake8
commands = flake8 acoustid/ tests/
+[pytest]
+python_classes = !TestApp
+
[flake8]
ignore = E128,E121
max-line-length = 160
@@ -36,3 +41,4 @@ per-file-ignores =
tests/__init__.py:E501
tests/test_api_v1.py:E501
tests/test_pg.py:E231,E501
+ tests/data.py:E501