Current File : /home/jvzmxxx/wiki1/extensions/EventLogging/server/tests/fixtures.py
# -*- coding: utf-8 -*-
"""
  eventlogging unit tests
  ~~~~~~~~~~~~~~~~~~~~~~~

  This module contains test fixtures.

"""
from __future__ import unicode_literals

import copy
import signal

import eventlogging
import eventlogging.factory
import sqlalchemy


TEST_SCHEMA_SCID = ('TestSchema', 123)

_schemas = {
    eventlogging.schema.CAPSULE_SCID: {
        'properties': {
            'clientIp': {
                'type': 'string'
            },
            'event': {
                'type': 'object',
                'required': True
            },
            'wiki': {
                'type': 'string',
                'required': True
            },
            'webHost': {
                'type': 'string'
            },
            'revision': {
                'type': 'integer',
                'required': True
            },
            'schema': {
                'type': 'string',
                'required': True
            },
            'recvFrom': {
                'type': 'string',
                'required': True
            },
            'seqId': {
                'type': 'integer'
            },
            'timestamp': {
                'type': 'number',
                'required': True,
                'format': 'utc-millisec'
            },
            'uuid': {
                'type': 'string',
                'required': True,
                'format': 'uuid5-hex'
            }
        },
        'additionalProperties': False
    },
    eventlogging.schema.ERROR_SCID: {
        'properties': {
            'rawEvent': {
                'type': 'string',
                'required': True
            },
            'message': {
                'type': 'string',
                'required': True
            },
            'code': {
                'type': 'string',
                'required': True,
                'enum': [
                    'processor',
                    'consumer',
                    'validation'
                ],
            },
            'schema': {
                'type': 'string',
                'required': True
            },
            'revision': {
                'type': 'integer',
                'required': True
            }
        }
    },
    TEST_SCHEMA_SCID: {
        'properties': {
            'value': {
                'type': 'string',
                'required': True
            },
            'nested': {
                'type': 'object',
                'properties': {
                    'deeplyNested': {
                        'type': 'object',
                        'properties': {
                            'pi': {
                                'type': 'number',
                            }
                        }
                    }
                }
            }
        }
    }
}


_event = {
    'event': {
        'value': '☆ 彡',
        'nested': {
            'deeplyNested': {
                'pi': 3.14159
            }
        }
    },
    'seqId': 12345,
    'clientIp': '127.0.0.1',
    'timestamp': 1358791834912,
    'wiki': 'enwiki',
    'webHost': 'en.m.wikipedia.org',
    'recvFrom': 'fenari',
    'revision': 123,
    'schema': 'TestSchema',
    'uuid': 'babb66f34a0a5de3be0c6513088be33e'
}

# {} is preferred and PHP side of EL
# should be translating empty events to {} but this is
# to test that [] also works
_incorrectly_serialized_empty_event = {
    'event': [],
    'seqId': 12345,
    'clientIp': '127.0.0.1',
    'timestamp': 1358791834912,
    'wiki': 'enwiki',
    'webHost': 'en.m.wikipedia.org',
    'recvFrom': 'fenari',
    'revision': 123,
    'schema': 'TestSchema',
    'uuid': 'babb66f34a0a5de3be0c6513088be33e'
}


class HttpRequestAttempted(RuntimeError):
    """Raised on attempt to retrieve a schema via HTTP."""
    pass


# We'll be replacing :func:`eventlogging.schemas.http_get_schema` with a
# mock object, so set aside an unpatched copy so we can clean up.
orig_http_get_schema = eventlogging.schema.http_get_schema


def mock_http_get_schema(scid):
    """Mock of :func:`eventlogging.schemas.http_get_schema`
    Used to detect when :func:`eventlogging.schemas.get_schema`
    delegates to HTTP retrieval.
    """
    raise HttpRequestAttempted('Attempted HTTP fetch: %s' % (scid,))


def _get_event():
    """ Creates events on demand with unique ids"""
    for i in range(1, 100):
        event = copy.deepcopy(_event)
        event['uuid'] = i
        yield event


class SchemaTestMixin(object):
    """A :class:`unittest.TestCase` mix-in for test cases that depend on
    schema look-ups."""

    def setUp(self):
        """Stub `http_get_schema` and pre-fill schema cache."""
        super(SchemaTestMixin, self).setUp()
        self.event = copy.deepcopy(_event)
        self.incorrectly_serialized_empty_event = copy.deepcopy(
            _incorrectly_serialized_empty_event)
        eventlogging.schema.schema_cache = copy.deepcopy(_schemas)
        eventlogging.schema.http_get_schema = mock_http_get_schema
        self.event_generator = _get_event()

    def tearDown(self):
        """Clear schema cache and restore stubbed `http_get_schema`."""
        super(SchemaTestMixin, self).tearDown()
        eventlogging.schema.schema_cache.clear()
        eventlogging.schema.http_get_schema = orig_http_get_schema

    def assertIsValid(self, event, msg=None):
        """Assert that capsule 'event' object validates."""
        return self.assertIsNone(eventlogging.validate(event), msg)

    def assertIsInvalid(self, event, msg=None):
        """Assert that capsule 'event' object fails validation."""
        with self.assertRaises(eventlogging.ValidationError, msg):
            eventlogging.validate(event)


class DatabaseTestMixin(SchemaTestMixin):
    """A :class:`unittest.TestCase` mix-in for database testing using an
    in-memory sqlite database."""

    def setUp(self):
        """Configure :class:`sqlalchemy.engine.Engine` and
        :class:`sqlalchemy.schema.MetaData` objects."""
        super(DatabaseTestMixin, self).setUp()
        self.engine = sqlalchemy.create_engine('sqlite://', echo=False)
        self.meta = sqlalchemy.MetaData(bind=self.engine)

    def tearDown(self):
        """Dispose of the database access objects."""
        super(DatabaseTestMixin, self).tearDown()
        self.meta.drop_all()
        self.engine.dispose()


class HttpSchemaTestMixin(object):
    """A :class:`unittest.TestCase` mix-in for stubbing HTTP responses."""

    http_resp = ''

    def setUp(self):
        """Replace `http_get` with stub."""
        super(HttpSchemaTestMixin, self).setUp()
        self.orig_http_get = eventlogging.schema.http_get
        eventlogging.schema.http_get = self.http_get_stub
        eventlogging.schema.schema_cache.clear()

    def tearDown(self):
        """Restore original `http_get`."""
        eventlogging.schema.http_get = self.orig_http_get

    def http_get_stub(self, url):
        """Test stub for `http_get`."""
        return self.http_resp


class HandlerTestMixin(object):
    def setUp(self):
        self.orig_writers = eventlogging.factory._writers.copy()
        eventlogging.factory._writers.clear()
        self.orig_readers = eventlogging.factory._readers.copy()
        eventlogging.factory._readers.clear()


class TimeoutTestMixin(object):
    """A :class:`unittest.TestCase` mix-in that imposes a time-limit on
    tests. Tests exceeding the limit are failed."""

    # Max time (in seconds) to allow tests to run before failing.
    max_time = 2

    def setUp(self):
        """Set the alarm."""
        super(TimeoutTestMixin, self).setUp()
        signal.signal(signal.SIGALRM, self.timeOut)
        signal.alarm(self.max_time)

    def tearDown(self):
        """Disable the alarm."""
        signal.alarm(0)

    def timeOut(self, signum, frame):
        """SIGALRM handler. Fails test if triggered."""
        self.fail('Timed out.')