def setUp(self):
     super(InfluxWriterTestCase, self).setUp()
     self.ls = InfluxListenStore({ 'REDIS_HOST': config.REDIS_HOST,
                          'REDIS_PORT': config.REDIS_PORT,
                          'REDIS_NAMESPACE': config.REDIS_NAMESPACE,
                          'INFLUX_HOST': config.INFLUX_HOST,
                          'INFLUX_PORT': config.INFLUX_PORT,
                          'INFLUX_DB_NAME': config.INFLUX_DB_NAME})
예제 #2
0
 def setUp(self):
     super(APICompatDeprecatedTestCase, self).setUp()
     self.user = db_user.get_or_create('apicompatoldtestuser')
     self.ls = InfluxListenStore({
         'REDIS_HOST': config.REDIS_HOST,
         'REDIS_PORT': config.REDIS_PORT,
         'INFLUX_HOST': config.INFLUX_HOST,
         'INFLUX_PORT': config.INFLUX_PORT,
         'INFLUX_DB_NAME': config.INFLUX_DB_NAME,
     })
 def setUp(self):
     super(InfluxWriterTestCase, self).setUp()
     self.ls = InfluxListenStore({ 'REDIS_HOST': config.REDIS_HOST,
                          'REDIS_PORT': config.REDIS_PORT,
                          'REDIS_NAMESPACE': config.REDIS_NAMESPACE,
                          'INFLUX_HOST': config.INFLUX_HOST,
                          'INFLUX_PORT': config.INFLUX_PORT,
                          'INFLUX_DB_NAME': config.INFLUX_DB_NAME}, self.app.logger)
예제 #4
0
    def setUp(self):
        super(APICompatTestCase, self).setUp()
        self.lb_user = db_user.get_or_create(1, 'apicompattestuser')
        self.lfm_user = User(
            self.lb_user['id'],
            self.lb_user['created'],
            self.lb_user['musicbrainz_id'],
            self.lb_user['auth_token'],
        )

        self.ls = InfluxListenStore(
            {
                'REDIS_HOST': current_app.config['REDIS_HOST'],
                'REDIS_PORT': current_app.config['REDIS_PORT'],
                'REDIS_NAMESPACE': current_app.config['REDIS_NAMESPACE'],
                'INFLUX_HOST': current_app.config['INFLUX_HOST'],
                'INFLUX_PORT': current_app.config['INFLUX_PORT'],
                'INFLUX_DB_NAME': current_app.config['INFLUX_DB_NAME'],
            }, self.app.logger)
예제 #5
0
    def start(self):
        self.log.info("influx-writer init")

        self._verify_hosts_in_config()

        if not hasattr(self.config, "INFLUX_HOST"):
            self.log.error("Influx service not defined. Sleeping {0} seconds and exiting.".format(self.ERROR_RETRY_DELAY))
            sleep(self.ERROR_RETRY_DELAY)
            sys.exit(-1)

        while True:
            try:
                self.ls = InfluxListenStore({ 'REDIS_HOST' : self.config.REDIS_HOST,
                                              'REDIS_PORT' : self.config.REDIS_PORT,
                                              'INFLUX_HOST': self.config.INFLUX_HOST,
                                              'INFLUX_PORT': self.config.INFLUX_PORT,
                                              'INFLUX_DB_NAME': self.config.INFLUX_DB_NAME})
                self.influx = InfluxDBClient(host=self.config.INFLUX_HOST, port=self.config.INFLUX_PORT, database=self.config.INFLUX_DB_NAME)
                break
            except Exception as err:
                self.log.error("Cannot connect to influx: %s. Retrying in 2 seconds and trying again." % str(err))
                sleep(ERROR_RETRY_DELAY)

        while True:
            try:
                self.redis = Redis(host=self.config.REDIS_HOST, port=self.config.REDIS_PORT, decode_responses=True)
                self.redis.ping()
                break
            except Exception as err:
                self.log.error("Cannot connect to redis: %s. Retrying in 2 seconds and trying again." % str(err))
                sleep(ERROR_RETRY_DELAY)

        while True:
            self.connect_to_rabbitmq()
            self.incoming_ch = self.connection.channel()
            self.incoming_ch.exchange_declare(exchange=self.config.INCOMING_EXCHANGE, exchange_type='fanout')
            self.incoming_ch.queue_declare(self.config.INCOMING_QUEUE, durable=True)
            self.incoming_ch.queue_bind(exchange=self.config.INCOMING_EXCHANGE, queue=self.config.INCOMING_QUEUE)
            self.incoming_ch.basic_consume(
                lambda ch, method, properties, body: self.static_callback(ch, method, properties, body, obj=self),
                queue=self.config.INCOMING_QUEUE,
            )

            self.unique_ch = self.connection.channel()
            self.unique_ch.exchange_declare(exchange=self.config.UNIQUE_EXCHANGE, exchange_type='fanout')

            self.log.info("influx-writer started")
            try:
                self.incoming_ch.start_consuming()
            except pika.exceptions.ConnectionClosed:
                self.log.info("Connection to rabbitmq closed. Re-opening.")
                self.connection = None
                continue

            self.connection.close()
 def setUp(self):
     super(APICompatDeprecatedTestCase, self).setUp()
     self.user = db_user.get_or_create(1, 'apicompatoldtestuser')
     self.ls = InfluxListenStore({
         'REDIS_HOST': config.REDIS_HOST,
         'REDIS_PORT': config.REDIS_PORT,
         'REDIS_NAMESPACE': config.REDIS_NAMESPACE,
         'INFLUX_HOST': config.INFLUX_HOST,
         'INFLUX_PORT': config.INFLUX_PORT,
         'INFLUX_DB_NAME': config.INFLUX_DB_NAME,
     }, self.app.logger)
예제 #7
0
def init_influx_connection(logger, conf):
    global _influx
    while True:
        try:
            _influx = InfluxListenStore(conf)
            break
        except Exception as e:
            logger.error(
                "Couldn't create InfluxListenStore instance: {}".format(
                    str(e)))
            logger.error("Sleeping 2 seconds and then retrying...")
            time.sleep(2)

    logger.info("Successfully created InfluxListenStore instance!")
    return _influx
def init_influx_connection(logger, conf):
    global _influx
    while True:
        try:
            _influx = InfluxListenStore(conf, logger)
            break
        except Exception as e:
            logger.error(
                "Couldn't create InfluxListenStore instance: {}, sleeping and trying again..."
                .format(str(e)),
                exc_info=True)
            time.sleep(2)

    logger.info("Successfully created InfluxListenStore instance!")
    return _influx
예제 #9
0
def init_influx_connection():
    """ Connects to influx and returns an InfluxListenStore instance """
    while True:
        try:
            return InfluxListenStore({
                'REDIS_HOST': config.REDIS_HOST,
                'REDIS_PORT': config.REDIS_PORT,
                'INFLUX_HOST': config.INFLUX_HOST,
                'INFLUX_PORT': config.INFLUX_PORT,
                'INFLUX_DB_NAME': config.INFLUX_DB_NAME,
            })
        except Exception as e:
            logger.error(
                "Couldn't create InfluxListenStore instance: {}".format(
                    str(e)))
            logger.error("Sleeping 2 seconds and then retrying...")
            time.sleep(2)
    def setUp(self):
        super(APICompatTestCase, self).setUp()
        self.lb_user = db_user.get_or_create(1, 'apicompattestuser')
        self.lfm_user = User(
            self.lb_user['id'],
            self.lb_user['created'],
            self.lb_user['musicbrainz_id'],
            self.lb_user['auth_token'],
        )

        self.ls = InfluxListenStore({
            'REDIS_HOST': current_app.config['REDIS_HOST'],
            'REDIS_PORT': current_app.config['REDIS_PORT'],
            'REDIS_NAMESPACE': current_app.config['REDIS_NAMESPACE'],
            'INFLUX_HOST': current_app.config['INFLUX_HOST'],
            'INFLUX_PORT': current_app.config['INFLUX_PORT'],
            'INFLUX_DB_NAME': current_app.config['INFLUX_DB_NAME'],
        }, self.app.logger)
class APICompatDeprecatedTestCase(APICompatIntegrationTestCase):

    def setUp(self):
        super(APICompatDeprecatedTestCase, self).setUp()
        self.user = db_user.get_or_create(1, 'apicompatoldtestuser')
        self.ls = InfluxListenStore({
            'REDIS_HOST': config.REDIS_HOST,
            'REDIS_PORT': config.REDIS_PORT,
            'REDIS_NAMESPACE': config.REDIS_NAMESPACE,
            'INFLUX_HOST': config.INFLUX_HOST,
            'INFLUX_PORT': config.INFLUX_PORT,
            'INFLUX_DB_NAME': config.INFLUX_DB_NAME,
        }, self.app.logger)


    def handshake(self, user_name, auth_token, timestamp):
        """ Makes a request to the handshake endpoint of the AudioScrobbler API and
            returns the response.
        """

        args = {
            'hs': 'true',
            'p': '1.2',
            'c': 'tst',
            'v': '0.1',
            'u': user_name,
            't': timestamp,
            'a': auth_token
        }

        return self.client.get('/', query_string=args)


    def test_handshake(self):
        """ Tests handshake for a user that exists """

        timestamp = int(time.time())
        audioscrobbler_auth_token = _get_audioscrobbler_auth_token(self.user['auth_token'], timestamp)

        r = self.handshake(self.user['musicbrainz_id'], audioscrobbler_auth_token, timestamp)

        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(len(response), 5)
        self.assertEqual(response[0], 'OK')
        self.assertEqual(len(response[1]), 32)

    def test_handshake_post(self):
        """ Tests POST requests to handshake endpoint """

        ts = int(time.time())
        args = {
            'hs': 'true',
            'p': '1.2',
            'c': 'tst',
            'v': '0.1',
            'u': self.user['musicbrainz_id'],
            't': ts,
            'a': _get_audioscrobbler_auth_token(self.user['auth_token'], ts)
        }

        r = self.client.post('/', query_string=args)

        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(len(response), 5)
        self.assertEqual(response[0], 'OK')
        self.assertEqual(len(response[1]), 32)


    def test_root_url_when_no_handshake(self):
        """ Tests the root url when there's no handshaking taking place """

        r = self.client.get('/')
        self.assertStatus(r, 302)


    def test_handshake_unknown_user(self):
        """ Tests handshake for user that is not in the db """

        r = self.handshake('', '', '')
        self.assert401(r)


    def test_handshake_invalid_auth(self):
        """ Tests handshake when invalid authorization token is sent """

        r = self.handshake(self.user['musicbrainz_id'], '', int(time.time()))
        self.assert401(r)


    def test_submit_listen(self):
        """ Sends a valid listen after handshaking and checks if it is present in the
            listenstore
        """

        timestamp = int(time.time())
        audioscrobbler_auth_token = _get_audioscrobbler_auth_token(self.user['auth_token'], timestamp)

        r = self.handshake(self.user['musicbrainz_id'], audioscrobbler_auth_token, timestamp)
        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(response[0], 'OK')

        sid = response[1]
        data = {
            's': sid,
            'a[0]': 'Kishore Kumar',
            't[0]': 'Saamne Ye Kaun Aya',
            'o[0]': 'P',
            'l[0]': 300,
            'b[0]': 'Jawani Diwani',
            'i[0]': int(time.time()),
        }

        r = self.client.post(url_for('api_compat_old.submit_listens'), data=data)
        self.assert200(r)
        self.assertEqual(r.data.decode('utf-8'), 'OK\n')

        time.sleep(1)
        to_ts = int(time.time())
        listens = self.ls.fetch_listens(self.user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)


    def test_submit_listen_invalid_sid(self):
        """ Tests endpoint for 400 Bad Request if invalid session id is sent """

        sid = ''
        data = {
            's': sid,
            'a[0]': 'Kishore Kumar',
            't[0]': 'Saamne Ye Kaun Aya',
            'o[0]': 'P',
            'l[0]': 300,
            'b[0]': 'Jawani Diwani',
            'i[0]': int(time.time()),
        }

        r = self.client.post(url_for('api_compat_old.submit_listens'), data=data)
        self.assert401(r)
        self.assertEqual(r.data.decode('utf-8'), 'BADSESSION\n')


    def test_submit_listen_invalid_data(self):
        """ Tests endpoint for 400 Bad Request if invalid data is sent """

        timestamp = int(time.time())
        audioscrobbler_auth_token = _get_audioscrobbler_auth_token(self.user['auth_token'], timestamp)

        r = self.handshake(self.user['musicbrainz_id'], audioscrobbler_auth_token, timestamp)
        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(response[0], 'OK')

        sid = response[1]

        # no artist in data
        data = {
            's': sid,
            't[0]': 'Saamne Ye Kaun Aya',
            'o[0]': 'P',
            'l[0]': 300,
            'b[0]': 'Jawani Diwani',
            'i[0]': int(time.time()),
        }

        r = self.client.post(url_for('api_compat_old.submit_listens'), data=data)
        self.assert400(r)
        self.assertEqual(r.data.decode('utf-8').split()[0], 'FAILED')

        # add artist and remove track name
        data['a[0]'] = 'Kishore Kumar'
        del data['t[0]']
        r = self.client.post(url_for('api_compat_old.submit_listens'), data=data)
        self.assert400(r)
        self.assertEqual(r.data.decode('utf-8').split()[0], 'FAILED')

        # add track name and remove timestamp
        data['t[0]'] = 'Saamne Ye Kaun Aya'
        del data['i[0]']
        r = self.client.post(url_for('api_compat_old.submit_listens'), data=data)
        self.assert400(r)
        self.assertEqual(r.data.decode('utf-8').split()[0], 'FAILED')

        # re-add a timestamp in ns
        data['i[0]'] = int(time.time()) * 10**9
        r = self.client.post(url_for('api_compat_old.submit_listens'), data=data)
        self.assert400(r)
        self.assertEqual(r.data.decode('utf-8').split()[0], 'FAILED')


    def test_playing_now(self):
        """ Tests playing now notifications """

        timestamp = int(time.time())
        audioscrobbler_auth_token = _get_audioscrobbler_auth_token(self.user['auth_token'], timestamp)

        r = self.handshake(self.user['musicbrainz_id'], audioscrobbler_auth_token, timestamp)
        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(response[0], 'OK')

        sid = response[1]
        data = {
            's': sid,
            'a': 'Kishore Kumar',
            't': 'Saamne Ye Kaun Aya',
            'b': 'Jawani Diwani',
        }

        r = self.client.post(url_for('api_compat_old.submit_now_playing'), data=data)
        self.assert200(r)
        self.assertEqual(r.data.decode('utf-8'), 'OK\n')


    def test_get_session(self):
        """ Tests _get_session method in api_compat_deprecated """

        s = Session.create_by_user_id(self.user['id'])

        session = _get_session(s.sid)
        self.assertEqual(s.sid, session.sid)


    def test_get_session_which_doesnt_exist(self):
        """ Make sure BadRequest is raised when we try to get a session that doesn't exists """

        with self.assertRaises(BadRequest):
            session = _get_session('')


    def test_404(self):

        r = self.client.get('/thisurldoesnotexist')
        self.assert404(r)


    def test_to_native_api_now_playing(self):
        """ Tests _to_native_api when used with data sent to the now_playing endpoint """

        data = {
            's': '',
            'a': 'Kishore Kumar',
            't': 'Saamne Ye Kaun Aya',
            'b': 'Jawani Diwani',
        }

        native_data = _to_native_api(data, '')
        self.assertDictEqual(native_data, {
                'track_metadata': {
                    'track_name': 'Saamne Ye Kaun Aya',
                    'artist_name': 'Kishore Kumar',
                    'release_name': 'Jawani Diwani'
                }
            }
        )

        del data['a']
        native_data = _to_native_api(data, '')
        self.assertIsNone(native_data)

        data['a'] = 'Kishore Kumar'
        del data['t']
        native_data = _to_native_api(data, '')
        self.assertIsNone(native_data)

        data['t'] = 'Saamne Ye Kaun Aya'
        del data['b']
        native_data = _to_native_api(data, '')
        self.assertIsNone(native_data)


    def test_to_native_api(self):
        """ Tests _to_native_api with data that is sent to the submission endpoint """

        t = int(time.time())

        data = {
            's': '',
            'a[0]': 'Kishore Kumar',
            't[0]': 'Saamne Ye Kaun Aya',
            'o[0]': 'P',
            'l[0]': 300,
            'b[0]': 'Jawani Diwani',
            'i[0]': t,
            'a[1]': 'Kishore Kumar',
            't[1]': 'Wada Karo',
            'o[1]': 'P',
            'l[1]': 300,
            'b[1]': 'Jawani Diwani',
            'i[1]': t + 10,
        }

        self.assertDictEqual(_to_native_api(data, '[0]'), {
            'listened_at': t,
            'track_metadata': {
                'artist_name': 'Kishore Kumar',
                'track_name':  'Saamne Ye Kaun Aya',
                'release_name': 'Jawani Diwani',
                'additional_info': {
                    'source': 'P',
                    'track_length': 300
                }
            }
        })

        self.assertDictEqual(_to_native_api(data, '[1]'), {
            'listened_at': t + 10,
            'track_metadata': {
                'artist_name': 'Kishore Kumar',
                'track_name':  'Wada Karo',
                'release_name': 'Jawani Diwani',
                'additional_info': {
                    'source': 'P',
                    'track_length': 300
                }
            }
        })

        del data['a[0]']
        self.assertIsNone(_to_native_api(data, '[0]'))

        data['a[0]'] = 'Kishore Kumar'
        del data['t[0]']
        native_data = _to_native_api(data, '[0]')
        self.assertIsNone(native_data)

        data['t[0]'] = 'Saamne Ye Kaun Aya'
        del data['b[0]']
        native_data = _to_native_api(data, '[0]')
        self.assertIsNone(native_data)
#!/usr/bin/env python3

# This script flushes out the listencounts that are in the 7day retention policy and sums them
# into the permanent listen count measurement.

from listenbrainz import config
from listenbrainz.listenstore import InfluxListenStore

ls = InfluxListenStore({ 'REDIS_HOST': config.REDIS_HOST,
                         'REDIS_PORT': config.REDIS_PORT,
                         'REDIS_NAMESPACE': config.REDIS_NAMESPACE,
                         'INFLUX_HOST': config.INFLUX_HOST,
                         'INFLUX_PORT': config.INFLUX_PORT,
                         'INFLUX_DB_NAME': config.INFLUX_DB_NAME})
ls.update_listen_counts()
class InfluxWriterTestCase(IntegrationTestCase):
    def setUp(self):
        super(InfluxWriterTestCase, self).setUp()
        self.ls = InfluxListenStore({
            'REDIS_HOST': config.REDIS_HOST,
            'REDIS_PORT': config.REDIS_PORT,
            'INFLUX_HOST': config.INFLUX_HOST,
            'INFLUX_PORT': config.INFLUX_PORT,
            'INFLUX_DB_NAME': config.INFLUX_DB_NAME
        })

    def send_listen(self, user, filename):
        with open(self.path_to_data_file(filename)) as f:
            payload = json.load(f)
        return self.client.post(
            url_for('api_v1.submit_listen'),
            data=json.dumps(payload),
            headers={'Authorization': 'Token {}'.format(user['auth_token'])},
            content_type='application/json')

    def test_dedup(self):

        user = db_user.get_or_create('testinfluxwriteruser')

        # send the same listen twice
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)
        time.sleep(2)
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)
        time.sleep(2)

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

    def test_dedup_user_special_characters(self):

        user = db_user.get_or_create('i have a\\weird\\user, name"\n')

        # send the same listen twice
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)
        time.sleep(2)
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)
        time.sleep(2)

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

    def test_dedup_same_batch(self):

        user = db_user.get_or_create('phifedawg')
        r = self.send_listen(user, 'same_batch_duplicates.json')
        self.assert200(r)
        time.sleep(2)

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

    def test_dedup_different_users(self):
        """
        Test to make sure influx writer doesn't confuse listens with same timestamps
        but different users to be duplicates
        """

        user1 = db_user.get_or_create('testuser1')
        user2 = db_user.get_or_create('testuser2')

        r = self.send_listen(user1, 'valid_single.json')
        self.assert200(r)
        r = self.send_listen(user2, 'valid_single.json')
        self.assert200(r)

        time.sleep(2)  # sleep to allow influx-writer to do its thing

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user1['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

        listens = self.ls.fetch_listens(user2['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

    def test_dedup_same_timestamp_different_tracks(self):
        """ Test to check that if there are two tracks w/ the same timestamp,
            they don't get considered as duplicates
        """

        user = db_user.get_or_create('difftracksametsuser')

        # send four different tracks with the same timestamp
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)

        r = self.send_listen(user,
                             'same_timestamp_diff_track_valid_single.json')
        self.assert200(r)

        r = self.send_listen(user,
                             'same_timestamp_diff_track_valid_single_2.json')
        self.assert200(r)

        r = self.send_listen(user,
                             'same_timestamp_diff_track_valid_single_3.json')
        self.assert200(r)
        time.sleep(2)

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 4)
class APICompatDeprecatedTestCase(APICompatIntegrationTestCase):
    def setUp(self):
        super(APICompatDeprecatedTestCase, self).setUp()
        self.user = db_user.get_or_create(1, 'apicompatoldtestuser')
        self.ls = InfluxListenStore(
            {
                'REDIS_HOST': config.REDIS_HOST,
                'REDIS_PORT': config.REDIS_PORT,
                'REDIS_NAMESPACE': config.REDIS_NAMESPACE,
                'INFLUX_HOST': config.INFLUX_HOST,
                'INFLUX_PORT': config.INFLUX_PORT,
                'INFLUX_DB_NAME': config.INFLUX_DB_NAME,
            }, self.app.logger)

    def handshake(self, user_name, auth_token, timestamp):
        """ Makes a request to the handshake endpoint of the AudioScrobbler API and
            returns the response.
        """

        args = {
            'hs': 'true',
            'p': '1.2',
            'c': 'tst',
            'v': '0.1',
            'u': user_name,
            't': timestamp,
            'a': auth_token
        }

        return self.client.get('/', query_string=args)

    def test_handshake(self):
        """ Tests handshake for a user that exists """

        timestamp = int(time.time())
        audioscrobbler_auth_token = _get_audioscrobbler_auth_token(
            self.user['auth_token'], timestamp)

        r = self.handshake(self.user['musicbrainz_id'],
                           audioscrobbler_auth_token, timestamp)

        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(len(response), 5)
        self.assertEqual(response[0], 'OK')
        self.assertEqual(len(response[1]), 32)

    def test_handshake_post(self):
        """ Tests POST requests to handshake endpoint """

        ts = int(time.time())
        args = {
            'hs': 'true',
            'p': '1.2',
            'c': 'tst',
            'v': '0.1',
            'u': self.user['musicbrainz_id'],
            't': ts,
            'a': _get_audioscrobbler_auth_token(self.user['auth_token'], ts)
        }

        r = self.client.post('/', query_string=args)

        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(len(response), 5)
        self.assertEqual(response[0], 'OK')
        self.assertEqual(len(response[1]), 32)

    def test_root_url_when_no_handshake(self):
        """ Tests the root url when there's no handshaking taking place """

        r = self.client.get('/')
        self.assertStatus(r, 302)

    def test_handshake_unknown_user(self):
        """ Tests handshake for user that is not in the db """

        r = self.handshake('', '', '')
        self.assert401(r)

    def test_handshake_invalid_auth(self):
        """ Tests handshake when invalid authorization token is sent """

        r = self.handshake(self.user['musicbrainz_id'], '', int(time.time()))
        self.assert401(r)

    def test_submit_listen(self):
        """ Sends a valid listen after handshaking and checks if it is present in the
            listenstore
        """

        timestamp = int(time.time())
        audioscrobbler_auth_token = _get_audioscrobbler_auth_token(
            self.user['auth_token'], timestamp)

        r = self.handshake(self.user['musicbrainz_id'],
                           audioscrobbler_auth_token, timestamp)
        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(response[0], 'OK')

        sid = response[1]
        data = {
            's': sid,
            'a[0]': 'Kishore Kumar',
            't[0]': 'Saamne Ye Kaun Aya',
            'o[0]': 'P',
            'l[0]': 300,
            'b[0]': 'Jawani Diwani',
            'i[0]': int(time.time()),
        }

        r = self.client.post(url_for('api_compat_old.submit_listens'),
                             data=data)
        self.assert200(r)
        self.assertEqual(r.data.decode('utf-8'), 'OK\n')

        time.sleep(1)
        to_ts = int(time.time())
        listens = self.ls.fetch_listens(self.user['musicbrainz_id'],
                                        to_ts=to_ts)
        self.assertEqual(len(listens), 1)

    def test_submit_listen_invalid_sid(self):
        """ Tests endpoint for 400 Bad Request if invalid session id is sent """

        sid = ''
        data = {
            's': sid,
            'a[0]': 'Kishore Kumar',
            't[0]': 'Saamne Ye Kaun Aya',
            'o[0]': 'P',
            'l[0]': 300,
            'b[0]': 'Jawani Diwani',
            'i[0]': int(time.time()),
        }

        r = self.client.post(url_for('api_compat_old.submit_listens'),
                             data=data)
        self.assert401(r)
        self.assertEqual(r.data.decode('utf-8'), 'BADSESSION\n')

    def test_submit_listen_invalid_data(self):
        """ Tests endpoint for 400 Bad Request if invalid data is sent """

        timestamp = int(time.time())
        audioscrobbler_auth_token = _get_audioscrobbler_auth_token(
            self.user['auth_token'], timestamp)

        r = self.handshake(self.user['musicbrainz_id'],
                           audioscrobbler_auth_token, timestamp)
        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(response[0], 'OK')

        sid = response[1]

        # no artist in data
        data = {
            's': sid,
            't[0]': 'Saamne Ye Kaun Aya',
            'o[0]': 'P',
            'l[0]': 300,
            'b[0]': 'Jawani Diwani',
            'i[0]': int(time.time()),
        }

        r = self.client.post(url_for('api_compat_old.submit_listens'),
                             data=data)
        self.assert400(r)
        self.assertEqual(r.data.decode('utf-8').split()[0], 'FAILED')

        # add artist and remove track name
        data['a[0]'] = 'Kishore Kumar'
        del data['t[0]']
        r = self.client.post(url_for('api_compat_old.submit_listens'),
                             data=data)
        self.assert400(r)
        self.assertEqual(r.data.decode('utf-8').split()[0], 'FAILED')

        # add track name and remove timestamp
        data['t[0]'] = 'Saamne Ye Kaun Aya'
        del data['i[0]']
        r = self.client.post(url_for('api_compat_old.submit_listens'),
                             data=data)
        self.assert400(r)
        self.assertEqual(r.data.decode('utf-8').split()[0], 'FAILED')

        # re-add a timestamp in ns
        data['i[0]'] = int(time.time()) * 10**9
        r = self.client.post(url_for('api_compat_old.submit_listens'),
                             data=data)
        self.assert400(r)
        self.assertEqual(r.data.decode('utf-8').split()[0], 'FAILED')

    def test_playing_now(self):
        """ Tests playing now notifications """

        timestamp = int(time.time())
        audioscrobbler_auth_token = _get_audioscrobbler_auth_token(
            self.user['auth_token'], timestamp)

        r = self.handshake(self.user['musicbrainz_id'],
                           audioscrobbler_auth_token, timestamp)
        self.assert200(r)
        response = r.data.decode('utf-8').split('\n')
        self.assertEqual(response[0], 'OK')

        sid = response[1]
        data = {
            's': sid,
            'a': 'Kishore Kumar',
            't': 'Saamne Ye Kaun Aya',
            'b': 'Jawani Diwani',
        }

        r = self.client.post(url_for('api_compat_old.submit_now_playing'),
                             data=data)
        self.assert200(r)
        self.assertEqual(r.data.decode('utf-8'), 'OK\n')

    def test_get_session(self):
        """ Tests _get_session method in api_compat_deprecated """

        s = Session.create_by_user_id(self.user['id'])

        session = _get_session(s.sid)
        self.assertEqual(s.sid, session.sid)

    def test_get_session_which_doesnt_exist(self):
        """ Make sure BadRequest is raised when we try to get a session that doesn't exists """

        with self.assertRaises(BadRequest):
            session = _get_session('')

    def test_404(self):

        r = self.client.get('/thisurldoesnotexist')
        self.assert404(r)

    def test_to_native_api_now_playing(self):
        """ Tests _to_native_api when used with data sent to the now_playing endpoint """

        data = {
            's': '',
            'a': 'Kishore Kumar',
            't': 'Saamne Ye Kaun Aya',
            'b': 'Jawani Diwani',
        }

        native_data = _to_native_api(data, '')
        self.assertDictEqual(
            native_data, {
                'track_metadata': {
                    'track_name': 'Saamne Ye Kaun Aya',
                    'artist_name': 'Kishore Kumar',
                    'release_name': 'Jawani Diwani'
                }
            })

        del data['a']
        native_data = _to_native_api(data, '')
        self.assertIsNone(native_data)

        data['a'] = 'Kishore Kumar'
        del data['t']
        native_data = _to_native_api(data, '')
        self.assertIsNone(native_data)

        data['t'] = 'Saamne Ye Kaun Aya'
        del data['b']
        native_data = _to_native_api(data, '')
        self.assertIsNone(native_data)

    def test_to_native_api(self):
        """ Tests _to_native_api with data that is sent to the submission endpoint """

        t = int(time.time())

        data = {
            's': '',
            'a[0]': 'Kishore Kumar',
            't[0]': 'Saamne Ye Kaun Aya',
            'o[0]': 'P',
            'l[0]': 300,
            'b[0]': 'Jawani Diwani',
            'i[0]': t,
            'a[1]': 'Kishore Kumar',
            't[1]': 'Wada Karo',
            'o[1]': 'P',
            'l[1]': 300,
            'b[1]': 'Jawani Diwani',
            'i[1]': t + 10,
        }

        self.assertDictEqual(
            _to_native_api(data, '[0]'), {
                'listened_at': t,
                'track_metadata': {
                    'artist_name': 'Kishore Kumar',
                    'track_name': 'Saamne Ye Kaun Aya',
                    'release_name': 'Jawani Diwani',
                    'additional_info': {
                        'source': 'P',
                        'track_length': 300
                    }
                }
            })

        self.assertDictEqual(
            _to_native_api(data, '[1]'), {
                'listened_at': t + 10,
                'track_metadata': {
                    'artist_name': 'Kishore Kumar',
                    'track_name': 'Wada Karo',
                    'release_name': 'Jawani Diwani',
                    'additional_info': {
                        'source': 'P',
                        'track_length': 300
                    }
                }
            })

        del data['a[0]']
        self.assertIsNone(_to_native_api(data, '[0]'))

        data['a[0]'] = 'Kishore Kumar'
        del data['t[0]']
        native_data = _to_native_api(data, '[0]')
        self.assertIsNone(native_data)

        data['t[0]'] = 'Saamne Ye Kaun Aya'
        del data['b[0]']
        native_data = _to_native_api(data, '[0]')
        self.assertIsNone(native_data)
class APICompatTestCase(APICompatIntegrationTestCase):

    def setUp(self):
        super(APICompatTestCase, self).setUp()
        self.lb_user = db_user.get_or_create(1, 'apicompattestuser')
        self.lfm_user = User(
            self.lb_user['id'],
            self.lb_user['created'],
            self.lb_user['musicbrainz_id'],
            self.lb_user['auth_token'],
        )

        self.ls = InfluxListenStore({
            'REDIS_HOST': current_app.config['REDIS_HOST'],
            'REDIS_PORT': current_app.config['REDIS_PORT'],
            'REDIS_NAMESPACE': current_app.config['REDIS_NAMESPACE'],
            'INFLUX_HOST': current_app.config['INFLUX_HOST'],
            'INFLUX_PORT': current_app.config['INFLUX_PORT'],
            'INFLUX_DB_NAME': current_app.config['INFLUX_DB_NAME'],
        }, self.app.logger)

    def test_record_listen_now_playing(self):
        """ Tests if listen of type 'nowplaying' is recorded correctly
            if valid information is provided.
        """

        token = Token.generate(self.lfm_user.api_key)
        token.approve(self.lfm_user.name)
        session = Session.create(token)

        data = {
            'method': 'track.updateNowPlaying',
            'api_key': self.lfm_user.api_key,
            'sk': session.sid,
            'artist[0]': 'Kishore Kumar',
            'track[0]': 'Saamne Ye Kaun Aya',
            'album[0]': 'Jawani Diwani',
            'duration[0]': 300,
            'timestamp[0]': int(time.time()),
        }

        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')
        self.assertIsNotNone(response['lfm']['nowplaying'])

    def test_get_token(self):
        """ Tests if the token generated by get_token method is valid. """

        data = {
            'method': 'auth.gettoken',
            'api_key': self.lfm_user.api_key,
        }

        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')

        token = Token.load(response['lfm']['token'], api_key=self.lfm_user.api_key)
        self.assertIsNotNone(token)

    def test_get_session(self):
        """ Tests if the session key is valid and session is established correctly. """

        token = Token.generate(self.lfm_user.api_key)
        token.approve(self.lfm_user.name)

        data = {
            'method': 'auth.getsession',
            'api_key': self.lfm_user.api_key,
            'token': token.token,
        }
        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')
        self.assertEqual(response['lfm']['session']['name'], self.lfm_user.name)

        session_key = Session.load(response['lfm']['session']['key'])
        self.assertIsNotNone(session_key)

    def test_get_session_invalid_token(self):
        """ Tests if correct error codes are returned in case token supplied
            is invalid during establishment of session.
        """

        data = {
            'method': 'auth.getsession',
            'api_key': self.lfm_user.api_key,
            'token': '',
        }
        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'failed')
        self.assertEqual(response['lfm']['error']['@code'], '4')

    def test_record_listen(self):
        """ Tests if listen is recorded correctly if valid information is provided. """

        token = Token.generate(self.lfm_user.api_key)
        token.approve(self.lfm_user.name)
        session = Session.create(token)

        timestamp = int(time.time())
        data = {
            'method': 'track.scrobble',
            'api_key': self.lfm_user.api_key,
            'sk': session.sid,
            'artist[0]': 'Kishore Kumar',
            'track[0]': 'Saamne Ye Kaun Aya',
            'album[0]': 'Jawani Diwani',
            'duration[0]': 300,
            'timestamp[0]': timestamp,
        }

        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')
        self.assertEqual(response['lfm']['scrobbles']['@accepted'], '1')

        # Check if listen reached the influx listenstore
        time.sleep(1)
        listens = self.ls.fetch_listens(self.lb_user['musicbrainz_id'], from_ts=timestamp-1)
        self.assertEqual(len(listens), 1)

    def test_record_listen_multiple_listens(self):
        """ Tests if multiple listens get recorded correctly in case valid information
            is provided.
        """

        token = Token.generate(self.lfm_user.api_key)
        token.approve(self.lfm_user.name)
        session = Session.create(token)

        timestamp = int(time.time())
        data = {
            'method': 'track.scrobble',
            'api_key': self.lfm_user.api_key,
            'sk': session.sid,
            'artist[0]': 'Kishore Kumar',
            'track[0]': 'Saamne Ye Kaun Aya',
            'album[0]': 'Jawani Diwani',
            'duration[0]': 300,
            'timestamp[0]': timestamp,
            'artist[1]': 'Fifth Harmony',
            'track[1]': 'Deliver',
            'duration[1]': 200,
            'timestamp[1]': timestamp+300,
        }

        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')
        self.assertEqual(response['lfm']['scrobbles']['@accepted'], '2')

        # Check if listens reached the influx listenstore
        time.sleep(1)
        listens = self.ls.fetch_listens(self.lb_user['musicbrainz_id'], from_ts=timestamp-1)
        self.assertEqual(len(listens), 2)

    def test_create_response_for_single_listen(self):
        """ Tests create_response_for_single_listen method in api_compat
            to check if responses are generated correctly.
        """

        from listenbrainz.webserver.views.api_compat import create_response_for_single_listen

        timestamp = int(time.time())

        original_listen = {
            'artist': 'Kishore Kumar',
            'track': 'Saamne Ye Kaun Aya',
            'album': 'Jawani Diwani',
            'duration': 300,
            'timestamp': timestamp,
        }

        augmented_listen = {
            'listened_at': timestamp,
            'track_metadata': {
                'artist_name': 'Kishore Kumar',
                'track_name':  'Saamne Ye Kaun Aya',
                'release_name': 'Jawani Diwani',
                'additional_info': {
                    'track_length': 300
                }
            }
        }

        # If original listen and augmented listen are same
        xml_response = create_response_for_single_listen(original_listen, augmented_listen, listen_type="listens")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['scrobble']['track']['#text'], 'Saamne Ye Kaun Aya')
        self.assertEqual(response['scrobble']['track']['@corrected'], '0')
        self.assertEqual(response['scrobble']['artist']['#text'], 'Kishore Kumar')
        self.assertEqual(response['scrobble']['artist']['@corrected'], '0')
        self.assertEqual(response['scrobble']['album']['#text'], 'Jawani Diwani')
        self.assertEqual(response['scrobble']['timestamp'], str(timestamp))

        # If listen type is 'playing_now'
        xml_response = create_response_for_single_listen(original_listen, augmented_listen, listen_type="playing_now")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['nowplaying']['track']['#text'], 'Saamne Ye Kaun Aya')
        self.assertEqual(response['nowplaying']['track']['@corrected'], '0')
        self.assertEqual(response['nowplaying']['artist']['#text'], 'Kishore Kumar')
        self.assertEqual(response['nowplaying']['artist']['@corrected'], '0')
        self.assertEqual(response['nowplaying']['album']['#text'], 'Jawani Diwani')
        self.assertEqual(response['nowplaying']['album']['@corrected'], '0')
        self.assertEqual(response['nowplaying']['timestamp'], str(timestamp))

        # If artist was corrected
        original_listen['artist'] = 'Pink'

        xml_response = create_response_for_single_listen(original_listen, augmented_listen, listen_type="listens")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['scrobble']['track']['#text'], 'Saamne Ye Kaun Aya')
        self.assertEqual(response['scrobble']['track']['@corrected'], '0')
        self.assertEqual(response['scrobble']['artist']['#text'], 'Kishore Kumar')
        self.assertEqual(response['scrobble']['artist']['@corrected'], '1')
        self.assertEqual(response['scrobble']['album']['#text'], 'Jawani Diwani')
        self.assertEqual(response['scrobble']['album']['@corrected'], '0')
        self.assertEqual(response['scrobble']['timestamp'], str(timestamp))

        # If track was corrected
        original_listen['artist'] = 'Kishore Kumar'
        original_listen['track'] = 'Deliver'

        xml_response = create_response_for_single_listen(original_listen, augmented_listen, listen_type="listens")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['scrobble']['track']['#text'], 'Saamne Ye Kaun Aya')
        self.assertEqual(response['scrobble']['track']['@corrected'], '1')
        self.assertEqual(response['scrobble']['artist']['#text'], 'Kishore Kumar')
        self.assertEqual(response['scrobble']['artist']['@corrected'], '0')
        self.assertEqual(response['scrobble']['album']['#text'], 'Jawani Diwani')
        self.assertEqual(response['scrobble']['album']['@corrected'], '0')
        self.assertEqual(response['scrobble']['timestamp'], str(timestamp))

        # If album was corrected
        original_listen['track'] = 'Saamne Ye Kaun Aya'
        original_listen['album'] = 'Good Life'

        xml_response = create_response_for_single_listen(original_listen, augmented_listen, listen_type="listens")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['scrobble']['track']['#text'], 'Saamne Ye Kaun Aya')
        self.assertEqual(response['scrobble']['track']['@corrected'], '0')
        self.assertEqual(response['scrobble']['artist']['#text'], 'Kishore Kumar')
        self.assertEqual(response['scrobble']['artist']['@corrected'], '0')
        self.assertEqual(response['scrobble']['album']['#text'], 'Jawani Diwani')
        self.assertEqual(response['scrobble']['album']['@corrected'], '1')
        self.assertEqual(response['scrobble']['timestamp'], str(timestamp))
예제 #16
0
class APICompatTestCase(APICompatIntegrationTestCase):
    def setUp(self):
        super(APICompatTestCase, self).setUp()
        self.lb_user = db_user.get_or_create(1, 'apicompattestuser')
        self.lfm_user = User(
            self.lb_user['id'],
            self.lb_user['created'],
            self.lb_user['musicbrainz_id'],
            self.lb_user['auth_token'],
        )

        self.ls = InfluxListenStore(
            {
                'REDIS_HOST': current_app.config['REDIS_HOST'],
                'REDIS_PORT': current_app.config['REDIS_PORT'],
                'REDIS_NAMESPACE': current_app.config['REDIS_NAMESPACE'],
                'INFLUX_HOST': current_app.config['INFLUX_HOST'],
                'INFLUX_PORT': current_app.config['INFLUX_PORT'],
                'INFLUX_DB_NAME': current_app.config['INFLUX_DB_NAME'],
            }, self.app.logger)

    def test_record_listen_now_playing(self):
        """ Tests if listen of type 'nowplaying' is recorded correctly
            if valid information is provided.
        """

        token = Token.generate(self.lfm_user.api_key)
        token.approve(self.lfm_user.name)
        session = Session.create(token)

        data = {
            'method': 'track.updateNowPlaying',
            'api_key': self.lfm_user.api_key,
            'sk': session.sid,
            'artist[0]': 'Kishore Kumar',
            'track[0]': 'Saamne Ye Kaun Aya',
            'album[0]': 'Jawani Diwani',
            'duration[0]': 300,
            'timestamp[0]': int(time.time()),
        }

        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')
        self.assertIsNotNone(response['lfm']['nowplaying'])

    def test_get_token(self):
        """ Tests if the token generated by get_token method is valid. """

        data = {
            'method': 'auth.gettoken',
            'api_key': self.lfm_user.api_key,
        }

        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')

        token = Token.load(response['lfm']['token'],
                           api_key=self.lfm_user.api_key)
        self.assertIsNotNone(token)

    def test_get_session(self):
        """ Tests if the session key is valid and session is established correctly. """

        token = Token.generate(self.lfm_user.api_key)
        token.approve(self.lfm_user.name)

        data = {
            'method': 'auth.getsession',
            'api_key': self.lfm_user.api_key,
            'token': token.token,
        }
        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')
        self.assertEqual(response['lfm']['session']['name'],
                         self.lfm_user.name)

        session_key = Session.load(response['lfm']['session']['key'])
        self.assertIsNotNone(session_key)

    def test_get_session_invalid_token(self):
        """ Tests if correct error codes are returned in case token supplied
            is invalid during establishment of session.
        """

        data = {
            'method': 'auth.getsession',
            'api_key': self.lfm_user.api_key,
            'token': '',
        }
        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'failed')
        self.assertEqual(response['lfm']['error']['@code'], '4')

    def test_record_listen(self):
        """ Tests if listen is recorded correctly if valid information is provided. """

        token = Token.generate(self.lfm_user.api_key)
        token.approve(self.lfm_user.name)
        session = Session.create(token)

        timestamp = int(time.time())
        data = {
            'method': 'track.scrobble',
            'api_key': self.lfm_user.api_key,
            'sk': session.sid,
            'artist[0]': 'Kishore Kumar',
            'track[0]': 'Saamne Ye Kaun Aya',
            'album[0]': 'Jawani Diwani',
            'duration[0]': 300,
            'timestamp[0]': timestamp,
        }

        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')
        self.assertEqual(response['lfm']['scrobbles']['@accepted'], '1')

        # Check if listen reached the influx listenstore
        time.sleep(1)
        listens = self.ls.fetch_listens(self.lb_user['musicbrainz_id'],
                                        from_ts=timestamp - 1)
        self.assertEqual(len(listens), 1)

    def test_record_listen_multiple_listens(self):
        """ Tests if multiple listens get recorded correctly in case valid information
            is provided.
        """

        token = Token.generate(self.lfm_user.api_key)
        token.approve(self.lfm_user.name)
        session = Session.create(token)

        timestamp = int(time.time())
        data = {
            'method': 'track.scrobble',
            'api_key': self.lfm_user.api_key,
            'sk': session.sid,
            'artist[0]': 'Kishore Kumar',
            'track[0]': 'Saamne Ye Kaun Aya',
            'album[0]': 'Jawani Diwani',
            'duration[0]': 300,
            'timestamp[0]': timestamp,
            'artist[1]': 'Fifth Harmony',
            'track[1]': 'Deliver',
            'duration[1]': 200,
            'timestamp[1]': timestamp + 300,
        }

        r = self.client.post(url_for('api_compat.api_methods'), data=data)
        self.assert200(r)

        response = xmltodict.parse(r.data)
        self.assertEqual(response['lfm']['@status'], 'ok')
        self.assertEqual(response['lfm']['scrobbles']['@accepted'], '2')

        # Check if listens reached the influx listenstore
        time.sleep(1)
        listens = self.ls.fetch_listens(self.lb_user['musicbrainz_id'],
                                        from_ts=timestamp - 1)
        self.assertEqual(len(listens), 2)

    def test_create_response_for_single_listen(self):
        """ Tests create_response_for_single_listen method in api_compat
            to check if responses are generated correctly.
        """

        from listenbrainz.webserver.views.api_compat import create_response_for_single_listen

        timestamp = int(time.time())

        original_listen = {
            'artist': 'Kishore Kumar',
            'track': 'Saamne Ye Kaun Aya',
            'album': 'Jawani Diwani',
            'duration': 300,
            'timestamp': timestamp,
        }

        augmented_listen = {
            'listened_at': timestamp,
            'track_metadata': {
                'artist_name': 'Kishore Kumar',
                'track_name': 'Saamne Ye Kaun Aya',
                'release_name': 'Jawani Diwani',
                'additional_info': {
                    'track_length': 300
                }
            }
        }

        # If original listen and augmented listen are same
        xml_response = create_response_for_single_listen(original_listen,
                                                         augmented_listen,
                                                         listen_type="listens")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['scrobble']['track']['#text'],
                         'Saamne Ye Kaun Aya')
        self.assertEqual(response['scrobble']['track']['@corrected'], '0')
        self.assertEqual(response['scrobble']['artist']['#text'],
                         'Kishore Kumar')
        self.assertEqual(response['scrobble']['artist']['@corrected'], '0')
        self.assertEqual(response['scrobble']['album']['#text'],
                         'Jawani Diwani')
        self.assertEqual(response['scrobble']['timestamp'], str(timestamp))

        # If listen type is 'playing_now'
        xml_response = create_response_for_single_listen(
            original_listen, augmented_listen, listen_type="playing_now")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['nowplaying']['track']['#text'],
                         'Saamne Ye Kaun Aya')
        self.assertEqual(response['nowplaying']['track']['@corrected'], '0')
        self.assertEqual(response['nowplaying']['artist']['#text'],
                         'Kishore Kumar')
        self.assertEqual(response['nowplaying']['artist']['@corrected'], '0')
        self.assertEqual(response['nowplaying']['album']['#text'],
                         'Jawani Diwani')
        self.assertEqual(response['nowplaying']['album']['@corrected'], '0')
        self.assertEqual(response['nowplaying']['timestamp'], str(timestamp))

        # If artist was corrected
        original_listen['artist'] = 'Pink'

        xml_response = create_response_for_single_listen(original_listen,
                                                         augmented_listen,
                                                         listen_type="listens")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['scrobble']['track']['#text'],
                         'Saamne Ye Kaun Aya')
        self.assertEqual(response['scrobble']['track']['@corrected'], '0')
        self.assertEqual(response['scrobble']['artist']['#text'],
                         'Kishore Kumar')
        self.assertEqual(response['scrobble']['artist']['@corrected'], '1')
        self.assertEqual(response['scrobble']['album']['#text'],
                         'Jawani Diwani')
        self.assertEqual(response['scrobble']['album']['@corrected'], '0')
        self.assertEqual(response['scrobble']['timestamp'], str(timestamp))

        # If track was corrected
        original_listen['artist'] = 'Kishore Kumar'
        original_listen['track'] = 'Deliver'

        xml_response = create_response_for_single_listen(original_listen,
                                                         augmented_listen,
                                                         listen_type="listens")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['scrobble']['track']['#text'],
                         'Saamne Ye Kaun Aya')
        self.assertEqual(response['scrobble']['track']['@corrected'], '1')
        self.assertEqual(response['scrobble']['artist']['#text'],
                         'Kishore Kumar')
        self.assertEqual(response['scrobble']['artist']['@corrected'], '0')
        self.assertEqual(response['scrobble']['album']['#text'],
                         'Jawani Diwani')
        self.assertEqual(response['scrobble']['album']['@corrected'], '0')
        self.assertEqual(response['scrobble']['timestamp'], str(timestamp))

        # If album was corrected
        original_listen['track'] = 'Saamne Ye Kaun Aya'
        original_listen['album'] = 'Good Life'

        xml_response = create_response_for_single_listen(original_listen,
                                                         augmented_listen,
                                                         listen_type="listens")
        response = xmltodict.parse(xml_response)

        self.assertEqual(response['scrobble']['track']['#text'],
                         'Saamne Ye Kaun Aya')
        self.assertEqual(response['scrobble']['track']['@corrected'], '0')
        self.assertEqual(response['scrobble']['artist']['#text'],
                         'Kishore Kumar')
        self.assertEqual(response['scrobble']['artist']['@corrected'], '0')
        self.assertEqual(response['scrobble']['album']['#text'],
                         'Jawani Diwani')
        self.assertEqual(response['scrobble']['album']['@corrected'], '1')
        self.assertEqual(response['scrobble']['timestamp'], str(timestamp))
class InfluxWriterTestCase(IntegrationTestCase):

    def setUp(self):
        super(InfluxWriterTestCase, self).setUp()
        self.ls = InfluxListenStore({ 'REDIS_HOST': config.REDIS_HOST,
                             'REDIS_PORT': config.REDIS_PORT,
                             'REDIS_NAMESPACE': config.REDIS_NAMESPACE,
                             'INFLUX_HOST': config.INFLUX_HOST,
                             'INFLUX_PORT': config.INFLUX_PORT,
                             'INFLUX_DB_NAME': config.INFLUX_DB_NAME}, self.app.logger)

    def send_listen(self, user, filename):
        with open(self.path_to_data_file(filename)) as f:
            payload = json.load(f)
        return self.client.post(
            url_for('api_v1.submit_listen'),
            data = json.dumps(payload),
            headers = {'Authorization': 'Token {}'.format(user['auth_token'])},
            content_type = 'application/json'
        )

    def test_dedup(self):

        user = db_user.get_or_create(1, 'testinfluxwriteruser')

        # send the same listen twice
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)
        time.sleep(2)
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)
        time.sleep(2)

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

    def test_dedup_user_special_characters(self):

        user = db_user.get_or_create(2, 'i have a\\weird\\user, name"\n')

        # send the same listen twice
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)
        time.sleep(2)
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)
        time.sleep(2)

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

    def test_dedup_same_batch(self):

        user = db_user.get_or_create(3, 'phifedawg')
        r = self.send_listen(user, 'same_batch_duplicates.json')
        self.assert200(r)
        time.sleep(2)

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)


    def test_dedup_different_users(self):
        """
        Test to make sure influx writer doesn't confuse listens with same timestamps
        but different users to be duplicates
        """

        user1 = db_user.get_or_create(1, 'testuser1')
        user2 = db_user.get_or_create(2, 'testuser2')

        r = self.send_listen(user1, 'valid_single.json')
        self.assert200(r)
        r = self.send_listen(user2, 'valid_single.json')
        self.assert200(r)

        time.sleep(2) # sleep to allow influx-writer to do its thing

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user1['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

        listens = self.ls.fetch_listens(user2['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 1)

    def test_dedup_same_timestamp_different_tracks(self):
        """ Test to check that if there are two tracks w/ the same timestamp,
            they don't get considered as duplicates
        """

        user = db_user.get_or_create(1, 'difftracksametsuser')

        # send four different tracks with the same timestamp
        r = self.send_listen(user, 'valid_single.json')
        self.assert200(r)

        r = self.send_listen(user, 'same_timestamp_diff_track_valid_single.json')
        self.assert200(r)

        r = self.send_listen(user, 'same_timestamp_diff_track_valid_single_2.json')
        self.assert200(r)

        r = self.send_listen(user, 'same_timestamp_diff_track_valid_single_3.json')
        self.assert200(r)
        time.sleep(2)

        to_ts = int(time.time())
        listens = self.ls.fetch_listens(user['musicbrainz_id'], to_ts=to_ts)
        self.assertEqual(len(listens), 4)
예제 #18
0
#!/usr/bin/env python3

# This script flushes out the listencounts that are in the 7day retention policy and sums them
# into the permanent listen count measurement.

from listenbrainz import default_config as config
try:
    from listenbrainz import custom_config as config
except ImportError:
    pass
from listenbrainz.listenstore import InfluxListenStore

ls = InfluxListenStore({
    'REDIS_HOST': config.REDIS_HOST,
    'REDIS_PORT': config.REDIS_PORT,
    'INFLUX_HOST': config.INFLUX_HOST,
    'INFLUX_PORT': config.INFLUX_PORT,
    'INFLUX_DB_NAME': config.INFLUX_DB_NAME
})
ls.update_listen_counts()
    def start(self):
        app = create_app()
        with app.app_context():
            current_app.logger.info("influx-writer init")
            self._verify_hosts_in_config()

            if "INFLUX_HOST" not in current_app.config:
                current_app.logger.critical(
                    "Influx service not defined. Sleeping {0} seconds and exiting."
                    .format(self.ERROR_RETRY_DELAY))
                sleep(self.ERROR_RETRY_DELAY)
                sys.exit(-1)

            while True:
                try:
                    self.ls = InfluxListenStore(
                        {
                            'REDIS_HOST':
                            current_app.config['REDIS_HOST'],
                            'REDIS_PORT':
                            current_app.config['REDIS_PORT'],
                            'REDIS_NAMESPACE':
                            current_app.config['REDIS_NAMESPACE'],
                            'INFLUX_HOST':
                            current_app.config['INFLUX_HOST'],
                            'INFLUX_PORT':
                            current_app.config['INFLUX_PORT'],
                            'INFLUX_DB_NAME':
                            current_app.config['INFLUX_DB_NAME'],
                        },
                        logger=current_app.logger)
                    self.influx = InfluxDBClient(
                        host=current_app.config['INFLUX_HOST'],
                        port=current_app.config['INFLUX_PORT'],
                        database=current_app.config['INFLUX_DB_NAME'],
                    )
                    break
                except Exception as err:
                    current_app.logger.error(
                        "Cannot connect to influx: %s. Retrying in 2 seconds and trying again."
                        % str(err),
                        exc_info=True)
                    sleep(self.ERROR_RETRY_DELAY)

            while True:
                try:
                    self.redis = Redis(host=current_app.config['REDIS_HOST'],
                                       port=current_app.config['REDIS_PORT'],
                                       decode_responses=True)
                    self.redis.ping()
                    self.redis_listenstore = RedisListenStore(
                        current_app.logger, current_app.config)
                    break
                except Exception as err:
                    current_app.logger.error(
                        "Cannot connect to redis: %s. Retrying in 2 seconds and trying again."
                        % str(err),
                        exc_info=True)
                    sleep(self.ERROR_RETRY_DELAY)

            while True:
                self.connect_to_rabbitmq()
                self.incoming_ch = self.connection.channel()
                self.incoming_ch.exchange_declare(
                    exchange=current_app.config['INCOMING_EXCHANGE'],
                    exchange_type='fanout')
                self.incoming_ch.queue_declare(
                    current_app.config['INCOMING_QUEUE'], durable=True)
                self.incoming_ch.queue_bind(
                    exchange=current_app.config['INCOMING_EXCHANGE'],
                    queue=current_app.config['INCOMING_QUEUE'])
                self.incoming_ch.basic_consume(
                    lambda ch, method, properties, body: self.static_callback(
                        ch, method, properties, body, obj=self),
                    queue=current_app.config['INCOMING_QUEUE'],
                )

                self.unique_ch = self.connection.channel()
                self.unique_ch.exchange_declare(
                    exchange=current_app.config['UNIQUE_EXCHANGE'],
                    exchange_type='fanout')

                current_app.logger.info("influx-writer started")
                try:
                    self.incoming_ch.start_consuming()
                except pika.exceptions.ConnectionClosed:
                    current_app.logger.warn(
                        "Connection to rabbitmq closed. Re-opening.",
                        exc_info=True)
                    self.connection = None
                    continue

                self.connection.close()