예제 #1
0
    def __init__(self, cardinal, config):
        self.logger = logging.getLogger(__name__)

        self.admins = []

        if config is None or not config.get('admins', False):
            self.logger.warning("No admins configured for admin plugin -- "
                                "copy config.example.json to config.json and "
                                "add your information.")
            return

        for admin in config['admins']:
            user = user_info(
                admin.get('nick', None),
                admin.get('user', None),
                admin.get('vhost', None),
            )

            if user.nick is None and user.user is None and user.vhost is None:
                self.logger.error(
                    "Invalid admin listed in admin plugin config -- at least "
                    "one of nick, user, or vhost must be present.")
                continue

            self.admins.append(user)
예제 #2
0
def test_on_part():
    channel1 = '#channel1'
    channel2 = '#channel2'
    user = user_info('nick', None, None)
    msg = 'msg'

    plugin = SedPlugin()
    cardinal = Mock()

    plugin.on_msg(cardinal, user, channel1, msg)
    plugin.on_msg(cardinal, user, channel2, msg)
    assert plugin.history[channel1] == {
        user.nick: msg
    }
    assert plugin.history[channel2] == {
        user.nick: msg
    }

    plugin.on_part(cardinal, user, channel1, 'message')
    assert plugin.history[channel1] == {}
    assert plugin.history[channel2] == {
        user.nick: msg
    }

    plugin.on_part(cardinal, user, channel2, 'message')
    assert plugin.history[channel2] == {}
예제 #3
0
    def __init__(self, cardinal, config):
        self.logger = logging.getLogger(__name__)
        self.cardinal = cardinal

        self.config = config or {}
        self.config.setdefault('api_key', None)
        self.config.setdefault('channels', [])
        self.config.setdefault('stocks', {})
        self.config.setdefault('relay_bots', [])

        if not self.config["channels"]:
            self.logger.warning("No channels for ticker defined in config --"
                                "ticker will be disabled")
        if not self.config["stocks"]:
            self.logger.warning("No stocks for ticker defined in config -- "
                                "ticker will be disabled")

        if not self.config["api_key"]:
            raise KeyError("Missing required api_key in ticker config")
        if len(self.config["stocks"]) > 5:
            raise ValueError("No more than 5 stocks may be present in ticker "
                             "config")

        self.relay_bots = []
        for relay_bot in self.config['relay_bots']:
            user = user_info(relay_bot['nick'], relay_bot['user'],
                             relay_bot['vhost'])
            self.relay_bots.append(user)

        self.db = cardinal.get_db('ticker', default={
            'predictions': {},
        })

        self.call_id = None
        self.wait()
예제 #4
0
def test_substitute_escaping(message, new_message):
    user = user_info('user', None, None)
    channel = '#channel'

    plugin = SedPlugin()
    plugin.history[channel][user.nick] = 'hi/hey/hello'

    assert plugin.substitute(user, channel, message) == new_message
예제 #5
0
def test_not_a_substitute():
    user = user_info('user', None, None)
    channel = '#channel'

    plugin = SedPlugin()
    plugin.history[channel][user.nick] = 'doesnt matter'

    assert plugin.substitute(user, channel, 'foobar') == None
예제 #6
0
def test_subsitution_no_history():
    user = user_info('user', None, None)
    channel = '#channel'

    plugin = SedPlugin()

    # make sure this doesn't raise
    plugin.on_msg(Mock(), user, channel, 's/foo/bar/')
예제 #7
0
def test_substitute_modifiers(message, new_message):
    user = user_info('user', None, None)
    channel = '#channel'

    plugin = SedPlugin()
    plugin.history[channel][user.nick] = 'this is a test message'

    assert plugin.substitute(user, channel, message) == new_message
예제 #8
0
def test_substitution_doesnt_match():
    user = user_info('user', None, None)
    channel = '#channel'

    plugin = SedPlugin()
    plugin.history[channel][user.nick] = 'doesnt matter'

    assert plugin.substitute(user, channel, 's/foo/bar/') == 'doesnt matter'
예제 #9
0
    def test_predict_not_relay_bot(self, input_msg):
        channel = "#finance"

        yield self.plugin.predict(self.mock_cardinal,
                                  user_info("nick", "user", "vhost"), channel,
                                  input_msg)

        assert len(self.db['predictions']) == 0
        assert self.mock_cardinal.sendMsg.mock_calls == []
예제 #10
0
def test_on_kick_no_history():
    channel = '#channel'
    user = user_info('nick', None, None)

    plugin = SedPlugin()
    cardinal = Mock()

    # make sure this doesn't raise
    plugin.on_kick(cardinal, user, channel, user.nick, 'message')
예제 #11
0
def test_on_quit_no_history():
    channel = '#channel'
    user = user_info('nick', None, None)

    plugin = SedPlugin()
    assert plugin.history[channel] == {}
    cardinal = Mock()

    # make sure this doesn't raise
    plugin.on_quit(cardinal, user, 'message')
    assert plugin.history[channel] == {}
예제 #12
0
def test_on_part_self_no_history():
    cardinal = Mock()
    cardinal.nickname = 'Cardinal'

    channel = '#channel'
    user = user_info(cardinal.nickname, None, None)

    plugin = SedPlugin()

    # make sure this doesn't raise
    plugin.on_part(cardinal, user, channel, 'message')
예제 #13
0
def test_on_msg_failed_correction():
    user = user_info('user', None, None)
    channel = '#channel'

    plugin = SedPlugin()
    cardinal = Mock()

    plugin.history[channel][user.nick] = 'doesnt matter'

    # make sure this doesn't raise
    plugin.on_msg(cardinal, user, channel, 's/foo/bar/')
    cardinal.sendMsg.assert_not_called()
예제 #14
0
    def setup_method(self):
        self.channel = '#cah'
        self.player = 'player1'
        self.user = user_info(self.player, 'user', 'vhost')

        self.mock_cardinal = Mock(spec=CardinalBot)
        self.mock_cardinal.nickname = 'Cardinal'

        self.plugin = CAHPlugin(self.mock_cardinal,
                                {'channel': self.channel})

        self.plugin.game = game.Game()
        self.plugin.game.add_player(self.player)
예제 #15
0
    def test_predict_relay_bot(self, input_msg, output_msg):
        symbol = 'INX'
        channel = "#finance"

        response = make_global_quote_response(symbol, previous_close=100)
        with mock_api(response):
            yield self.plugin.predict(self.mock_cardinal,
                                      user_info("relay.bot", "relay", "relay"),
                                      channel, input_msg)

        assert symbol in self.db['predictions']
        assert len(self.db['predictions'][symbol]) == 1

        self.mock_cardinal.sendMsg.assert_called_once_with(channel, output_msg)
예제 #16
0
파일: plugin.py 프로젝트: obviyus/Cardinal
    def predict_relayed(self, cardinal, user, channel, msg):
        """Hack to support relayed messages"""
        match = re.match(PREDICT_RELAY_REGEX, msg)

        # this regex should only match when a relay bot is relaying a message
        # for another user - make sure this is really a relay bot
        if not self.is_relay_bot(user):
            return

        user = user_info(util.strip_formatting(match.group(1)),
                         user.user,
                         user.vhost,
                         )

        yield self.predict(cardinal, user, channel, match.group(2))
예제 #17
0
def test_on_msg_failed_correction():
    user = user_info('user', None, None)
    channel = '#channel'

    plugin = SedPlugin()
    cardinal = Mock()

    plugin.history[channel][user.nick] = 'yo, foo matters'

    # make sure this doesn't raise
    plugin.on_msg(cardinal, user, channel, 's/foo/bar/')
    cardinal.sendMsg.assert_called_with(
        channel,
        "{} meant: yo bar matters".format(nick),
    )
예제 #18
0
    def test_predict(self, symbol, input_msg, output_msg, market_is_open):
        channel = "#finance"

        fake_now = get_fake_now(market_is_open=market_is_open)

        kwargs = {'previous_close': 100} if market_is_open else {'close': 100}
        response = make_global_quote_response(symbol, **kwargs)

        with mock_api(response, fake_now=fake_now):
            yield self.plugin.predict(self.mock_cardinal,
                                      user_info("nick", "user", "vhost"),
                                      channel, input_msg)

        assert symbol in self.db['predictions']
        assert len(self.db['predictions'][symbol]) == 1

        self.mock_cardinal.sendMsg.assert_called_once_with(channel, output_msg)
예제 #19
0
    def test_cmd_info(self, mock_datetime):
        channel = '#test'
        msg = '.info'

        now = datetime.now()
        reloads = 123
        mock_datetime.now = Mock(return_value=now)

        type(self.mock_cardinal).reloads = PropertyMock(return_value=123)
        self.mock_cardinal.booted = now - timedelta(seconds=30)
        self.mock_cardinal.uptime = now - timedelta(seconds=15)
        self.mock_cardinal.config.return_value = {
            'admins': [
                {
                    'nick': 'whoami'
                },
                {
                    'nick': 'test_foo'
                },
            ]
        }

        self.plugin.cmd_info(
            self.mock_cardinal,
            user_info(None, None, None),
            channel,
            msg,
        )

        assert self.mock_cardinal.sendMsg.mock_calls == [
            call(
                channel,
                "I have been connected without downtime for 00:00:15, and was "
                "initially launched 00:00:30 ago. Plugins have been reloaded "
                "123 times since then.".format(reloads),
            ),
            call(
                channel,
                "My admins are: test_foo, whoami. Visit "
                "https://github.com/JohnMaguire/Cardinal for more info about "
                "me. (Use .help to see my commands.)",
            ),
        ]
예제 #20
0
    def test_predict_replace(self, message_pairs):
        channel = "#finance"
        symbol = 'INX'

        response = make_global_quote_response(symbol, previous_close=100)

        fake_now = get_fake_now()
        for input_msg, output_msg in message_pairs:
            with mock_api(response, fake_now):
                yield self.plugin.predict(self.mock_cardinal,
                                          user_info("nick", "user", "vhost"),
                                          channel, input_msg)

                assert symbol in self.db['predictions']
                assert len(self.db['predictions'][symbol]) == 1

                self.mock_cardinal.sendMsg.assert_called_with(
                    channel,
                    output_msg.format(
                        fake_now.strftime('%Y-%m-%d %H:%M:%S %Z'))
                    if '{}' in output_msg else output_msg)
예제 #21
0
    def test_admins_translation(self):
        plugin = AdminPlugin(
            None, {
                'admins': [
                    {
                        'nick': 'nick',
                        'user': '******',
                        'vhost': 'vhost'
                    },
                    {
                        'nick': 'nick',
                        'user': '******'
                    },
                    {
                        'nick': 'nick',
                        'vhost': 'vhost'
                    },
                    {
                        'user': '******',
                        'vhost': 'vhost'
                    },
                    {
                        'nick': 'nick'
                    },
                    {
                        'user': '******'
                    },
                    {
                        'vhost': 'vhost'
                    },
                ]
            })

        assert plugin.admins == [
            user_info('nick', 'user', 'vhost'),
            user_info('nick', 'user', None),
            user_info('nick', None, 'vhost'),
            user_info(None, 'user', 'vhost'),
            user_info('nick', None, None),
            user_info(None, 'user', None),
            user_info(None, None, 'vhost'),
        ]
예제 #22
0
    def test_cmd_info(self, mock_datetime):
        channel = '#test'
        msg = '.info'

        now = datetime.now()
        mock_datetime.now = Mock(return_value=now)

        self.mock_cardinal.booted = now - timedelta(seconds=30)
        self.mock_cardinal.uptime = now - timedelta(seconds=15)
        self.mock_cardinal.config.return_value = {
            'admins': [
                {
                    'nick': 'whoami'
                },
                {
                    'nick': 'test_foo'
                },
            ]
        }

        self.plugin.cmd_info(
            self.mock_cardinal,
            user_info(None, None, None),
            channel,
            msg,
        )

        assert self.mock_cardinal.sendMsg.mock_calls == [
            call(
                channel,
                "I am a Python 3 IRC bot, online since 00:00:15. I initially "
                "connected 00:00:30 ago. My admins are: test_foo, whoami. "
                "Use .help to list commands."),
            call(
                channel,
                "Visit https://github.com/JohnMaguire/Cardinal to learn more."
            ),
        ]
예제 #23
0
class TestTickerPlugin:
    @pytest.fixture(autouse=True)
    def setup_method_fixture(self, request, tmpdir):
        self.api_key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        self.channel = '#test'
        self.channels = [self.channel]
        self.stocks = [
            ['SPY', 'S&P 500'],
            ['DIA', 'Dow'],
            ['VEU', 'Foreign'],
            ['AGG', 'US Bond'],
        ]
        self.relay_bots = [
            {"nick": "relay.bot", "user": "******", "vhost": "relay"},
        ]

        d = tmpdir.mkdir('storage')

        get_db, self.db = get_mock_db()
        self.mock_cardinal = Mock(spec=CardinalBot)
        self.mock_cardinal.network = self.network = 'irc.darkscience.net'
        self.mock_cardinal.storage_path = str(d.dirpath())
        self.mock_cardinal.get_db.side_effect = get_db

        self.plugin = TickerPlugin(self.mock_cardinal, {
            'api_key': self.api_key,
            'channels': self.channels,
            'stocks': self.stocks,
            'relay_bots': self.relay_bots,
        })

    def test_config_defaults(self):
        plugin = TickerPlugin(self.mock_cardinal, {
            'api_key': self.api_key,
        })
        assert plugin.config['api_key'] == self.api_key
        assert plugin.config['channels'] == []
        assert plugin.config['stocks'] == []
        assert plugin.config['relay_bots'] == []

    def test_missing_api_key(self):
        with pytest.raises(KeyError):
            TickerPlugin(self.mock_cardinal, {})

    def test_missing_stocks(self):
        with pytest.raises(ValueError):
            TickerPlugin(self.mock_cardinal, {
                'api_key': self.api_key,
                'stocks': [
                    ['a', 'a'],
                    ['b', 'b'],
                    ['c', 'c'],
                    ['d', 'd'],
                    ['e', 'e'],
                    ['f', 'f'],
                ],
            })

    @defer.inlineCallbacks
    def test_send_ticker(self):
        responses = [
            make_iex_response('DIA',
                              previous_close=100,
                              price=200),
            make_iex_response('AGG',
                              previous_close=100,
                              price=150.50),
            make_iex_response('VEU',
                              previous_close=100,
                              price=105),
            make_iex_response('SPY',
                              previous_close=100,
                              price=50),
        ]

        with mock_api(responses, fake_now=get_fake_now(market_is_open=True)):
            yield self.plugin.send_ticker()

        # These should be ordered per the config
        self.mock_cardinal.sendMsg.assert_called_once_with(
            self.channel,
            'S&P 500 (\x02SPY\x02): \x0304-50.00%\x03 | '
            'Dow (\x02DIA\x02): \x0309100.00%\x03 | '
            'Foreign (\x02VEU\x02): \x03095.00%\x03 | '
            'US Bond (\x02AGG\x02): \x030950.50%\x03'
        )

    @pytest.mark.parametrize("dt,should_send_ticker,should_do_predictions", [
        (datetime.datetime(2020, 3, 21, 16, 0, 0),  # Saturday 4pm
         False,
         False,),
        (datetime.datetime(2020, 3, 22, 16, 0, 0),  # Sunday 4pm
         False,
         False,),
        (datetime.datetime(2020, 3, 23, 15, 45, 45),  # Monday 3:45pm
         True,
         False,),
        (datetime.datetime(2020, 3, 23, 16, 0, 30),  # Monday 4pm
         True,
         True,),
        (datetime.datetime(2020, 3, 23, 16, 15, 0),  # Monday 4:15pm
         False,
         False,),
        (datetime.datetime(2020, 3, 27, 9, 15, 0),  # Friday 9:15am
         False,
         False,),
        (datetime.datetime(2020, 3, 27, 9, 30, 15),  # Friday 9:30am
         True,
         True,),
        (datetime.datetime(2020, 3, 27, 9, 45, 15),  # Friday 9:45am
         True,
         False,),
    ])
    @patch.object(plugin.TickerPlugin, 'do_predictions')
    @patch.object(plugin.TickerPlugin, 'send_ticker')
    @patch.object(util, 'sleep')
    @patch.object(plugin, 'est_now')
    @pytest_twisted.inlineCallbacks
    def test_tick(self,
                  est_now,
                  sleep,
                  send_ticker,
                  do_predictions,
                  dt,
                  should_send_ticker,
                  should_do_predictions):
        est_now.return_value = dt

        yield self.plugin.tick()

        if should_send_ticker:
            send_ticker.assert_called_once_with()
        else:
            assert send_ticker.mock_calls == []

        if should_do_predictions:
            sleep.assert_called_once_with(60)
            do_predictions.assert_called_once_with()
        else:
            assert sleep.mock_calls == []
            assert do_predictions.mock_calls == []

    @pytest.mark.parametrize("market_is_open", [True, False])
    @patch.object(util, 'reactor', new_callable=Clock)
    @pytest_twisted.inlineCallbacks
    def test_do_predictions(self, mock_reactor, market_is_open):
        symbol = 'SPY'
        base = 100.0

        user1 = 'user1'
        user2 = 'user2'
        prediction1 = 105.0
        prediction2 = 96.0

        actual = 95.0

        yield self.plugin.save_prediction(
            symbol,
            user1,
            base,
            prediction1,
        )
        yield self.plugin.save_prediction(
            symbol,
            user2,
            base,
            prediction2,
        )

        assert len(self.db['predictions']) == 1
        assert len(self.db['predictions'][symbol]) == 2

        response = make_iex_response(symbol, price=actual)

        with mock_api(response, fake_now=get_fake_now(market_is_open)):
            d = self.plugin.do_predictions()
            mock_reactor.advance(15)

            yield d

        assert len(self.mock_cardinal.sendMsg.mock_calls) == 3
        self.mock_cardinal.sendMsg.assert_called_with(
            self.channel,
            '{} had the closest guess for \x02{}\x02 out of {} predictions '
            'with a prediction of {:.2f} (\x0304{:.2f}%\x03) '
            'compared to the actual {} of {:.2f} (\x0304{:.2f}%\x03).'.format(
                user2,
                symbol,
                2,
                prediction2,
                -4,
                'open' if market_is_open else 'close',
                actual,
                -5))

    @patch.object(plugin, 'est_now')
    def test_send_prediction(self, mock_now):
        prediction = 105
        actual = 110
        base = 100
        nick = "nick"
        symbol = "SPY"

        # Set the datetime to a known value so the message can be tested
        tz = pytz.timezone('America/New_York')
        mock_now.return_value = tz.localize(
            datetime.datetime(2020, 3, 20, 10, 50, 0, 0))

        prediction_ = {'when': '2020-03-20 10:50:00 EDT',
                       'prediction': prediction,
                       'base': base,
                       }
        self.plugin.send_prediction(nick, symbol, prediction_, actual)

        message = ("Prediction by nick for \x02SPY\02: 105.00 (\x03095.00%\x03). "
                   "Actual value at open: 110.00 (\x030910.00%\x03). "
                   "Prediction set at 2020-03-20 10:50:00 EDT.")
        self.mock_cardinal.sendMsg.assert_called_once_with('#test', message)

    @pytest.mark.skip(reason="Not written yet")
    def test_check(self):
        pass

    @pytest.mark.parametrize("symbol,input_msg,output_msg,market_is_open", [
        ("SPY",
         "!predict SPY +5%",
         "Prediction by nick for \x02SPY\x02 at market close: 105.00 (\x03095.00%\x03) ",
         True,
         ),
        ("SPY",
         "!predict SPY -5%",
         "Prediction by nick for \x02SPY\x02 at market close: 95.00 (\x0304-5.00%\x03) ",
         True,
         ),
        ("SPY",
         "!predict SPY -5%",
         "Prediction by nick for \x02SPY\x02 at market open: 95.00 (\x0304-5.00%\x03) ",
         False,
         ),
        # testing a few more formats of stock symbols
        ("^RUT",
         "!predict ^RUT -5%",
         "Prediction by nick for \x02^RUT\x02 at market open: 95.00 (\x0304-5.00%\x03) ",
         False,
         ),
        ("REE.MC",
         "!predict REE.MC -5%",
         "Prediction by nick for \x02REE.MC\x02 at market open: 95.00 (\x0304-5.00%\x03) ",
         False,
         ),
        ("LON:HDLV",
         "!predict LON:HDLV -5%",
         "Prediction by nick for \x02LON:HDLV\x02 at market open: 95.00 (\x0304-5.00%\x03) ",
         False,
         ),
    ])
    @pytest_twisted.inlineCallbacks
    def test_predict(self,
                     symbol,
                     input_msg,
                     output_msg,
                     market_is_open):
        channel = "#finance"

        fake_now = get_fake_now(market_is_open=market_is_open)

        kwargs = {'previous_close': 100} if market_is_open else {'price': 100}
        response = make_iex_response(symbol, **kwargs)

        with mock_api(response, fake_now=fake_now):
            yield self.plugin.predict(self.mock_cardinal,
                                      user_info("nick", "user", "vhost"),
                                      channel,
                                      input_msg)

        assert symbol in self.db['predictions']
        assert len(self.db['predictions'][symbol]) == 1

        self.mock_cardinal.sendMsg.assert_called_once_with(
            channel,
            output_msg)

    @pytest.mark.parametrize("message_pairs", [
        (("!predict SPY +5%",
          "Prediction by nick for \x02SPY\x02 at market close: 105.00 (\x03095.00%\x03) ",
          ),
         ("!predict SPY -5%",
          "Prediction by nick for \x02SPY\x02 at market close: 95.00 (\x0304-5.00%\x03) "
          "(replaces old prediction of 105.00 (\x03095.00%\x03) set at {})"
          ),
         )
    ])
    @pytest_twisted.inlineCallbacks
    def test_predict_replace(self, message_pairs):
        channel = "#finance"
        symbol = 'SPY'

        response = make_iex_response(symbol, previous_close=100)

        fake_now = get_fake_now()
        for input_msg, output_msg in message_pairs:
            with mock_api(response, fake_now):
                yield self.plugin.predict(self.mock_cardinal,
                                          user_info("nick", "user", "vhost"),
                                          channel,
                                          input_msg)

                assert symbol in self.db['predictions']
                assert len(self.db['predictions'][symbol]) == 1

                self.mock_cardinal.sendMsg.assert_called_with(
                    channel,
                    output_msg.format(fake_now.strftime('%Y-%m-%d %H:%M:%S %Z'))
                    if '{}' in output_msg else
                    output_msg)

    @pytest.mark.parametrize("input_msg,output_msg", [
        ("<nick> !predict SPY +5%",
         "Prediction by nick for \x02SPY\x02 at market close: 105.00 (\x03095.00%\x03) ",
         ),
        ("<nick> !predict SPY -5%",
         "Prediction by nick for \x02SPY\x02 at market close: 95.00 (\x0304-5.00%\x03) ",
         ),
    ])
    @pytest_twisted.inlineCallbacks
    def test_predict_relay_bot(self, input_msg, output_msg):
        symbol = 'SPY'
        channel = "#finance"

        response = make_iex_response(symbol, previous_close=100)
        with mock_api(response):
            yield self.plugin.predict(self.mock_cardinal,
                                      user_info("relay.bot", "relay", "relay"),
                                      channel,
                                      input_msg)

        assert symbol in self.db['predictions']
        assert len(self.db['predictions'][symbol]) == 1

        self.mock_cardinal.sendMsg.assert_called_once_with(
            channel,
            output_msg)

    @pytest.mark.parametrize("input_msg", [
        "<whoami> !predict SPY +5%",
        "<whoami> !predict SPY -5%",
    ])
    @pytest_twisted.inlineCallbacks
    def test_predict_not_relay_bot(self, input_msg):
        channel = "#finance"

        yield self.plugin.predict(self.mock_cardinal,
                                  user_info("nick", "user", "vhost"),
                                  channel,
                                  input_msg)

        assert len(self.db['predictions']) == 0
        assert self.mock_cardinal.sendMsg.mock_calls == []

    @pytest.mark.parametrize("user,message,value,expected", [
        (
            user_info("whoami", None, None),
            "!predict SPY 5%",
            100,
            ("whoami", "SPY", 105, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict SPY +5%",
            100,
            ("whoami", "SPY", 105, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict SPY -5%",
            100,
            ("whoami", "SPY", 95, 100),
        ),
        (
            user_info("not.a.relay.bot", None, None),
            "<whoami> !predict SPY -5%",
            100,
            None,
        ),
        (
            user_info("relay.bot", "relay", "relay"),
            "<whoami> !predict SPY -5%",
            100,
            ("whoami", "SPY", 95, 100),
        ),
    ])
    @pytest_twisted.inlineCallbacks
    def test_parse_prediction_open(
            self,
            user,
            message,
            value,
            expected,
    ):
        symbol = 'SPY'

        response = make_iex_response(symbol, previous_close=value)
        with mock_api(response):
            result = yield self.plugin.parse_prediction(user, message)

        assert result == expected

    @pytest.mark.parametrize("user,message,value,expected", [
        (
            user_info("whoami", None, None),
            "!predict SPY 500",
            100,
            ("whoami", "SPY", 500, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict SPY $100",
            100,
            ("whoami", "SPY", 100, 100),
        ),
        (
            user_info("relay.bot", "relay", "relay"),
            "<whoami> !predict SPY 500",
            100,
            ("whoami", "SPY", 500, 100),
        ),
    ])
    @pytest_twisted.inlineCallbacks
    def test_parse_prediction_open_dollar_amount(
            self,
            user,
            message,
            value,
            expected,
    ):
        symbol = 'SPY'

        response = make_iex_response(symbol, previous_close=value)
        with mock_api(response):
            result = yield self.plugin.parse_prediction(user, message)

        assert result == expected

    @pytest.mark.parametrize("user,message,value,expected", [
        (
            user_info("whoami", None, None),
            "!predict SPY 5%",
            100,
            ("whoami", "SPY", 105, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict SPY +5%",
            100,
            ("whoami", "SPY", 105, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict SPY -5%",
            100,
            ("whoami", "SPY", 95, 100),
        ),
        (
            user_info("not.a.relay.bot", None, None),
            "<whoami> !predict SPY -5%",
            100,
            None,
        ),
        (
            user_info("relay.bot", "relay", "relay"),
            "<whoami> !predict SPY -5%",
            100,
            ("whoami", "SPY", 95, 100),
        ),
    ])
    @pytest_twisted.inlineCallbacks
    def test_parse_prediction_close(
            self,
            user,
            message,
            value,
            expected,
    ):
        symbol = 'SPY'

        response = make_iex_response(symbol, price=value)
        with mock_api(response, fake_now=get_fake_now(market_is_open=False)):
            result = yield self.plugin.parse_prediction(user, message)

        assert result == expected

    @patch.object(plugin, 'est_now')
    def test_save_prediction(self, mock_now):
        symbol = 'SPY'
        nick = 'whoami'
        base = 100
        prediction = 105

        tz = pytz.timezone('America/New_York')
        mock_now.return_value = tz.localize(datetime.datetime(
            2020,
            3,
            23,
            12,
            0,
            0,
        ))
        self.plugin.save_prediction(
            symbol,
            nick,
            base,
            prediction,
        )

        assert symbol in self.db['predictions']
        assert nick in self.db['predictions'][symbol]
        actual = self.db['predictions'][symbol][nick]
        assert actual == {
            'when': '2020-03-23 12:00:00 EDT',
            'base': base,
            'prediction': prediction,
        }

    @defer.inlineCallbacks
    def test_get_daily(self):
        symbol = 'SPY'
        price = 101.0
        previous_close = 102.0

        response = make_iex_response(symbol,
                                     price=price,
                                     previous_close=previous_close,
                                     )

        expected = {
            'symbol': symbol,
            'price': price,
            'previous close': previous_close,
            # this one is calculated by our mock response function so it
            # doesn't really test anything anymore
            'change': ((price - previous_close) / previous_close) * 100,
        }

        with mock_api(response):
            result = yield self.plugin.get_daily(symbol)
        assert result == expected

    @patch.object(plugin, 'est_now')
    def test_market_is_open(self, mock_now):
        tz = pytz.timezone('America/New_York')

        # Nothing special about this time - it's a Thursday 7:49pm
        mock_now.return_value = tz.localize(datetime.datetime(
            2020,
            3,
            19,
            19,
            49,
            55,
            0,
        ))
        assert plugin.market_is_open() is False

        # The market was open earlier though
        mock_now.return_value = tz.localize(datetime.datetime(
            2020,
            3,
            19,
            13,
            49,
            55,
            0,
        ))
        assert plugin.market_is_open() is True

        # But not before 9:30am
        mock_now.return_value = tz.localize(datetime.datetime(
            2020,
            3,
            19,
            9,
            29,
            59,
            0,
        ))
        assert plugin.market_is_open() is False

        # Or this weekend
        mock_now.return_value = tz.localize(datetime.datetime(
            2020,
            3,
            14,
            13,
            49,
            55,
            0,
        ))
        assert plugin.market_is_open() is False
예제 #24
0
 def get_user():
     user = user_info('nick', 'user', 'vhost')
     return "{}!{}@{}".format(user.nick, user.user, user.vhost), user
예제 #25
0
class TestTickerPlugin(object):
    @pytest.fixture(autouse=True)
    def setup_method_fixture(self, request, tmpdir):
        self.api_key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        self.channel = '#test'
        self.channels = [self.channel]
        self.stocks = {
            'INX': 'S&P 500',
            'DJI': 'Dow',
            'VEU': 'Foreign',
            'AGG': 'US Bond',
        }
        self.relay_bots = [
            {
                "nick": "relay.bot",
                "user": "******",
                "vhost": "relay"
            },
        ]

        d = tmpdir.mkdir('storage')

        get_db, self.db = get_mock_db()
        self.mock_cardinal = Mock(spec=CardinalBot)
        self.mock_cardinal.network = self.network = 'irc.darkscience.net'
        self.mock_cardinal.storage_path = str(d.dirpath())
        self.mock_cardinal.get_db.side_effect = get_db

        self.plugin = TickerPlugin(
            self.mock_cardinal, {
                'api_key': self.api_key,
                'channels': self.channels,
                'stocks': self.stocks,
                'relay_bots': self.relay_bots,
            })

    def test_config_defaults(self):
        plugin = TickerPlugin(self.mock_cardinal, {
            'api_key': self.api_key,
        })
        assert plugin.config['api_key'] == self.api_key
        assert plugin.config['channels'] == []
        assert plugin.config['stocks'] == {}
        assert plugin.config['relay_bots'] == []

    def test_missing_api_key(self):
        with pytest.raises(KeyError):
            TickerPlugin(self.mock_cardinal, {})

    def test_missing_stocks(self):
        with pytest.raises(ValueError):
            TickerPlugin(
                self.mock_cardinal, {
                    'api_key': self.api_key,
                    'stocks': {
                        'a': 'a',
                        'b': 'b',
                        'c': 'c',
                        'd': 'd',
                        'e': 'e',
                        'f': 'f',
                    },
                })

    @defer.inlineCallbacks
    def test_send_ticker(self):
        responses = [
            make_global_quote_response('DJI', previous_close=100, close=200),
            make_global_quote_response('AGG', previous_close=100,
                                       close=150.50),
            make_global_quote_response('VEU', previous_close=100, close=105),
            make_global_quote_response('INX', previous_close=100, close=50),
        ]

        with mock_api(responses, fake_now=get_fake_now(market_is_open=True)):
            yield self.plugin.send_ticker()

        self.mock_cardinal.sendMsg.assert_called_once_with(
            self.channel, 'Dow (\x02DJI\x02): \x0309100.00%\x03 | '
            'Foreign (\x02VEU\x02): \x03095.00%\x03 | '
            'S&P 500 (\x02INX\x02): \x0304-50.00%\x03 | '
            'US Bond (\x02AGG\x02): \x030950.50%\x03')

    @pytest.mark.parametrize(
        "dt,should_send_ticker,should_do_predictions",
        [
            (
                datetime.datetime(2020, 3, 21, 16, 0, 0),  # Saturday 4pm
                False,
                False,
            ),
            (
                datetime.datetime(2020, 3, 22, 16, 0, 0),  # Sunday 4pm
                False,
                False,
            ),
            (
                datetime.datetime(2020, 3, 23, 15, 45, 45),  # Monday 3:45pm
                True,
                False,
            ),
            (
                datetime.datetime(2020, 3, 23, 16, 0, 30),  # Monday 4pm
                True,
                True,
            ),
            (
                datetime.datetime(2020, 3, 23, 16, 15, 0),  # Monday 4:15pm
                False,
                False,
            ),
            (
                datetime.datetime(2020, 3, 27, 9, 15, 0),  # Friday 9:15am
                False,
                False,
            ),
            (
                datetime.datetime(2020, 3, 27, 9, 30, 15),  # Friday 9:30am
                True,
                True,
            ),
            (
                datetime.datetime(2020, 3, 27, 9, 45, 15),  # Friday 9:45am
                True,
                False,
            ),
        ])
    @patch.object(plugin.TickerPlugin, 'do_predictions')
    @patch.object(plugin.TickerPlugin, 'send_ticker')
    @patch.object(util, 'sleep')
    @patch.object(plugin, 'est_now')
    @pytest_twisted.inlineCallbacks
    def test_tick(self, est_now, sleep, send_ticker, do_predictions, dt,
                  should_send_ticker, should_do_predictions):
        est_now.return_value = dt

        yield self.plugin.tick()

        if should_send_ticker:
            send_ticker.assert_called_once_with()
        else:
            assert send_ticker.mock_calls == []

        if should_do_predictions:
            sleep.assert_called_once_with(60)
            do_predictions.assert_called_once_with()
        else:
            assert sleep.mock_calls == []
            assert do_predictions.mock_calls == []

    @pytest.mark.parametrize("market_is_open", [True, False])
    @patch.object(util, 'reactor', new_callable=Clock)
    @pytest_twisted.inlineCallbacks
    def test_do_predictions(self, mock_reactor, market_is_open):
        symbol = 'INX'
        base = 100.0

        user1 = 'user1'
        user2 = 'user2'
        prediction1 = 105.0
        prediction2 = 96.0

        actual = 95.0

        yield self.plugin.save_prediction(
            symbol,
            user1,
            base,
            prediction1,
        )
        yield self.plugin.save_prediction(
            symbol,
            user2,
            base,
            prediction2,
        )

        assert len(self.db['predictions']) == 1
        assert len(self.db['predictions'][symbol]) == 2

        response = make_global_quote_response(symbol, close=actual)

        with mock_api(response, fake_now=get_fake_now(market_is_open)):
            d = self.plugin.do_predictions()
            mock_reactor.advance(15)

            yield d

        assert len(self.mock_cardinal.sendMsg.mock_calls) == 3
        self.mock_cardinal.sendMsg.assert_called_with(
            self.channel,
            '{} had the closest guess for \x02{}\x02 out of {} predictions '
            'with a prediction of {} (\x0304{:.2f}%\x03) '
            'compared to the actual {} of {} (\x0304{:.2f}%\x03).'.format(
                user2, symbol, 2, prediction2, -4,
                'open' if market_is_open else 'close', actual, -5))

    @patch.object(plugin, 'est_now')
    def test_send_prediction(self, mock_now):
        prediction = 105
        actual = 110
        base = 100
        nick = "nick"
        symbol = "INX"

        # Set the datetime to a known value so the message can be tested
        tz = pytz.timezone('America/New_York')
        mock_now.return_value = tz.localize(
            datetime.datetime(2020, 3, 20, 10, 50, 0, 0))

        prediction_ = {
            'when': '2020-03-20 10:50:00 EDT',
            'prediction': prediction,
            'base': base,
        }
        self.plugin.send_prediction(nick, symbol, prediction_, actual)

        message = ("Prediction by nick for \x02INX\02: 105 (\x03095.00%\x03). "
                   "Actual value at open: 110 (\x030910.00%\x03). "
                   "Prediction set at 2020-03-20 10:50:00 EDT.")
        self.mock_cardinal.sendMsg.assert_called_once_with('#test', message)

    @pytest.mark.skip(reason="Not written yet")
    def test_check(self):
        pass

    @pytest.mark.parametrize(
        "symbol,input_msg,output_msg,market_is_open",
        [
            (
                "INX",
                "!predict INX +5%",
                "Prediction by nick for \x02INX\x02 at market close: 105.00 (\x03095.00%\x03) ",
                True,
            ),
            (
                "INX",
                "!predict INX -5%",
                "Prediction by nick for \x02INX\x02 at market close: 95.00 (\x0304-5.00%\x03) ",
                True,
            ),
            (
                "INX",
                "!predict INX -5%",
                "Prediction by nick for \x02INX\x02 at market open: 95.00 (\x0304-5.00%\x03) ",
                False,
            ),
            # testing a few more formats of stock symbols
            (
                "^RUT",
                "!predict ^RUT -5%",
                "Prediction by nick for \x02^RUT\x02 at market open: 95.00 (\x0304-5.00%\x03) ",
                False,
            ),
            (
                "REE.MC",
                "!predict REE.MC -5%",
                "Prediction by nick for \x02REE.MC\x02 at market open: 95.00 (\x0304-5.00%\x03) ",
                False,
            ),
            (
                "LON:HDLV",
                "!predict LON:HDLV -5%",
                "Prediction by nick for \x02LON:HDLV\x02 at market open: 95.00 (\x0304-5.00%\x03) ",
                False,
            ),
        ])
    @pytest_twisted.inlineCallbacks
    def test_predict(self, symbol, input_msg, output_msg, market_is_open):
        channel = "#finance"

        fake_now = get_fake_now(market_is_open=market_is_open)

        kwargs = {'previous_close': 100} if market_is_open else {'close': 100}
        response = make_global_quote_response(symbol, **kwargs)

        with mock_api(response, fake_now=fake_now):
            yield self.plugin.predict(self.mock_cardinal,
                                      user_info("nick", "user", "vhost"),
                                      channel, input_msg)

        assert symbol in self.db['predictions']
        assert len(self.db['predictions'][symbol]) == 1

        self.mock_cardinal.sendMsg.assert_called_once_with(channel, output_msg)

    @pytest.mark.parametrize("message_pairs", [(
        (
            "!predict INX +5%",
            "Prediction by nick for \x02INX\x02 at market close: 105.00 (\x03095.00%\x03) ",
        ),
        ("!predict INX -5%",
         "Prediction by nick for \x02INX\x02 at market close: 95.00 (\x0304-5.00%\x03) "
         "(replaces old prediction of 105.00 (\x03095.00%\x03) set at {})"),
    )])
    @pytest_twisted.inlineCallbacks
    def test_predict_replace(self, message_pairs):
        channel = "#finance"
        symbol = 'INX'

        response = make_global_quote_response(symbol, previous_close=100)

        fake_now = get_fake_now()
        for input_msg, output_msg in message_pairs:
            with mock_api(response, fake_now):
                yield self.plugin.predict(self.mock_cardinal,
                                          user_info("nick", "user", "vhost"),
                                          channel, input_msg)

                assert symbol in self.db['predictions']
                assert len(self.db['predictions'][symbol]) == 1

                self.mock_cardinal.sendMsg.assert_called_with(
                    channel,
                    output_msg.format(
                        fake_now.strftime('%Y-%m-%d %H:%M:%S %Z'))
                    if '{}' in output_msg else output_msg)

    @pytest.mark.parametrize("input_msg,output_msg", [
        (
            "<nick> !predict INX +5%",
            "Prediction by nick for \x02INX\x02 at market close: 105.00 (\x03095.00%\x03) ",
        ),
        (
            "<nick> !predict INX -5%",
            "Prediction by nick for \x02INX\x02 at market close: 95.00 (\x0304-5.00%\x03) ",
        ),
    ])
    @pytest_twisted.inlineCallbacks
    def test_predict_relay_bot(self, input_msg, output_msg):
        symbol = 'INX'
        channel = "#finance"

        response = make_global_quote_response(symbol, previous_close=100)
        with mock_api(response):
            yield self.plugin.predict(self.mock_cardinal,
                                      user_info("relay.bot", "relay", "relay"),
                                      channel, input_msg)

        assert symbol in self.db['predictions']
        assert len(self.db['predictions'][symbol]) == 1

        self.mock_cardinal.sendMsg.assert_called_once_with(channel, output_msg)

    @pytest.mark.parametrize("input_msg", [
        "<whoami> !predict INX +5%",
        "<whoami> !predict INX -5%",
    ])
    @pytest_twisted.inlineCallbacks
    def test_predict_not_relay_bot(self, input_msg):
        channel = "#finance"

        yield self.plugin.predict(self.mock_cardinal,
                                  user_info("nick", "user", "vhost"), channel,
                                  input_msg)

        assert len(self.db['predictions']) == 0
        assert self.mock_cardinal.sendMsg.mock_calls == []

    @pytest.mark.parametrize("user,message,value,expected", [
        (
            user_info("whoami", None, None),
            "!predict INX 5%",
            100,
            ("whoami", "INX", 105, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict INX +5%",
            100,
            ("whoami", "INX", 105, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict INX -5%",
            100,
            ("whoami", "INX", 95, 100),
        ),
        (
            user_info("not.a.relay.bot", None, None),
            "<whoami> !predict INX -5%",
            100,
            None,
        ),
        (
            user_info("relay.bot", "relay", "relay"),
            "<whoami> !predict INX -5%",
            100,
            ("whoami", "INX", 95, 100),
        ),
    ])
    @pytest_twisted.inlineCallbacks
    def test_parse_prediction_open(
        self,
        user,
        message,
        value,
        expected,
    ):
        symbol = 'INX'

        response = make_global_quote_response(symbol, previous_close=value)
        with mock_api(response):
            result = yield self.plugin.parse_prediction(user, message)

        assert result == expected

    @pytest.mark.parametrize("user,message,value,expected", [
        (
            user_info("whoami", None, None),
            "!predict INX 5%",
            100,
            ("whoami", "INX", 105, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict INX +5%",
            100,
            ("whoami", "INX", 105, 100),
        ),
        (
            user_info("whoami", None, None),
            "!predict INX -5%",
            100,
            ("whoami", "INX", 95, 100),
        ),
        (
            user_info("not.a.relay.bot", None, None),
            "<whoami> !predict INX -5%",
            100,
            None,
        ),
        (
            user_info("relay.bot", "relay", "relay"),
            "<whoami> !predict INX -5%",
            100,
            ("whoami", "INX", 95, 100),
        ),
    ])
    @pytest_twisted.inlineCallbacks
    def test_parse_prediction_close(
        self,
        user,
        message,
        value,
        expected,
    ):
        symbol = 'INX'

        response = make_global_quote_response(symbol, close=value)
        with mock_api(response, fake_now=get_fake_now(market_is_open=False)):
            result = yield self.plugin.parse_prediction(user, message)

        assert result == expected

    @patch.object(plugin, 'est_now')
    def test_save_prediction(self, mock_now):
        symbol = 'INX'
        nick = 'whoami'
        base = 100
        prediction = 105

        tz = pytz.timezone('America/New_York')
        mock_now.return_value = tz.localize(
            datetime.datetime(
                2020,
                3,
                23,
                12,
                0,
                0,
            ))
        self.plugin.save_prediction(
            symbol,
            nick,
            base,
            prediction,
        )

        assert symbol in self.db['predictions']
        assert nick in self.db['predictions'][symbol]
        actual = self.db['predictions'][symbol][nick]
        assert actual == {
            'when': '2020-03-23 12:00:00 EDT',
            'base': base,
            'prediction': prediction,
        }

    @defer.inlineCallbacks
    def test_get_quote(self):
        symbol = 'INX'
        response = make_global_quote_response(symbol)
        r = response["Global Quote"]

        expected = {
            'symbol':
            symbol,
            'open':
            float(r['02. open']),
            'high':
            float(r['03. high']),
            'low':
            float(r['04. low']),
            'price':
            float(r['05. price']),
            'volume':
            int(r['06. volume']),
            'latest trading day':
            datetime.datetime.today().replace(hour=0,
                                              minute=0,
                                              second=0,
                                              microsecond=0),
            'previous close':
            float(r['08. previous close']),
            'change':
            float(r['09. change']),
            'change percent':
            float(r['10. change percent'][:-1]),
        }

        with mock_api(response):
            result = yield self.plugin.get_quote(symbol)

        assert result == expected

    @defer.inlineCallbacks
    def test_get_daily(self):
        symbol = 'INX'
        last_open = 100.0
        last_close = 101.0
        previous_close = 102.0

        response = make_global_quote_response(
            symbol,
            open_=last_open,
            close=last_close,
            previous_close=previous_close,
        )

        expected = {
            'symbol':
            symbol,
            'close':
            last_close,
            'open':
            last_open,
            'previous close':
            previous_close,
            # this one is calculated by our make response function so it
            # doesn't really test anything anymore
            'change':
            float('{:.4f}'.format(get_delta(last_close, previous_close))),
        }

        with mock_api(response):
            result = yield self.plugin.get_daily(symbol)
        assert result == expected

    @defer.inlineCallbacks
    def test_get_time_series_daily(self):
        symbol = 'INX'

        response = make_time_series_daily_response(symbol)
        with mock_api(response):
            result = yield self.plugin.get_time_series_daily(symbol)

        for date in response['Time Series (Daily)']:
            assert date in result
            # verify prefix is stripped and values are floats
            for key in ('open', 'high', 'low', 'close', 'volume'):
                assert key in result[date]
                assert isinstance(result[date][key], float)

    @defer.inlineCallbacks
    def test_get_time_series_daily_bad_format(self):
        symbol = 'INX'

        response = {}
        with mock_api(response):
            with pytest.raises(KeyError):
                yield self.plugin.get_time_series_daily(symbol)

    @defer.inlineCallbacks
    def test_make_av_request(self):
        # Verify that this returns the response unmodified, and that it
        # properly calculates params
        function = 'TIME_SERIES_DAILY'
        symbol = 'INX'
        outputsize = 'compact'

        response = make_time_series_daily_response(symbol)
        with mock_api(response) as defer_mock:
            result = yield self.plugin.make_av_request(function,
                                                       params={
                                                           'symbol':
                                                           symbol,
                                                           'outputsize':
                                                           outputsize,
                                                       })

        assert result == response

        defer_mock.assert_called_once_with(plugin.requests.get,
                                           plugin.AV_API_URL,
                                           params={
                                               'apikey': self.api_key,
                                               'function': function,
                                               'symbol': symbol,
                                               'outputsize': outputsize,
                                               'datatype': 'json',
                                           })

    @defer.inlineCallbacks
    def test_make_av_request_no_params(self):
        # This one is mostly just for coverage
        function = 'TIME_SERIES_DAILY'
        symbol = 'INX'

        response = make_time_series_daily_response(symbol)
        with mock_api(response) as defer_mock:
            result = yield self.plugin.make_av_request(function)

        assert result == response

        defer_mock.assert_called_once_with(plugin.requests.get,
                                           plugin.AV_API_URL,
                                           params={
                                               'apikey': self.api_key,
                                               'function': function,
                                               'datatype': 'json',
                                           })

    @patch.object(util, 'reactor', new_callable=Clock)
    @defer.inlineCallbacks
    def test_make_av_request_retry_when_throttled(self, mock_reactor):
        # Verify that this returns the response unmodified, and that it
        # properly calculates params
        function = 'TIME_SERIES_DAILY'
        symbol = 'INX'
        outputsize = 'compact'

        response = make_time_series_daily_response(symbol)
        throttle_times = plugin.MAX_RETRIES - 1
        with mock_api(response, throttle_times=throttle_times) as defer_mock:
            d = self.plugin.make_av_request(function,
                                            params={
                                                'symbol': symbol,
                                                'outputsize': outputsize,
                                            })

            # loop through retries
            for _ in range(throttle_times):
                mock_reactor.advance(plugin.RETRY_WAIT)

            result = yield d

        assert result == response

        defer_mock.assert_has_calls([
            call(plugin.requests.get,
                 plugin.AV_API_URL,
                 params={
                     'apikey': self.api_key,
                     'function': function,
                     'symbol': symbol,
                     'outputsize': outputsize,
                     'datatype': 'json',
                 })
        ] * (throttle_times + 1))

    @patch.object(util, 'reactor', new_callable=Clock)
    @defer.inlineCallbacks
    def test_make_av_request_retry_on_exception(self, mock_reactor):
        # Verify that this returns the response unmodified, and that it
        # properly calculates params
        function = 'TIME_SERIES_DAILY'
        symbol = 'INX'
        outputsize = 'compact'

        response = make_time_series_daily_response(symbol)
        raise_times = plugin.MAX_RETRIES - 1
        with mock_api(response, raise_times=raise_times) as defer_mock:
            d = self.plugin.make_av_request(function,
                                            params={
                                                'symbol': symbol,
                                                'outputsize': outputsize,
                                            })

            # loop through retries
            for _ in range(raise_times):
                mock_reactor.advance(plugin.RETRY_WAIT)

            result = yield d

        assert result == response

        defer_mock.assert_has_calls([
            call(plugin.requests.get,
                 plugin.AV_API_URL,
                 params={
                     'apikey': self.api_key,
                     'function': function,
                     'symbol': symbol,
                     'outputsize': outputsize,
                     'datatype': 'json',
                 })
        ] * (raise_times + 1))

    @patch.object(util, 'reactor', new_callable=Clock)
    @defer.inlineCallbacks
    def test_make_av_request_give_up_after_max_retries(self, mock_reactor):
        # Verify that this returns the response unmodified, and that it
        # properly calculates params
        function = 'TIME_SERIES_DAILY'
        symbol = 'INX'
        outputsize = 'compact'

        response = make_time_series_daily_response(symbol)
        raise_times = plugin.MAX_RETRIES
        with mock_api(response, raise_times=raise_times) as defer_mock:
            d = self.plugin.make_av_request(function,
                                            params={
                                                'symbol': symbol,
                                                'outputsize': outputsize,
                                            })

            # loop through retries
            for _ in range(raise_times):
                mock_reactor.advance(plugin.RETRY_WAIT)

            with pytest.raises(Exception):
                yield d

        defer_mock.assert_has_calls([
            call(plugin.requests.get,
                 plugin.AV_API_URL,
                 params={
                     'apikey': self.api_key,
                     'function': function,
                     'symbol': symbol,
                     'outputsize': outputsize,
                     'datatype': 'json',
                 })
        ] * (raise_times))

    @patch.object(plugin, 'est_now')
    def test_market_is_open(self, mock_now):
        tz = pytz.timezone('America/New_York')

        # Nothing special about this time - it's a Thursday 7:49pm
        mock_now.return_value = tz.localize(
            datetime.datetime(
                2020,
                3,
                19,
                19,
                49,
                55,
                0,
            ))
        assert plugin.market_is_open() is False

        # The market was open earlier though
        mock_now.return_value = tz.localize(
            datetime.datetime(
                2020,
                3,
                19,
                13,
                49,
                55,
                0,
            ))
        assert plugin.market_is_open() is True

        # But not before 9:30am
        mock_now.return_value = tz.localize(
            datetime.datetime(
                2020,
                3,
                19,
                9,
                29,
                59,
                0,
            ))
        assert plugin.market_is_open() is False

        # Or this weekend
        mock_now.return_value = tz.localize(
            datetime.datetime(
                2020,
                3,
                14,
                13,
                49,
                55,
                0,
            ))
        assert plugin.market_is_open() is False
예제 #26
0
    def test_is_admin(self):
        plugin = AdminPlugin(None, {'admins': [{'nick': 'nick'}]})
        assert plugin.is_admin(user_info('nick', 'user', 'vhost')) is True
        assert plugin.is_admin(user_info('bad_nick', 'user', 'vhost')) is False

        plugin = AdminPlugin(None, {'admins': [{'user': '******'}]})
        assert plugin.is_admin(user_info('nick', 'user', 'vhost')) is True
        assert plugin.is_admin(user_info('nick', 'bad_user', 'vhost')) is False

        plugin = AdminPlugin(None, {'admins': [{'vhost': 'vhost'}]})
        assert plugin.is_admin(user_info('nick', 'user', 'vhost')) is True
        assert plugin.is_admin(user_info('nick', 'user', 'bad_vhost')) is False

        plugin = AdminPlugin(None,
                             {'admins': [{
                                 'nick': 'nick',
                                 'vhost': 'vhost'
                             }]})
        assert plugin.is_admin(user_info('nick', 'user', 'vhost')) is True
        assert plugin.is_admin(user_info('bad_nick', 'user', 'vhost')) is False
        assert plugin.is_admin(user_info('nick', 'user', 'bad_vhost')) is False

        plugin = AdminPlugin(None,
                             {'admins': [{
                                 'user': '******',
                                 'vhost': 'vhost'
                             }]})
        assert plugin.is_admin(user_info('nick', 'user', 'vhost')) is True
        assert plugin.is_admin(user_info('nick', 'bad_user', 'vhost')) is False
        assert plugin.is_admin(user_info('nick', 'user', 'bad_vhost')) is False

        plugin = AdminPlugin(None,
                             {'admins': [{
                                 'nick': 'nick',
                                 'user': '******'
                             }]})
        assert plugin.is_admin(user_info('nick', 'user', 'vhost')) is True
        assert plugin.is_admin(user_info('bad_nick', 'user', 'vhost')) is False
        assert plugin.is_admin(user_info('nick', 'bad_user', 'vhost')) is False

        plugin = AdminPlugin(
            None,
            {'admins': [{
                'nick': 'nick',
                'user': '******',
                'vhost': 'vhost'
            }]})
        assert plugin.is_admin(user_info('nick', 'user', 'vhost')) is True
        assert plugin.is_admin(user_info('bad_nick', 'user', 'vhost')) is False
        assert plugin.is_admin(user_info('nick', 'bad_user', 'vhost')) is False
        assert plugin.is_admin(user_info('nick', 'user', 'bad_vhost')) is False