def add_default_application_settings():
    """Add the default application settings to the database."""
    orthography1 = h.generate_default_orthography1()
    orthography2 = h.generate_default_orthography2()
    contributor = Session.query(User).filter(User.role==u'contributor').first()
    application_settings = h.generate_default_application_settings([orthography1, orthography2], [contributor])
    Session.add(application_settings)
    Session.commit()
    return application_settings
    def test_a_initialize(self):
        """Initialize the database for /rememberedforms tests."""
        h.clear_all_models()
        administrator = h.generate_default_administrator()
        contributor = h.generate_default_contributor()
        viewer = h.generate_default_viewer()
        Session.add_all([administrator, contributor, viewer])
        Session.commit()

        _create_test_data(self.n)
        self._add_SEARCH_to_web_test_valid_methods()

        # Create an application settings where the contributor is unrestricted
        viewer, contributor, administrator = get_users()
        application_settings = h.generate_default_application_settings()
        application_settings.unrestricted_users = [contributor]
        Session.add(application_settings)
        Session.commit()
    def test_a_initialize(self):
        """Initialize the database for /rememberedforms tests."""
        h.clear_all_models()
        administrator = h.generate_default_administrator()
        contributor = h.generate_default_contributor()
        viewer = h.generate_default_viewer()
        Session.add_all([administrator, contributor, viewer])
        Session.commit()

        _create_test_data(self.n)
        self._add_SEARCH_to_web_test_valid_methods()

        # Create an application settings where the contributor is unrestricted
        viewer, contributor, administrator = get_users()
        application_settings = h.generate_default_application_settings()
        application_settings.unrestricted_users = [contributor]
        Session.add(application_settings)
        Session.commit()
Exemple #4
0
    def test_delete(self):
        """Tests that DELETE /orthographies/id deletes the orthography with id=id."""

        # Create an orthography to delete.
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c'})
        params = json.dumps(params)
        response = self.app.post(url('orthographies'), params, self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        orthography_count = Session.query(Orthography).count()
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c'
        assert resp['lowercase'] == False   # default value from model/orthography.py
        assert resp['initial_glottal_stops'] == True    # default value from model/orthography.py
        orthography_id = resp['id']
        original_datetime_modified = resp['datetime_modified']

        # Now delete the orthography
        response = self.app.delete(url('orthography', id=orthography_id), headers=self.json_headers,
            extra_environ=self.extra_environ_admin)
        resp = json.loads(response.body)
        new_orthography_count = Session.query(Orthography).count()
        assert new_orthography_count == orthography_count - 1
        assert resp['id'] == orthography_id
        assert response.content_type == 'application/json'

        # Trying to get the deleted orthography from the db should return None
        deleted_orthography = Session.query(Orthography).get(orthography_id)
        assert deleted_orthography == None

        # Delete with an invalid id
        id = 9999999999999
        response = self.app.delete(url('orthography', id=id),
            headers=self.json_headers, extra_environ=self.extra_environ_admin,
            status=404)
        assert u'There is no orthography with id %s' % id in json.loads(response.body)['error']
        assert response.content_type == 'application/json'

        # Delete without an id
        response = self.app.delete(url('orthography', id=''), status=404,
            headers=self.json_headers, extra_environ=self.extra_environ_admin)
        assert json.loads(response.body)['error'] == 'The resource could not be found.'
        assert response.content_type == 'application/json'

        # Observe how deletions are restricted when an orthography is part of an
        # active application settings ...

        # Create an orthography to demonstrate.
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c'})
        params = json.dumps(params)
        response = self.app.post(url('orthographies'), params, self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        orthography_count = Session.query(Orthography).count()
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c'
        assert resp['lowercase'] == False   # default value from model/orthography.py
        assert resp['initial_glottal_stops'] == True    # default value from model/orthography.py
        orthography_id = resp['id']

        # Create an application settings with the above orthography as the storage orthography
        app_set = h.generate_default_application_settings()
        app_set.storage_orthography = Session.query(Orthography).get(orthography_id)
        Session.add(app_set)
        Session.commit()

        # Now attempting to delete as a contributor should fail
        response = self.app.delete(url('orthography', id=orthography_id),
                    headers=self.json_headers, extra_environ=self.extra_environ_contrib, status=403)
        resp = json.loads(response.body)
        assert resp['error'] == u'Only administrators are permitted to delete orthographies that are used in the active application settings.'
        assert response.content_type == 'application/json'

        # If we now remove the orthography from the application settings, the
        # contributor will be able to delete it.
        app_set = h.get_application_settings()
        app_set.storage_orthography = None
        Session.commit()
        response = self.app.delete(url('orthography', id=orthography_id),
                    headers=self.json_headers, extra_environ=self.extra_environ_contrib)
        resp = json.loads(response.body)
        assert response.content_type == 'application/json'
        assert resp['orthography'] == u'a, b, c'
Exemple #5
0
    def test_update(self):
        """Tests that PUT /orthographies/id updates the orthography with id=id."""

        # Create an orthography to update.
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c'})
        params = json.dumps(params)
        response = self.app.post(url('orthographies'), params, self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        orthography_count = Session.query(Orthography).count()
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c'
        assert resp['lowercase'] == False   # default value from model/orthography.py
        assert resp['initial_glottal_stops'] == True    # default value from model/orthography.py
        assert response.content_type == 'application/json'
        orthography_id = resp['id']
        original_datetime_modified = resp['datetime_modified']

        # Update the orthography
        sleep(1)    # sleep for a second to ensure that MySQL registers a different datetime_modified for the update
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c, d'})
        params = json.dumps(params)
        response = self.app.put(url('orthography', id=orthography_id), params, self.json_headers,
                                 self.extra_environ_admin)
        resp = json.loads(response.body)
        datetime_modified = resp['datetime_modified']
        new_orthography_count = Session.query(Orthography).count()
        assert orthography_count == new_orthography_count
        assert datetime_modified != original_datetime_modified
        assert resp['orthography'] == u'a, b, c, d'
        assert response.content_type == 'application/json'

        # Attempt an update with no new input and expect to fail
        sleep(1)    # sleep for a second to ensure that MySQL could register a different datetime_modified for the update
        response = self.app.put(url('orthography', id=orthography_id), params, self.json_headers,
                                 self.extra_environ_admin, status=400)
        resp = json.loads(response.body)
        orthography_count = new_orthography_count
        new_orthography_count = Session.query(Orthography).count()
        our_orthography_datetime_modified = Session.query(Orthography).get(orthography_id).datetime_modified
        assert our_orthography_datetime_modified.isoformat() == datetime_modified
        assert orthography_count == new_orthography_count
        assert resp['error'] == u'The update request failed because the submitted data were not new.'
        assert response.content_type == 'application/json'

        # Observe how updates are restricted when an orthography is part of an
        # active application settings ...
        app_set = h.generate_default_application_settings()
        app_set.storage_orthography = Session.query(Orthography).get(orthography_id)
        Session.add(app_set)
        Session.commit()

        # Now attempting a valid update as a contributor should fail
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c, d, e'})
        params = json.dumps(params)
        response = self.app.put(url('orthography', id=orthography_id), params, self.json_headers,
                                 self.extra_environ_contrib, status=403)
        resp = json.loads(response.body)
        assert resp['error'] == u'Only administrators are permitted to update orthographies that are used in the active application settings.'
        assert response.content_type == 'application/json'

        # The same update as an admin should succeed.
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c, d, e'})
        params = json.dumps(params)
        response = self.app.put(url('orthography', id=orthography_id), params, self.json_headers,
                                 self.extra_environ_admin)
        resp = json.loads(response.body)
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c, d, e'
        assert response.content_type == 'application/json'

        # If we now remove the orthography from the application settings, the
        # contributor will be able to edit it.
        app_set = h.get_application_settings()
        app_set.storage_orthography = None
        Session.commit()
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c, d, e, f'})
        params = json.dumps(params)
        response = self.app.put(url('orthography', id=orthography_id), params, self.json_headers,
                                 self.extra_environ_contrib)
        resp = json.loads(response.body)
        assert response.content_type == 'application/json'
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c, d, e, f'
    def test_category_percolation(self):
        """Tests that changes to a category's name and deletion of a category trigger updates to forms containing morphemes of that category.
        """

        application_settings = h.generate_default_application_settings()
        Session.add(application_settings)
        Session.commit()

        extra_environ = {'test.authentication.role': u'administrator',
                               'test.application_settings': True}

        # Create an N category
        params = json.dumps({'name': u'N', 'type': u'lexical', 'description': u''})
        response = self.app.post(url('syntacticcategories'), params, self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        NId = resp['id']
        assert resp['name'] == u'N'
        assert response.content_type == 'application/json'

        # Create a lexical form 'chien/dog' of category N
        params = self.form_create_params.copy()
        params.update({
            'transcription': u'chien',
            'morpheme_break': u'chien',
            'morpheme_gloss': u'dog',
            'translations': [{'transcription': u'dog', 'grammaticality': u''}],
            'syntactic_category': NId
        })
        params = json.dumps(params)
        response = self.app.post(url('forms'), params, self.json_headers, extra_environ)
        resp = json.loads(response.body)
        chien_id = resp['id']
        assert resp['morpheme_break_ids'][0][0][0][1] == u'dog'
        assert resp['morpheme_break_ids'][0][0][0][2] == u'N'
        assert resp['morpheme_gloss_ids'][0][0][0][1] == u'chien'
        assert resp['morpheme_gloss_ids'][0][0][0][2] == u'N'
        assert resp['syntactic_category_string'] == u'N'
        assert resp['break_gloss_category'] == u'chien|dog|N'

        # Create a phrasal form 'chien-s/dog-PL' that will contain 'chien/dog'
        params = self.form_create_params.copy()
        params.update({
            'transcription': u'chiens',
            'morpheme_break': u'chien-s',
            'morpheme_gloss': u'dog-PL',
            'translations': [{'transcription': u'dogs', 'grammaticality': u''}],
            'syntactic_category': NId
        })
        params = json.dumps(params)
        response = self.app.post(url('forms'), params, self.json_headers, extra_environ)
        resp = json.loads(response.body)
        chiens_id = resp['id']
        assert resp['morpheme_break_ids'][0][0][0][1] == u'dog'
        assert resp['morpheme_break_ids'][0][0][0][2] == u'N'
        assert resp['morpheme_gloss_ids'][0][0][0][1] == u'chien'
        assert resp['morpheme_gloss_ids'][0][0][0][2] == u'N'
        assert resp['syntactic_category_string'] == u'N-?'
        assert resp['break_gloss_category'] == u'chien|dog|N-s|PL|?'

        # Now update the name of the N category and expect that change to cause
        # an update to the chien/dog and chien-s/dog-PL forms.
        form_backup_count = Session.query(model.FormBackup).count()
        params = json.dumps({'name': u'Noun', 'type': u'lexical', 'description': u''})
        response = self.app.put(url('syntacticcategory', id=NId), params, self.json_headers, extra_environ)
        new_form_backup_count = Session.query(model.FormBackup).count()
        chien = Session.query(model.Form).get(chien_id)
        chiens = Session.query(model.Form).get(chiens_id)
        assert new_form_backup_count == form_backup_count + 2
        assert chien.syntactic_category_string == u'Noun'
        assert chiens.syntactic_category_string == u'Noun-?'
        assert json.loads(chiens.morpheme_break_ids)[0][0][0][2] == u'Noun'

        # Now update something besides the name attribute of the N/Noun category
        # and expect no updates to any forms.
        params = json.dumps({'name': u'Noun', 'type': u'lexical', 'description': u'Blah!'})
        response = self.app.put(url('syntacticcategory', id=NId), params, self.json_headers, extra_environ)
        form_backup_count = new_form_backup_count
        new_form_backup_count = Session.query(model.FormBackup).count()
        chien = chiens = None
        chien = Session.query(model.Form).get(chien_id)
        chiens = Session.query(model.Form).get(chiens_id)
        assert new_form_backup_count == form_backup_count
        assert chien.syntactic_category_string == u'Noun'
        assert chiens.syntactic_category_string == u'Noun-?'
        assert json.loads(chiens.morpheme_break_ids)[0][0][0][2] == u'Noun'

        # Test deletion of sc
        response = self.app.delete(url('syntacticcategory', id=NId), headers=self.json_headers,
                                   extra_environ=extra_environ)
        form_backup_count = new_form_backup_count
        new_form_backup_count = Session.query(model.FormBackup).count()
        chien = chiens = None
        chien = Session.query(model.Form).get(chien_id)
        chiens = Session.query(model.Form).get(chiens_id)
        assert new_form_backup_count == form_backup_count + 2
        assert chien.syntactic_category == None
        assert chien.syntactic_category_string == u'?'
        assert chiens.syntactic_category_string == u'?-?'
        assert json.loads(chiens.morpheme_break_ids)[0][0][0][2] == None
Exemple #7
0
    def test_aaa_initialize(self):
        """Initialize the database using pseudo-data generated from random lorem ipsum sentences.

        These are located in ``onlinelinguisticdatabase/tests/data/corpora``.
        The data contain morphologically analyzed sentences, their component
        morphemes, and syntactic categories.  The sentences have phrase
        structure trees in bracket notation.

        The test will try to load the lorem ipsum dataset from a MySQL/SQLite
        dump file in ``onlinelinguisticdatabase/tests/data/corpora``.  If the
        dump file corresponding to ``loremipsum_path`` does not exist, it will
        import the lorem ipsum data directly from the text files and create
        the dump file so that future tests can run more speedily.  The
        ``loremipsum100_path``, ``loremipsum1000_path``, ``loremipsum10000_path``
        and ``loremipsum30000_path`` files are available and contain 100, 1000
        and 10,000 sentences, respectively.

        Setting the ``via_request`` variable to ``True`` will cause all of the
        forms to be created via request, i.e., via
        ``self.app.post(url('forms))...``.  This is much slower but may be
        desirable since values for the morphological analysis attributes
        will be generated.

        .. note::

            In order to run ``mysqldump`` with the MySQL user listed in
            ``test.ini``, that user must have permission to lock and update
            tables::

                mysql -u root -p<root_password>
                grant lock tables, update on old_test.* to 'old'@'localhost';

        .. warning::

            Loading the .txt or .sql files with the ``via_request`` option set to
            ``True`` will take a very long time.  This might be an argument for
            separating the interface and logic components of the controllers so
            that a "core" HTTP-less OLD application could be exposed.  This
            would facilitate the creation of models with system-generated data
            and validation but without the HTTP overhead...

        """


        ########################################################################
        # Configure lorem ipsum data set import
        ########################################################################

        # Set ``loremipsum_path`` this to ``self.loremipsum100_path``,
        # ``self.loremipsum1000_path`` or ``self.loremipsum10000_path``.
        # WARNING: the larger ones will take a long time.
        # Use the 10,000-sentence lorem ipsum dataset to ensure that
        # very large corpora are handled correctly.
        loremipsum_path = self.loremipsum100_path 

        # Set ``via_request`` to ``True`` to create all forms via HTTP requests.
        via_request = True

        self._add_SEARCH_to_web_test_valid_methods()

        # Add an application settings so that morpheme references will work out right.
        application_settings = h.generate_default_application_settings()
        Session.add(application_settings)
        Session.commit()

        def create_model(line, categories, via_request=False):
            """Create a model (form or syncat) using the string in ``line``."""
            model = 'Form'
            elements = unicode(line).split('\t')
            non_empty_elements = filter(None, elements)
            try:
                ol, mb, mg, ml, sc, sx = non_empty_elements
            except Exception:
                try:
                    ol, mb, mg, ml, sc = non_empty_elements
                    sx = u''
                except Exception:
                    try:
                        model = 'SyntacticCategory'
                        n, t = non_empty_elements
                    except Exception:
                        return categories
            if via_request:
                if model == 'SyntacticCategory':
                    params = self.syntactic_category_create_params.copy()
                    params.update({
                        'name': n,
                        'type': t
                    })
                    params = json.dumps(params)
                    response = self.app.post(url('syntacticcategories'), params, self.json_headers,
                                  self.extra_environ_admin)
                    cat_id = json.loads(response.body)['id']
                    categories[n] = cat_id
                else:
                    params = self.form_create_params.copy()
                    params.update({
                        'transcription': ol,
                        'morpheme_break': mb,
                        'morpheme_gloss': mg,
                        'translations': [{'transcription': ml, 'grammaticality': u''}],
                        'syntax': sx,
                        'syntactic_category': categories.get(sc, u'')
                    })
                    params = json.dumps(params)
                    self.app.post(url('forms'), params, self.json_headers,
                                  self.extra_environ_admin)
            else:
                if model == 'SyntacticCategory':
                    syntactic_category = model.SyntacticCategory()
                    syntactic_category.name = n
                    syntactic_category.type = t
                    Session.add(syntactic_category)
                    categories[n] = syntactic_category.id
                else:
                    form = model.Form()
                    form.transcription = ol
                    form.morpheme_break = mb
                    form.morpheme_gloss = mg
                    translation = model.Translation()
                    translation.transcription = ml
                    form.translations.append(translation)
                    form.syntax = sx
                    form.syntacticcategory_id = categories.get(sc, None)
                    Session.add(form)
            return categories

        def add_loremipsum_to_db(loremipsum_path, via_request=False):
            """Add the contents of the file at ``loremipsum_path`` to the database."""
            categories = {}
            with open(loremipsum_path, 'r') as f:
                i = 0
                for l in f:
                    if i % 100 == 0:
                        if not via_request: Session.commit()
                        log.debug('%d lines processed' % i)
                    i = i + 1
                    categories = create_model(l.replace('\n', ''), categories,
                                             via_request)
                Session.commit()

        loremipsum_path_no_ext = os.path.splitext(loremipsum_path)[0]
        sqlalchemy_URL = self.config['sqlalchemy.url']
        sqlalchemy_URL_list = sqlalchemy_URL.split(':')
        olddump_script_path = os.path.join(self.test_scripts_path, 'olddump.sh')
        oldload_script_path = os.path.join(self.test_scripts_path, 'oldload.sh')
        RDBMS = sqlalchemy_URL_list[0]

        if RDBMS == 'mysql':
            mysql_dump_path = '%s_mysql.sql' % loremipsum_path_no_ext
            username = sqlalchemy_URL_list[1][2:]
            password = sqlalchemy_URL_list[2].split('@')[0]
            dbname = sqlalchemy_URL_list[3].split('/')[1]
            if os.path.exists(mysql_dump_path):
                log.debug('The lorem ipsum MySQL dump file exists.  Loading it...')
                # Clear the current DB completely
                h.clear_all_models(retain=[])
                # Load the dump file to the DB
                shell_script = '#!/bin/sh\nmysql -u %s -p%s %s < %s' % (
                    username, password, dbname, mysql_dump_path)
                with open(oldload_script_path, 'w') as f:
                    f.write(shell_script)
                os.chmod(oldload_script_path, 0744)
                # Load the DB
                with open(os.devnull, 'w') as f:
                    call([oldload_script_path], stdout=f, stderr=f)
                # Destroy the load script
                os.remove(oldload_script_path)
                log.debug('Loaded.')
            else:
                log.debug('Have to import the lorem ipsum dataset from the text file and create the MySQL dump file.')
                # Populate the database from the loremipusm text file and dump it
                add_loremipsum_to_db(loremipsum_path, via_request=via_request)
                # Write the DB dump shell script
                shell_script = '#!/bin/sh\nmysqldump -u %s -p%s --no-create-info %s > %s' % (
                    username, password, dbname, mysql_dump_path)
                with open(olddump_script_path, 'w') as f:
                    f.write(shell_script)
                os.chmod(olddump_script_path, 0744)
                # Dump the DB
                with open(os.devnull, 'w') as f:
                    call([olddump_script_path], stdout=f, stderr=f)
                # Destroy the dump script
                os.remove(olddump_script_path)
                log.debug('Imported and dumped.')
        elif RDBMS == 'sqlite' and h.command_line_program_installed('sqlite3'):
            sqlite_dump_path = '%s_sqlite.sql' % loremipsum_path_no_ext
            sqlite_db = sqlalchemy_URL.split('/')[-1]
            dbpath = os.path.join(self.here, sqlite_db)
            if os.path.exists(sqlite_dump_path):
                log.debug('The lorem ipsum SQLite dump file exists.  Loading it...')
                # Clear the current DB completely
                h.clear_all_models(retain=[])
                # Load the dump file to the DB
                shell_script = '#!/bin/sh\nsqlite3 %s < %s' % (
                    dbpath, sqlite_dump_path)
                with open(oldload_script_path, 'w') as f:
                    f.write(shell_script)
                os.chmod(oldload_script_path, 0744)
                # Load the DB
                with open(os.devnull, 'w') as f:
                    call([oldload_script_path], stdout=f, stderr=f)
                # Destroy the load script
                os.remove(oldload_script_path)
                log.debug('Loaded.')
            else:
                log.debug('Have to import the lorem ipsum dataset from the text file and create the SQLite dump file.')
                # Populate the database from the loremipusm text file and dump it
                add_loremipsum_to_db(loremipsum_path, via_request=via_request)
                # Write the DB dump shell script
                shell_script = '#!/bin/sh\nsqlite3 %s ".dump" | grep -v "^CREATE" > %s' % (
                    dbpath, sqlite_dump_path)
                with open(olddump_script_path, 'w') as f:
                    f.write(shell_script)
                os.chmod(olddump_script_path, 0744)
                # Dump the DB
                with open(os.devnull, 'w') as f:
                    call([olddump_script_path], stdout=f, stderr=f)
                # Destroy the dump script
                os.remove(olddump_script_path)
                log.debug('Imported and dumped.')
        forms = h.get_forms()
        log.debug('Lorem ipsum data loaded.  There are now %d forms in the db.' % len(forms))

        # Restrict one sentential form in the db.
        restricted_tag = h.generate_restricted_tag()
        Session.add(restricted_tag)
        Session.commit()
        a_form = Session.query(model.Form).\
            filter(model.Form.syntactic_category.\
                has(model.SyntacticCategory.name==u'S')).first()
        a_form_id = a_form.id
        a_form.tags.append(restricted_tag)
        Session.commit()
        restricted_form = Session.query(model.Form).\
            filter(model.Form.tags.any(model.Tag.name==u'restricted')).first()
        assert a_form_id == restricted_form.id
Exemple #8
0
def setup_app(command, conf, vars):
    """Commands to setup onlinelinguisticdatabase."""

    config = load_environment(conf.global_conf, conf.local_conf)
    log.info('Environment loaded.')

    Base.metadata.create_all(bind=Session.bind)
    filename = os.path.split(conf.filename)[
        -1]  # e.g., production.ini, development.ini, test.ini, ...

    # Create the ``store`` directory and those for file, analysis and corpora
    # objects and their subdirectories.  See ``lib.utils.py`` for details.
    h.create_OLD_directories(config=config)

    # ISO-639-3 Language data for the languages table
    log.info("Retrieving ISO-639-3 languages data.")
    languages = h.get_language_objects(filename, config)

    # Get default users.
    log.info("Creating a default administrator, contributor and viewer.")
    administrator = h.generate_default_administrator(config_filename=filename)
    contributor = h.generate_default_contributor(config_filename=filename)
    viewer = h.generate_default_viewer(config_filename=filename)

    # If we are running tests, make sure the test db contains only language data.
    if filename == 'test.ini':
        # Permanently drop any existing tables
        Base.metadata.drop_all(bind=Session.bind, checkfirst=True)
        log.info("Existing tables dropped.")

        # Create the tables if they don't already exist
        Base.metadata.create_all(bind=Session.bind, checkfirst=True)
        log.info('Tables created.')

        Session.add_all(languages + [administrator, contributor, viewer])
        Session.commit()

    # Not a test: add a bunch of nice defaults.
    else:
        # Create the _requests_tests.py script
        requests_tests_path = os.path.join(config['pylons.paths']['root'],
                                           'tests', 'scripts',
                                           '_requests_tests.py')

        # This line is problematic in production apps because the
        # _requests_tests.py file is not included in the build. So, I'm
        # commenting it out by default.
        # copyfile(requests_tests_path, '_requests_tests.py')

        # Create the tables if they don't already exist
        Base.metadata.create_all(bind=Session.bind, checkfirst=True)
        log.info('Tables created.')

        # Get default home & help pages.
        log.info("Creating default home and help pages.")
        homepage = h.generate_default_home_page()
        helppage = h.generate_default_help_page()

        # Get default application settings.
        log.info("Generating default application settings.")
        application_settings = h.generate_default_application_settings()

        # Get default tags and categories
        log.info("Creating some useful tags and categories.")
        restricted_tag = h.generate_restricted_tag()
        foreign_word_tag = h.generate_foreign_word_tag()
        S = h.generate_s_syntactic_category()
        N = h.generate_n_syntactic_category()
        V = h.generate_v_syntactic_category()

        # Initialize the database
        log.info("Adding defaults.")
        data = [
            administrator, contributor, viewer, homepage, helppage,
            application_settings, restricted_tag, foreign_word_tag
        ]
        if config['add_language_data'] != '0':
            data += languages
        if config['empty_database'] == '0':
            Session.add_all(data)
            Session.commit()
        log.info("OLD successfully set up.")
Exemple #9
0
    def test_delete(self):
        """Tests that DELETE /orthographies/id deletes the orthography with id=id."""

        # Create an orthography to delete.
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c'})
        params = json.dumps(params)
        response = self.app.post(url('orthographies'), params,
                                 self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        orthography_count = Session.query(Orthography).count()
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c'
        assert resp[
            'lowercase'] == False  # default value from model/orthography.py
        assert resp[
            'initial_glottal_stops'] == True  # default value from model/orthography.py
        orthography_id = resp['id']
        original_datetime_modified = resp['datetime_modified']

        # Now delete the orthography
        response = self.app.delete(url('orthography', id=orthography_id),
                                   headers=self.json_headers,
                                   extra_environ=self.extra_environ_admin)
        resp = json.loads(response.body)
        new_orthography_count = Session.query(Orthography).count()
        assert new_orthography_count == orthography_count - 1
        assert resp['id'] == orthography_id
        assert response.content_type == 'application/json'

        # Trying to get the deleted orthography from the db should return None
        deleted_orthography = Session.query(Orthography).get(orthography_id)
        assert deleted_orthography == None

        # Delete with an invalid id
        id = 9999999999999
        response = self.app.delete(url('orthography', id=id),
                                   headers=self.json_headers,
                                   extra_environ=self.extra_environ_admin,
                                   status=404)
        assert u'There is no orthography with id %s' % id in json.loads(
            response.body)['error']
        assert response.content_type == 'application/json'

        # Delete without an id
        response = self.app.delete(url('orthography', id=''),
                                   status=404,
                                   headers=self.json_headers,
                                   extra_environ=self.extra_environ_admin)
        assert json.loads(
            response.body)['error'] == 'The resource could not be found.'
        assert response.content_type == 'application/json'

        # Observe how deletions are restricted when an orthography is part of an
        # active application settings ...

        # Create an orthography to demonstrate.
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c'})
        params = json.dumps(params)
        response = self.app.post(url('orthographies'), params,
                                 self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        orthography_count = Session.query(Orthography).count()
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c'
        assert resp[
            'lowercase'] == False  # default value from model/orthography.py
        assert resp[
            'initial_glottal_stops'] == True  # default value from model/orthography.py
        orthography_id = resp['id']

        # Create an application settings with the above orthography as the storage orthography
        app_set = h.generate_default_application_settings()
        app_set.storage_orthography = Session.query(Orthography).get(
            orthography_id)
        Session.add(app_set)
        Session.commit()

        # Now attempting to delete as a contributor should fail
        response = self.app.delete(url('orthography', id=orthography_id),
                                   headers=self.json_headers,
                                   extra_environ=self.extra_environ_contrib,
                                   status=403)
        resp = json.loads(response.body)
        assert resp[
            'error'] == u'Only administrators are permitted to delete orthographies that are used in the active application settings.'
        assert response.content_type == 'application/json'

        # If we now remove the orthography from the application settings, the
        # contributor will be able to delete it.
        app_set = h.get_application_settings()
        app_set.storage_orthography = None
        Session.commit()
        response = self.app.delete(url('orthography', id=orthography_id),
                                   headers=self.json_headers,
                                   extra_environ=self.extra_environ_contrib)
        resp = json.loads(response.body)
        assert response.content_type == 'application/json'
        assert resp['orthography'] == u'a, b, c'
Exemple #10
0
    def test_update(self):
        """Tests that PUT /orthographies/id updates the orthography with id=id."""

        # Create an orthography to update.
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c'})
        params = json.dumps(params)
        response = self.app.post(url('orthographies'), params,
                                 self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        orthography_count = Session.query(Orthography).count()
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c'
        assert resp[
            'lowercase'] == False  # default value from model/orthography.py
        assert resp[
            'initial_glottal_stops'] == True  # default value from model/orthography.py
        assert response.content_type == 'application/json'
        orthography_id = resp['id']
        original_datetime_modified = resp['datetime_modified']

        # Update the orthography
        sleep(
            1
        )  # sleep for a second to ensure that MySQL registers a different datetime_modified for the update
        params = self.orthography_create_params.copy()
        params.update({'name': u'orthography', 'orthography': u'a, b, c, d'})
        params = json.dumps(params)
        response = self.app.put(url('orthography', id=orthography_id), params,
                                self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        datetime_modified = resp['datetime_modified']
        new_orthography_count = Session.query(Orthography).count()
        assert orthography_count == new_orthography_count
        assert datetime_modified != original_datetime_modified
        assert resp['orthography'] == u'a, b, c, d'
        assert response.content_type == 'application/json'

        # Attempt an update with no new input and expect to fail
        sleep(
            1
        )  # sleep for a second to ensure that MySQL could register a different datetime_modified for the update
        response = self.app.put(url('orthography', id=orthography_id),
                                params,
                                self.json_headers,
                                self.extra_environ_admin,
                                status=400)
        resp = json.loads(response.body)
        orthography_count = new_orthography_count
        new_orthography_count = Session.query(Orthography).count()
        our_orthography_datetime_modified = Session.query(Orthography).get(
            orthography_id).datetime_modified
        assert our_orthography_datetime_modified.isoformat(
        ) == datetime_modified
        assert orthography_count == new_orthography_count
        assert resp[
            'error'] == u'The update request failed because the submitted data were not new.'
        assert response.content_type == 'application/json'

        # Observe how updates are restricted when an orthography is part of an
        # active application settings ...
        app_set = h.generate_default_application_settings()
        app_set.storage_orthography = Session.query(Orthography).get(
            orthography_id)
        Session.add(app_set)
        Session.commit()

        # Now attempting a valid update as a contributor should fail
        params = self.orthography_create_params.copy()
        params.update({
            'name': u'orthography',
            'orthography': u'a, b, c, d, e'
        })
        params = json.dumps(params)
        response = self.app.put(url('orthography', id=orthography_id),
                                params,
                                self.json_headers,
                                self.extra_environ_contrib,
                                status=403)
        resp = json.loads(response.body)
        assert resp[
            'error'] == u'Only administrators are permitted to update orthographies that are used in the active application settings.'
        assert response.content_type == 'application/json'

        # The same update as an admin should succeed.
        params = self.orthography_create_params.copy()
        params.update({
            'name': u'orthography',
            'orthography': u'a, b, c, d, e'
        })
        params = json.dumps(params)
        response = self.app.put(url('orthography', id=orthography_id), params,
                                self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c, d, e'
        assert response.content_type == 'application/json'

        # If we now remove the orthography from the application settings, the
        # contributor will be able to edit it.
        app_set = h.get_application_settings()
        app_set.storage_orthography = None
        Session.commit()
        params = self.orthography_create_params.copy()
        params.update({
            'name': u'orthography',
            'orthography': u'a, b, c, d, e, f'
        })
        params = json.dumps(params)
        response = self.app.put(url('orthography', id=orthography_id), params,
                                self.json_headers, self.extra_environ_contrib)
        resp = json.loads(response.body)
        assert response.content_type == 'application/json'
        assert resp['name'] == u'orthography'
        assert resp['orthography'] == u'a, b, c, d, e, f'
    def test_index(self):
        """Tests that GET & SEARCH /collectionbackups behave correctly.
        """

        # Add some test data to the database.
        application_settings = h.generate_default_application_settings()
        source = h.generate_default_source()
        restricted_tag = h.generate_restricted_tag()
        file1 = h.generate_default_file()
        file1.name = u'file1'
        file2 = h.generate_default_file()
        file2.name = u'file2'
        speaker = h.generate_default_speaker()
        Session.add_all([application_settings, source, restricted_tag, file1,
                         file2, speaker])
        Session.commit()
        speaker_id = speaker.id
        restricted_tag_id = restricted_tag.id
        tag_ids = [restricted_tag_id]
        file1_id = file1.id
        file2_id = file2.id
        file_ids = [file1_id, file2_id]

        # Create a restricted collection (via request) as the default contributor
        users = h.get_users()
        contributor_id = [u for u in users if u.role==u'contributor'][0].id
        administrator_id = [u for u in users if u.role==u'administrator'][0].id

        # Define some extra_environs
        view = {'test.authentication.role': u'viewer', 'test.application_settings': True}
        contrib = {'test.authentication.role': u'contributor', 'test.application_settings': True}
        admin = {'test.authentication.role': u'administrator', 'test.application_settings': True}

        params = self.collection_create_params.copy()
        params.update({
            'title': u'Created by the Contributor',
            'elicitor': contributor_id,
            'tags': [restricted_tag_id]
        })
        params = json.dumps(params)
        response = self.app.post(url('collections'), params, self.json_headers, contrib)
        collection_count = Session.query(model.Collection).count()
        resp = json.loads(response.body)
        collection_id = resp['id']
        assert response.content_type == 'application/json'
        assert collection_count == 1

        # Update our collection (via request) as the default administrator; this
        # will create one collection backup.
        params = self.collection_create_params.copy()
        params.update({
            'url': u'find/me/here',
            'title': u'Updated by the Administrator',
            'speaker': speaker_id,
            'tags': tag_ids + [None, u''], # None and u'' ('') will be ignored by collections.update_collection
            'enterer': administrator_id  # This should change nothing.
        })
        params = json.dumps(params)
        response = self.app.put(url('collection', id=collection_id), params,
                        self.json_headers, admin)
        resp = json.loads(response.body)
        collection_count = Session.query(model.Collection).count()
        assert response.content_type == 'application/json'
        assert collection_count == 1

        # Finally, update our collection (via request) as the default contributor.
        # Now we will have two collection backups.
        params = self.collection_create_params.copy()
        params.update({
            'title': u'Updated by the Contributor',
            'speaker': speaker_id,
            'tags': tag_ids,
            'files': file_ids
        })
        params = json.dumps(params)
        response = self.app.put(url('collection', id=collection_id), params,
                        self.json_headers, contrib)
        resp = json.loads(response.body)
        collection_count = Session.query(model.Collection).count()
        assert collection_count == 1

        # Now GET the collection backups as the restricted enterer of the original
        # collection and expect to get them all.
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=contrib)
        resp = json.loads(response.body)
        assert len(resp) == 2
        assert response.content_type == 'application/json'

        # The admin should get them all too.
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=admin)
        resp = json.loads(response.body)
        assert len(resp) == 2

        # The viewer should get none because they're all restricted.
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=view)
        resp = json.loads(response.body)
        assert len(resp) == 0

        # Now update the collection and de-restrict it.
        params = self.collection_create_params.copy()
        params.update({
            'title': u'Updated and de-restricted by the Contributor',
            'speaker': speaker_id,
            'tags': [],
            'files': file_ids
        })
        params = json.dumps(params)
        response = self.app.put(url('collection', id=collection_id), params,
                        self.json_headers, contrib)
        resp = json.loads(response.body)
        collection_count = Session.query(model.Collection).count()
        assert collection_count == 1

        # Now GET the collection backups.  Admin and contrib should see 3 but the
        # viewer should still see none.
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=contrib)
        resp = json.loads(response.body)
        assert len(resp) == 3
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=admin)
        resp = json.loads(response.body)
        assert len(resp) == 3
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=view)
        resp = json.loads(response.body)
        assert len(resp) == 0
        assert response.content_type == 'application/json'

        # Finally, update our collection in some trivial way.
        params = self.collection_create_params.copy()
        params.update({
            'title': u'Updated by the Contributor *again*',
            'speaker': speaker_id,
            'tags': [],
            'files': file_ids
        })
        params = json.dumps(params)
        response = self.app.put(url('collection', id=collection_id), params,
                        self.json_headers, contrib)
        resp = json.loads(response.body)
        collection_count = Session.query(model.Collection).count()
        assert collection_count == 1

        # Now GET the collection backups.  Admin and contrib should see 4 and the
        # viewer should see 1
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=contrib)
        resp = json.loads(response.body)
        assert len(resp) == 4
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=admin)
        resp = json.loads(response.body)
        all_collection_backups = resp
        assert len(resp) == 4
        response = self.app.get(url('collectionbackups'), headers=self.json_headers, extra_environ=view)
        resp = json.loads(response.body)
        unrestricted_collection_backup = resp[0]
        assert len(resp) == 1
        assert resp[0]['title'] == u'Updated and de-restricted by the Contributor'
        restricted_collection_backups = [cb for cb in all_collection_backups
                                       if cb != unrestricted_collection_backup]
        assert len(restricted_collection_backups) == 3

        # Test the paginator GET params.
        paginator = {'items_per_page': 1, 'page': 2}
        response = self.app.get(url('collectionbackups'), paginator, headers=self.json_headers,
                                extra_environ=admin)
        resp = json.loads(response.body)
        assert len(resp['items']) == 1
        assert resp['items'][0]['title'] == all_collection_backups[1]['title']
        assert response.content_type == 'application/json'

        # Test the order_by GET params.
        order_by_params = {'order_by_model': 'CollectionBackup',
            'order_by_attribute': 'id', 'order_by_direction': 'desc'}
        response = self.app.get(url('collectionbackups'), order_by_params,
                        headers=self.json_headers, extra_environ=admin)
        resp = json.loads(response.body)
        result_set = sorted(all_collection_backups, key=lambda cb: cb['id'], reverse=True)
        assert [cb['id'] for cb in resp] == [cb['id'] for cb in result_set] 

        # Test the order_by *with* paginator.  
        params = {'order_by_model': 'CollectionBackup', 'order_by_attribute': 'id',
                     'order_by_direction': 'desc', 'items_per_page': 1, 'page': 3}
        response = self.app.get(url('collectionbackups'), params,
                        headers=self.json_headers, extra_environ=admin)
        resp = json.loads(response.body)
        assert result_set[2]['title'] == resp['items'][0]['title']

        # Now test the show action:

        # Admin should be able to GET a particular restricted collection backup
        response = self.app.get(url('collectionbackup', id=restricted_collection_backups[0]['id']),
                                headers=self.json_headers, extra_environ=admin)
        resp = json.loads(response.body)
        assert resp['title'] == restricted_collection_backups[0]['title']
        assert response.content_type == 'application/json'

        # Viewer should receive a 403 error when attempting to do so.
        response = self.app.get(url('collectionbackup', id=restricted_collection_backups[0]['id']),
                                headers=self.json_headers, extra_environ=view, status=403)
        resp = json.loads(response.body)
        assert resp['error'] == u'You are not authorized to access this resource.'
        assert response.content_type == 'application/json'

        # Viewer should be able to GET the unrestricted collection backup
        response = self.app.get(url('collectionbackup', id=unrestricted_collection_backup['id']),
                                headers=self.json_headers, extra_environ=view)
        resp = json.loads(response.body)
        assert resp['title'] == unrestricted_collection_backup['title']

        # A nonexistent cb id will return a 404 error
        response = self.app.get(url('collectionbackup', id=100987),
                    headers=self.json_headers, extra_environ=view, status=404)
        resp = json.loads(response.body)
        assert resp['error'] == u'There is no collection backup with id 100987'
        assert response.content_type == 'application/json'

        # Test the search action
        self._add_SEARCH_to_web_test_valid_methods()

        # A search on collection backup titles using POST /collectionbackups/search
        json_query = json.dumps({'query': {'filter':
                        ['CollectionBackup', 'title', 'like', u'%Contributor%']}})
        response = self.app.post(url('/collectionbackups/search'), json_query,
                        self.json_headers, admin)
        resp = json.loads(response.body)
        result_set = [cb for cb in all_collection_backups if u'Contributor' in cb['title']]
        assert len(resp) == len(result_set) == 3
        assert set([cb['id'] for cb in resp]) == set([cb['id'] for cb in result_set])
        assert response.content_type == 'application/json'

        # A search on collection backup titles using SEARCH /collectionbackups
        json_query = json.dumps({'query': {'filter':
                        ['CollectionBackup', 'title', 'like', u'%Administrator%']}})
        response = self.app.request(url('collectionbackups'), method='SEARCH', body=json_query,
            headers=self.json_headers, environ=admin)
        resp = json.loads(response.body)
        result_set = [cb for cb in all_collection_backups if u'Administrator' in cb['title']]
        assert len(resp) == len(result_set) == 1
        assert set([cb['id'] for cb in resp]) == set([cb['id'] for cb in result_set])

        # Perform the two previous searches as a restricted viewer to show that
        # the restricted tag is working correctly.
        json_query = json.dumps({'query': {'filter':
                        ['CollectionBackup', 'title', 'like', u'%Contributor%']}})
        response = self.app.post(url('/collectionbackups/search'), json_query,
                        self.json_headers, view)
        resp = json.loads(response.body)
        result_set = [cb for cb in [unrestricted_collection_backup]
                     if u'Contributor' in cb['title']]
        assert len(resp) == len(result_set) == 1
        assert set([cb['id'] for cb in resp]) == set([cb['id'] for cb in result_set])

        json_query = json.dumps({'query': {'filter':
                        ['CollectionBackup', 'title', 'like', u'%Administrator%']}})
        response = self.app.request(url('collectionbackups'), method='SEARCH', body=json_query,
            headers=self.json_headers, environ=view)
        resp = json.loads(response.body)
        result_set = [cb for cb in [unrestricted_collection_backup]
                     if u'Administrator' in cb['title']]
        assert len(resp) == len(result_set) == 0

        # I'm just going to assume that the order by and pagination functions are
        # working correctly since the implementation is essentially equivalent
        # to that in the index action already tested above.

        # Attempting to call edit/new/create/delete/update on a read-only resource
        # will return a 404 response
        response = self.app.get(url('edit_collectionbackup', id=2232), status=404)
        assert json.loads(response.body)['error'] == u'This resource is read-only.'
        response = self.app.get(url('new_collectionbackup', id=2232), status=404)
        assert json.loads(response.body)['error'] == u'This resource is read-only.'
        response = self.app.post(url('collectionbackups'), status=404)
        assert json.loads(response.body)['error'] == u'This resource is read-only.'
        response = self.app.put(url('collectionbackup', id=2232), status=404)
        assert json.loads(response.body)['error'] == u'This resource is read-only.'
        response = self.app.delete(url('collectionbackup', id=2232), status=404)
        assert json.loads(response.body)['error'] == u'This resource is read-only.'
        assert response.content_type == 'application/json'
    def test_index(self):
        """Tests that GET & SEARCH /collectionbackups behave correctly.
        """

        # Add some test data to the database.
        application_settings = h.generate_default_application_settings()
        source = h.generate_default_source()
        restricted_tag = h.generate_restricted_tag()
        file1 = h.generate_default_file()
        file1.name = u'file1'
        file2 = h.generate_default_file()
        file2.name = u'file2'
        speaker = h.generate_default_speaker()
        Session.add_all([
            application_settings, source, restricted_tag, file1, file2, speaker
        ])
        Session.commit()
        speaker_id = speaker.id
        restricted_tag_id = restricted_tag.id
        tag_ids = [restricted_tag_id]
        file1_id = file1.id
        file2_id = file2.id
        file_ids = [file1_id, file2_id]

        # Create a restricted collection (via request) as the default contributor
        users = h.get_users()
        contributor_id = [u for u in users if u.role == u'contributor'][0].id
        administrator_id = [u for u in users
                            if u.role == u'administrator'][0].id

        # Define some extra_environs
        view = {
            'test.authentication.role': u'viewer',
            'test.application_settings': True
        }
        contrib = {
            'test.authentication.role': u'contributor',
            'test.application_settings': True
        }
        admin = {
            'test.authentication.role': u'administrator',
            'test.application_settings': True
        }

        params = self.collection_create_params.copy()
        params.update({
            'title': u'Created by the Contributor',
            'elicitor': contributor_id,
            'tags': [restricted_tag_id]
        })
        params = json.dumps(params)
        response = self.app.post(url('collections'), params, self.json_headers,
                                 contrib)
        collection_count = Session.query(model.Collection).count()
        resp = json.loads(response.body)
        collection_id = resp['id']
        assert response.content_type == 'application/json'
        assert collection_count == 1

        # Update our collection (via request) as the default administrator; this
        # will create one collection backup.
        params = self.collection_create_params.copy()
        params.update({
            'url': u'find/me/here',
            'title': u'Updated by the Administrator',
            'speaker': speaker_id,
            'tags': tag_ids + [
                None, u''
            ],  # None and u'' ('') will be ignored by collections.update_collection
            'enterer': administrator_id  # This should change nothing.
        })
        params = json.dumps(params)
        response = self.app.put(url('collection', id=collection_id), params,
                                self.json_headers, admin)
        resp = json.loads(response.body)
        collection_count = Session.query(model.Collection).count()
        assert response.content_type == 'application/json'
        assert collection_count == 1

        # Finally, update our collection (via request) as the default contributor.
        # Now we will have two collection backups.
        params = self.collection_create_params.copy()
        params.update({
            'title': u'Updated by the Contributor',
            'speaker': speaker_id,
            'tags': tag_ids,
            'files': file_ids
        })
        params = json.dumps(params)
        response = self.app.put(url('collection', id=collection_id), params,
                                self.json_headers, contrib)
        resp = json.loads(response.body)
        collection_count = Session.query(model.Collection).count()
        assert collection_count == 1

        # Now GET the collection backups as the restricted enterer of the original
        # collection and expect to get them all.
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=contrib)
        resp = json.loads(response.body)
        assert len(resp) == 2
        assert response.content_type == 'application/json'

        # The admin should get them all too.
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=admin)
        resp = json.loads(response.body)
        assert len(resp) == 2

        # The viewer should get none because they're all restricted.
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=view)
        resp = json.loads(response.body)
        assert len(resp) == 0

        # Now update the collection and de-restrict it.
        params = self.collection_create_params.copy()
        params.update({
            'title': u'Updated and de-restricted by the Contributor',
            'speaker': speaker_id,
            'tags': [],
            'files': file_ids
        })
        params = json.dumps(params)
        response = self.app.put(url('collection', id=collection_id), params,
                                self.json_headers, contrib)
        resp = json.loads(response.body)
        collection_count = Session.query(model.Collection).count()
        assert collection_count == 1

        # Now GET the collection backups.  Admin and contrib should see 3 but the
        # viewer should still see none.
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=contrib)
        resp = json.loads(response.body)
        assert len(resp) == 3
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=admin)
        resp = json.loads(response.body)
        assert len(resp) == 3
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=view)
        resp = json.loads(response.body)
        assert len(resp) == 0
        assert response.content_type == 'application/json'

        # Finally, update our collection in some trivial way.
        params = self.collection_create_params.copy()
        params.update({
            'title': u'Updated by the Contributor *again*',
            'speaker': speaker_id,
            'tags': [],
            'files': file_ids
        })
        params = json.dumps(params)
        response = self.app.put(url('collection', id=collection_id), params,
                                self.json_headers, contrib)
        resp = json.loads(response.body)
        collection_count = Session.query(model.Collection).count()
        assert collection_count == 1

        # Now GET the collection backups.  Admin and contrib should see 4 and the
        # viewer should see 1
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=contrib)
        resp = json.loads(response.body)
        assert len(resp) == 4
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=admin)
        resp = json.loads(response.body)
        all_collection_backups = resp
        assert len(resp) == 4
        response = self.app.get(url('collectionbackups'),
                                headers=self.json_headers,
                                extra_environ=view)
        resp = json.loads(response.body)
        unrestricted_collection_backup = resp[0]
        assert len(resp) == 1
        assert resp[0][
            'title'] == u'Updated and de-restricted by the Contributor'
        restricted_collection_backups = [
            cb for cb in all_collection_backups
            if cb != unrestricted_collection_backup
        ]
        assert len(restricted_collection_backups) == 3

        # Test the paginator GET params.
        paginator = {'items_per_page': 1, 'page': 2}
        response = self.app.get(url('collectionbackups'),
                                paginator,
                                headers=self.json_headers,
                                extra_environ=admin)
        resp = json.loads(response.body)
        assert len(resp['items']) == 1
        assert resp['items'][0]['title'] == all_collection_backups[1]['title']
        assert response.content_type == 'application/json'

        # Test the order_by GET params.
        order_by_params = {
            'order_by_model': 'CollectionBackup',
            'order_by_attribute': 'id',
            'order_by_direction': 'desc'
        }
        response = self.app.get(url('collectionbackups'),
                                order_by_params,
                                headers=self.json_headers,
                                extra_environ=admin)
        resp = json.loads(response.body)
        result_set = sorted(all_collection_backups,
                            key=lambda cb: cb['id'],
                            reverse=True)
        assert [cb['id'] for cb in resp] == [cb['id'] for cb in result_set]

        # Test the order_by *with* paginator.
        params = {
            'order_by_model': 'CollectionBackup',
            'order_by_attribute': 'id',
            'order_by_direction': 'desc',
            'items_per_page': 1,
            'page': 3
        }
        response = self.app.get(url('collectionbackups'),
                                params,
                                headers=self.json_headers,
                                extra_environ=admin)
        resp = json.loads(response.body)
        assert result_set[2]['title'] == resp['items'][0]['title']

        # Now test the show action:

        # Admin should be able to GET a particular restricted collection backup
        response = self.app.get(url('collectionbackup',
                                    id=restricted_collection_backups[0]['id']),
                                headers=self.json_headers,
                                extra_environ=admin)
        resp = json.loads(response.body)
        assert resp['title'] == restricted_collection_backups[0]['title']
        assert response.content_type == 'application/json'

        # Viewer should receive a 403 error when attempting to do so.
        response = self.app.get(url('collectionbackup',
                                    id=restricted_collection_backups[0]['id']),
                                headers=self.json_headers,
                                extra_environ=view,
                                status=403)
        resp = json.loads(response.body)
        assert resp[
            'error'] == u'You are not authorized to access this resource.'
        assert response.content_type == 'application/json'

        # Viewer should be able to GET the unrestricted collection backup
        response = self.app.get(url('collectionbackup',
                                    id=unrestricted_collection_backup['id']),
                                headers=self.json_headers,
                                extra_environ=view)
        resp = json.loads(response.body)
        assert resp['title'] == unrestricted_collection_backup['title']

        # A nonexistent cb id will return a 404 error
        response = self.app.get(url('collectionbackup', id=100987),
                                headers=self.json_headers,
                                extra_environ=view,
                                status=404)
        resp = json.loads(response.body)
        assert resp['error'] == u'There is no collection backup with id 100987'
        assert response.content_type == 'application/json'

        # Test the search action
        self._add_SEARCH_to_web_test_valid_methods()

        # A search on collection backup titles using POST /collectionbackups/search
        json_query = json.dumps({
            'query': {
                'filter':
                ['CollectionBackup', 'title', 'like', u'%Contributor%']
            }
        })
        response = self.app.post(url('/collectionbackups/search'), json_query,
                                 self.json_headers, admin)
        resp = json.loads(response.body)
        result_set = [
            cb for cb in all_collection_backups
            if u'Contributor' in cb['title']
        ]
        assert len(resp) == len(result_set) == 3
        assert set([cb['id']
                    for cb in resp]) == set([cb['id'] for cb in result_set])
        assert response.content_type == 'application/json'

        # A search on collection backup titles using SEARCH /collectionbackups
        json_query = json.dumps({
            'query': {
                'filter':
                ['CollectionBackup', 'title', 'like', u'%Administrator%']
            }
        })
        response = self.app.request(url('collectionbackups'),
                                    method='SEARCH',
                                    body=json_query,
                                    headers=self.json_headers,
                                    environ=admin)
        resp = json.loads(response.body)
        result_set = [
            cb for cb in all_collection_backups
            if u'Administrator' in cb['title']
        ]
        assert len(resp) == len(result_set) == 1
        assert set([cb['id']
                    for cb in resp]) == set([cb['id'] for cb in result_set])

        # Perform the two previous searches as a restricted viewer to show that
        # the restricted tag is working correctly.
        json_query = json.dumps({
            'query': {
                'filter':
                ['CollectionBackup', 'title', 'like', u'%Contributor%']
            }
        })
        response = self.app.post(url('/collectionbackups/search'), json_query,
                                 self.json_headers, view)
        resp = json.loads(response.body)
        result_set = [
            cb for cb in [unrestricted_collection_backup]
            if u'Contributor' in cb['title']
        ]
        assert len(resp) == len(result_set) == 1
        assert set([cb['id']
                    for cb in resp]) == set([cb['id'] for cb in result_set])

        json_query = json.dumps({
            'query': {
                'filter':
                ['CollectionBackup', 'title', 'like', u'%Administrator%']
            }
        })
        response = self.app.request(url('collectionbackups'),
                                    method='SEARCH',
                                    body=json_query,
                                    headers=self.json_headers,
                                    environ=view)
        resp = json.loads(response.body)
        result_set = [
            cb for cb in [unrestricted_collection_backup]
            if u'Administrator' in cb['title']
        ]
        assert len(resp) == len(result_set) == 0

        # I'm just going to assume that the order by and pagination functions are
        # working correctly since the implementation is essentially equivalent
        # to that in the index action already tested above.

        # Attempting to call edit/new/create/delete/update on a read-only resource
        # will return a 404 response
        response = self.app.get(url('edit_collectionbackup', id=2232),
                                status=404)
        assert json.loads(
            response.body)['error'] == u'This resource is read-only.'
        response = self.app.get(url('new_collectionbackup', id=2232),
                                status=404)
        assert json.loads(
            response.body)['error'] == u'This resource is read-only.'
        response = self.app.post(url('collectionbackups'), status=404)
        assert json.loads(
            response.body)['error'] == u'This resource is read-only.'
        response = self.app.put(url('collectionbackup', id=2232), status=404)
        assert json.loads(
            response.body)['error'] == u'This resource is read-only.'
        response = self.app.delete(url('collectionbackup', id=2232),
                                   status=404)
        assert json.loads(
            response.body)['error'] == u'This resource is read-only.'
        assert response.content_type == 'application/json'
Exemple #13
0
    def test_aaa_initialize(self):
        """Initialize the database using pseudo-data generated from random lorem ipsum sentences.

        These are located in ``onlinelinguisticdatabase/tests/data/corpora``.
        The data contain morphologically analyzed sentences, their component
        morphemes, and syntactic categories.  The sentences have phrase
        structure trees in bracket notation.

        The test will try to load the lorem ipsum dataset from a MySQL/SQLite
        dump file in ``onlinelinguisticdatabase/tests/data/corpora``.  If the
        dump file corresponding to ``loremipsum_path`` does not exist, it will
        import the lorem ipsum data directly from the text files and create
        the dump file so that future tests can run more speedily.  The
        ``loremipsum100_path``, ``loremipsum1000_path``, ``loremipsum10000_path``
        and ``loremipsum30000_path`` files are available and contain 100, 1000
        and 10,000 sentences, respectively.

        Setting the ``via_request`` variable to ``True`` will cause all of the
        forms to be created via request, i.e., via
        ``self.app.post(url('forms))...``.  This is much slower but may be
        desirable since values for the morphological analysis attributes
        will be generated.

        .. note::

            In order to run ``mysqldump`` with the MySQL user listed in
            ``test.ini``, that user must have permission to lock and update
            tables (alter and file privileges may also be required ...)::

                mysql -u root -p<root_password>
                grant lock tables, update on old_test.* to 'old'@'localhost';

        .. warning::

            Loading the .txt or .sql files with the ``via_request`` option set to
            ``True`` will take a very long time.  This might be an argument for
            separating the interface and logic components of the controllers so
            that a "core" HTTP-less OLD application could be exposed.  This
            would facilitate the creation of models with system-generated data
            and validation but without the HTTP overhead...

        """

        ########################################################################
        # Configure lorem ipsum data set import
        ########################################################################

        # Set ``loremipsum_path`` this to ``self.loremipsum100_path``,
        # ``self.loremipsum1000_path`` or ``self.loremipsum10000_path``.
        # WARNING: the larger ones will take a long time.
        # Use the 10,000-sentence lorem ipsum dataset to ensure that
        # very large corpora are handled correctly.
        loremipsum_path = self.loremipsum100_path

        # Set ``via_request`` to ``True`` to create all forms via HTTP requests.
        via_request = True

        self._add_SEARCH_to_web_test_valid_methods()

        # Add an application settings so that morpheme references will work out right.
        application_settings = h.generate_default_application_settings()
        Session.add(application_settings)
        Session.commit()

        def create_model(line, categories, via_request=False):
            """Create a model (form or syncat) using the string in ``line``."""
            model = 'Form'
            elements = unicode(line).split('\t')
            non_empty_elements = filter(None, elements)
            try:
                ol, mb, mg, ml, sc, sx = non_empty_elements
            except Exception:
                try:
                    ol, mb, mg, ml, sc = non_empty_elements
                    sx = u''
                except Exception:
                    try:
                        model = 'SyntacticCategory'
                        n, t = non_empty_elements
                    except Exception:
                        return categories
            if via_request:
                if model == 'SyntacticCategory':
                    params = self.syntactic_category_create_params.copy()
                    params.update({'name': n, 'type': t})
                    params = json.dumps(params)
                    response = self.app.post(url('syntacticcategories'),
                                             params, self.json_headers,
                                             self.extra_environ_admin)
                    cat_id = json.loads(response.body)['id']
                    categories[n] = cat_id
                else:
                    params = self.form_create_params.copy()
                    params.update({
                        'transcription':
                        ol,
                        'morpheme_break':
                        mb,
                        'morpheme_gloss':
                        mg,
                        'translations': [{
                            'transcription': ml,
                            'grammaticality': u''
                        }],
                        'syntax':
                        sx,
                        'syntactic_category':
                        categories.get(sc, u'')
                    })
                    params = json.dumps(params)
                    self.app.post(url('forms'), params, self.json_headers,
                                  self.extra_environ_admin)
            else:
                if model == 'SyntacticCategory':
                    syntactic_category = model.SyntacticCategory()
                    syntactic_category.name = n
                    syntactic_category.type = t
                    Session.add(syntactic_category)
                    categories[n] = syntactic_category.id
                else:
                    form = model.Form()
                    form.transcription = ol
                    form.morpheme_break = mb
                    form.morpheme_gloss = mg
                    translation = model.Translation()
                    translation.transcription = ml
                    form.translations.append(translation)
                    form.syntax = sx
                    form.syntacticcategory_id = categories.get(sc, None)
                    Session.add(form)
            return categories

        def add_loremipsum_to_db(loremipsum_path, via_request=False):
            """Add the contents of the file at ``loremipsum_path`` to the database."""
            categories = {}
            with open(loremipsum_path, 'r') as f:
                i = 0
                for l in f:
                    if i % 100 == 0:
                        if not via_request: Session.commit()
                        log.debug('%d lines processed' % i)
                    i = i + 1
                    categories = create_model(l.replace('\n', ''), categories,
                                              via_request)
                Session.commit()

        loremipsum_path_no_ext = os.path.splitext(loremipsum_path)[0]
        sqlalchemy_URL = self.config['sqlalchemy.url']
        sqlalchemy_URL_list = sqlalchemy_URL.split(':')
        olddump_script_path = os.path.join(self.test_scripts_path,
                                           'olddump.sh')
        oldload_script_path = os.path.join(self.test_scripts_path,
                                           'oldload.sh')
        RDBMS = sqlalchemy_URL_list[0]

        if RDBMS == 'mysql':
            mysql_dump_path = '%s_mysql.sql' % loremipsum_path_no_ext
            username = sqlalchemy_URL_list[1][2:]
            password = sqlalchemy_URL_list[2].split('@')[0]
            dbname = sqlalchemy_URL_list[3].split('/')[1]
            if os.path.exists(mysql_dump_path):
                log.debug(
                    'The lorem ipsum MySQL dump file exists.  Loading it...')
                # Clear the current DB completely
                h.clear_all_models(retain=[])
                # Load the dump file to the DB
                shell_script = '#!/bin/sh\nmysql -u %s -p%s %s < %s' % (
                    username, password, dbname, mysql_dump_path)
                with open(oldload_script_path, 'w') as f:
                    f.write(shell_script)
                os.chmod(oldload_script_path, 0744)
                # Load the DB
                with open(os.devnull, 'w') as f:
                    call([oldload_script_path], stdout=f, stderr=f)
                # Destroy the load script
                os.remove(oldload_script_path)
                log.debug('Loaded.')
            else:
                log.debug(
                    'Have to import the lorem ipsum dataset from the text file and create the MySQL dump file.'
                )
                # Populate the database from the loremipusm text file and dump it
                add_loremipsum_to_db(loremipsum_path, via_request=via_request)
                # Write the DB dump shell script
                # Note: the --single-transaction option seems to be required (on Mac MySQL 5.6 using InnoDB tables ...)
                # see http://forums.mysql.com/read.php?10,108835,112951#msg-112951
                shell_script = '#!/bin/sh\nmysqldump -u %s -p%s --single-transaction --no-create-info --result-file=%s %s' % (
                    username, password, mysql_dump_path, dbname)
                with open(olddump_script_path, 'w') as f:
                    f.write(shell_script)
                os.chmod(olddump_script_path, 0744)
                # Dump the DB
                with open(os.devnull, 'w') as f:
                    call([olddump_script_path], stdout=f, stderr=f)
                # Destroy the dump script
                os.remove(olddump_script_path)
                log.debug('Imported and dumped.')
        elif RDBMS == 'sqlite' and h.command_line_program_installed('sqlite3'):
            sqlite_dump_path = '%s_sqlite.sql' % loremipsum_path_no_ext
            sqlite_db = sqlalchemy_URL.split('/')[-1]
            dbpath = os.path.join(self.here, sqlite_db)
            if os.path.exists(sqlite_dump_path):
                log.debug(
                    'The lorem ipsum SQLite dump file exists.  Loading it...')
                # Clear the current DB completely
                h.clear_all_models(retain=[])
                # Load the dump file to the DB
                shell_script = '#!/bin/sh\nsqlite3 %s < %s' % (
                    dbpath, sqlite_dump_path)
                with open(oldload_script_path, 'w') as f:
                    f.write(shell_script)
                os.chmod(oldload_script_path, 0744)
                # Load the DB
                with open(os.devnull, 'w') as f:
                    call([oldload_script_path], stdout=f, stderr=f)
                # Destroy the load script
                os.remove(oldload_script_path)
                log.debug('Loaded.')
            else:
                log.debug(
                    'Have to import the lorem ipsum dataset from the text file and create the SQLite dump file.'
                )
                # Populate the database from the loremipusm text file and dump it
                add_loremipsum_to_db(loremipsum_path, via_request=via_request)
                # Write the DB dump shell script
                shell_script = '#!/bin/sh\nsqlite3 %s ".dump" | grep -v "^CREATE" > %s' % (
                    dbpath, sqlite_dump_path)
                with open(olddump_script_path, 'w') as f:
                    f.write(shell_script)
                os.chmod(olddump_script_path, 0744)
                # Dump the DB
                with open(os.devnull, 'w') as f:
                    call([olddump_script_path], stdout=f, stderr=f)
                # Destroy the dump script
                os.remove(olddump_script_path)
                log.debug('Imported and dumped.')
        forms = h.get_forms()
        log.debug(
            'Lorem ipsum data loaded.  There are now %d forms in the db.' %
            len(forms))

        # Restrict one sentential form in the db.
        restricted_tag = h.generate_restricted_tag()
        Session.add(restricted_tag)
        Session.commit()
        a_form = Session.query(model.Form).\
            filter(model.Form.syntactic_category.\
                has(model.SyntacticCategory.name==u'S')).first()
        a_form_id = a_form.id
        a_form.tags.append(restricted_tag)
        Session.commit()
        restricted_form = Session.query(model.Form).\
            filter(model.Form.tags.any(model.Tag.name==u'restricted')).first()
        assert a_form_id == restricted_form.id
Exemple #14
0
def setup_app(command, conf, vars):
    """Commands to setup onlinelinguisticdatabase."""

    config = load_environment(conf.global_conf, conf.local_conf)
    log.info('Environment loaded.')

    Base.metadata.create_all(bind=Session.bind)
    filename = os.path.split(conf.filename)[-1] # e.g., production.ini, development.ini, test.ini, ...

    # Create the ``store`` directory and those for file, analysis and corpora
    # objects and their subdirectories.  See ``lib.utils.py`` for details.
    h.create_OLD_directories(config=config)

    # ISO-639-3 Language data for the languages table
    log.info("Retrieving ISO-639-3 languages data.")
    languages = h.get_language_objects(filename, config)

    # Get default users.
    log.info("Creating a default administrator, contributor and viewer.")
    administrator = h.generate_default_administrator(config_filename=filename)
    contributor = h.generate_default_contributor(config_filename=filename)
    viewer = h.generate_default_viewer(config_filename=filename)

    # If we are running tests, make sure the test db contains only language data.
    if filename == 'test.ini':
        # Permanently drop any existing tables
        Base.metadata.drop_all(bind=Session.bind, checkfirst=True)
        log.info("Existing tables dropped.")

        # Create the tables if they don't already exist
        Base.metadata.create_all(bind=Session.bind, checkfirst=True)
        log.info('Tables created.')

        Session.add_all(languages + [administrator, contributor, viewer])
        Session.commit()

    # Not a test: add a bunch of nice defaults.
    else:
        # Create the _requests_tests.py script
        requests_tests_path = os.path.join(config['pylons.paths']['root'], 'tests',
                                         'scripts', '_requests_tests.py')

        # This line is problematic in production apps because the
        # _requests_tests.py file is not included in the build. So, I'm
        # commenting it out by default.
        # copyfile(requests_tests_path, '_requests_tests.py')

        # Create the tables if they don't already exist
        Base.metadata.create_all(bind=Session.bind, checkfirst=True)
        log.info('Tables created.')

        # Get default home & help pages.
        log.info("Creating default home and help pages.")
        homepage = h.generate_default_home_page()
        helppage = h.generate_default_help_page()
    
        # Get default application settings.
        log.info("Generating default application settings.")
        application_settings = h.generate_default_application_settings()
    
        # Get default tags and categories
        log.info("Creating some useful tags and categories.")
        restricted_tag = h.generate_restricted_tag()
        foreign_word_tag = h.generate_foreign_word_tag()
        S = h.generate_s_syntactic_category()
        N = h.generate_n_syntactic_category()
        V = h.generate_v_syntactic_category()
    
        # Initialize the database
        log.info("Adding defaults.")
        data = [administrator, contributor, viewer, homepage, helppage,
                application_settings, restricted_tag, foreign_word_tag]
        if config['add_language_data'] != '0':
            data += languages
        if config['empty_database'] == '0':
            Session.add_all(data)
            Session.commit()
        log.info("OLD successfully set up.")
Exemple #15
0
    def test_email_reset_password(self):
        """Tests that POST /login/email_reset_password sends a user a newly generated password.

        I gave up trying to get Python's smtplib to work on Mac OS X.  The email
        functionality in this controller action appears to work on my Debian
        production system.  See the links below for some Mac head-bashing:

        http://pivotallabs.com/users/chad/blog/articles/507-enabling-the-postfix-mail-daemon-on-leopard
        http://webcache.googleusercontent.com/search?q=cache:http://blog.subtlecoolness.com/2009/06/enabling-postfix-sendmail-on-mac-os-x.html
        http://www.agileapproach.com/blog-entry/how-enable-local-smtp-server-postfix-os-x-leopard.
        """

        # Create an application settings so that there is an object language id
        application_settings = h.generate_default_application_settings()
        Session.add(application_settings)
        Session.commit()

        # Get the contributor
        contributor = Session.query(model.User).filter(model.User.username==u'contributor').first()
        contributor_email = contributor.email

        # Ensure that we can authenticate the contributor
        params = json.dumps({'username': '******', 'password': '******'})
        response = self.app.post(url(controller='login', action='authenticate'), params, self.json_headers)
        resp = json.loads(response.body)
        assert resp['authenticated'] == True
        assert response.content_type == 'application/json'

        # Now request a password change.  Depending on whether the mail server is
        # set up correctly and whether we have an internet connection, we will
        # receive a 200 OK or a 500 Server Error:
        config = h.get_config(config_filename='test.ini')
        password_reset_smtp_server = config.get('password_reset_smtp_server')
        test_email_to = config.get('test_email_to')
        to_address = test_email_to or contributor_email
        params = json.dumps({'username': '******'})
        response = self.app.post(url(controller='login', action='email_reset_password'),
                                 params, self.json_headers, status=[200, 500])
        resp = json.loads(response.body)
        assert (response.status_int == 200 and
                resp['valid_username'] == True and
                resp['password_reset'] == True) or (
                response.status_int == 500 and
                resp['error'] == u'The server is unable to send email.')
        assert response.content_type == 'application/json'

        # The email was sent and the password was reset.  Because this is a test,
        # the password was sent back to us in the JSON response.  In a production
        # or development environment, this would not be the case.
        if response.status_int == 200:
            new_password = resp['new_password']
            if password_reset_smtp_server == 'smtp.gmail.com':
                log.info('A new password was emailed via Gmail to %s.' % to_address)
            else:
                log.info('A new password was emailed from localhost to %s.' % to_address)

            # Make sure that the old password no longer works.
            params = json.dumps({'username': '******', 'password': '******'})
            response = self.app.post(url(controller='login', action='authenticate'),
                                     params, self.json_headers, status=401)
            resp = json.loads(response.body)
            assert resp['error'] == u'The username and password provided are not valid.'
            assert response.content_type == 'application/json'

            # Make sure that the new password works.
            params = json.dumps({'username': '******', 'password': new_password})
            response = self.app.post(url(controller='login', action='authenticate'),
                                     params, self.json_headers)
            resp = json.loads(response.body)
            assert resp['authenticated'] == True
            assert response.content_type == 'application/json'

        # If the email was *not* sent and the password was not reset.  Make sure
        # that the old password does still work.
        else:
            log.info('localhost was unable to send email.')
            params = json.dumps({'username': '******', 'password': '******'})
            response = self.app.post(url(controller='login', action='authenticate'), params, self.json_headers)
            resp = json.loads(response.body)
            assert resp['authenticated'] == True
            assert response.content_type == 'application/json'

        # Invalid username.
        params = json.dumps({'username': '******'})
        response = self.app.post(url(controller='login',
            action='email_reset_password'), params, self.json_headers,
            status=400)
        resp = json.loads(response.body)
        resp['error'] == u'The username provided is not valid.'
        assert response.content_type == 'application/json'

        # Invalid POST parameters.
        params = json.dumps({'badparam': 'irrelevant'})
        response = self.app.post(url(controller='login',
            action='email_reset_password'), params, self.json_headers, status=400)
        resp = json.loads(response.body)
        assert resp['errors']['username'] == u'Missing value'
        assert response.content_type == 'application/json'
Exemple #16
0
    def test_email_reset_password(self):
        """Tests that POST /login/email_reset_password sends a user a newly generated password.

        I gave up trying to get Python's smtplib to work on Mac OS X.  The email
        functionality in this controller action appears to work on my Debian
        production system.  See the links below for some Mac head-bashing:

        http://pivotallabs.com/users/chad/blog/articles/507-enabling-the-postfix-mail-daemon-on-leopard
        http://webcache.googleusercontent.com/search?q=cache:http://blog.subtlecoolness.com/2009/06/enabling-postfix-sendmail-on-mac-os-x.html
        http://www.agileapproach.com/blog-entry/how-enable-local-smtp-server-postfix-os-x-leopard.
        """

        # Create an application settings so that there is an object language id
        application_settings = h.generate_default_application_settings()
        Session.add(application_settings)
        Session.commit()

        # Get the contributor
        contributor = Session.query(
            model.User).filter(model.User.username == u'contributor').first()
        contributor_email = contributor.email

        # Ensure that we can authenticate the contributor
        params = json.dumps({
            'username': '******',
            'password': '******'
        })
        response = self.app.post(
            url(controller='login', action='authenticate'), params,
            self.json_headers)
        resp = json.loads(response.body)
        assert resp['authenticated'] == True
        assert response.content_type == 'application/json'

        # Now request a password change.  Depending on whether the mail server is
        # set up correctly and whether we have an internet connection, we will
        # receive a 200 OK or a 500 Server Error:
        config = h.get_config(config_filename='test.ini')
        password_reset_smtp_server = config.get('password_reset_smtp_server')
        test_email_to = config.get('test_email_to')
        to_address = test_email_to or contributor_email
        params = json.dumps({'username': '******'})
        response = self.app.post(url(controller='login',
                                     action='email_reset_password'),
                                 params,
                                 self.json_headers,
                                 status=[200, 500])
        resp = json.loads(response.body)
        assert (response.status_int == 200 and resp['valid_username'] == True
                and resp['password_reset']
                == True) or (response.status_int == 500 and resp['error']
                             == u'The server is unable to send email.')
        assert response.content_type == 'application/json'

        # The email was sent and the password was reset.  Because this is a test,
        # the password was sent back to us in the JSON response.  In a production
        # or development environment, this would not be the case.
        if response.status_int == 200:
            new_password = resp['new_password']
            if password_reset_smtp_server == 'smtp.gmail.com':
                log.info('A new password was emailed via Gmail to %s.' %
                         to_address)
            else:
                log.info('A new password was emailed from localhost to %s.' %
                         to_address)

            # Make sure that the old password no longer works.
            params = json.dumps({
                'username': '******',
                'password': '******'
            })
            response = self.app.post(url(controller='login',
                                         action='authenticate'),
                                     params,
                                     self.json_headers,
                                     status=401)
            resp = json.loads(response.body)
            assert resp[
                'error'] == u'The username and password provided are not valid.'
            assert response.content_type == 'application/json'

            # Make sure that the new password works.
            params = json.dumps({
                'username': '******',
                'password': new_password
            })
            response = self.app.post(
                url(controller='login', action='authenticate'), params,
                self.json_headers)
            resp = json.loads(response.body)
            assert resp['authenticated'] == True
            assert response.content_type == 'application/json'

        # If the email was *not* sent and the password was not reset.  Make sure
        # that the old password does still work.
        else:
            log.info('localhost was unable to send email.')
            params = json.dumps({
                'username': '******',
                'password': '******'
            })
            response = self.app.post(
                url(controller='login', action='authenticate'), params,
                self.json_headers)
            resp = json.loads(response.body)
            assert resp['authenticated'] == True
            assert response.content_type == 'application/json'

        # Invalid username.
        params = json.dumps({'username': '******'})
        response = self.app.post(url(controller='login',
                                     action='email_reset_password'),
                                 params,
                                 self.json_headers,
                                 status=400)
        resp = json.loads(response.body)
        resp['error'] == u'The username provided is not valid.'
        assert response.content_type == 'application/json'

        # Invalid POST parameters.
        params = json.dumps({'badparam': 'irrelevant'})
        response = self.app.post(url(controller='login',
                                     action='email_reset_password'),
                                 params,
                                 self.json_headers,
                                 status=400)
        resp = json.loads(response.body)
        assert resp['errors']['username'] == u'Missing value'
        assert response.content_type == 'application/json'
    def test_category_percolation(self):
        """Tests that changes to a category's name and deletion of a category trigger updates to forms containing morphemes of that category.
        """

        application_settings = h.generate_default_application_settings()
        Session.add(application_settings)
        Session.commit()

        extra_environ = {'test.authentication.role': u'administrator',
                               'test.application_settings': True}

        # Create an N category
        params = json.dumps({'name': u'N', 'type': u'lexical', 'description': u''})
        response = self.app.post(url('syntacticcategories'), params, self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        NId = resp['id']
        assert resp['name'] == u'N'
        assert response.content_type == 'application/json'

        # Create a lexical form 'chien/dog' of category N
        params = self.form_create_params.copy()
        params.update({
            'transcription': u'chien',
            'morpheme_break': u'chien',
            'morpheme_gloss': u'dog',
            'translations': [{'transcription': u'dog', 'grammaticality': u''}],
            'syntactic_category': NId
        })
        params = json.dumps(params)
        response = self.app.post(url('forms'), params, self.json_headers, extra_environ)
        resp = json.loads(response.body)
        chien_id = resp['id']
        assert resp['morpheme_break_ids'][0][0][0][1] == u'dog'
        assert resp['morpheme_break_ids'][0][0][0][2] == u'N'
        assert resp['morpheme_gloss_ids'][0][0][0][1] == u'chien'
        assert resp['morpheme_gloss_ids'][0][0][0][2] == u'N'
        assert resp['syntactic_category_string'] == u'N'
        assert resp['break_gloss_category'] == u'chien|dog|N'

        # Create a phrasal form 'chien-s/dog-PL' that will contain 'chien/dog'
        params = self.form_create_params.copy()
        params.update({
            'transcription': u'chiens',
            'morpheme_break': u'chien-s',
            'morpheme_gloss': u'dog-PL',
            'translations': [{'transcription': u'dogs', 'grammaticality': u''}],
            'syntactic_category': NId
        })
        params = json.dumps(params)
        response = self.app.post(url('forms'), params, self.json_headers, extra_environ)
        resp = json.loads(response.body)
        chiens_id = resp['id']
        assert resp['morpheme_break_ids'][0][0][0][1] == u'dog'
        assert resp['morpheme_break_ids'][0][0][0][2] == u'N'
        assert resp['morpheme_gloss_ids'][0][0][0][1] == u'chien'
        assert resp['morpheme_gloss_ids'][0][0][0][2] == u'N'
        assert resp['syntactic_category_string'] == u'N-?'
        assert resp['break_gloss_category'] == u'chien|dog|N-s|PL|?'

        # Now update the name of the N category and expect that change to cause
        # an update to the chien/dog and chien-s/dog-PL forms.
        form_backup_count = Session.query(model.FormBackup).count()
        params = json.dumps({'name': u'Noun', 'type': u'lexical', 'description': u''})
        response = self.app.put(url('syntacticcategory', id=NId), params, self.json_headers, extra_environ)
        new_form_backup_count = Session.query(model.FormBackup).count()
        chien = Session.query(model.Form).get(chien_id)
        chiens = Session.query(model.Form).get(chiens_id)
        assert new_form_backup_count == form_backup_count + 2
        assert chien.syntactic_category_string == u'Noun'
        assert chiens.syntactic_category_string == u'Noun-?'
        assert json.loads(chiens.morpheme_break_ids)[0][0][0][2] == u'Noun'

        # Now update something besides the name attribute of the N/Noun category
        # and expect no updates to any forms.
        params = json.dumps({'name': u'Noun', 'type': u'lexical', 'description': u'Blah!'})
        response = self.app.put(url('syntacticcategory', id=NId), params, self.json_headers, extra_environ)
        form_backup_count = new_form_backup_count
        new_form_backup_count = Session.query(model.FormBackup).count()
        chien = chiens = None
        chien = Session.query(model.Form).get(chien_id)
        chiens = Session.query(model.Form).get(chiens_id)
        assert new_form_backup_count == form_backup_count
        assert chien.syntactic_category_string == u'Noun'
        assert chiens.syntactic_category_string == u'Noun-?'
        assert json.loads(chiens.morpheme_break_ids)[0][0][0][2] == u'Noun'

        # Test deletion of sc
        response = self.app.delete(url('syntacticcategory', id=NId), headers=self.json_headers,
                                   extra_environ=extra_environ)
        form_backup_count = new_form_backup_count
        new_form_backup_count = Session.query(model.FormBackup).count()
        chien = chiens = None
        chien = Session.query(model.Form).get(chien_id)
        chiens = Session.query(model.Form).get(chiens_id)
        assert new_form_backup_count == form_backup_count + 2
        assert chien.syntactic_category == None
        assert chien.syntactic_category_string == u'?'
        assert chiens.syntactic_category_string == u'?-?'
        assert json.loads(chiens.morpheme_break_ids)[0][0][0][2] == None